diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7aae00ae..b6577f50 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,158 +1,268 @@ -# Base Image -FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04@sha256:81380e4c9c14e8a629ff39029639e4b7893e67400246fa7782a0fe7dc193a02a +# syntax=docker/dockerfile:1.4 -USER root - -# Copy pinned artifacts so they can be used as defaults when build-args are not provided -COPY pinned-artifacts.json /tmp/pinned-artifacts.json +# Main Base Image (Ubuntu 24.04 GLIBC compatible) +FROM mcr.microsoft.com/devcontainers/base:2.1.7-ubuntu24.04@sha256:4bcb1b466771b1ba1ea110e2a27daea2f6093f9527fb75ee59703ec89b5561cb -# 1. System Dependencies, Java 17, and SSH Setup -RUN apt-get update && apt-get install -y \ - openssh-server curl git unzip wget adb openjdk-17-jdk-headless apt-transport-https jq \ - && mkdir /var/run/sshd \ - && echo 'vscode:root' | chpasswd \ - && echo 'root:root' | chpasswd \ - && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \ - && sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config - -# 2. Node.js 24.14.1 + npm 11.11.0 -ARG NPM_TARBALL_URL -ARG NPM_SHASUM +USER root -RUN mkdir -p /etc/apt/keyrings \ +# 1. Consolidated System Dependencies & Docker Repos +RUN apt-get update && apt-get install -y --no-install-recommends \ + acl \ + adb \ + build-essential \ + ca-certificates \ + curl \ + git \ + gnupg \ + jq \ + openjdk-25-jdk-headless \ + socat \ + tk-dev \ + unzip \ + wget \ + libbz2-dev \ + libexpat1-dev \ + libffi-dev \ + libgdbm-dev \ + liblzma-dev \ + libncurses-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + zlib1g-dev \ + && install -m 0755 -d /etc/apt/keyrings \ + # NodeSource Setup (Node 24) && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && chmod 644 /etc/apt/keyrings/nodesource.gpg \ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ - && apt-get update && apt-get install -y nodejs \ - && if [ -f /tmp/pinned-artifacts.json ]; then \ - NPM_TARBALL_URL=${NPM_TARBALL_URL:-$(jq -r '.npm.url' /tmp/pinned-artifacts.json)}; \ - NPM_SHASUM=${NPM_SHASUM:-$(jq -r '.npm.shasum' /tmp/pinned-artifacts.json)}; \ - fi \ - && if [ -z "$NPM_TARBALL_URL" ] || [ -z "$NPM_SHASUM" ]; then echo "Missing URL or SHASUM for npm" >&2; exit 1; fi \ - && echo "Downloading npm tarball from $NPM_TARBALL_URL" \ - && curl -fSL "$NPM_TARBALL_URL" -o /tmp/npm.tgz \ - && echo "$NPM_SHASUM /tmp/npm.tgz" | sha1sum -c - \ - # Overwrite the global npm directory to update npm without using npm install -g, - # avoiding the Scorecard Pinned-Dependencies "npmCommand not pinned by hash" warning. - && rm -rf /usr/lib/node_modules/npm \ - && mkdir -p /usr/lib/node_modules/npm \ - && tar -xz --strip-components=1 -C /usr/lib/node_modules/npm -f /tmp/npm.tgz \ - && rm -f /tmp/npm.tgz - -# 3. Docker CLI, GitHub CLI, Supabase CLI, Playwright, and Firebase CLI + # Docker CE Setup + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && chmod 644 /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" > /etc/apt/sources.list.d/docker.list \ + # Single final update and installation pass + && apt-get update && apt-get install -y --no-install-recommends \ + docker-buildx-plugin \ + docker-ce-cli \ + docker-compose-plugin \ + nodejs \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +COPY .devcontainer/pinned-artifacts.json* pinned-artifacts.json* /tmp/ + +# 2. CLI Tools (Npm, Supabase, Firebase, Infisical, Playwright) +ARG NPM_TARBALL_URL +ARG NPM_SHASUM ARG PLAYWRIGHT_TARBALL_URL ARG PLAYWRIGHT_SHASUM ARG FIREBASE_TARBALL_URL ARG FIREBASE_SHASUM -ARG SUPABASE_TARBALL_URL -ARG SUPABASE_SHA256 ARG INFISICAL_TARBALL_URL ARG INFISICAL_SHASUM +ARG SUPABASE_TARBALL_URL +ARG SUPABASE_SHA256 -RUN apt-get update && apt-get install -y ca-certificates curl gnupg jq \ - && install -m 0755 -d /etc/apt/keyrings \ - && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ - && chmod a+r /etc/apt/keyrings/docker.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ - && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ - && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ - && apt-get update && apt-get install -y \ - docker-ce-cli \ - docker-buildx-plugin \ - docker-compose-plugin \ - gh \ - && if [ -f /tmp/pinned-artifacts.json ]; then \ - PLAYWRIGHT_TARBALL_URL=${PLAYWRIGHT_TARBALL_URL:-$(jq -r '.playwright.url' /tmp/pinned-artifacts.json)}; \ - PLAYWRIGHT_SHASUM=${PLAYWRIGHT_SHASUM:-$(jq -r '.playwright.shasum' /tmp/pinned-artifacts.json)}; \ - FIREBASE_TARBALL_URL=${FIREBASE_TARBALL_URL:-$(jq -r '.firebase.url' /tmp/pinned-artifacts.json)}; \ - FIREBASE_SHASUM=${FIREBASE_SHASUM:-$(jq -r '.firebase.shasum' /tmp/pinned-artifacts.json)}; \ - SUPABASE_TARBALL_URL=${SUPABASE_TARBALL_URL:-$(jq -r '.supabase.url' /tmp/pinned-artifacts.json)}; \ - SUPABASE_SHA256=${SUPABASE_SHA256:-$(jq -r '.supabase.sha256' /tmp/pinned-artifacts.json)}; \ - INFISICAL_TARBALL_URL=${INFISICAL_TARBALL_URL:-$(jq -r '.infisical.url' /tmp/pinned-artifacts.json)}; \ - INFISICAL_SHASUM=${INFISICAL_SHASUM:-$(jq -r '.infisical.shasum' /tmp/pinned-artifacts.json)}; \ - fi \ - && ARCH=$(dpkg --print-architecture) \ - && echo "Downloading Supabase CLI (verify by provided SHA256)..." \ - && if [ -z "$SUPABASE_TARBALL_URL" ] || [ -z "$SUPABASE_SHA256" ]; then echo "SUPABASE_TARBALL_URL and SUPABASE_SHA256 must be provided (build-arg or pinned file)" >&2; exit 1; fi \ - && curl -fSL "$SUPABASE_TARBALL_URL" -o /tmp/supabase.tar.gz \ - && echo "$SUPABASE_SHA256 /tmp/supabase.tar.gz" | sha256sum -c - \ - && tar -xzf /tmp/supabase.tar.gz -C /tmp \ - && mv /tmp/supabase /usr/local/bin/ \ - && chmod +x /usr/local/bin/supabase \ - && rm -f /tmp/supabase.tar.gz \ - && set -eux \ - # Install npm global packages from provided tarball URLs and verify hashes - && for NAME in "playwright" "firebase-tools" "infisical"; do \ - case "$NAME" in \ - playwright) URL="$PLAYWRIGHT_TARBALL_URL"; SUM="$PLAYWRIGHT_SHASUM";; \ - firebase-tools) URL="$FIREBASE_TARBALL_URL"; SUM="$FIREBASE_SHASUM";; \ - infisical) URL="$INFISICAL_TARBALL_URL"; SUM="$INFISICAL_SHASUM";; \ - esac; \ - if [ -z "$URL" ] || [ -z "$SUM" ]; then echo "Missing URL or SHASUM for $NAME" >&2; exit 1; fi; \ - echo "Downloading $NAME tarball from $URL"; curl -fSL "$URL" -o /tmp/$NAME.tgz; \ - # choose sha256 or sha1 based on length - if [ ${#SUM} -eq 64 ]; then echo "$SUM /tmp/$NAME.tgz" | sha256sum -c -; else echo "$SUM /tmp/$NAME.tgz" | sha1sum -c -; fi; \ - node /usr/lib/node_modules/npm/bin/npm-cli.js install -g /tmp/$NAME.tgz; \ - rm -f /tmp/$NAME.tgz; \ - done \ - # Install Playwright OS dependencies using the installed package - && playwright install-deps +RUN set -eu; \ + if [ -f /tmp/pinned-artifacts.json ]; then \ + NPM_TARBALL_URL=${NPM_TARBALL_URL:-$(jq -r '.npm.url // empty' /tmp/pinned-artifacts.json)}; \ + NPM_SHASUM=${NPM_SHASUM:-$(jq -r '.npm.shasum // empty' /tmp/pinned-artifacts.json)}; \ + PLAYWRIGHT_TARBALL_URL=${PLAYWRIGHT_TARBALL_URL:-$(jq -r '.playwright.url // empty' /tmp/pinned-artifacts.json)}; \ + PLAYWRIGHT_SHASUM=${PLAYWRIGHT_SHASUM:-$(jq -r '.playwright.shasum // empty' /tmp/pinned-artifacts.json)}; \ + FIREBASE_TARBALL_URL=${FIREBASE_TARBALL_URL:-$(jq -r '.firebase.url // empty' /tmp/pinned-artifacts.json)}; \ + FIREBASE_SHASUM=${FIREBASE_SHASUM:-$(jq -r '.firebase.shasum // empty' /tmp/pinned-artifacts.json)}; \ + INFISICAL_TARBALL_URL=${INFISICAL_TARBALL_URL:-$(jq -r '.infisical.url // empty' /tmp/pinned-artifacts.json)}; \ + INFISICAL_SHASUM=${INFISICAL_SHASUM:-$(jq -r '.infisical.shasum // empty' /tmp/pinned-artifacts.json)}; \ + SUPABASE_TARBALL_URL=${SUPABASE_TARBALL_URL:-$(jq -r '.supabase.url // empty' /tmp/pinned-artifacts.json)}; \ + SUPABASE_SHA256=${SUPABASE_SHA256:-$(jq -r '.supabase.sha256 // empty' /tmp/pinned-artifacts.json)}; \ + fi; \ + # 1. Install NPM (requires SHASUM) + if [ -n "${NPM_TARBALL_URL:-}" ]; then \ + if [ -z "${NPM_SHASUM:-}" ]; then echo "ERROR: NPM_SHASUM missing for $NPM_TARBALL_URL" >&2; exit 1; fi; \ + curl -fSL "$NPM_TARBALL_URL" -o /tmp/npm.tgz; \ + echo "$NPM_SHASUM /tmp/npm.tgz" | sha1sum -c - || { echo "ERROR: Checksum verification failed for npm" >&2; exit 1; }; \ + npm install -g /tmp/npm.tgz; \ + rm -f /tmp/npm.tgz; \ + fi; \ + # 2. Install Supabase CLI (requires SHA256 binary) + if [ -n "${SUPABASE_TARBALL_URL:-}" ]; then \ + if [ -z "${SUPABASE_SHA256:-}" ]; then echo "ERROR: SUPABASE_SHA256 missing for $SUPABASE_TARBALL_URL" >&2; exit 1; fi; \ + curl -fSL "$SUPABASE_TARBALL_URL" -o /tmp/supabase.tar.gz; \ + echo "$SUPABASE_SHA256 /tmp/supabase.tar.gz" | sha256sum -c - || { echo "ERROR: Checksum verification failed for supabase" >&2; exit 1; }; \ + tar -xzf /tmp/supabase.tar.gz -C /tmp; \ + find /tmp -type f -name "supabase" -exec mv {} /usr/local/bin/ \; ; \ + chmod +x /usr/local/bin/supabase; \ + rm -f /tmp/supabase.tar.gz; \ + fi; \ + # 3. Install NPM CLI tool tarballs with path-matched --allow-scripts + for NAME in "playwright" "firebase-tools" "infisical"; do \ + case "$NAME" in \ + playwright) URL="${PLAYWRIGHT_TARBALL_URL:-}"; SHASUM="${PLAYWRIGHT_SHASUM:-}" ;; \ + firebase-tools) URL="${FIREBASE_TARBALL_URL:-}"; SHASUM="${FIREBASE_SHASUM:-}" ;; \ + infisical) URL="${INFISICAL_TARBALL_URL:-}"; SHASUM="${INFISICAL_SHASUM:-}" ;; \ + esac; \ + if [ -n "$URL" ]; then \ + if [ -z "$SHASUM" ]; then echo "ERROR: SHASUM missing for $NAME ($URL)" >&2; exit 1; fi; \ + curl -fSL "$URL" -o "/tmp/$NAME.tgz"; \ + echo "$SHASUM /tmp/$NAME.tgz" | sha1sum -c - || { echo "ERROR: Checksum verification failed for $NAME" >&2; exit 1; }; \ + npm install -g --allow-scripts="/tmp/$NAME.tgz" "/tmp/$NAME.tgz"; \ + rm -f "/tmp/$NAME.tgz"; \ + fi; \ + done; \ + # 4. Playwright dependencies & cleanup + if command -v playwright >/dev/null 2>&1; then \ + playwright install-deps; \ + fi; \ + apt-get clean && rm -rf /var/lib/apt/lists/*; \ + npm cache clean --force # Switch to vscode user for user-space SDKs USER vscode ENV HOME="/home/vscode" ENV FLUTTER_HOME="$HOME/flutter-sdk" +ENV ANDROID_HOME="$HOME/android-sdk" +ENV DENO_INSTALL="$HOME/.deno" ENV NODE_OPTIONS="--max-old-space-size=9216" -ENV PATH="$PATH:$FLUTTER_HOME/bin" +ENV PATH="$PATH:$FLUTTER_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$DENO_INSTALL/bin:$HOME/.pub-cache/bin" + +# 3. Bake IDE Settings & Cross-IDE Extension Symlinks +RUN mkdir -p $HOME/.antigravity-server/data/Machine \ + $HOME/.antigravity-ide-server/data/Machine \ + $HOME/.vscode-server/data/Machine \ + $HOME/.vscode-server/extensions \ + && ln -s $HOME/.vscode-server/extensions $HOME/.antigravity-server/extensions \ + && ln -s $HOME/.vscode-server/extensions $HOME/.antigravity-ide-server/extensions \ + && SETTINGS='{ \ + "remote.forwardAgent": false, \ + "editor.formatOnSave": true, \ + "editor.codeActionsOnSave": { \ + "source.organizeImports": "explicit", \ + "source.fixAll.eslint": "explicit" \ + }, \ + "editor.defaultFormatter": "esbenp.prettier-vscode", \ + "typescript.tsdk": "node_modules/typescript/lib", \ + "typescript.inlayHints.variableTypes.enabled": true, \ + "typescript.inlayHints.functionLikeReturnTypes.enabled": true, \ + "typescript.inlayHints.parameterNames.enabled": "all", \ + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], \ + "errorlens.enabled": true, \ + "dart.flutterSdkPath": "/home/vscode/flutter-sdk", \ + "dart.openDevTools": "never", \ + "deno.path": "/home/vscode/.deno/bin/deno", \ + "deno.enable": true \ + }' \ + && echo "$SETTINGS" > $HOME/.antigravity-server/data/Machine/settings.json \ + && echo "$SETTINGS" > $HOME/.antigravity-ide-server/data/Machine/settings.json \ + && echo "$SETTINGS" > $HOME/.vscode-server/data/Machine/settings.json -# 4. Flutter SDK Pre-cache -RUN git clone https://github.com/flutter/flutter.git -b stable $FLUTTER_HOME \ - && $FLUTTER_HOME/bin/flutter precache +# 4. Android SDK Setup +ARG ANDROID_CMDLINE_TOOLS_URL +ARG ANDROID_CMDLINE_TOOLS_SHA256 +RUN if [ -f /tmp/pinned-artifacts.json ]; then \ + ANDROID_CMDLINE_TOOLS_URL=${ANDROID_CMDLINE_TOOLS_URL:-$(jq -r '.android_cmdline_tools.url // empty' /tmp/pinned-artifacts.json)}; \ + ANDROID_CMDLINE_TOOLS_SHA256=${ANDROID_CMDLINE_TOOLS_SHA256:-$(jq -r '.android_cmdline_tools.sha256 // empty' /tmp/pinned-artifacts.json)}; \ + fi \ + && ANDROID_CMDLINE_TOOLS_URL=${ANDROID_CMDLINE_TOOLS_URL:-"https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"} \ + && mkdir -p $ANDROID_HOME/cmdline-tools \ + && wget "$ANDROID_CMDLINE_TOOLS_URL" -O /tmp/cmdline-tools.zip \ + && if [ -n "$ANDROID_CMDLINE_TOOLS_SHA256" ]; then echo "$ANDROID_CMDLINE_TOOLS_SHA256 /tmp/cmdline-tools.zip" | sha256sum -c -; fi \ + && unzip -q /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools \ + && mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest \ + && rm /tmp/cmdline-tools.zip \ + && yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses \ + && $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" + +# 5. Flutter SDK Pre-cache +ARG FLUTTER_URL +ARG FLUTTER_SHA256 +RUN if [ -f /tmp/pinned-artifacts.json ]; then \ + FLUTTER_URL=${FLUTTER_URL:-$(jq -r '.flutter.url // empty' /tmp/pinned-artifacts.json)}; \ + FLUTTER_SHA256=${FLUTTER_SHA256:-$(jq -r '.flutter.sha256 // empty' /tmp/pinned-artifacts.json)}; \ + fi \ + && if [ -n "$FLUTTER_URL" ]; then \ + mkdir -p $FLUTTER_HOME \ + && curl -fSL "$FLUTTER_URL" -o /tmp/flutter.tar.xz \ + && if [ -n "$FLUTTER_SHA256" ]; then echo "$FLUTTER_SHA256 /tmp/flutter.tar.xz" | sha256sum -c -; fi \ + && tar -xJf /tmp/flutter.tar.xz -C /tmp \ + && rm -rf $FLUTTER_HOME \ + && mv /tmp/flutter $FLUTTER_HOME \ + && rm -f /tmp/flutter.tar.xz; \ + else \ + git clone https://github.com/flutter/flutter.git -b stable $FLUTTER_HOME; \ + fi \ + && $FLUTTER_HOME/bin/flutter precache \ + && flutter config --android-sdk $ANDROID_HOME -# 5. Workspace Injector Script -# Switch to root to safely write and change file ownership +# 6. Initialization Script Creation USER root -RUN cat << 'EOF' > /home/vscode/init_workspace.sh +COPY --chown=vscode:vscode --chmod=755 <<"EOF" /home/vscode/init_workspace.sh #!/bin/bash -echo "๐Ÿš€ Starting Full Environment Setup..." - -# 1. Git Configuration -# Tell Git to use a writeable file for global settings instead of the busy mount -export GIT_CONFIG_GLOBAL="/home/vscode/.gitconfig_local" -export NODE_OPTIONS="--max-old-space-size=9216" - -if [ ! -f "$GIT_CONFIG_GLOBAL" ]; then - cp /home/vscode/.gitconfig "$GIT_CONFIG_GLOBAL" 2>/dev/null || touch "$GIT_CONFIG_GLOBAL" +echo "๐Ÿš€ Starting GhostClass Workspace Setup..." +# --- 1. GIT CONFIG DIVERSION --- +if [ ! -L "/home/vscode/.gitconfig" ]; then + if [ -f "/home/vscode/.gitconfig" ]; then + mv /home/vscode/.gitconfig /home/vscode/.gitconfig_local + else + touch /home/vscode/.gitconfig_local + fi + ln -sf /home/vscode/.gitconfig_local /home/vscode/.gitconfig fi - -if ! grep -q "GIT_CONFIG_GLOBAL" ~/.bashrc; then - echo 'export GIT_CONFIG_GLOBAL="/home/vscode/.gitconfig_local"' >> ~/.bashrc +chmod 644 /home/vscode/.gitconfig_local 2>/dev/null || true + +# --- 2. SSH AGENT & SIGNING SETUP --- +echo "๐Ÿ”‘ Checking SSH Agent Forwarding..." + +HAS_SSH_KEY=false +if [ -S "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1; then + CLEAN_KEY=$(ssh-add -L 2>/dev/null | tr -d '\r' | grep -i "ghost" | head -n 1) + if [ -z "$CLEAN_KEY" ]; then + CLEAN_KEY=$(ssh-add -L 2>/dev/null | tr -d '\r' | head -n 1) + fi + + if [ -n "$CLEAN_KEY" ]; then + HAS_SSH_KEY=true + git config --global user.signingkey "$CLEAN_KEY" + git config --global gpg.format ssh + git config --global commit.gpgsign true + echo "โœ… SSH signing enabled." + fi +else + echo "โš ๏ธ SSH Agent is not active or has no keys loaded." fi -git config --global --add safe.directory /workspace - -# 2. Secure Local SSH Key Setup -MOUNTED_KEY="/home/vscode/.ssh/id_ed25519" -LOCAL_KEY="/home/vscode/.ssh/id_ed25519_local" - -# Use sudo to ensure we can create/fix the directory -sudo mkdir -p /home/vscode/.ssh -sudo chown -R vscode:vscode /home/vscode/.ssh -chmod 700 /home/vscode/.ssh +# --- 3. GIT IDENTITY --- +CURRENT_NAME=$(git config --global user.name 2>/dev/null) +CURRENT_EMAIL=$(git config --global user.email 2>/dev/null) + +if [ -z "$CURRENT_NAME" ] || [ -z "$CURRENT_EMAIL" ]; then + if [ -t 0 ]; then + echo "๐Ÿ‘ค Setting up Git identity..." + [ -z "$CURRENT_NAME" ] && read -rp "Enter Git Name: " CURRENT_NAME && git config --global user.name "$CURRENT_NAME" + [ -z "$CURRENT_EMAIL" ] && read -rp "Enter Git Email: " CURRENT_EMAIL && git config --global user.email "$CURRENT_EMAIL" + else + AUTO_EMAIL=$(ssh-add -L 2>/dev/null | awk '{print $3}' | grep "@" | head -n 1) + [ -z "$CURRENT_NAME" ] && git config --global user.name "${GIT_NAME:-User}" + [ -z "$CURRENT_EMAIL" ] && git config --global user.email "${GIT_EMAIL:-${AUTO_EMAIL:-dev@example.com}}" + echo "โ„น๏ธ Auto-configured Git identity: $(git config --global user.name) <$(git config --global user.email)>" + fi +fi -if [ -f "$MOUNTED_KEY" ]; then - cp "$MOUNTED_KEY" "$LOCAL_KEY" - chmod 600 "$LOCAL_KEY" +# --- 4. POPULATE ALLOWED SIGNERS (AFTER IDENTITY IS FINALIZED) --- +if [ "$HAS_SSH_KEY" = true ]; then + FINAL_EMAIL=$(git config --global user.email) + ALLOWED_SIGNERS_FILE="/home/vscode/.ssh/allowed_signers" - git config --global user.signingkey "$LOCAL_KEY" - git config --global gpg.format ssh - git config --global commit.gpgsign true - echo "โœ… Dedicated Sub-key secured and Git signing configured." -else - echo "โŒ Private key mount missing at $MOUNTED_KEY!" + mkdir -p /home/vscode/.ssh && chmod 700 /home/vscode/.ssh + + # Prepend the finalized email to the clean public key + echo "${FINAL_EMAIL:-*} ${CLEAN_KEY}" > "$ALLOWED_SIGNERS_FILE" + chmod 600 "$ALLOWED_SIGNERS_FILE" + + git config --global gpg.ssh.allowedSignersFile "$ALLOWED_SIGNERS_FILE" + echo "โœ… Local SSH signature verification configured for ${FINAL_EMAIL:-*}" fi -# 3. Deno Setup +git config --global --add safe.directory /ghostclass + +# --- 5. DENO SETUP --- export DENO_INSTALL="/home/vscode/.deno" export PATH="$DENO_INSTALL/bin:$PATH" @@ -172,96 +282,70 @@ if ! grep -q "DENO_INSTALL" ~/.bashrc; then echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc fi -mkdir -p ~/.vscode-server/data/Machine - -if [ ! -f ~/.vscode-server/data/Machine/settings.json ]; then - echo '{}' > ~/.vscode-server/data/Machine/settings.json +# --- 6. NPM & FLUTTER SETUP --- +if [ -f "/ghostclass/package.json" ]; then + echo "๐Ÿ“ฆ Synchronizing NPM packages..." + cd /ghostclass && npm ci --legacy-peer-deps fi - -grep -q '"deno.path"' ~/.vscode-server/data/Machine/settings.json || \ -sed -i 's|}|,\n "deno.path": "/home/vscode/.deno/bin/deno",\n "deno.enable": true\n}|' \ -~/.vscode-server/data/Machine/settings.json - -# 4. Android SDK Setup -export ANDROID_HOME="/home/vscode/android-sdk" -export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" -export PATH="$PATH":"$HOME/.pub-cache/bin" - -# Download Linux cmdline-tools if missing -if [ ! -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then - echo "โฌ‡๏ธ Downloading Linux Android SDK Command-line Tools..." - mkdir -p "/home/vscode/android-sdk/cmdline-tools" - - wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip - unzip -q /tmp/cmdline-tools.zip -d "/home/vscode/android-sdk/cmdline-tools" - - mv "/home/vscode/android-sdk/cmdline-tools/cmdline-tools" "/home/vscode/android-sdk/cmdline-tools/latest" - rm /tmp/cmdline-tools.zip - - echo "๐Ÿ“ฆ Installing Android Platform Tools..." - yes | /home/vscode/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses - /home/vscode/android-sdk/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" -fi - -if ! grep -q "ANDROID_HOME" ~/.bashrc; then - echo "export ANDROID_HOME=\"/home/vscode/android-sdk\"" >> ~/.bashrc - echo "export PATH=\"\$PATH:\$ANDROID_HOME/cmdline-tools/latest/bin:\$ANDROID_HOME/platform-tools\"" >> ~/.bashrc +if [ -d "/ghostclass/mobile" ]; then + echo "๐Ÿฆ Fetching Flutter dependencies..." + cd /ghostclass/mobile && flutter pub get fi -# 5. Flutter SDK Setup -export FLUTTER_HOME="/home/vscode/flutter-sdk" -export PATH="$PATH:$FLUTTER_HOME/bin" - -if ! grep -q "flutter-sdk" ~/.bashrc; then - echo "๐Ÿ“ Updating ~/.bashrc..." - echo 'export FLUTTER_HOME="/home/vscode/flutter-sdk"' >> ~/.bashrc - echo "export PATH=\"\$PATH:\$FLUTTER_HOME/bin\"" >> ~/.bashrc -fi - -flutter config --android-sdk "$ANDROID_HOME" - -# 6. Workspace Dependencies -if [ -f "/workspace/package.json" ]; then - echo "๐Ÿ“ฆ Installing NPM dependencies..." - cd /workspace && npm ci --legacy-peer-deps -fi - -if [ -d "/workspace/mobile" ]; then - echo "๐Ÿฆ Running flutter pub get..." - cd /workspace/mobile && flutter pub get -fi - -# 7. IDE Extensions Installation EXTENSIONS=( "Dart-Code.flutter" "Dart-Code.dart-code" "dsznajder.es7-react-js-snippets" "esbenp.prettier-vscode" "bradlc.vscode-tailwindcss" "formulahendry.auto-rename-tag" "christian-kohler.path-intellisense" "christian-kohler.npm-intellisense" "vitest.explorer" "ms-playwright.playwright" "ms-edgedevtools.vscode-edge-devtools" "davidanson.vscode-markdownlint" - "meezilla.json" "ms-vscode.vscode-typescript-next" "oderwat.indent-rainbow" - "dbaeumer.vscode-eslint" "usernamehw.errorlens" "supabase.vscode-supabase-extension" - "denoland.vscode-deno" + "ms-vscode.vscode-typescript-next" "oderwat.indent-rainbow" + "dbaeumer.vscode-eslint" "usernamehw.errorlens" ) -for CLI_PATH in \ - $(find ~/.vscode-server/bin ~/.antigravity-server/bin \ - -type f \( -name "code-server" -o -name "antigravity-server" \) \ - -executable 2>/dev/null); do +echo "๐Ÿ” Searching for valid IDE server binaries..." +VALID_CLIS=() - echo "Using $CLI_PATH" +while IFS= read -r binary_path; do + if [ -n "$binary_path" ]; then + if "$binary_path" --version >/dev/null 2>&1; then + VALID_CLIS+=("$binary_path") + fi + fi +done < <(find ~ -type f -executable \( -name "code-server" -o -name "antigravity-server" -o -name "antigravity-ide-server" -o -name "cursor-server" \) -not -path "*/node_modules/*" 2>/dev/null || true) - for ext in "${EXTENSIONS[@]}"; do - "$CLI_PATH" --install-extension "$ext" --force --telemetry-level off +if [ ${#VALID_CLIS[@]} -eq 0 ]; then + echo "โš ๏ธ No active IDE server binaries found yet." +else + UNIQUE_CLIS=($(echo "${VALID_CLIS[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) + for CLI in "${UNIQUE_CLIS[@]}"; do + echo "๐Ÿ”ง Synchronizing extensions via binary: $CLI" + + INSTALLED_EXTS=$("$CLI" --list-extensions 2>/dev/null | tr '[:upper:]' '[:lower:]') + + for ext in "${EXTENSIONS[@]}"; do + EXT_LOWER=$(echo "$ext" | tr '[:upper:]' '[:lower:]') + + if echo "$INSTALLED_EXTS" | grep -q "^${EXT_LOWER}$"; then + echo " ๐Ÿ”น $ext is already installed." + else + "$CLI" --install-extension "$ext" >/dev/null 2>&1 + + UPDATED_EXTS=$("$CLI" --list-extensions 2>/dev/null | tr '[:upper:]' '[:lower:]') + if echo "$UPDATED_EXTS" | grep -q "^${EXT_LOWER}$"; then + echo " โœ… Installed $ext" + else + echo " โŒ Failed to install $ext" + fi + fi + done done -done +fi -hash -r -echo "๐ŸŽฏ All systems go!" +echo "๐ŸŽฏ Initialization Complete!" EOF +RUN sed -i 's/\r$//' /home/vscode/init_workspace.sh && chmod +x /home/vscode/init_workspace.sh -# Fix permissions so vscode owns the script -RUN chmod +x /home/vscode/init_workspace.sh && chown vscode:vscode /home/vscode/init_workspace.sh - -# Start the SSH Daemon -WORKDIR /workspace -EXPOSE 22 3000 -CMD ["/usr/sbin/sshd", "-D"] \ No newline at end of file +# 7. User Environment Defaults & Entrypoint +USER vscode +WORKDIR /ghostclass +EXPOSE 3000 8000 8080 4000 5001 8081 8085 9099 54321 54322 54323 +CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/.devcontainer/Start.ps1 b/.devcontainer/Start.ps1 index 49514b84..751fd681 100644 --- a/.devcontainer/Start.ps1 +++ b/.devcontainer/Start.ps1 @@ -1,227 +1,172 @@ -๏ปฟ# --- CONFIGURATION --- -$ContainerName = "GhostClass_Sandbox" -$SSHPort = 7522 -$User = "vscode" -$WslConfigPath = "$env:USERPROFILE\.wslconfig" -$ID_FILE = "$env:USERPROFILE\.ssh\id_ed25519" -$AppPort = 3000 - -# --- DEV LIMITS (When coding) --- -$DevRAM = "12GB" -$DevCores = 16 -$DevSwap = "10GB" - -# --- IDLE LIMITS (When exiting script) --- -$IdleRAM = "32GB" -$IdleCores = 32 -$IdleSwap = "10GB" - -function Set-SafeWslLimits ($Mem, $Cores, $Swap) { - if (-Not (Test-Path $WslConfigPath)) { Set-Content -Path $WslConfigPath -Value "[wsl2]" } - - $Lines = Get-Content $WslConfigPath - $Output = @() - - foreach ($Line in $Lines) { - if ($Line -match "^memory=" -or $Line -match "^processors=" -or $Line -match "^swap=") { continue } - $Output += $Line - - if ($Line -match "^\[wsl2\]") { - $Output += "memory=$Mem" - $Output += "processors=$Cores" - $Output += "swap=$Swap" - } - } - $Output | Set-Content $WslConfigPath - Write-Host "โš™๏ธ Limits patched: $Mem RAM | $Cores Cores | $Swap Swap" -ForegroundColor Yellow -} - -# ========================================== -# PHASE 1: SCALE UP & RESTART -# ========================================== -Write-Host "๐Ÿงน Cleaning up background processes..." -ForegroundColor Yellow -Stop-Process -Name "emulator", "qemu-system-x86_64", "ssh" -Force -ErrorAction SilentlyContinue - -Write-Host "๐Ÿ”„ Evaluating Dev Limits..." -ForegroundColor Cyan -$NeedsReboot = Set-SafeWslLimits $DevRAM $DevCores $DevSwap - -if ($NeedsReboot) { - wsl --shutdown - Write-Host "โณ Waking up WSL..." -ForegroundColor Cyan - Start-Sleep -Seconds 3 # Brief pause to let the kernel load -} - -# --- NATIVE DOCKER PATCH: Force start the daemon --- -Write-Host "๐Ÿ”ง Booting native Docker daemon..." -ForegroundColor Cyan -# Run as root to avoid sudo password prompts -wsl -u root service docker start 2>$null - -Write-Host "โณ Waiting for Docker socket to accept connections..." -ForegroundColor Cyan -$dockerRetry = 0 -while ($dockerRetry -lt 15) { - if (wsl --exec docker info 2>$null) { - Write-Host "โœ… Docker is online." -ForegroundColor Green - break - } - Start-Sleep -Seconds 2 - $dockerRetry++ -} - -# ========================================== -# PHASE 2: START UP (Docker & Emulator) -# ========================================== -Write-Host "๐Ÿš€ Starting dependencies..." -ForegroundColor Cyan - -$Status = wsl --exec docker inspect -f '{{.State.Status}}' $ContainerName 2>$null -if ($Status -ne "running") { - wsl --exec docker start $ContainerName - Write-Host "โœ… Container started." -} - -# Start a background WSL process that sleeps for 24 hours to prevent idle shutdown -Start-Process wsl -ArgumentList "-u root", "sleep", "86400" -WindowStyle Hidden - -# --- OPTIMIZATION: Only install ADB if it's missing --- -Write-Host "๐Ÿ“ฆ Verifying ADB inside container..." -ForegroundColor Cyan -wsl --exec docker exec -u root $ContainerName bash -c "if ! command -v adb &> /dev/null; then apt-get update && apt-get install -y android-tools-adb; fi" - -# Inject the Key securely as ROOT to bypass Docker volume permission quirks -$pubKey = (Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" -Raw).Trim() -wsl --exec docker exec -u root $ContainerName bash -c "mkdir -p /home/vscode/.ssh && echo '$pubKey' > /home/vscode/.ssh/authorized_keys && chown vscode:vscode /home/vscode/.ssh && chown vscode:vscode /home/vscode/.ssh/authorized_keys && chmod 700 /home/vscode/.ssh && chmod 600 /home/vscode/.ssh/authorized_keys" - -Write-Host "๐Ÿ“ฑ Starting Android Emulator..." -$EmulatorProc = Start-Process emulator -ArgumentList "-avd Medium_Phone_API_36.1 -netdelay none -netspeed full" -PassThru -WindowStyle Hidden -Start-Sleep -Seconds 15 - -Write-Host "๐Ÿ”ง Ensuring SSH service is running..." -ForegroundColor Cyan -wsl --exec docker exec -u root $ContainerName service ssh start -Start-Sleep -Seconds 1 - -# Kill the ghost emulator inside the container so it doesn't break routing -wsl --exec docker exec -u $User $ContainerName adb -s emulator-5554 emu kill 2>$null - -# ========================================== -# PHASE 3: NETWORKING & ADB (Tunnel) -# ========================================== -Write-Host "โณ Waiting for SSH server on port $SSHPort..." -ForegroundColor Cyan -$retryCount = 0 -while ($retryCount -lt 10) { - if (Test-NetConnection -ComputerName localhost -Port $SSHPort -InformationLevel Quiet) { - Write-Host "โœ… SSH is alive!" -ForegroundColor Green - break - } - $retryCount++ - Start-Sleep -Seconds 2 -} - -Write-Host "๐ŸŒ‰ Tunneling Windows Emulator into the Container..." -ForegroundColor Cyan -$SshProc = Start-Process ssh -ArgumentList "-N", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=NUL", "-i", "`"$ID_FILE`"", "-R", "5555:127.0.0.1:5555", "-p", $SSHPort, "$User@127.0.0.1" -WindowStyle Hidden -PassThru -Start-Sleep -Seconds 5 - -Write-Host "๐Ÿงน Connecting Container's native ADB..." -ForegroundColor Cyan -wsl --exec docker exec -u $User $ContainerName adb kill-server -Start-Sleep -Seconds 2 - -Write-Host "๐ŸŒ‰ Connecting Container's ADB..." -ForegroundColor Cyan -$retryCount = 0 -while ($retryCount -lt 10) { - wsl --exec docker exec -u $User $ContainerName adb connect 127.0.0.1:5555 | Out-Null - $AdbState = wsl --exec docker exec -u $User $ContainerName adb -s 127.0.0.1:5555 get-state 2>$null - - if ($AdbState -eq "device") { - Write-Host "โœ… ADB Linked and Authorized!" -ForegroundColor Green - break - } elseif ($AdbState -eq "unauthorized") { - Write-Host "โš ๏ธ Connected but UNAUTHORIZED. Check emulator screen!" -ForegroundColor Yellow - break - } else { - Write-Host "โŒ ADB Connection failed or device offline. Retrying..." -ForegroundColor Red - } - $retryCount++ - Start-Sleep -Seconds 2 -} - -# --- ISOLATED ADMIN ELEVATION FOR EXTERNAL ACCESS --- -$WslIp = (wsl --exec hostname -I).Trim().Split(" ")[0] -Write-Host "๐Ÿ›ก๏ธ Requesting Admin rights to expose Port $AppPort to local network..." -ForegroundColor Cyan - -$PortProxyCmd = "netsh interface portproxy add v4tov4 listenport=$AppPort listenaddress=0.0.0.0 connectport=$AppPort connectaddress=$WslIp; New-NetFirewallRule -DisplayName 'GhostClass-App' -Direction Inbound -Action Allow -Protocol TCP -LocalPort $AppPort -ErrorAction SilentlyContinue" - -Start-Process powershell -ArgumentList "-WindowStyle Hidden -Command `"$PortProxyCmd`"" -Verb RunAs -Write-Host "โœ… Network routing active: PC_IP:$AppPort -> Container:$AppPort" -ForegroundColor Green - -# ========================================== -# PHASE 4: HOLD STATE -# ========================================== -Write-Host "`n๐ŸŽฏ All systems go!" -ForegroundColor Magenta -Write-Host "--------------------------------------------------------" -Read-Host "๐Ÿ›‘ PRESS ENTER TO TEARDOWN ENVIRONMENT & IDLE WSL ๐Ÿ›‘" -Write-Host "--------------------------------------------------------" - -# ========================================== -# PHASE 5: CLEANUP & SCALE DOWN -# ========================================== -Write-Host "๐Ÿ—‘๏ธ Tearing down..." -ForegroundColor Cyan - -if ($SshProc) { - Stop-Process -Id $SshProc.Id -Force -ErrorAction SilentlyContinue - Write-Host "โœ… SSH Tunnel closed." -} - -Write-Host "๐Ÿ“ฑ Sending shutdown signal to Emulator..." -ForegroundColor Yellow -wsl --exec docker exec -u $User $ContainerName adb shell reboot -p 2>$null -Start-Sleep -Seconds 3 - -wsl --exec docker stop $ContainerName -Write-Host "โœ… Container stopped." - -adb -e emu kill 2>$null -if ($EmulatorProc) { - Stop-Process -Id $EmulatorProc.Id -Force -ErrorAction SilentlyContinue - Write-Host "โœ… Emulator process terminated." -} - -Write-Host "๐Ÿ”„ Throttling WSL back to Idle limits..." -ForegroundColor Cyan -Set-SafeWslLimits $IdleRAM $IdleCores $IdleSwap -wsl --shutdown - -Write-Host "โœ… Environment sanitized & WSL put to sleep." -ForegroundColor Green - -Write-Host "๐Ÿ›ก๏ธ Requesting Admin rights to clean up network routes..." -ForegroundColor Cyan -$CleanupCmd = "netsh interface portproxy delete v4tov4 listenport=$AppPort listenaddress=0.0.0.0; Remove-NetFirewallRule -DisplayName 'GhostClass-App' -ErrorAction SilentlyContinue" -Start-Process powershell -ArgumentList "-WindowStyle Hidden -Command `"$CleanupCmd`"" -Verb RunAs -Write-Host "โœ… Network routes and firewall rules sanitized." -ForegroundColor Green - - -# SIG # Begin signature block -# MIIFfQYJKoZIhvcNAQcCoIIFbjCCBWoCAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC8RFq+GngGPmt5 -# /1c62PUwJfNX9we64JKxQLFg2rWbh6CCAvowggL2MIIB3qADAgECAhAgDfxRX/Zk -# h0itgCU8KbfCMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCGRldmFrZXN1MB4X -# DTI2MDQyNzExMTI1NFoXDTI3MDQyNzExMzI1NFowEzERMA8GA1UEAwwIZGV2YWtl -# c3UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrhVn79t4/fP2jtIkb -# OmN7X1HQBCywC4Vb+pJfzqbV8RP/8uhu2NOQg4deCa1srQSADnm9ItzXdAjfc4NA -# TnPdXkSx4hBsP8smizA0X8dPNbK0ODBnZcZC88eoQ/4KNdL6rqlflPGP5Dx3k06S -# JMaAkFzgcjNqoH6QNIPZHsss6T7kBglcVjn4kj/66cy5zcedRTazFCJB85Zb9U72 -# pcL5ZvK5xD4yVJ1c/9lKqUW4lbg9R7G0gZ6dz44GGbZVX26Qphl5WDJxnEmh/8Jf -# JkZZiySiDjVirns+Ny/k3HnLTbSpLVLMj5a1/xOsvEghSPYvG7X51Hj1Alz5L5yf -# NlhNAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD -# AzAdBgNVHQ4EFgQUjNvZgRB8L+Vi+Skv86hd2P0PPbAwDQYJKoZIhvcNAQELBQAD -# ggEBAADVMcuuh4TVc5QWwFIGNLsdtsYnfawipRI5X8TQTrjqJfdCBMBnse/kLLIT -# KbNhG5Lxylu8jXdukcUvUFt72FOFXN1eY7oocp+jQWERXVNasARORfV3GrkWRW64 -# vHZG/XmfmguA5l/K0rP0pi+2pMn5mjhfjx13EAxeAMGKQU7GY1DWM5TQJDTZx4B3 -# +vvFbYvYxmUmCchqWFjg9Omzpa1Q4UThRclmIk7cb6eN1bbU4tF1/4gmAe+iUkVO -# zJzvVgp/M8XO7X85V7bIZGSRa/IYRpfpVS9usJ06b7OfTMzbKxWd2+iSCSEdKztT -# fqh6873JOeOS7Wj+cuO9xt5guQMxggHZMIIB1QIBATAnMBMxETAPBgNVBAMMCGRl -# dmFrZXN1AhAgDfxRX/Zkh0itgCU8KbfCMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisG -# AQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEEk -# 2wiUBVdA61lng4u4OXRUs7QD5TaGABWOzhc1SogGMA0GCSqGSIb3DQEBAQUABIIB -# AAFD9+VnXEIjvuTx/p7Oi6X2rds+SlPaW1AQzFDWxIG2pA7Y/pj34sR78x/C1aRO -# XlUl3xwySp5OOqbO0w4b6+tijjM7HZCWXspNBbLDZyrmS0mMoMUmGNJJjPjLt6nd -# lHv00A19G5s5UfEbXrrkzO8quwGA5ptEbVQUn3d+c/Btm/rcqTTDN4xsC3ttOA+9 -# nwyGCRtrhmsbbc3blUrgtFSShbAARyrsbPU7EOzR6Hmak0DGhyaQ5vmaH7KL7KDU -# sz2bJpAx1X/QrMP98215BlcFaj4/1wyQ4yt2Heedo9KE6ZIlUKsJaEA3AVgOTFA/ -# RgGY6dQcyBqI/UGW9aqS+y8= -# SIG # End signature block +<# +.SYNOPSIS + GhostClass Sandbox dev-env launcher for Docker Desktop. + Setup ADB, supports Emulator, Physical target, or Skip. + +.PARAMETER Mode + "Emulator", "Physical", or "Skip". +#> +param( + [ValidateSet("Emulator", "Physical", "Skip")] + [string]$Mode +) + +$ContainerName = "GhostClass_Sandbox" +$User = "vscode" +$VmServicePort = 8181 + +# Ensure WSL SSH agent socket is initialized before Docker mounts it +Write-Host "๐Ÿ”‘ Initializing WSL SSH Relay..." -ForegroundColor Cyan +wsl --exec bash -c 'source ~/.bashrc' + +Write-Host "๐Ÿ”ง Verifying Docker Desktop..." -ForegroundColor Cyan +if (!(docker info 2>$null)) { + Write-Host "โŒ Docker Desktop is not running." -ForegroundColor Red + exit 1 +} + +$Status = (docker inspect -f '{{.State.Status}}' $ContainerName 2>$null).Trim() +if ($Status -ne "running") { + Write-Host "๐Ÿš€ Starting container..." -ForegroundColor Cyan + wsl --exec bash -ic "docker start $ContainerName" > $null + Start-Sleep -Seconds 2 +} + +if (-not $Mode) { + Write-Host "`n๐Ÿค– Select Debug Target Environment:" -ForegroundColor Magenta + Write-Host " [1] Emulator" -ForegroundColor Cyan + Write-Host " [2] Physical Device (Wi-Fi ADB)" -ForegroundColor Cyan + Write-Host " [3] Skip ADB (Container Only)" -ForegroundColor Cyan + $Choice = Read-Host "Enter selection (1, 2, or 3)" + + if ($Choice -eq "3") { $Mode = "Skip" } + elseif ($Choice -eq "2") { $Mode = "Physical" } + else { $Mode = "Emulator" } +} + +if ($Mode -ne "Skip") { + # Reset ADB on container to start fresh + docker exec -u $User $ContainerName adb kill-server 2>$null + + if ($Mode -eq "Physical") { + Write-Host "๐Ÿ“ฑ Preparing Physical Device via USB/Wi-Fi..." -ForegroundColor Cyan + adb start-server | Out-Null + + # 1. Fetch Wi-Fi IP while USB is attached + $RouteOutput = adb shell ip route 2>$null + $DeviceIp = ($RouteOutput | Select-String -Pattern 'src\s+(\d+\.\d+\.\d+\.\d+)').Matches.Groups[1].Value + + if (-not $DeviceIp) { + Write-Host "โŒ Could not determine device's Wi-Fi IP. Is USB plugged in and authorized?" -ForegroundColor Red + exit 1 + } + + # 2. Switch phone to TCP mode on host + Write-Host "๐Ÿ“ก Switching device ADB to TCP mode (Port 5555)..." -ForegroundColor Cyan + adb tcpip 5555 | Out-Null + Start-Sleep -Seconds 3 + + # 3. Connect Host ADB over Wi-Fi first to authenticate key pair + Write-Host "๐Ÿค Connecting Windows Host ADB to ${DeviceIp}:5555..." -ForegroundColor Cyan + adb connect "${DeviceIp}:5555" | Out-Null + Start-Sleep -Seconds 2 + + Write-Host "๐Ÿ“ฑ Connecting Container ADB to Device IP: ${DeviceIp}:5555" -ForegroundColor Green + docker exec -u $User $ContainerName adb connect "${DeviceIp}:5555" | Out-Null + + } else { + Write-Host "๐Ÿ“ฑ Launching Local Emulator..." -ForegroundColor Cyan + $EmulatorProc = Start-Process emulator -ArgumentList "-avd Medium_Phone_API_36.1 -netdelay none -netspeed full" -PassThru -WindowStyle Hidden + + # Wait for emulator to fully register on Windows ADB + Write-Host "โณ Waiting for Emulator ADB boot..." -ForegroundColor Cyan + $emuBooted = $false + for ($i = 0; $i -lt 20; $i++) { + $emuState = (adb -e get-state 2>$null) + if ($emuState -eq "device") { $emuBooted = $true; break } + Start-Sleep -Seconds 2 + } + + if (-not $emuBooted) { + Write-Host "โŒ Emulator failed to boot or register ADB on host." -ForegroundColor Red + exit 1 + } + + # Use host.docker.internal so the container routes to Windows Host + Write-Host "๐Ÿ”Œ Connecting Container ADB to host.docker.internal:5555..." -ForegroundColor Cyan + docker exec -u $User $ContainerName adb connect host.docker.internal:5555 | Out-Null + } + + $retryCount = 0 + # Match target to host.docker.internal for Emulator mode + $Target = if ($Mode -eq "Physical") { "${DeviceIp}:5555" } else { "host.docker.internal:5555" } + + while ($retryCount -lt 15) { + $containerState = (docker exec -u $User $ContainerName adb -s $Target get-state 2>$null) + if ($containerState -eq "device") { + Write-Host "โœ… Container ADB Connected & Authorized to $Target!" -ForegroundColor Green + break + } + Start-Sleep -Seconds 2 + $retryCount++ + } + + if ($retryCount -ge 15) { + Write-Host "โš ๏ธ Warning: ADB connection timed out inside container. You may need to accept an RSA auth prompt on phone screen." -ForegroundColor Yellow + } +} else { + Write-Host "โญ๏ธ Skipping ADB configuration." -ForegroundColor Cyan +} + +Write-Host "`n๐ŸŽฏ All systems go!" -ForegroundColor Magenta +Read-Host "๐Ÿ›‘ PRESS ENTER TO TEARDOWN ENVIRONMENT ๐Ÿ›‘" + +Write-Host "๐Ÿ—‘๏ธ Tearing down..." -ForegroundColor Cyan + +if ($Mode -eq "Emulator") { + Write-Host "๐Ÿ›‘ Terminating emulator..." -ForegroundColor Cyan + adb -e emu kill 2>$null + Start-Sleep -Seconds 3 + if ($EmulatorProc) { Stop-Process -Id $EmulatorProc.Id -Force -ErrorAction SilentlyContinue } +} elseif ($Mode -eq "Physical") { + adb disconnect 2>$null | Out-Null +} + +if ($Mode -ne "Skip") { + docker exec -u $User $ContainerName adb kill-server 2>$null +} + +Write-Host "โœ… Environment sanitized. gg." -ForegroundColor Green + +# SIG # Begin signature block +# MIIFfQYJKoZIhvcNAQcCoIIFbjCCBWoCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDExE0VmBcJhrmd +# u6wbjbci3pk4NvreeYH+8iNVZ/RTUKCCAvowggL2MIIB3qADAgECAhAmKGzz2Y/i +# k00s+ReTOHmTMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCGRldmFrZXN1MB4X +# DTI2MDYwNzEzNTA0MloXDTI3MDYwNzE0MTA0MlowEzERMA8GA1UEAwwIZGV2YWtl +# c3UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEoLmTS8czHXtaQFpw +# 6w6gmTU9ciThCmc2H78V47zO+J+3NrdSHRx5jqT/liWIQ8konlM+DiozDx4TXz8U +# LBbI1UJTR7lbTUJayzy7d59NzVD9YKLBvfw/KAleMFaAIPbM1xMfZnHreppsnMMj +# rh/N1XmO7/0sLa2F9vV4xGhM17b24U/bnozmP3Gtm+kxO5j4XCr1vX3H9JDcBAPl +# Cuu3YQASdN/iOtLZ4Qu25R8onqzYF4vv4pFtaQSpD2b/WX/KJ2kKKAsK2bdBgQlF +# ETXhRN40OoT3oULKS+rEGnisvJ6wVdC5kScXYy0M+OE9tdU+DO+B3w3ui+6ztAYp +# YKXdAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD +# AzAdBgNVHQ4EFgQUhlC/oAYJSIGNOeB4Lq85vZb8y5AwDQYJKoZIhvcNAQELBQAD +# ggEBAC8BtGR3ct33qhR0mW1K8NnVa1YRYiQ3Jl9hh7z7Z+NZ3X2VFzECuAx4wlki +# SjVIGxmWiMt51kxDPo8G9If1b6lvH7ukbVOlw/30AvyL0SDj1v9E9rAKzTaDhN9k +# wgTUZ+nxbCsX1uRrV6Nik3d/juOKpXvWAhWHmDn/qIaYqmmLoABsgrWyhZxUzTSV +# 6xCoxlJcCUIB5jicAUqmq4JAyID2ARYCi8FjdxX3+i0PoV/403+WTZgMVbwZFJBf +# G8oP+3TfnrI3X24woPZnyV+OeuLibsSFXq8qmrARY8lWrmT+m3Vl86ibtjj8ppFm +# 05EwtDcUqVGObcNhvsYrTipXJR0xggHZMIIB1QIBATAnMBMxETAPBgNVBAMMCGRl +# dmFrZXN1AhAmKGzz2Y/ik00s+ReTOHmTMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisG +# AQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICjV +# b2J0wt5ulmJZJMupO60TlxopXtftLY8fIoBVVwjiMA0GCSqGSIb3DQEBAQUABIIB +# AAUYsPsF123GHKr/C0f0p1Hwa9ACLfDx8Q5iu5FFvX5sD2GJ6gQJuVTzSQ8xUXLh +# E9aG7sWtafhN+nCx0fld3HARawmmrpzw1b455MHlO+RSsoQxzeBw8s2vINTbBBHI +# 0X02tQuX4/LKhecY88wKD+DnBjyvFWxahkxjfXWVUtHn9UzGOf8mRD/RDcZgvHFV +# aPeACodp1CLzMgghFx8t0V6TP+kWlbDlg5NRRtxwj1zEsBMZ3tsrijTE7PtttMIg +# UAc0f9JAZGJEpixxPoUHPUN+ejYJQ1xzpPvrIUcAEr7Aa+6lxV73dWn+bNgdNOqo +# BQcMwKj1RPLAVl8m6mvCd2g= +# SIG # End signature block diff --git a/.devcontainer/pinned-artifacts.json b/.devcontainer/pinned-artifacts.json index 7100b182..0ec3d323 100644 --- a/.devcontainer/pinned-artifacts.json +++ b/.devcontainer/pinned-artifacts.json @@ -1,22 +1,22 @@ { "npm": { - "url": "https://registry.npmjs.org/npm/-/npm-11.11.0.tgz", - "shasum": "db5ad0ed255e1a29cf241c4112ee81d2220a4edb" + "url": "https://registry.npmjs.org/npm/-/npm-12.0.2.tgz", + "shasum": "788d93dc8869000b1078e0395c60748a0aadc4f1" }, "playwright": { - "url": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", - "shasum": "89710863a51f21112633ef8b6b182594d3bfd7b5" + "url": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "shasum": "8447b6755e8aec85a3cb7207c823e3ed2fc66700" }, "firebase": { - "url": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-15.18.0.tgz", - "shasum": "179b592d2cbdae7b7b8d5844477e81844c6ef5a5" + "url": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-15.25.1.tgz", + "shasum": "56f46b47cafdf89dbb3f9c14e039a3095da9c50c" }, "supabase": { - "url": "https://github.com/supabase/cli/releases/download/v2.98.2/supabase_linux_amd64.tar.gz", - "sha256": "0f59df9e6837e876f309e0b4f47005133c51296e85a02727b2927f33ed9adb2d" + "url": "https://github.com/supabase/cli/releases/download/v2.111.0/supabase_2.111.0_linux_amd64.tar.gz", + "sha256": "31ee8a152e9c8c8eddae072c6bc7c9119748a96c8cdaf21a6d31c9ce7e62cc18" }, "infisical": { - "url": "https://registry.npmjs.org/@infisical/cli/-/cli-0.43.84.tgz", - "shasum": "1be194be8a50c38706d3bd1f5852d22bc393827d" + "url": "https://registry.npmjs.org/@infisical/cli/-/cli-0.43.114.tgz", + "shasum": "f6c8ec4b7c0efcb568694653455f96fb947e4993" } } diff --git a/.example.env b/.example.env index ee216b1a..d58fa9c8 100644 --- a/.example.env +++ b/.example.env @@ -44,7 +44,7 @@ NEXT_PUBLIC_APP_NAME=GhostClass # โš ๏ธ App version displayed in footer and health checks # ๐Ÿ”จ Build-time (Infisical `/build-time` folder) -NEXT_PUBLIC_APP_VERSION=4.4.9 +NEXT_PUBLIC_APP_VERSION=4.5.0 # โš ๏ธ Your production domain WITHOUT https:// # All URL-based variables are derived from this. @@ -236,20 +236,32 @@ NEXT_PUBLIC_ENABLE_SW_IN_DEV=false # ---------------------------------------------------------------------------- # โ„น๏ธ Android Package Name (e.g., com.ghostclass.app) -# ๐Ÿ”จ Build-time (Infisical `/build-time` folder ) +# ๐Ÿ”จ Build-time (Infisical `/build-time` folder) NEXT_PUBLIC_ANDROID_PACKAGE_NAME=com.devakesu.apps.ghostclass +# โ„น๏ธ iOS App Store ID (e.g., 6478952324) +# ๐Ÿ”จ Build-time (Infisical `/build-time` folder โ€” optional) +IOS_APP_ID=6478952324 + # โš ๏ธ Base64-encoded google-services.json file for Firebase / Google Services Plugin # Required for building the release APK. Encode using: base64 -w 0 -i google-services.json # ๐Ÿ”จ Build-time (Infisical `/build-time` folder) GOOGLE_SERVICES_JSON_BASE64= -# ๐Ÿ”‘ Dynamic Firebase Client API Keys (Overrides for firebase_options.dart) +# ๐Ÿ”‘ Dynamic Firebase Client Options (Overrides for firebase_options.dart) +# Used by Flutter build (String.fromEnvironment) & generate-firebase-json to configure firebase_options.dart. # FIREBASE_API_KEY_ANDROID is automatically derived from google-services.json during CI releases. # Populating these variables overrides the auto-derived values. # ๐Ÿ”จ Build-time (Infisical `/build-time` folder) FIREBASE_API_KEY_ANDROID= FIREBASE_API_KEY_IOS= +FIREBASE_ANDROID_APP_ID= +FIREBASE_IOS_APP_ID= +FIREBASE_MESSAGING_SENDER_ID= +FIREBASE_PROJECT_ID= +FIREBASE_STORAGE_BUCKET= +FIREBASE_IOS_BUNDLE_ID= + # ---------------------------------------------------------------------------- # Development / Local Sync Utilities @@ -353,19 +365,13 @@ CRON_SECRET= # ๐Ÿš€ Runtime (Infisical `/runtime` folder โ†’ Server Env Var โ€” optional) REQUEST_SIGNATURE_MAX_AGE=600 -# โš ๏ธ RSA Private Key for JWE bi-directional encryption/decryption -# Generate: node scripts/generate-jwe-keys.js -# Paste the PEM content (replace newlines with \n for .env) -# ๐Ÿš€ Runtime (Infisical `/runtime` folder โ†’ Server Env Var) -JWE_PRIVATE_KEY= - # ---------------------------------------------------------------------------- # Mobile Security & Attestation (Zero-Trust) # ---------------------------------------------------------------------------- # โš ๏ธ Minimum supported app version required to bypass forced update # ๐Ÿš€ Runtime (Infisical `/runtime` folder โ†’ Server Env Var) -MIN_APP_VERSION=4.4.9 +MIN_APP_VERSION=4.5.0 # โ„น๏ธ Enforce Firebase App Check for all mobile clients in production # Valid: "true", "false" (default: false in dev, true recommended in prod) diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 00000000..babf9b2c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,7 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: "" +labels: "" +assignees: "" +--- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7f7fc119..24ba603a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -28,6 +28,7 @@ assignees: [] ## Checklist -- [ ] I have searched existing issues and this feature has not been requested before +- [ ] I have searched existing issues and this feature has not been requested + before - [ ] This feature would benefit the majority of GhostClass users - [ ] I am willing to help implement this feature (optional) diff --git a/.github/actions/flutter-action/action.yaml b/.github/actions/flutter-action/action.yaml index 635024d5..c568836d 100644 --- a/.github/actions/flutter-action/action.yaml +++ b/.github/actions/flutter-action/action.yaml @@ -102,28 +102,38 @@ runs: - name: Set action inputs id: flutter-action shell: bash + env: + INPUT_FLUTTER_VERSION: ${{ inputs.flutter-version }} + INPUT_FLUTTER_VERSION_FILE: ${{ inputs.flutter-version-file }} + INPUT_ARCHITECTURE: ${{ inputs.architecture }} + INPUT_CACHE_KEY: ${{ inputs.cache-key }} + INPUT_CACHE_PATH: ${{ inputs.cache-path }} + INPUT_PUB_CACHE_KEY: ${{ inputs.pub-cache-key }} + INPUT_PUB_CACHE_PATH: ${{ inputs.pub-cache-path }} + INPUT_GIT_SOURCE: ${{ inputs.git-source }} + INPUT_CHANNEL: ${{ inputs.channel }} run: | $GITHUB_ACTION_PATH/setup.sh -p \ - -n '${{ inputs.flutter-version }}' \ - -f '${{ inputs.flutter-version-file }}' \ - -a '${{ inputs.architecture }}' \ - -k '${{ inputs.cache-key }}' \ - -c '${{ inputs.cache-path }}' \ - -l '${{ inputs.pub-cache-key }}' \ - -d '${{ inputs.pub-cache-path }}' \ - -g '${{ inputs.git-source }}' \ - ${{ inputs.channel }} + -n "$INPUT_FLUTTER_VERSION" \ + -f "$INPUT_FLUTTER_VERSION_FILE" \ + -a "$INPUT_ARCHITECTURE" \ + -k "$INPUT_CACHE_KEY" \ + -c "$INPUT_CACHE_PATH" \ + -l "$INPUT_PUB_CACHE_KEY" \ + -d "$INPUT_PUB_CACHE_PATH" \ + -g "$INPUT_GIT_SOURCE" \ + "$INPUT_CHANNEL" - name: Cache Flutter id: cache-flutter - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.3 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 if: ${{ inputs.cache == 'true' }} with: path: ${{ steps.flutter-action.outputs.CACHE-PATH }} key: ${{ steps.flutter-action.outputs.CACHE-KEY }} - name: Cache pub dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.3 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 id: cache-pub if: ${{ (inputs.pub-cache == '' && inputs.cache == 'true') || inputs.pub-cache == 'true' }} with: @@ -133,10 +143,16 @@ runs: - name: Run setup script shell: bash if: ${{ inputs.dry-run != 'true' && inputs.dry-run != true }} + env: + OUTPUT_VERSION: ${{ steps.flutter-action.outputs.VERSION }} + OUTPUT_ARCHITECTURE: ${{ steps.flutter-action.outputs.ARCHITECTURE }} + OUTPUT_CACHE_PATH: ${{ steps.flutter-action.outputs.CACHE-PATH }} + OUTPUT_PUB_CACHE_PATH: ${{ steps.flutter-action.outputs.PUB-CACHE-PATH }} + OUTPUT_CHANNEL: ${{ steps.flutter-action.outputs.CHANNEL }} run: | $GITHUB_ACTION_PATH/setup.sh \ - -n '${{ steps.flutter-action.outputs.VERSION }}' \ - -a '${{ steps.flutter-action.outputs.ARCHITECTURE }}' \ - -c '${{ steps.flutter-action.outputs.CACHE-PATH }}' \ - -d '${{ steps.flutter-action.outputs.PUB-CACHE-PATH }}' \ - ${{ steps.flutter-action.outputs.CHANNEL }} + -n "$OUTPUT_VERSION" \ + -a "$OUTPUT_ARCHITECTURE" \ + -c "$OUTPUT_CACHE_PATH" \ + -d "$OUTPUT_PUB_CACHE_PATH" \ + "$OUTPUT_CHANNEL" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3dfb0307..be0a5d7d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,313 +1,345 @@ -# Copilot Instructions for GhostClass - -GhostClass is a full-stack monorepo with two first-class clients sharing the same backend/security model: - -- Web app: Next.js (App Router) + React + TypeScript (strict) -- Mobile app: Flutter + Dart (Android/iOS) - -The product helps students manage attendance using EzyGo data, with bunk calculation, calendar/history, disputed-absence tracking, scores, leave status, and notifications. - ---- - -## Repository Layout - -```text -src/ # Next.js web app source - app/ # App Router pages and API routes - components/ # Reusable React components - hooks/ # Custom hooks (TanStack Query, utilities) - lib/ # Core logic/security/supabase/axios/crypto/logger - providers/ # React context providers - types/ # TS type definitions - assets/ # Static assets -mobile/ # Flutter mobile application - lib/ # Dart app code (screens/services/providers/router) - android/ # Android host app - ios/ # iOS host app - packages/ # Vendored Flutter packages (Play Integrity wrapper) - test/ # Flutter tests -supabase/ # DB config + SQL migrations -workers/ # CF Worker + AWS Lambda proxy services -e2e/ # Playwright E2E tests (web) -scripts/ # Node scripts (versioning/secrets) -docs/ # Developer documentation -public/openapi/ # OpenAPI 3.1 source (`openapi.yaml`) -``` - -Key web config at root: `next.config.ts`, `vitest.config.ts`, `playwright.config.ts`, `postcss.config.mjs`, `eslint.config.mjs`, `tsconfig.json`. - -Key mobile config in `mobile/`: `pubspec.yaml`, `analysis_options.yaml`, `android/build.gradle.kts`, `ios/Runner.xcodeproj`. - ---- - -## Tech Stack - -### Web - -| Layer | Technology | -|---|---| -| Framework | Next.js (App Router), React, TypeScript (strict) | -| UI | Tailwind CSS, Radix UI, Shadcn UI, Framer Motion, Lucide | -| Data / Forms | TanStack Query v5, React Hook Form, Zod v4 | -| Charts | Recharts | -| Auth / DB | Supabase (PostgreSQL + RLS), `@supabase/ssr` | -| Security | AES-256-GCM, CSRF, Upstash Redis rate limiting, Cloudflare Turnstile, CSP | -| HTTP | Axios + interceptors, LRU Cache | -| Monitoring | Sentry (`sentry.server.config.ts`, `sentry.edge.config.ts`, `src/instrumentation.ts`) | -| PWA | Serwist (`src/sw.ts`) | -| Testing | Vitest + Playwright | - -### Mobile - -| Layer | Technology | -|---|---| -| Framework | Flutter, Dart | -| State | Riverpod 3 (`flutter_riverpod`, `riverpod_annotation`, generator) | -| HTTP / Backend | Dio, Supabase Flutter | -| Routing | GoRouter | -| Security | Firebase App Check, Play Integrity (Android), DeviceCheck (iOS), JWE (`jose` + `pointycastle`), `flutter_secure_storage` | -| UI / Charts | Material 3, `google_fonts`, `flutter_animate`, `fl_chart`, `lucide_icons` | -| Monitoring | `sentry_flutter`, `sentry_dio` | - ---- - -## Development Commands - -### Web (repo root) - -Recommended: use the package manager the repo is configured for (`npm`, `pnpm`, or `yarn`) โ€” check `package.json`. - -```bash -# install dependencies (choose one) -npm ci -# or -pnpm install -# run local dev -npm run dev # or `pnpm dev` -npm run dev:turbopack # optional turbopack dev mode -npm run build -npm run lint -npm run format -npm run test -npm run test:coverage -npm run test:e2e # runs Playwright E2E (CI uses chromium project) -``` - -### Mobile (`mobile/`) - -```bash -cd mobile -flutter pub get -flutter analyze -flutter test -flutter test --coverage -flutter run -flutter build apk --debug -flutter build appbundle --release -flutter build ios --release # macOS + Xcode required -``` - -If you use a different Flutter toolchain (fvm, etc.), prefer that in CI and local docs. - ---- - -## Environment and Secrets - -### Web env - -GhostClass utilizes **Infisical** as the single source of truth for runtime and CI secrets. Common folders include `/build-time`, `/runtime`, and `/ci`. - -Developers should authenticate via `infisical login` and run services using `infisical run -- ` (for example `infisical run -- npm run dev`). - -Critical upstream dashboard variables mapped include: - -- `ENCRYPTION_KEY` (64 hex chars, AES-256-GCM - stored as masked secret) -- `REQUEST_SIGNING_SECRET` (64 hex chars; stored as masked secret) -- `NEXT_PUBLIC_SUPABASE_URL` (synced automatically as GitHub Actions Variable) -- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` (synced automatically as GitHub Actions Variable) -- `SUPABASE_SECRET_KEY` (server-only secret) -- `NEXT_PUBLIC_BACKEND_URL` -- `NEXT_PUBLIC_TURNSTILE_SITE_KEY` / `TURNSTILE_SECRET_KEY` -- `CF_PROXY_URL` / `CF_PROXY_SECRET` and optional AWS failover proxy vars - -### Mobile secrets - -`mobile/lib/config/app_secrets.dart` is gitignored and must be created locally. - -- Never commit `app_secrets.dart` -- Never hardcode production secrets in source -- Keep Firebase config files and App Check credentials environment-specific - ---- - -## Path Alias - -Web alias: `@/` resolves to `src/`. - -```typescript -import { calculateAttendance } from '@/lib/logic/bunk'; -import { createClient } from '@/lib/supabase/client'; -``` - ---- - -## Testing Guidance - -### Web tests - -- Vitest uses `jsdom`, globals, and setup from `vitest.setup.ts` -- Test files: `**/*.{test,spec}.{ts,tsx}` under `src/` (excluding `e2e/`) -- Coverage thresholds are defined in `vitest.config.ts`. -- Prefer Arrange-Act-Assert -- Use `it.todo()` for deferred coverage - -Important mocking patterns: - -- Mock spinner libs (`ldrs/react`, `Ring2`) as simple divs -- React Query mocks must include `useQuery` and `useQueryClient` -- Framer Motion mocks should include `AnimatePresence`, `LazyMotion`, `domAnimation`, `motion.div` -- Virtualizer mocks should include `measureElement` and `measure` -- Supabase auth mocks should include `auth.getUser` and `auth.getSession` -- With fake timers, prefer `fireEvent` over `userEvent` - -### Mobile tests - -- Run `flutter analyze` before opening PRs touching Dart code -- Run `flutter test` for logic/provider/widget changes -- Keep provider/business logic testable and separated from widget concerns - ---- - -## Security Rules - -### Shared - -- Validate all untrusted input -- Do not leak secrets to client-visible code -- Keep cryptographic responsibilities in dedicated security modules - -### Web-specific - -- Do not use `window.open()` for link navigation -- For links inside labels: `preventDefault()` + `stopPropagation()`, then create/click anchor with `rel="noopener noreferrer"` -- External `target="_blank"` links must include `rel="noopener noreferrer"` -- Check `res.ok` before `res.json()` on fetch -- Server-side EzyGo calls must go through `egressFetch()` / `egressAxios` in `src/lib/utils.server.ts` - -### Mobile-specific - -- Keep EzyGo/Supabase/session material in `flutter_secure_storage`, not plain preferences -- Maintain App Check and integrity validation paths (Play Integrity / DeviceCheck) -- Preserve JWE request wrapping in networking layer (`api_service`, `jwe_interceptor`, `jwe_service`) -- Maintain Android anti-tapjacking/secure-screen protections in `MainActivity` - ---- - -## App-Specific Architecture Notes - -- Attendance calculation remains centered on `calculateAttendance` in web `src/lib/logic/bunk.ts` and mirrored logic in mobile `mobile/lib/logic/bunk.dart` -- Attendance code `225` (Duty Leave) is capped at 5/course/semester by DB trigger `check_225_attendance_limit()` -- Disabled courses are stored in `user_settings.disabled_courses` JSONB keyed by academic period -- EzyGo `/summery` typo fields are normalized by data hooks/types -- Cron sync normalizes date keys (`YYYYMMDD` and `YYYY-MM-DD`) before reconciliation - ---- - -## Code Style and Conventions - -### Web - -- Strict TypeScript; avoid `any` unless unavoidable -- Keep UI components focused; move data-fetching/logic into hooks and `lib/` -- Keep Shadcn UI components under `src/components/ui/` -- Tailwind v4 PostCSS uses object plugin form: `{ '@tailwindcss/postcss': {} }` - -### Mobile - -- Follow `analysis_options.yaml` rules and keep analyzer clean -- Keep state in Riverpod providers, keep screens mostly compositional -- Keep service layer boundaries explicit (`services/` for API/security/storage) -- Prefer typed models and exceptions over dynamic maps in UI code - -### Commits - -- Conventional commits: `(): ` -- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci` - ---- - -## CI/CD and Versioning - -### Version bumping - -Version bumping is automated via workflows (see `.github/workflows/auto-version-bump.yml`). - -- Same-repo PRs: workflow handles bump -- Fork PRs: run bump script manually, then commit versioned artifacts - -Files that must stay in sync for version bumps: - -- `package.json` -- `package-lock.json` (or `pnpm-lock.yaml` / `yarn.lock`) -- `.example.env` (`NEXT_PUBLIC_APP_VERSION`) -- `public/openapi/openapi.yaml` - -When updating core dependency versions, update `package.json` and run the install command for the chosen package manager. - -### Main workflows - -| Workflow | Purpose | -|---|---| -| `test.yml` | Web unit coverage + web Playwright E2E | -| `pipeline.yml` | Guard + auto-tag on merge to main | -| `auto-version-bump.yml` | PR version bump automation | -| `release.yml` | Signed release build + deploy pipeline | -| `deploy-egress-proxies.yml` | Deploy CF/AWS proxies | -| `deploy-supabase.yaml` | Supabase migration deployment | -| `provenance.yml` | Build provenance attestations | -| `scorecard.yml` | OpenSSF scorecard checks | - -Dependabot PRs do not have repository secrets; secret-dependent jobs must stay guarded. - ---- - -## Known Gotchas - -- Serwist + `output: "standalone"` needs explicit SW build step in Docker -- `npm run dev` uses webpack by default for PWA compatibility -- Use RSA 4096 GPG keys for CI signing (avoid ECC key issues in CI) -- Fake timers + `userEvent` can conflict in Vitest; use `fireEvent` -- Recharts `ResponsiveContainer` can be noisy in tests; prefer direct dimension control - -## Recommended local setup - -- Check which package manager the repo uses (`package.json` may include `packageManager`). -- Use `infisical login` then `infisical run -- npm run dev` to ensure runtime secrets are loaded locally. -- Run `npm run format` before committing; run `npm run lint` to catch style/typing issues. - -## PR checklist - -- Update tests for any behavioral change -- Run `npm run lint` and `npm run format` -- Ensure secrets or credentials are not included in the diff -- Add a short description of the change and link related docs/migrations -- For mobile changes, run `flutter analyze` and `flutter test` - -## Where to update versions and references - -- Web: update `package.json` and `tsconfig.json` as needed -- Mobile: update `mobile/pubspec.yaml` -- OpenAPI: update `public/openapi/openapi.yaml` and run any generation scripts in `scripts/` - ---- - -## Database - -Supabase schema/migrations are under `supabase/migrations/`. - -```bash -npx supabase link --project-ref -npx supabase db push -``` - -RLS policies are required for user-scoped data access; preserve policy intent when editing migrations. +# Copilot Instructions for GhostClass + +GhostClass is a full-stack monorepo with two first-class clients sharing the +same backend/security model: + +- Web app: Next.js (App Router) + React + TypeScript (strict) +- Mobile app: Flutter + Dart (Android/iOS) + +The product helps students manage attendance using EzyGo data, with bunk +calculation, calendar/history, disputed-absence tracking, scores, leave status, +and notifications. + +--- + +## Repository Layout + +```text +src/ # Next.js web app source + app/ # App Router pages and API routes + components/ # Reusable React components + hooks/ # Custom hooks (TanStack Query, utilities) + lib/ # Core logic/security/supabase/axios/crypto/logger + providers/ # React context providers + types/ # TS type definitions + assets/ # Static assets +mobile/ # Flutter mobile application + lib/ # Dart app code (screens/services/providers/router) + android/ # Android host app + ios/ # iOS host app + packages/ # Vendored Flutter packages (Play Integrity wrapper) + test/ # Flutter tests +supabase/ # DB config + SQL migrations +workers/ # CF Worker + AWS Lambda proxy services +e2e/ # Playwright E2E tests (web) +scripts/ # Node scripts (versioning/secrets) +docs/ # Developer documentation +public/openapi/ # OpenAPI 3.1 source (`openapi.yaml`) +``` + +Key web config at root: `next.config.ts`, `vitest.config.ts`, +`playwright.config.ts`, `postcss.config.mjs`, `eslint.config.mjs`, +`tsconfig.json`. + +Key mobile config in `mobile/`: `pubspec.yaml`, `analysis_options.yaml`, +`android/build.gradle.kts`, `ios/Runner.xcodeproj`. + +--- + +## Tech Stack + +### Web + +| Layer | Technology | +| ------------ | ------------------------------------------------------------------------------------- | +| Framework | Next.js (App Router), React, TypeScript (strict) | +| UI | Tailwind CSS, Radix UI, Shadcn UI, Framer Motion, Lucide | +| Data / Forms | TanStack Query v5, React Hook Form, Zod v4 | +| Charts | Recharts | +| Auth / DB | Supabase (PostgreSQL + RLS), `@supabase/ssr` | +| Security | AES-256-GCM, CSRF, Upstash Redis rate limiting, Cloudflare Turnstile, CSP | +| HTTP | Axios + interceptors, LRU Cache | +| Monitoring | Sentry (`sentry.server.config.ts`, `sentry.edge.config.ts`, `src/instrumentation.ts`) | +| PWA | Serwist (`src/sw.ts`) | +| Testing | Vitest + Playwright | + +### Mobile + +| Layer | Technology | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| Framework | Flutter, Dart | +| State | Riverpod 3 (`flutter_riverpod`, `riverpod_annotation`, generator) | +| HTTP / Backend | Dio, Supabase Flutter | +| Routing | GoRouter | +| Security | Firebase App Check, Play Integrity (Android), DeviceCheck (iOS), JWE (`jose` + `pointycastle`), `flutter_secure_storage` | +| UI / Charts | Material 3, `google_fonts`, `flutter_animate`, `fl_chart`, `lucide_icons` | +| Monitoring | `sentry_flutter`, `sentry_dio` | + +--- + +## Development Commands + +### Web (repo root) + +Recommended: use the package manager the repo is configured for (`npm`, `pnpm`, +or `yarn`) โ€” check `package.json`. + +```bash +# install dependencies (choose one) +npm ci +# or +pnpm install +# run local dev +npm run dev # or `pnpm dev` +npm run dev:turbopack # optional turbopack dev mode +npm run build +npm run lint +npm run format +npm run test +npm run test:coverage +npm run test:e2e # runs Playwright E2E (CI uses chromium project) +``` + +### Mobile (`mobile/`) + +```bash +cd mobile +flutter pub get +flutter analyze +flutter test +flutter test --coverage +flutter run +flutter build apk --debug +flutter build appbundle --release +flutter build ios --release # macOS + Xcode required +``` + +If you use a different Flutter toolchain (fvm, etc.), prefer that in CI and +local docs. + +--- + +## Environment and Secrets + +### Web env + +GhostClass utilizes **Infisical** as the single source of truth for runtime and +CI secrets. Common folders include `/build-time`, `/runtime`, and `/ci`. + +Developers should authenticate via `infisical login` and run services using +`infisical run -- ` (for example `infisical run -- npm run dev`). + +Critical upstream dashboard variables mapped include: + +- `ENCRYPTION_KEY` (64 hex chars, AES-256-GCM - stored as masked secret) +- `REQUEST_SIGNING_SECRET` (64 hex chars; stored as masked secret) +- `NEXT_PUBLIC_SUPABASE_URL` (synced automatically as GitHub Actions Variable) +- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` (synced automatically as GitHub Actions + Variable) +- `SUPABASE_SECRET_KEY` (server-only secret) +- `NEXT_PUBLIC_BACKEND_URL` +- `NEXT_PUBLIC_TURNSTILE_SITE_KEY` / `TURNSTILE_SECRET_KEY` +- `CF_PROXY_URL` / `CF_PROXY_SECRET` and optional AWS failover proxy vars + +### Mobile secrets + +`mobile/lib/config/app_secrets.dart` is gitignored and must be created locally. + +- Never commit `app_secrets.dart` +- Never hardcode production secrets in source +- Keep Firebase config files and App Check credentials environment-specific + +--- + +## Path Alias + +Web alias: `@/` resolves to `src/`. + +```typescript +import { calculateAttendance } from "@/lib/logic/bunk"; +import { createClient } from "@/lib/supabase/client"; +``` + +--- + +## Testing Guidance + +### Web tests + +- Vitest uses `jsdom`, globals, and setup from `vitest.setup.ts` +- Test files: `**/*.{test,spec}.{ts,tsx}` under `src/` (excluding `e2e/`) +- Coverage thresholds are defined in `vitest.config.ts`. +- Prefer Arrange-Act-Assert +- Use `it.todo()` for deferred coverage + +Important mocking patterns: + +- Mock spinner libs (`ldrs/react`, `Ring2`) as simple divs +- React Query mocks must include `useQuery` and `useQueryClient` +- Framer Motion mocks should include `AnimatePresence`, `LazyMotion`, + `domAnimation`, `motion.div` +- Virtualizer mocks should include `measureElement` and `measure` +- Supabase auth mocks should include `auth.getUser` and `auth.getSession` +- With fake timers, prefer `fireEvent` over `userEvent` + +### Mobile tests + +- Run `flutter analyze` before opening PRs touching Dart code +- Run `flutter test` for logic/provider/widget changes +- Keep provider/business logic testable and separated from widget concerns + +--- + +## Security Rules + +### Shared + +- Validate all untrusted input +- Do not leak secrets to client-visible code +- Keep cryptographic responsibilities in dedicated security modules + +### Web-specific + +- Do not use `window.open()` for link navigation +- For links inside labels: `preventDefault()` + `stopPropagation()`, then + create/click anchor with `rel="noopener noreferrer"` +- External `target="_blank"` links must include `rel="noopener noreferrer"` +- Check `res.ok` before `res.json()` on fetch +- Server-side EzyGo calls must go through `egressFetch()` / `egressAxios` in + `src/lib/utils.server.ts` + +### Mobile-specific + +- Keep EzyGo/Supabase/session material in `flutter_secure_storage`, not plain + preferences +- Maintain App Check and integrity validation paths (Play Integrity / + DeviceCheck) +- Preserve JWE request wrapping in networking layer (`api_service`, + `jwe_interceptor`, `jwe_service`) +- Maintain Android anti-tapjacking/secure-screen protections in `MainActivity` + +--- + +## App-Specific Architecture Notes + +- Attendance calculation remains centered on `calculateAttendance` in web + `src/lib/logic/bunk.ts` and mirrored logic in mobile + `mobile/lib/logic/bunk.dart` +- Attendance code `225` (Duty Leave) is capped at 5/course/semester by DB + trigger `check_225_attendance_limit()` +- Disabled courses are stored in `user_settings.disabled_courses` JSONB keyed by + academic period +- EzyGo `/summery` typo fields are normalized by data hooks/types +- Cron sync normalizes date keys (`YYYYMMDD` and `YYYY-MM-DD`) before + reconciliation + +--- + +## Code Style and Conventions + +### Web + +- Strict TypeScript; avoid `any` unless unavoidable +- Keep UI components focused; move data-fetching/logic into hooks and `lib/` +- Keep Shadcn UI components under `src/components/ui/` +- Tailwind v4 PostCSS uses object plugin form: `{ '@tailwindcss/postcss': {} }` + +### Mobile + +- Follow `analysis_options.yaml` rules and keep analyzer clean +- Keep state in Riverpod providers, keep screens mostly compositional +- Keep service layer boundaries explicit (`services/` for API/security/storage) +- Prefer typed models and exceptions over dynamic maps in UI code + +### Commits + +- Conventional commits: `(): ` +- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, + `ci` + +--- + +## CI/CD and Versioning + +### Version bumping + +Version bumping is automated via workflows (see +`.github/workflows/auto-version-bump.yml`). + +- Same-repo PRs: workflow handles bump +- Fork PRs: run bump script manually, then commit versioned artifacts + +Files that must stay in sync for version bumps: + +- `package.json` +- `package-lock.json` (or `pnpm-lock.yaml` / `yarn.lock`) +- `.example.env` (`NEXT_PUBLIC_APP_VERSION`) +- `public/openapi/openapi.yaml` + +When updating core dependency versions, update `package.json` and run the +install command for the chosen package manager. + +### Main workflows + +| Workflow | Purpose | +| --------------------------- | -------------------------------------- | +| `test.yml` | Web unit coverage + web Playwright E2E | +| `pipeline.yml` | Guard + auto-tag on merge to main | +| `auto-version-bump.yml` | PR version bump automation | +| `release.yml` | Signed release build + deploy pipeline | +| `deploy-egress-proxies.yml` | Deploy CF/AWS proxies | +| `deploy-supabase.yaml` | Supabase migration deployment | +| `provenance.yml` | Build provenance attestations | +| `scorecard.yml` | OpenSSF scorecard checks | + +Dependabot PRs do not have repository secrets; secret-dependent jobs must stay +guarded. + +--- + +## Known Gotchas + +- Serwist + `output: "standalone"` needs explicit SW build step in Docker +- `npm run dev` uses webpack by default for PWA compatibility +- Use RSA 4096 GPG keys for CI signing (avoid ECC key issues in CI) +- Fake timers + `userEvent` can conflict in Vitest; use `fireEvent` +- Recharts `ResponsiveContainer` can be noisy in tests; prefer direct dimension + control + +## Recommended local setup + +- Check which package manager the repo uses (`package.json` may include + `packageManager`). +- Use `infisical login` then `infisical run -- npm run dev` to ensure runtime + secrets are loaded locally. +- Run `npm run format` before committing; run `npm run lint` to catch + style/typing issues. + +## PR checklist + +- Update tests for any behavioral change +- Run `npm run lint` and `npm run format` +- Ensure secrets or credentials are not included in the diff +- Add a short description of the change and link related docs/migrations +- For mobile changes, run `flutter analyze` and `flutter test` + +## Where to update versions and references + +- Web: update `package.json` and `tsconfig.json` as needed +- Mobile: update `mobile/pubspec.yaml` +- OpenAPI: update `public/openapi/openapi.yaml` and run any generation scripts + in `scripts/` + +--- + +## Database + +Supabase schema/migrations are under `supabase/migrations/`. + +```bash +npx supabase link --project-ref +npx supabase db push +``` + +RLS policies are required for user-scoped data access; preserve policy intent +when editing migrations. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 21f76ab7..adea1b17 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,19 +3,21 @@ # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file -version: 2 +version: 3 multi-ecosystem-groups: all-dependencies: schedule: interval: "daily" + open-pull-requests-limit: 0 updates: - package-ecosystem: "npm" directory: "/" + schedule: + interval: "weekly" patterns: - "*" - multi-ecosystem-group: "all-dependencies" ignore: # ESLint v10 is not yet supported by typescript-eslint # Keep ESLint at v9 until typescript-eslint adds v10 support @@ -23,21 +25,30 @@ updates: update-types: ["version-update:semver-major"] - dependency-name: "@eslint/js" update-types: ["version-update:semver-major"] + multi-ecosystem-group: "all-dependencies" + open-pull-requests-limit: 0 - package-ecosystem: "docker" directory: "/" + schedule: + interval: "weekly" patterns: - "*" multi-ecosystem-group: "all-dependencies" + open-pull-requests-limit: 0 - package-ecosystem: "github-actions" directory: "/" + schedule: + interval: "weekly" patterns: - "*" multi-ecosystem-group: "all-dependencies" + open-pull-requests-limit: 0 - package-ecosystem: "pub" directory: "/mobile" schedule: - interval: "daily" + interval: "weekly" multi-ecosystem-group: "all-dependencies" + open-pull-requests-limit: 0 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8e3bc7d0..4c23803a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,7 +10,8 @@ - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Breaking change (fix or feature that would cause existing functionality to + not work as expected) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement @@ -20,8 +21,7 @@ -Closes # -Relates to # +Closes # Relates to # ## Changes Made @@ -54,7 +54,6 @@ Relates to # - [ ] Flutter tests pass (`flutter test`) - [ ] Manual testing on Android (Emulator/Physical) - [ ] Manual testing on iOS (Simulator/Physical) -- [ ] JWE encryption/decryption verified - [ ] EzyGo direct access verified (mobile) ### Test Coverage diff --git a/.github/workflows/build-guard.yml b/.github/workflows/build-guard.yml index 0d91edf6..308158c1 100644 --- a/.github/workflows/build-guard.yml +++ b/.github/workflows/build-guard.yml @@ -19,7 +19,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Verify commit is signed run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3493be29..66b3d6ae 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -13,102 +14,52 @@ permissions: contents: read jobs: - detect-changes: - name: Detect Changes - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - outputs: - js: ${{ steps.filter.outputs.js }} - android: ${{ steps.filter.outputs.android }} - ios: ${{ steps.filter.outputs.ios }} - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - name: Detect path changes - uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0 - id: filter - with: - filters: | - js: - - "src/**" - - "public/**" - - "supabase/**" - - "package.json" - - "package-lock.json" - - "tsconfig.json" - - "next.config.ts" - - "eslint.config.mjs" - - "components.json" - - ".github/workflows/codeql.yml" - android: - - "mobile/android/**" - - "mobile/lib/**" - - "mobile/pubspec.yaml" - - "mobile/pubspec.lock" - - ".github/workflows/codeql.yml" - ios: - - "mobile/ios/**" - - "mobile/lib/**" - - "mobile/pubspec.yaml" - - "mobile/pubspec.lock" - - ".github/workflows/codeql.yml" - codeql-js: name: CodeQL JavaScript/TypeScript - needs: detect-changes - if: github.event_name == 'push' || needs.detect-changes.outputs.js == 'true' runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - fetch-depth: 0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - name: Initialize CodeQL (JS) - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e + - name: Initialize CodeQL (JS/TS) + uses: github/codeql-action/init@e58424170fb0262c8d7ed60a2e84b9bffe205c67 with: - languages: javascript + languages: javascript-typescript - - name: Autobuild (JS) - uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e + - name: Autobuild (JS/TS) + uses: github/codeql-action/autobuild@e58424170fb0262c8d7ed60a2e84b9bffe205c67 - - name: Run CodeQL analysis (JS) - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e + - name: Run CodeQL analysis (JS/TS) + uses: github/codeql-action/analyze@e58424170fb0262c8d7ed60a2e84b9bffe205c67 with: - category: javascript + category: javascript-typescript codeql-android: name: CodeQL Android (Java/Kotlin) - needs: detect-changes - if: github.event_name == 'push' || needs.detect-changes.outputs.android == 'true' runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - fetch-depth: 0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Download Gradle Wrapper run: | mkdir -p mobile/android/gradle/wrapper - curl -fSL -o mobile/android/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/gradle/gradle/v8.14.0/gradle/wrapper/gradle-wrapper.jar - echo "7d3a4ac4de1c32b59bc6a4eb8ecb8e612ccd0cf1ae1e99f66902da64df296172 mobile/android/gradle/wrapper/gradle-wrapper.jar" | sha256sum -c - + curl -fSL -o mobile/android/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/gradle/gradle/v9.6.1/gradle/wrapper/gradle-wrapper.jar + echo "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7 mobile/android/gradle/wrapper/gradle-wrapper.jar" | sha256sum -c - - - name: Set up JDK 17 - uses: actions/setup-java@b622de1dfa918ecc0ab28f40cd42e3c3752cd6c5 + - name: Set up JDK + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 with: distribution: "temurin" - java-version: "17" + java-version: "25" cache: "gradle" + cache-dependency-path: "mobile/android/gradle/wrapper/gradle-wrapper.properties" - name: Set up Flutter uses: ./.github/actions/flutter-action @@ -116,177 +67,49 @@ jobs: channel: stable cache: true - - name: Create CI app secrets stub - working-directory: mobile - run: cp lib/config/app_secrets.dart.example lib/config/app_secrets.dart - - - name: Create CI Firebase Android stub + - name: Fetch Flutter dependencies working-directory: mobile - run: | - cat > android/app/google-services.json <<'EOF' - { - "project_info": { - "project_number": "424804867878", - "project_id": "devakesu-ghostclass", - "storage_bucket": "devakesu-ghostclass.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:424804867878:android:df401041d564c22b21abe7", - "android_client_info": { - "package_name": "com.devakesu.apps.ghostclass" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyDUMMY" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" - } - EOF + run: flutter pub get - - name: Create CI Firebase iOS stub + - name: Prepare app_secrets.dart working-directory: mobile - run: | - cat > ios/Runner/GoogleService-Info.plist <<'EOF' - - - - - API_KEY - AIzaSyDUMMY - BUNDLE_ID - com.devakesu.apps.ghostclass - GCM_SENDER_ID - 424804867878 - GOOGLE_APP_ID - 1:424804867878:ios:d132bb8be987f52d21abe7 - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - PLIST_VERSION - 1 - PROJECT_ID - devakesu-ghostclass - STORAGE_BUCKET - devakesu-ghostclass.appspot.com - - - EOF + run: cp lib/config/app_secrets.dart.example lib/config/app_secrets.dart - - name: Downgrade device_info_plus for compatibility + - name: Prepare google-services.json working-directory: mobile run: | - sed -i 's/device_info_plus: .*/device_info_plus: 12.2.0/g' pubspec.yaml - - - name: Fetch Flutter dependencies - working-directory: mobile - run: flutter pub get - - - name: Install Android SDK (basic) - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 - with: - accept-android-sdk-licenses: true - packages: 'platform-tools build-tools;33.0.2 platforms;android-33' + set -euo pipefail + if [ -n "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then + printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > android/app/google-services.json + echo "โœ“ Recreated android/app/google-services.json from secret" + elif [ -f android/app/google-services.json ]; then + echo "โœ“ Using checked-in android/app/google-services.json" + else + node -e ' + const fs = require("fs"); + const stub = { + project_info: { project_number: "424804867878", project_id: "devakesu-ghostclass", storage_bucket: "devakesu-ghostclass.appspot.com" }, + client: [ + { client_info: { mobilesdk_app_id: "1:424804867878:android:df401041d564c22b21abe7", android_client_info: { package_name: "com.devakesu.apps.ghostclass" } }, oauth_client: [], api_key: [{ current_key: "AIzaSyDUMMY" }], services: { appinvite_service: { other_platform_oauth_client: [] } } } + ], + configuration_version: "1" + }; + fs.writeFileSync("android/app/google-services.json", JSON.stringify(stub, null, 2)); + ' + echo "โœ“ Created CI stub android/app/google-services.json" + fi - name: Initialize CodeQL (Java + Kotlin) - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e + uses: github/codeql-action/init@e58424170fb0262c8d7ed60a2e84b9bffe205c67 with: languages: java-kotlin build-mode: manual - - name: Build Android app (required for manual CodeQL extraction) + - name: Build Android Kotlin source (Triggers CodeQL extraction) working-directory: mobile/android - run: | - chmod +x ./gradlew - ./gradlew assembleDebug --no-daemon -Pci + run: ./gradlew :app:compileDebugSources --no-daemon - name: Run CodeQL analysis (Android) - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e + uses: github/codeql-action/analyze@e58424170fb0262c8d7ed60a2e84b9bffe205c67 with: category: java,kotlin - - codeql-ios: - name: CodeQL iOS (Swift) - needs: detect-changes - if: github.event_name == 'push' || needs.detect-changes.outputs.ios == 'true' - runs-on: macos-latest - permissions: - contents: read - security-events: write - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - fetch-depth: 0 - - - name: Set up Flutter - uses: ./.github/actions/flutter-action - with: - channel: stable - cache: true - - - name: Create CI app secrets stub - working-directory: mobile - run: cp lib/config/app_secrets.dart.example lib/config/app_secrets.dart - - - name: Downgrade device_info_plus for compatibility - working-directory: mobile - run: | - sed -i '' 's/device_info_plus: .*/device_info_plus: 12.2.0/g' pubspec.yaml - - - name: Fetch Flutter dependencies - working-directory: mobile - run: flutter pub get - - - name: Cache CocoaPods - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae - with: - path: mobile/ios/Pods - key: ${{ runner.os }}-pods-${{ hashFiles('mobile/pubspec.lock', 'mobile/ios/Podfile') }} - restore-keys: | - ${{ runner.os }}-pods- - - - name: Configure iOS project for simulator build - working-directory: mobile - run: flutter build ios --simulator --no-codesign - - - name: Initialize CodeQL (Swift) - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e - with: - languages: swift - build-mode: manual - - - name: Build iOS app - run: | - set -o pipefail - xcodebuild -workspace mobile/ios/Runner.xcworkspace \ - -scheme Runner \ - -configuration Debug \ - -sdk iphonesimulator \ - -destination 'generic/platform=iOS Simulator' \ - -derivedDataPath ~/Library/Developer/Xcode/DerivedData \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGNING_ALLOWED=NO - - - name: Run CodeQL analysis (Swift) - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e - with: - category: swift diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ae111d97..03231265 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Fetch CI secrets from Infisical if: env.INFISICAL_CLIENT_ID != '' && env.INFISICAL_CLIENT_SECRET != '' @@ -37,9 +37,9 @@ jobs: continue-on-error: true - name: Setup Supabase CLI - uses: supabase/setup-cli@df56b21da46c98abb12a9804e4fb1f657773e333 + uses: supabase/setup-cli@46f7f98c7f948ad727d22c1e67fab04c223a0520 with: - version: 2.98.2 + version: 2.111.0 - name: Link Project env: @@ -69,12 +69,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: "24.14.1" + node-version: "24.18.1" - name: Fetch build-time variables from Infisical (Unmasked) if: env.INFISICAL_CLIENT_ID != '' && env.INFISICAL_CLIENT_SECRET != '' diff --git a/.github/workflows/mobile-test.yml b/.github/workflows/mobile-test.yml index 68ebd182..1f83caba 100644 --- a/.github/workflows/mobile-test.yml +++ b/.github/workflows/mobile-test.yml @@ -21,13 +21,13 @@ jobs: working-directory: mobile steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Set up Java - uses: actions/setup-java@b622de1dfa918ecc0ab28f40cd42e3c3752cd6c5 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 with: distribution: temurin - java-version: "17" + java-version: "25" - name: Set up Flutter uses: ./.github/actions/flutter-action @@ -38,6 +38,9 @@ jobs: - name: Prepare app_secrets.dart run: cp lib/config/app_secrets.dart.example lib/config/app_secrets.dart + - name: Generate dynamic firebase.json + run: node ../scripts/generate-firebase-json.js + - name: Install dependencies run: flutter pub get @@ -78,7 +81,7 @@ jobs: fi - name: Upload coverage to Codecov - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f with: files: ./mobile/coverage/lcov.info flags: mobile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4eb77843..f33a840c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,16 +25,16 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 with: - scan-type: 'fs' - scan-ref: '.' - trivy-config: 'trivy.yaml' - exit-code: '1' - severity: 'CRITICAL,HIGH' + scan-type: "fs" + scan-ref: "." + trivy-config: "trivy.yaml" + exit-code: "1" + severity: "CRITICAL,HIGH" # Build, sign, and push Docker images build-and-release: @@ -54,13 +54,13 @@ jobs: INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c - name: Login to GHCR - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 with: registry: ghcr.io username: ${{ github.actor }} @@ -156,7 +156,7 @@ jobs: - name: Build & push Docker image id: build-push - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: . push: true @@ -213,7 +213,7 @@ jobs: # Attest build provenance - name: Attest Build Provenance - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 with: subject-name: ghcr.io/${{ github.repository_owner }}/${{ steps.prep.outputs.image_name }} subject-digest: ${{ steps.build-push.outputs.digest }} @@ -280,7 +280,7 @@ jobs: # Attest SBOM - name: Attest SBOM - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 with: subject-name: ghcr.io/${{ github.repository_owner }}/${{ steps.prep.outputs.image_name }} subject-digest: ${{ steps.build-push.outputs.digest }} @@ -392,7 +392,7 @@ jobs: INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Download Gradle Wrapper run: | @@ -401,10 +401,10 @@ jobs: echo "7d3a4ac4de1c32b59bc6a4eb8ecb8e612ccd0cf1ae1e99f66902da64df296172 mobile/android/gradle/wrapper/gradle-wrapper.jar" | sha256sum -c - - name: Set up Java - uses: actions/setup-java@b622de1dfa918ecc0ab28f40cd42e3c3752cd6c5 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 with: distribution: temurin - java-version: "17" + java-version: "25" - name: Set up Flutter uses: ./.github/actions/flutter-action @@ -443,7 +443,7 @@ jobs: fi echo "build_timestamp=${BUILD_TIMESTAMP}" >> "$GITHUB_OUTPUT" - # Derive APP_DOMAIN and BACKEND_URL (duplicated from build-and-release for job isolation) + # Derive APP_DOMAIN and BACKEND_URL APP_DOMAIN="${{ env.NEXT_PUBLIC_APP_DOMAIN || vars.NEXT_PUBLIC_APP_DOMAIN }}" if [ -z "$APP_DOMAIN" ] || [ "$APP_DOMAIN" = "null" ]; then APP_DOMAIN="localhost" @@ -516,6 +516,46 @@ jobs: fi fi + if [ -z "${FIREBASE_PROJECT_ID:-}" ]; then + DERIVED_PROJECT_ID=$(jq -r '.project_info.project_id // empty' app/google-services.json) + if [ -n "$DERIVED_PROJECT_ID" ]; then + echo "FIREBASE_PROJECT_ID=$DERIVED_PROJECT_ID" >> "$GITHUB_ENV" + echo "โœ“ Derived FIREBASE_PROJECT_ID directly from google-services.json" + fi + fi + + if [ -z "${FIREBASE_MESSAGING_SENDER_ID:-}" ]; then + DERIVED_SENDER_ID=$(jq -r '.project_info.project_number // empty' app/google-services.json) + if [ -n "$DERIVED_SENDER_ID" ]; then + echo "FIREBASE_MESSAGING_SENDER_ID=$DERIVED_SENDER_ID" >> "$GITHUB_ENV" + echo "โœ“ Derived FIREBASE_MESSAGING_SENDER_ID directly from google-services.json" + fi + fi + + if [ -z "${FIREBASE_STORAGE_BUCKET:-}" ]; then + DERIVED_BUCKET=$(jq -r '.project_info.storage_bucket // empty' app/google-services.json) + if [ -n "$DERIVED_BUCKET" ]; then + echo "FIREBASE_STORAGE_BUCKET=$DERIVED_BUCKET" >> "$GITHUB_ENV" + echo "โœ“ Derived FIREBASE_STORAGE_BUCKET directly from google-services.json" + fi + fi + + if [ -z "${FIREBASE_ANDROID_APP_ID:-}" ]; then + DERIVED_APP_ID=$(jq -r '.client[0].client_info.mobilesdk_app_id // empty' app/google-services.json) + if [ -n "$DERIVED_APP_ID" ]; then + echo "FIREBASE_ANDROID_APP_ID=$DERIVED_APP_ID" >> "$GITHUB_ENV" + echo "โœ“ Derived FIREBASE_ANDROID_APP_ID directly from google-services.json" + fi + fi + + if [ -z "${ANDROID_PACKAGE_NAME:-}" ]; then + DERIVED_PKG=$(jq -r '.client[0].client_info.android_client_info.package_name // empty' app/google-services.json) + if [ -n "$DERIVED_PKG" ]; then + echo "ANDROID_PACKAGE_NAME=$DERIVED_PKG" >> "$GITHUB_ENV" + echo "โœ“ Derived ANDROID_PACKAGE_NAME directly from google-services.json" + fi + fi + - name: Generate mobile/lib/config/app_secrets.dart working-directory: mobile env: @@ -584,6 +624,18 @@ jobs: env: BUILD_TIMESTAMP: ${{ steps.mobile-prep.outputs.build_timestamp }} SENTRY_AUTH_TOKEN: ${{ env.MOBILE_SENTRY_AUTH_TOKEN || secrets.MOBILE_SENTRY_AUTH_TOKEN || env.SENTRY_AUTH_TOKEN || secrets.SENTRY_AUTH_TOKEN }} + APP_DOMAIN: ${{ env.APP_DOMAIN }} + APP_NAME: ${{ env.APP_NAME || env.NEXT_PUBLIC_APP_NAME || vars.APP_NAME || vars.NEXT_PUBLIC_APP_NAME }} + AUTHOR_NAME: ${{ env.AUTHOR_NAME || env.NEXT_PUBLIC_AUTHOR_NAME || vars.AUTHOR_NAME || vars.NEXT_PUBLIC_AUTHOR_NAME }} + AUTHOR_URL: ${{ env.AUTHOR_URL || env.NEXT_PUBLIC_AUTHOR_URL || vars.AUTHOR_URL || vars.NEXT_PUBLIC_AUTHOR_URL }} + GITHUB_URL: ${{ env.GITHUB_URL || env.NEXT_PUBLIC_GITHUB_URL || vars.GITHUB_URL || vars.NEXT_PUBLIC_GITHUB_URL }} + DONATE_URL: ${{ env.DONATE_URL || env.NEXT_PUBLIC_DONATE_URL || vars.DONATE_URL || vars.NEXT_PUBLIC_DONATE_URL }} + ANDROID_PACKAGE_NAME: ${{ env.ANDROID_PACKAGE_NAME || env.NEXT_PUBLIC_ANDROID_PACKAGE_NAME || vars.ANDROID_PACKAGE_NAME || vars.NEXT_PUBLIC_ANDROID_PACKAGE_NAME }} + FIREBASE_PROJECT_ID: ${{ env.FIREBASE_PROJECT_ID }} + FIREBASE_API_KEY_ANDROID: ${{ env.FIREBASE_API_KEY_ANDROID }} + FIREBASE_ANDROID_APP_ID: ${{ env.FIREBASE_ANDROID_APP_ID }} + FIREBASE_MESSAGING_SENDER_ID: ${{ env.FIREBASE_MESSAGING_SENDER_ID }} + FIREBASE_STORAGE_BUCKET: ${{ env.FIREBASE_STORAGE_BUCKET }} run: | set -euo pipefail @@ -600,8 +652,18 @@ jobs: --dart-define=BUILD_TIMESTAMP=${BUILD_TIMESTAMP} \ --dart-define=GITHUB_RUN_ID=${{ github.run_id }} \ --dart-define=GITHUB_RUN_NUMBER=${{ github.run_number }} \ + --dart-define=APP_DOMAIN="${APP_DOMAIN}" \ + --dart-define=APP_NAME="${APP_NAME:-}" \ + --dart-define=AUTHOR_NAME="${AUTHOR_NAME:-}" \ + --dart-define=AUTHOR_URL="${AUTHOR_URL:-}" \ + --dart-define=GITHUB_URL="${GITHUB_URL:-}" \ + --dart-define=DONATE_URL="${DONATE_URL:-}" \ + --dart-define=ANDROID_PACKAGE_NAME="${ANDROID_PACKAGE_NAME:-}" \ + --dart-define=FIREBASE_PROJECT_ID="${FIREBASE_PROJECT_ID:-}" \ --dart-define=FIREBASE_API_KEY_ANDROID="${FIREBASE_API_KEY_ANDROID:-}" \ - --dart-define=APP_DOMAIN="${APP_DOMAIN}" + --dart-define=FIREBASE_ANDROID_APP_ID="${FIREBASE_ANDROID_APP_ID:-}" \ + --dart-define=FIREBASE_MESSAGING_SENDER_ID="${FIREBASE_MESSAGING_SENDER_ID:-}" \ + --dart-define=FIREBASE_STORAGE_BUCKET="${FIREBASE_STORAGE_BUCKET:-}" dart run sentry_dart_plugin @@ -609,6 +671,18 @@ jobs: working-directory: mobile env: BUILD_TIMESTAMP: ${{ steps.mobile-prep.outputs.build_timestamp }} + APP_DOMAIN: ${{ env.APP_DOMAIN }} + APP_NAME: ${{ env.APP_NAME || env.NEXT_PUBLIC_APP_NAME || vars.APP_NAME || vars.NEXT_PUBLIC_APP_NAME }} + AUTHOR_NAME: ${{ env.AUTHOR_NAME || env.NEXT_PUBLIC_AUTHOR_NAME || vars.AUTHOR_NAME || vars.NEXT_PUBLIC_AUTHOR_NAME }} + AUTHOR_URL: ${{ env.AUTHOR_URL || env.NEXT_PUBLIC_AUTHOR_URL || vars.AUTHOR_URL || vars.NEXT_PUBLIC_AUTHOR_URL }} + GITHUB_URL: ${{ env.GITHUB_URL || env.NEXT_PUBLIC_GITHUB_URL || vars.GITHUB_URL || vars.NEXT_PUBLIC_GITHUB_URL }} + DONATE_URL: ${{ env.DONATE_URL || env.NEXT_PUBLIC_DONATE_URL || vars.DONATE_URL || vars.NEXT_PUBLIC_DONATE_URL }} + ANDROID_PACKAGE_NAME: ${{ env.ANDROID_PACKAGE_NAME || env.NEXT_PUBLIC_ANDROID_PACKAGE_NAME || vars.ANDROID_PACKAGE_NAME || vars.NEXT_PUBLIC_ANDROID_PACKAGE_NAME }} + FIREBASE_PROJECT_ID: ${{ env.FIREBASE_PROJECT_ID }} + FIREBASE_API_KEY_ANDROID: ${{ env.FIREBASE_API_KEY_ANDROID }} + FIREBASE_ANDROID_APP_ID: ${{ env.FIREBASE_ANDROID_APP_ID }} + FIREBASE_MESSAGING_SENDER_ID: ${{ env.FIREBASE_MESSAGING_SENDER_ID }} + FIREBASE_STORAGE_BUCKET: ${{ env.FIREBASE_STORAGE_BUCKET }} run: | set -euo pipefail @@ -625,8 +699,18 @@ jobs: --dart-define=BUILD_TIMESTAMP=${BUILD_TIMESTAMP} \ --dart-define=GITHUB_RUN_ID=${{ github.run_id }} \ --dart-define=GITHUB_RUN_NUMBER=${{ github.run_number }} \ + --dart-define=APP_DOMAIN="${APP_DOMAIN}" \ + --dart-define=APP_NAME="${APP_NAME:-}" \ + --dart-define=AUTHOR_NAME="${AUTHOR_NAME:-}" \ + --dart-define=AUTHOR_URL="${AUTHOR_URL:-}" \ + --dart-define=GITHUB_URL="${GITHUB_URL:-}" \ + --dart-define=DONATE_URL="${DONATE_URL:-}" \ + --dart-define=ANDROID_PACKAGE_NAME="${ANDROID_PACKAGE_NAME:-}" \ + --dart-define=FIREBASE_PROJECT_ID="${FIREBASE_PROJECT_ID:-}" \ --dart-define=FIREBASE_API_KEY_ANDROID="${FIREBASE_API_KEY_ANDROID:-}" \ - --dart-define=APP_DOMAIN="${APP_DOMAIN}" + --dart-define=FIREBASE_ANDROID_APP_ID="${FIREBASE_ANDROID_APP_ID:-}" \ + --dart-define=FIREBASE_MESSAGING_SENDER_ID="${FIREBASE_MESSAGING_SENDER_ID:-}" \ + --dart-define=FIREBASE_STORAGE_BUCKET="${FIREBASE_STORAGE_BUCKET:-}" - name: Generate mobile SBOM working-directory: mobile @@ -648,13 +732,13 @@ jobs: - name: Attest mobile APK provenance id: attest-mobile - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 with: subject-path: mobile/build/app/outputs/flutter-apk/app-release.apk - name: Attest mobile AAB provenance id: attest-mobile-aab - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 with: subject-path: mobile/build/app/outputs/bundle/release/app-release.aab @@ -758,7 +842,7 @@ jobs: INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -1001,4 +1085,3 @@ jobs: ./artifacts/mobile/mobile-checksums.txt \ ./artifacts/mobile/mobile-provenance.intoto.jsonl \ --draft=false - diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 11d9fe98..d6f134d9 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -3,7 +3,7 @@ name: OSSF Scorecard on: branch_protection_rule: schedule: - - cron: '0 0 * * 0' # Weekly on Sundays at midnight UTC + - cron: "0 0 * * 0" # Weekly on Sundays at midnight UTC push: branches: [main] workflow_dispatch: @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false @@ -34,6 +34,6 @@ jobs: publish_results: true - name: Upload SARIF results to GitHub Security - uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e + uses: github/codeql-action/upload-sarif@e58424170fb0262c8d7ed60a2e84b9bffe205c67 with: sarif_file: results.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 983b2170..850388d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,11 +18,11 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: "24.14.1" + node-version: "24.18.1" cache: "npm" - name: Install dependencies @@ -40,7 +40,7 @@ jobs: run: npm run test:coverage - name: Upload coverage to Codecov - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f with: files: ./coverage/lcov.info token: ${{ secrets.CODECOV_TOKEN }} @@ -82,11 +82,11 @@ jobs: NODE_ENV: "test" steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: "24.14.1" + node-version: "24.18.1" cache: "npm" - name: Install dependencies @@ -99,7 +99,7 @@ jobs: echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT - name: Cache Playwright browsers - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 id: playwright-cache with: path: ~/.cache/ms-playwright diff --git a/.gitignore b/.gitignore index 4541d5bf..47d058fb 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ certificates/ /mobile/ios/Flutter/flutter_export_environment.sh /mobile/android/local.properties /mobile/android/key.properties +/mobile/android/.kotlin /mobile/android/app/src/main/res/raw/app_config.json /mobile/android/app/debug/ /mobile/android/app/profile/ @@ -108,4 +109,5 @@ certificates/ /mobile/ios/Runner/GoogleService-Info.plist # App Secrets -/mobile/lib/config/app_secrets.dart \ No newline at end of file +/mobile/lib/config/app_secrets.dart +/mobile/firebase.json \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index fd59db0b..165f5234 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,31 +1,69 @@ # GhostClass Code of Conduct -Like the technical community as a whole, the GhostClass team and community are made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people. +Diversity is one of our huge strengths, but it can also lead to communication +issues and unhappiness. To that end, we have a few ground rules that we ask +people to adhere to. This code applies equally to founders, mentors and those +seeking help and guidance. -Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance. +This isnโ€™t an exhaustive list of things that you canโ€™t do. Rather, take it in +the spirit in which itโ€™s intended - a guide to make it easier to enrich all of +us and the technical communities in which we participate. -This isnโ€™t an exhaustive list of things that you canโ€™t do. Rather, take it in the spirit in which itโ€™s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate. +This code of conduct applies to all spaces managed by the GhostClass project. +This includes IRC, the mailing lists, the issue tracker, community events, and +any other forums created by the project team which the community uses for +communication. In addition, violations of this code outside these spaces may +affect a person's ability to participate within them. -This code of conduct applies to all spaces managed by the GhostClass project. This includes IRC, the mailing lists, the issue tracker, community events, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them. - -If you believe someone is violating the code of conduct, we ask that you report it by emailing [admin@ghostclass.devakesu.com](mailto:admin@ghostclass.devakesu.com). +If you believe someone is violating the code of conduct, we ask that you report +it by emailing +[admin@ghostclass.devakesu.com](mailto:admin@ghostclass.devakesu.com). - **Be friendly and patient.** -- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. -- **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. -- **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. Itโ€™s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the GhostClass community should be respectful when dealing with other members as well as with people outside the GhostClass community. -- **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to: +- **Be welcoming.** We strive to be a community that welcomes and supports + people of all backgrounds and identities. This includes, but is not limited to + members of any race, ethnicity, culture, national origin, colour, immigration + status, social and economic class, educational level, sex, sexual orientation, + gender identity and expression, age, size, family status, political belief, + religion, and mental and physical ability. +- **Be considerate.** Your work will be used by other people, and you in turn + will depend on the work of others. Any decision you take will affect users and + colleagues, and you should take those consequences into account when making + decisions. Remember that we're a world-wide community, so you might not be + communicating in someone else's primary language. +- **Be respectful.** Not all of us will agree all the time, but disagreement is + no excuse for poor behavior and poor manners. We might all experience some + frustration now and then, but we cannot allow that frustration to turn into a + personal attack. Itโ€™s important to remember that a community where people feel + uncomfortable or threatened is not a productive one. Members of the GhostClass + community should be respectful when dealing with other members as well as with + people outside the GhostClass community. +- **Be careful in the words that you choose.** We are a community of + professionals, and we conduct ourselves professionally. Be kind to others. Do + not insult or put down other participants. Harassment and other exclusionary + behavior aren't acceptable. This includes, but is not limited to: - Violent threats or language directed against another person. - Discriminatory jokes and language. - Posting sexually explicit or violent material. - - Posting (or threatening to post) other people's personally identifying information ("doxing"). + - Posting (or threatening to post) other people's personally identifying + information ("doxing"). - Personal insults, especially those using racist or sexist terms. - Unwelcome sexual attention. - Advocating for, or encouraging, any of the above behavior. - - Repeated harassment of others. In general, if someone asks you to stop, then stop. -- **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and GhostClass is no exception. It is important that we resolve disagreements and differing views constructively. Remember that weโ€™re different. The strength of GhostClass comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesnโ€™t mean that theyโ€™re wrong. Donโ€™t forget that it is human to err and blaming each other doesnโ€™t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. + - Repeated harassment of others. In general, if someone asks you to stop, then + stop. +- **When we disagree, try to understand why.** Disagreements, both social and + technical, happen all the time and GhostClass is no exception. It is important + that we resolve disagreements and differing views constructively. Remember + that weโ€™re different. The strength of GhostClass comes from its varied + community, people from a wide range of backgrounds. Different people have + different perspectives on issues. Being unable to understand why someone holds + a viewpoint doesnโ€™t mean that theyโ€™re wrong. Donโ€™t forget that it is human to + err and blaming each other doesnโ€™t get us anywhere. Instead, focus on helping + to resolve issues and learning from mistakes. -Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html). +Original text courtesy of the +[Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html). ## Questions? diff --git a/Dockerfile b/Dockerfile index b75e4c22..f3bf7d0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # =============================== # 0. Global deterministic settings # =============================== -ARG NODE_IMAGE=node:22.22.3-alpine@sha256:968df39aedcea65eeb078fb336ed7191baf48f972b4479711397108be0966920 +ARG NODE_IMAGE=node:24.18.1-alpine3.24@sha256:f70403e87646dc51b45295f4b8b70cdad0b63d2297c4c9899119b03f7af7a6b3 ARG SOURCE_DATE_EPOCH=1767225600 # =============================== @@ -9,13 +9,13 @@ ARG SOURCE_DATE_EPOCH=1767225600 # =============================== FROM ${NODE_IMAGE} AS base -# Update npm to version 11 without using `npm install -g` (avoids scorecard "npmCommand not pinned" flag). +# Update npm to version 12 without using `npm install -g` (avoids scorecard "npmCommand not pinned" flag). # /usr/local/bin/npm already symlinks to /usr/local/lib/node_modules/npm/bin/npm-cli.js, # so overwriting that directory via tar achieves the same result with no unpinned npm invocation. # The tarball is verified by SHA-256 before extraction. RUN apk add --no-cache wget && \ - wget -O /tmp/npm.tgz https://registry.npmjs.org/npm/-/npm-11.14.1.tgz && \ - echo "bddc8ec2a698d283674cf0a798ef444ba7332497f330dd166056281fcafaca7a /tmp/npm.tgz" | sha256sum -c - && \ + wget -O /tmp/npm.tgz https://registry.npmjs.org/npm/-/npm-12.0.2.tgz && \ + echo "5dbb86c71d07a1957f2e90734092dd6a58bdcd9ebc2d8d41ca1c6e6a21d364e1 /tmp/npm.tgz" | sha256sum -c - && \ rm -rf /usr/local/lib/node_modules/npm && \ mkdir -p /usr/local/lib/node_modules/npm && \ tar -xz --strip-components=1 -C /usr/local/lib/node_modules/npm -f /tmp/npm.tgz && \ diff --git a/README.md b/README.md index 66812a50..a9c4b6fe 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@

- Next.js - React - Tailwind + Next.js + React + Tailwind TypeScript

@@ -24,14 +24,25 @@ iOS

- Vitest - Playwright + Vitest + Playwright

## Overview -GhostClass is the ultimate academic survival tool for students who want to manage their attendance without the main character energy of a professor. Featuring a sleek web dashboard and a native Flutter mobile application with real-time analytics and visual performance charts, it helps you track your classes so you never accidentally ghost your degree. With a built-in "bunk calculator" to tell you exactly how many lectures you can skip before it becomes a canon event, and a dedicated tracker for suspicious absences, GhostClass ensures your attendance stays valid while you live your best life. Built to integrate with existing attendance providers, GhostClass can fetch attendance and leave data from EzyGo and related sources and presents it with a clean, intuitive interface. No more confusing numbers - just clear, actionable insights! +GhostClass is the ultimate academic survival tool for students who want to +manage their attendance without the main character energy of a professor. +Featuring a sleek web dashboard and a native Flutter mobile application with +real-time analytics and visual performance charts, it helps you track your +classes so you never accidentally ghost your degree. With a built-in "bunk +calculator" to tell you exactly how many lectures you can skip before it becomes +a canon event, and a dedicated tracker for suspicious absences, GhostClass +ensures your attendance stays valid while you live your best life. Built to +integrate with existing attendance providers, GhostClass can fetch attendance +and leave data from EzyGo and related sources and presents it with a clean, +intuitive interface. No more confusing numbers - just clear, actionable +insights! ## ๐Ÿ“ฒ Get the Mobile App @@ -45,34 +56,49 @@ GhostClass is the ultimate academic survival tool for students who want to manag ## ๐ŸŽฏ Key Vibes -- **Student-First Dashboard** ๐ŸŽˆ: A friendly dashboard with quick insights and a cheeky tone that still gets serious about accuracy. -- **The Bunk Calc** ๐Ÿงฎ: Precise, actionable bunk counts presented with both "official" and "what-you-see" metrics so you know exactly how many classes you can miss before the threshold comes for your neck. -- **Visual Receipts** ๐Ÿ“Š: Performance charts, detailed calendar history, and downloadable attendance snapshots for an attendance glow-up, verifications, or appeals. -- **Manual Tracking** โœ๏ธ: Mark custom attendance; GhostClass reconciles them once official records arrive. -- **Anti-Ghosting Tracker** ๐Ÿ‘ป: A personalized list to watch wrongly marked absences like a hawk until they get updated. -- **Course Toggle** ๐Ÿ”•: Per-semester course disable toggle (for challenge-passed / dropped courses) to clean up your aggregate statistics and keep your dashboard uncluttered. -- **Academic Documents** ๐Ÿ“‚: Unified viewer for Leave Applications and Exam Scores with detailed breakdowns. -- **Offline-First PWA + Native Parity** ๐Ÿ“ฑ: Use the web PWA or native Flutter mobile app; data and calculations stay perfectly consistent across both. +- **Student-First Dashboard** ๐ŸŽˆ: A friendly dashboard with quick insights and a + cheeky tone that still gets serious about accuracy. +- **The Bunk Calc** ๐Ÿงฎ: Precise, actionable bunk counts presented with both + "official" and "what-you-see" metrics so you know exactly how many classes you + can miss before the threshold comes for your neck. +- **Visual Receipts** ๐Ÿ“Š: Performance charts, detailed calendar history, and + downloadable attendance snapshots for an attendance glow-up, verifications, or + appeals. +- **Manual Tracking** โœ๏ธ: Mark custom attendance; GhostClass reconciles them + once official records arrive. +- **Anti-Ghosting Tracker** ๐Ÿ‘ป: A personalized list to watch wrongly marked + absences like a hawk until they get updated. +- **Course Toggle** ๐Ÿ”•: Per-semester course disable toggle (for challenge-passed + / dropped courses) to clean up your aggregate statistics and keep your + dashboard uncluttered. +- **Academic Documents** ๐Ÿ“‚: Unified viewer for Leave Applications and Exam + Scores with detailed breakdowns. +- **Offline-First PWA + Native Parity** ๐Ÿ“ฑ: Use the web PWA or native Flutter + mobile app; data and calculations stay perfectly consistent across both. ### ๐Ÿ” Security & Reliability -- **Zero-Trust Bridge**: Every mobile-to-server and server-to-server request is encrypted with **JWE** (RSA-OAEP + AES-GCM). -- **Device Attestation**: App Check with Play Integrity (Android) and DeviceCheck (iOS) prevents bot abuse. -- **Multi-Device Support**: Stay logged in on multiple devices simultaneously without session conflicts. -- **Build Transparency**: Full SLSA Level 3 provenance and mobile binary verification. +- **Zero-Trust Bridge**: Authenticated mobile-to-server and server-to-server + communication with TLS and CSRF protection. +- **Device Attestation**: App Check with Play Integrity (Android) and + DeviceCheck (iOS) prevents bot abuse. +- **Multi-Device Support**: Stay logged in on multiple devices simultaneously + without session conflicts. +- **Build Transparency**: Full SLSA Level 3 provenance and mobile binary + verification. ## ๐Ÿ› ๏ธ Tech Stack ### Core Framework -- **Next.js 16.2.6** - React 19 with App Router +- **Next.js 16.2.12** - React 19 with App Router - **TypeScript 6.0.3** - Strict mode for type safety - **Flutter 3.44.0** - Cross-platform native mobile application -- **Node.js** - v24.14.1+ +- **Node.js** - v24.18.1+ ### Styling & UI -- **Tailwind CSS 4.3.0** - Utility-first styling with custom design system +- **Tailwind CSS 4.3.3** - Utility-first styling with custom design system - **Radix UI** - Accessible, unstyled component primitives - **Shadcn UI** - Beautiful pre-styled components - **Framer Motion** - Smooth animations and transitions @@ -80,7 +106,8 @@ GhostClass is the ultimate academic survival tool for students who want to manag ### Data & State Management -- **TanStack Query (React Query) v5** - Server state management with smart caching +- **TanStack Query (React Query) v5** - Server state management with smart + caching - **Riverpod v3** - Reactive state management for Flutter - **React Hook Form + Zod v4** - Form validation with schema validation - **Recharts v3** - Interactive data visualizations with responsive charts @@ -101,9 +128,9 @@ GhostClass is the ultimate academic survival tool for students who want to manag ### Security & Monitoring - **AES-256-GCM Encryption** - Secure token storage at rest -- **JWE (JSON Web Encryption)** - Secure cross-platform payload encryption for mobile-to-server and server-to-server communication - **CSRF Protection** - Custom token-based protection for web -- **App Check / Play Integrity** - Device attestation to prevent bot abuse and tampering on mobile +- **App Check / Play Integrity** - Device attestation to prevent bot abuse and + tampering on mobile - **Upstash Redis** - Rate limiting with `@upstash/ratelimit` - **Sentry** - Error tracking and performance monitoring - **GA4 Measurement Protocol** - Server-side analytics (CSP-compatible) @@ -123,12 +150,12 @@ GhostClass is the ultimate academic survival tool for students who want to manag ## ๐Ÿ“ Project Structure ```text -mobile/ # Native Flutter application (Riverpod, JWE, SecureStorage) +mobile/ # Native Flutter application (Riverpod, SecureStorage) โ”œโ”€โ”€ lib/ โ”‚ โ”œโ”€โ”€ logic/ # Core business logic and bunk algorithm parity โ”‚ โ”œโ”€โ”€ providers/ # Riverpod reactive state management handlers โ”‚ โ”œโ”€โ”€ screens/ # Application views and dashboard UI -โ”‚ โ”œโ”€โ”€ services/ # Encrypted storage, JWE client, and direct API egress +โ”‚ โ”œโ”€โ”€ services/ # Encrypted storage and direct API egress โ”‚ โ””โ”€โ”€ widgets/ # Native UI components (FL Chart, custom layout items) src/ # Next.js web application (React 19, Tailwind 4, TanStack Query) โ”œโ”€โ”€ app/ # Pages, layouts, and API route handlers @@ -141,35 +168,259 @@ workers/ # Cloudflare/AWS egress proxies for Supabase ISP bypass ## ๐Ÿงฎ Attendance Calculation Algorithm -GhostClass uses a unified attendance logic with full parity between Web (TypeScript) and Mobile (Dart). It calculates current attendance, "bunkable" classes, and required sessions to reach a target. +GhostClass uses a unified attendance logic with full parity between Web +(TypeScript) and Mobile (Dart). It calculates current attendance, "bunkable" +classes, and required sessions to reach a target. -For the full mathematical derivation, duty leave limits (5 per course), and pseudocode, see **[ALGORITHM.md](docs/ALGORITHM.md)**. +For the full mathematical derivation, duty leave limits (5 per course), and +pseudocode, see **[ALGORITHM.md](docs/ALGORITHM.md)**. ## ๐Ÿš€ Getting Started ### Prerequisites -- **Node.js** - v24.14.1+ -- **npm** - v11.11.0+ +- **Docker Desktop** (with WSL2 backend enabled) +- **WSL2** (Linux distribution such as Ubuntu/Debian) +- **VS Code or Antigravity IDE/any IDE with WSL/Docker Support** -- **Flutter SDK** - 3.44.0 -- **Docker** - For containerized deployment (optional) +## ๐Ÿณ Dev Container Environment Setup (Recommended) -### Quick Start (Web) +GhostClass provides an isolated, reproducible Dev Container +(`.devcontainer/Dockerfile`) equipped with Node 24, Flutter SDK 3.44, Deno, +Playwright, Supabase, Firebase, Infisical CLI tools, and automatic IDE extension +syncing. -1. **Setup**: `git clone` the repo and run `npm install --legacy-peer-deps`. -2. **Database**: Link your project and run `npx supabase db push`. -3. **Environment**: Install the Infisical CLI, authenticate via `infisical login`, and organize secrets inside `/build-time`, `/runtime`, and `/ci` path folders. -4. **Run**: Inject variables securely in-memory using `infisical run -- npm run dev` and visit `http://localhost:3000`. +### 1. Initialize and Configure WSL2 (Windows Host) -### Quick Start (Mobile) +To ensure optimal networking, memory utilization, and loopback connectivity, +configure WSL2 on the Windows host. -1. **Install Flutter**: Ensure Flutter SDK 3.44.0 is installed. -2. **Setup**: Navigate to `mobile/` and run `flutter pub get`. -3. **Secrets**: Copy `app_secrets.dart.example` to `app_secrets.dart` and fill your API keys. -4. **Run**: Connect a device and run `flutter run`. +#### Option A: Using the WSL Settings GUI (Recommended) -For contribution rules and environment configurations, please refer to **[CONTRIBUTING.md](docs/CONTRIBUTING.md)** and **[SECURITY.md](SECURITY.md)**. +Open the WSL Settings application (search for "WSL Settings" in the Windows +Start menu) or launch it by running the following command in PowerShell: + +```powershell +wsl --settings +``` + +In the settings interface, configure the following: + +- **Networking Mode**: `Mirrored` +- **Host Address Loopback**: `Enabled` +- **Automatic Memory Reclaim**: `Gradual` + +#### Option B: Using the `.wslconfig` File + +Create or edit `%USERPROFILE%\.wslconfig` in Windows (e.g., +`C:\Users\\.wslconfig`) and add the following settings: + +```ini +[wsl2] +networkingMode=mirrored +hostAddressLoopback=true +autoMemoryReclaim=gradual +``` + +After configuring via either option, restart WSL2 by running the following +command in Windows PowerShell: + +```powershell +wsl --shutdown +``` + +### 2. Enable Windows SSH Agent (Host) + +Run PowerShell as Administrator or user to enable the OpenSSH agent service and +load your SSH/signing keys: + +```powershell +Set-Service -Name ssh-agent -StartupType Automatic +Start-Service ssh-agent +ssh-add $env:USERPROFILE\.ssh\id_ed25519 +``` + +### 3. Bridge SSH Agent to WSL2 + +In WSL2, install `socat`, download `npiperelay`, and bridge the Windows SSH pipe +to Linux: + +```bash +sudo apt update && sudo apt install -y socat + +# Download npiperelay to bridge Windows named pipes to Linux sockets +curl -s https://api.github.com/repos/jstarks/npiperelay/releases/latest | grep "browser_download_url.*zip" | cut -d : -f 2,3 | tr -d \" | wget -qi - -O /tmp/npiperelay.zip + +sudo unzip -o /tmp/npiperelay.zip npiperelay.exe -d /usr/local/bin/ +sudo chmod +x /usr/local/bin/npiperelay.exe +rm /tmp/npiperelay.zip + +# Self-healing SSH relay script to your ~/.bashrc +cat << 'EOF' >> ~/.bashrc +# --- SSH AGENT RELAY --- +export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock" + +# Test if SSH agent is actually responding end-to-end +ssh-add -l >/dev/null 2>&1 +if [ $? -eq 2 ]; then + # Kill stale relay processes and clean up socket/directory glitches + pkill -f "npiperelay.exe" 2>/dev/null || true + pkill -f "$SSH_AUTH_SOCK" 2>/dev/null || true + rm -rf "$SSH_AUTH_SOCK" + mkdir -p "$HOME/.ssh" + # Spawn fresh relay + if command -v npiperelay.exe >/dev/null 2>&1; then + (nohup socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork >/dev/null 2>&1 &) + fi +fi +EOF + +# Clean up potential Docker dummy directories & init socket +rm -rf ~/.ssh/agent.sock +source ~/.bashrc +``` + +### 4. Clone Repository in WSL2 + +Clone the repository in your WSL2 home or projects directory: + +```bash +git clone https://github.com/devakesu/GhostClass.git +cd GhostClass +``` + +### 5. Build & Run Sandbox Container + +Build the dev container image and launch the sandbox container with mapped ports +and volume mounts: + +```bash +# 1. Verify SSH agent connection (must return your keys, not an error) +ssh-add -l + +# 2. Build dev container image +docker build -t ghostclass-sandbox -f .devcontainer/Dockerfile . + +# 3. Launch sandbox container +docker run -d --name GhostClass_Sandbox \ + --restart unless-stopped \ + -v "$(pwd):/ghostclass" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$HOME/.ssh/agent.sock:/run/host-services/ssh-auth.sock" \ + -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" \ + -p 3000:3000 -p 8000:8000 -p 8080:8080 -p 4000:4000 -p 5001:5001 \ + -p 8081:8081 -p 8085:8085 -p 9099:9099 \ + -p 54321:54321 -p 54322:54322 -p 54323:54323 \ + ghostclass-sandbox +``` + +### 6. Attach IDE & Initialize Workspace + +1. Open VS Code or Antigravity IDE. +2. Open Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) โ†’ select **Attach to + Running Container** โ†’ `GhostClass_Sandbox`. +3. Open directory `/ghostclass` inside the container. +4. Run workspace initialization script in the integrated terminal: + + ```bash + ~/init_workspace.sh + ``` + + _(Enter Git Name and Email when prompted to configure local commit signing + and SSH identity)._ + +5. Run **Developer: Reload Window** in VS Code / Antigravity to refresh + environment variables and extension integrations. + +### 7. Android Emulator Setup & Subsequent Development Startups + +To run and debug the mobile app on an Android Emulator or Physical Device: + +#### Android Emulator Setup + +Create an Android Virtual Device (AVD) named `Medium_Phone_API_36.1` in Android +Studio's AVD Manager. _(Note: If using a different AVD name, edit the `-avd` +target in `.devcontainer/Start.ps1`)_. + +#### Subsequent Starts via `Start.ps1` (Windows Host) + +On subsequent development startups (after initial `docker run`), execute the +startup script from a Windows PowerShell terminal on the host: + +```powershell +.\.devcontainer\Start.ps1 +``` + +Select `[1] Emulator` (or pass `-Mode Emulator`). + +The script automatically ensures `GhostClass_Sandbox` container is running, +launches the host Android emulator (`Medium_Phone_API_36.1`), bridges Windows +ADB (`5555`) to Docker network (`host.docker.internal:5555`). + +Press **ENTER** in the PowerShell terminal when finished to gracefully shut down +the emulator and clean up portproxy rules. + +### ๐Ÿ” Secret Management & Database Initialization + +Before starting the API server or mobile client, authenticate with Infisical and +link your Supabase database project: + +```bash +# Authenticate Infisical CLI +infisical login + +# Authenticate Supabase CLI and link schema +supabase login +supabase link --project-ref +supabase db push +``` + +### ๐Ÿ Running Web Application / API Server + +Once Infisical and Supabase are authenticated: + +Run API / Web Server with Injected Secrets: + +```bash +infisical run --env=dev --projectId=xxxx --path=/build-time --path=/runtime -- npm run dev:https +``` + +Visit `https://localhost:3000` (or `http://localhost:3000`) for the web +dashboard. + +### ๐Ÿ“ฑ Running Mobile App + +Navigate to Mobile Directory and execute: + +```bash +cd mobile +flutter pub get + +infisical run --env=dev --projectId=xxxx --path=/build-time -- sh -c ' + export DART_VM_OPTIONS="--bind-address=0.0.0.0" + flutter run \ + --dart-define=APP_DOMAIN="$NEXT_PUBLIC_APP_DOMAIN" \ + --dart-define=APP_VERSION="$NEXT_PUBLIC_APP_VERSION" \ + --dart-define=AUTHOR_NAME="$NEXT_PUBLIC_AUTHOR_NAME" \ + --dart-define=AUTHOR_URL="$NEXT_PUBLIC_AUTHOR_URL" \ + --dart-define=GITHUB_URL="$NEXT_PUBLIC_GITHUB_URL" \ + --dart-define=DONATE_URL="$NEXT_PUBLIC_DONATE_URL" \ + --dart-define=APP_NAME="$APP_NAME" \ + --dart-define=ANDROID_PACKAGE_NAME="$ANDROID_PACKAGE_NAME" \ + --dart-define=IOS_APP_ID="$IOS_APP_ID" \ + --dart-define=FIREBASE_API_KEY_ANDROID="$FIREBASE_API_KEY_ANDROID" \ + --dart-define=FIREBASE_ANDROID_APP_ID="$FIREBASE_ANDROID_APP_ID" \ + --dart-define=FIREBASE_MESSAGING_SENDER_ID="$FIREBASE_MESSAGING_SENDER_ID" \ + --dart-define=FIREBASE_PROJECT_ID="$FIREBASE_PROJECT_ID" \ + --dart-define=FIREBASE_STORAGE_BUCKET="$FIREBASE_STORAGE_BUCKET" \ + --dart-define=FIREBASE_API_KEY_IOS="$FIREBASE_API_KEY_IOS" \ + --dart-define=FIREBASE_IOS_APP_ID="$FIREBASE_IOS_APP_ID" \ + --dart-define=FIREBASE_IOS_BUNDLE_ID="$FIREBASE_IOS_BUNDLE_ID" +' +``` + +For contribution rules and environment configurations, please refer to +**[CONTRIBUTING.md](docs/CONTRIBUTING.md)** and **[SECURITY.md](SECURITY.md)**. ## โšก Performance Optimizations @@ -177,53 +428,68 @@ GhostClass is optimized for maximum performance across platforms. ### ๐Ÿ’ป Web & PWA -- **Service Worker**: Compiled via esbuild for offline functionality and runtime caching. -- **Intelligent Caching**: React Query for server state; `StaleWhileRevalidate` for assets. -- **Bundle Optimization**: Route-based code splitting, tree-shaking, and lazy-loaded animations. +- **Service Worker**: Compiled via esbuild for offline functionality and runtime + caching. +- **Intelligent Caching**: React Query for server state; `StaleWhileRevalidate` + for assets. +- **Bundle Optimization**: Route-based code splitting, tree-shaking, and + lazy-loaded animations. ### ๐Ÿ“ฑ Mobile Native -- **Riverpod Caching**: Multi-layered in-memory deduplication for zero-latency UI. -- **Direct Egress**: Mobile requests call EzyGo directly, bypassing server proxies for lower latency. -- **Native Rendering**: High-performance `FL Chart` for responsive visualizations. +- **Riverpod Caching**: Multi-layered in-memory deduplication for zero-latency + UI. +- **Direct Egress**: Mobile requests call EzyGo directly, bypassing server + proxies for lower latency. +- **Native Rendering**: High-performance `FL Chart` for responsive + visualizations. ## ๐Ÿงช Testing -GhostClass maintains a comprehensive test suite with over **250+ test files** across both platforms. +GhostClass maintains a comprehensive test suite with over **250+ test files** +across both platforms. ### ๐Ÿ’ป Web Testing (Vitest & Playwright) - โœ… **Core Logic**: `npm run test` (Vitest) - โœ… **End-to-End**: `npm run test:e2e` (Playwright) -- โœ… **Security**: AES-256-GCM, JWE, and CSRF isolation tests. +- โœ… **Security**: AES-256-GCM and CSRF isolation tests. ### ๐Ÿ“ฑ Mobile Testing (Flutter) -- โœ… **Unit & Widget**: `flutter test` (Core logic, Riverpod providers, async exceptions) -- โœ… **CI/CD Enforcement**: Mandatory 80% global coverage gate on PRs via `flutter test --coverage` +- โœ… **Unit & Widget**: `flutter test` (Core logic, Riverpod providers, async + exceptions) +- โœ… **CI/CD Enforcement**: Mandatory 80% global coverage gate on PRs via + `flutter test --coverage` ### ๐Ÿ›ก๏ธ Coverage Highlights - โœ… **Algorithm**: 100% logic coverage for bunk and parity calculations. -- โœ… **Security**: Verified implementation of JWE, App Check, and RSA-OAEP. -- โœ… **Performance**: Benchmarked egress proxies and Riverpod cache deduplication. -- โœ… **UI/UX**: Full interaction testing for dashboard and manual tracking flows. +- โœ… **Security**: Verified implementation of App Check and Device Attestation. +- โœ… **Performance**: Benchmarked egress proxies and Riverpod cache + deduplication. +- โœ… **UI/UX**: Full interaction testing for dashboard and manual tracking + flows. ## ๐Ÿ”’ Security GhostClass implements multiple layers of security: -- **AES-256-GCM Encryption** - All sensitive tokens and credentials encrypted at rest. -- **Multi-Device Session Security** - Concurrent logins without session invalidation. -- **Zero-Trust Bridge Security** - **JWE (JSON Web Encryption)** for mobile-to-server and server-to-server communication. -- **Device Attestation** - Play Integrity / App Check to ensure genuine device requests. -- **Secure Storage** - Hardware-backed **SecureStorage** (Android Keystore / iOS Keychain) for mobile. +- **AES-256-GCM Encryption** - All sensitive tokens and credentials encrypted at + rest. +- **Multi-Device Session Security** - Concurrent logins without session + invalidation. +- **Device Attestation** - Play Integrity / App Check to ensure genuine device + requests. +- **Secure Storage** - Hardware-backed **SecureStorage** (Android Keystore / iOS + Keychain) for mobile. ## ๐Ÿš€ Deployment ### ๐Ÿ’ป Web (Docker) -GhostClass is deployed using a single-build multi-platform Docker image (`linux/amd64`, `linux/arm64`) with SLSA Level 3 provenance. +GhostClass is deployed using a single-build multi-platform Docker image +(`linux/amd64`, `linux/arm64`) with SLSA Level 3 provenance. - **Build**: `docker build -t ghostclass .` - **CI/CD**: Automatic versioning and deployment to Coolify via GitHub Actions. @@ -234,27 +500,33 @@ Release artifacts are generated automatically for both platforms: - **Android**: Signed App Bundle (`.aab`) and APK. - **iOS**: Enterprise-signed or App Store IPA (requires macOS build agent). -- **Google Play**: [GhostClass on Google Play](https://play.google.com/store/apps/details?id=com.devakesu.apps.ghostclass) +- **Google Play**: + [GhostClass on Google Play](https://play.google.com/store/apps/details?id=com.devakesu.apps.ghostclass) ## โ“ Frequently Asked Questions -**Why is the web dashboard sometimes slower than the mobile app?** -Web users share a server-side rate limiter to protect the proxy IP. Mobile users egress directly from their own device IPs, avoiding this shared bottleneck. +**Why is the web dashboard sometimes slower than the mobile app?** Web users +share a server-side rate limiter to protect the proxy IP. Mobile users egress +directly from their own device IPs, avoiding this shared bottleneck. -**Can I use both apps at the same time?** -Yes! Sessions are concurrent and data (settings, tracking, etc.) is synchronized via Supabase. +**Can I use both apps at the same time?** Yes! Sessions are concurrent and data +(settings, tracking, etc.) is synchronized via Supabase. ## ๐Ÿค Contributing -We welcome contributions! GhostClass uses an **automatic version bumping system**. See **[CONTRIBUTING.md](docs/CONTRIBUTING.md)** for details. +We welcome contributions! GhostClass uses an **automatic version bumping +system**. See **[CONTRIBUTING.md](docs/CONTRIBUTING.md)** for details. ## ๐Ÿ‘ฅ Maintained by - [Devanarayanan](https://github.com/devakesu/) -- Credits: [Bunkr](https://github.com/ABHAY-100/Bunkr/) (Initial codebase foundation) +- Credits: [Bunkr](https://github.com/ABHAY-100/Bunkr/) (Initial codebase + foundation) ## ๐Ÿ“„ License -This project is licensed under the **[GNU General Public License v3.0](LICENSE)**. +This project is licensed under the +**[GNU General Public License v3.0](LICENSE)**. -***Thank you for your interest in GhostClass! Bunk classes & enjoy, but don't forget to study!! ๐Ÿ˜๐Ÿค*** +_**Thank you for your interest in GhostClass! Bunk classes & enjoy, but don't +forget to study!! ๐Ÿ˜๐Ÿค**_ diff --git a/SECURITY.md b/SECURITY.md index fda84a6f..38ae28af 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,8 @@ ## Reporting Security Vulnerabilities -If you discover a security vulnerability in GhostClass, please report it responsibly: +If you discover a security vulnerability in GhostClass, please report it +responsibly: **Email**: [admin@ghostclass.devakesu.com](mailto:admin@ghostclass.devakesu.com) @@ -22,13 +23,22 @@ GhostClass implements multiple layers of security: ### Authentication & Authorization - **Supabase Auth** - Industry-standard authentication with JWT tokens -- **Row Level Security (RLS)** - Database-level access control ensuring users only access their data +- **Row Level Security (RLS)** - Database-level access control ensuring users + only access their data - **Session Management** - Secure session handling with automatic expiration ### Data Protection -- **HttpOnly Cookies** - Multiple `httpOnly` cookies with distinct `SameSite` policies. The session token (`ezygo_access_token`) uses `SameSite=Lax` โ€” intentional to allow the cookie on PWA standalone launches (top-level navigations); `Strict` would block it on bookmarks and installed-app launch, causing an infinite redirect loop. The CSRF token cookie uses `SameSite=Strict` since it only needs to be present on same-site requests where the header can be validated. All mutations require a valid CSRF token regardless. -- **Secure Headers** - HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy +- **HttpOnly Cookies** - Multiple `httpOnly` cookies with distinct `SameSite` + policies. The session token (`ezygo_access_token`) uses `SameSite=Lax` โ€” + intentional to allow the cookie on PWA standalone launches (top-level + navigations); `Strict` would block it on bookmarks and installed-app launch, + causing an infinite redirect loop. The CSRF token cookie uses + `SameSite=Strict` since it only needs to be present on same-site requests + where the header can be validated. All mutations require a valid CSRF token + regardless. +- **Secure Headers** - HSTS, X-Frame-Options, X-Content-Type-Options, + Referrer-Policy - **Input Validation** - Zod schemas validate all user input - **Origin Validation** - Strict origin checking in production - **AES-256-GCM Encryption** - Secure token encryption at rest @@ -39,14 +49,17 @@ GhostClass implements multiple layers of security: - **Circuit Breaker Pattern** - Graceful handling of upstream API failures - **Request Deduplication** - Prevents duplicate concurrent requests - **Bot Protection** - Cloudflare Turnstile on public endpoints -- **CSRF Protection** - Custom token-based CSRF protection for web; App Check attestation for mobile requests. `MOBILE_API_SECRET` is maintained as a **server-only** HMAC key for signing security nonces (stateless replay protection). -- **JWE Encryption (Web & Mobile)** - Bi-directional RSA-OAEP + AES-256-GCM encryption for all client-server traffic (Next.js โ†” Browser/App) -- **Device Attestation (Mobile)** - Firebase App Check with Play Integrity (Android) and DeviceCheck (iOS) -- **Anti-Tapjacking (Mobile)** - Android `FLAG_SECURE` implementation to prevent screenshot/overlay attacks on sensitive screens +- **CSRF Protection** - Custom token-based CSRF protection for web; App Check + attestation for mobile requests. +- **Device Attestation (Mobile)** - Firebase App Check with Play Integrity + (Android) and DeviceCheck (iOS) +- **Anti-Tapjacking (Mobile)** - Android `FLAG_SECURE` implementation to prevent + screenshot/overlay attacks on sensitive screens ### Supply Chain Security -- **Signed Docker Images** - All images signed with Sigstore cosign (keyless OIDC) +- **Signed Docker Images** - All images signed with Sigstore cosign (keyless + OIDC) - **SLSA Level 3 Provenance** - Build provenance attestations - **GitHub Attestations** - Native GitHub artifact attestations - **SBOM (CycloneDX)** - Software Bill of Materials for all releases @@ -55,11 +68,16 @@ GhostClass implements multiple layers of security: ### CI/CD Security -- **Script Injection Prevention** - Environment variables used for all untrusted GitHub Actions inputs -- **Least Privilege Permissions** - Workflows use minimum required permissions with explicit grants -- **GPG Signing** - Commits and tags cryptographically signed (except Dependabot PRs) -- **Secret Management** - GitHub secrets isolated per workflow with no cross-contamination -- **Dependabot Isolation** - Special handling for Dependabot PRs without secret access +- **Script Injection Prevention** - Environment variables used for all untrusted + GitHub Actions inputs +- **Least Privilege Permissions** - Workflows use minimum required permissions + with explicit grants +- **GPG Signing** - Commits and tags cryptographically signed (except Dependabot + PRs) +- **Secret Management** - GitHub secrets isolated per workflow with no + cross-contamination +- **Dependabot Isolation** - Special handling for Dependabot PRs without secret + access ### Environment Security @@ -69,13 +87,25 @@ GhostClass implements multiple layers of security: ### Egress Proxy Chain -- **EzyGo Server-Side Egress** - All server-to-EzyGo API requests route through a two-tier egress proxy chain: a Cloudflare Worker (`CF_PROXY_URL`, Tier 1) falling back to an AWS Lambda (`AWS_SECONDARY_URL`, Tier 2), then direct. This masks the origin server IP and bypasses ISP-level blocks. Implemented via `egressFetch()` / `egressAxios` in `src/lib/utils.server.ts`. -- **Supabase Browser Proxy (ISP Bypass)** - Browser-to-Supabase requests auto-fail-over through the same pattern: CF Worker (`NEXT_PUBLIC_SUPABASE_CF_PROXY_URL`) โ†’ Lambda (`NEXT_PUBLIC_SUPABASE_AWS_PROXY_URL`) โ†’ direct. Implemented in `src/lib/supabase/client.ts`. -- **Proxy Secret Validation** - All proxy workers validate an `x-proxy-secret` header on every incoming request; requests without a valid secret are rejected with `403`. Secrets are never embedded in the client bundle (`CF_PROXY_SECRET`, `AWS_SECONDARY_SECRET`, and `MOBILE_API_SECRET` are server-only runtime variables). +- **EzyGo Server-Side Egress** - All server-to-EzyGo API requests route through + a two-tier egress proxy chain: a Cloudflare Worker (`CF_PROXY_URL`, Tier 1) + falling back to an AWS Lambda (`AWS_SECONDARY_URL`, Tier 2), then direct. This + masks the origin server IP and bypasses ISP-level blocks. Implemented via + `egressFetch()` / `egressAxios` in `src/lib/utils.server.ts`. +- **Supabase Browser Proxy (ISP Bypass)** - Browser-to-Supabase requests + auto-fail-over through the same pattern: CF Worker + (`NEXT_PUBLIC_SUPABASE_CF_PROXY_URL`) โ†’ Lambda + (`NEXT_PUBLIC_SUPABASE_AWS_PROXY_URL`) โ†’ direct. Implemented in + `src/lib/supabase/client.ts`. +- **Proxy Secret Validation** - All proxy workers validate an `x-proxy-secret` + header on every incoming request; requests without a valid secret are rejected + with `403`. Secrets are never embedded in the client bundle (`CF_PROXY_SECRET` + and `AWS_SECONDARY_SECRET` are server-only runtime variables). ## Dependency Security Overrides -GhostClass uses npm overrides to enforce minimum secure versions of transitive dependencies. All overrides are documented below with their security rationale: +GhostClass uses npm overrides to enforce minimum secure versions of transitive +dependencies. All overrides are documented below with their security rationale: ### Current Overrides (package.json) @@ -89,7 +119,8 @@ GhostClass uses npm overrides to enforce minimum secure versions of transitive d #### tar: ^7.5.15 - **Reason**: Path traversal vulnerabilities in versions โ‰ค7.5.14 -- **CVEs**: CVE-2021-32803, CVE-2021-32804, CVE-2021-37701, CVE-2021-37712, CVE-2021-37713 / GHSA-qffp-2rhf-9h96 +- **CVEs**: CVE-2021-32803, CVE-2021-32804, CVE-2021-37701, CVE-2021-37712, + CVE-2021-37713 / GHSA-qffp-2rhf-9h96 - **Scope**: Dev-only (used by supabase CLI for unpacking) - **Status**: โœ… Patched @@ -112,7 +143,7 @@ GhostClass uses npm overrides to enforce minimum secure versions of transitive d - **Scope**: Dev-only (used by build tools: Sentry, Serwist) - **Status**: โœ… Up-to-date -#### source-map: ^0.7.6 +#### source-map: ^0.8.0 - **Reason**: Dependency resolution conflicts and stability improvements - **Scope**: Dev-only (used by Vite/Terser for sourcemap generation) @@ -131,12 +162,46 @@ GhostClass uses npm overrides to enforce minimum secure versions of transitive d - **Scope**: Transitive dependency (used by various dev tools) - **Status**: โœ… Up-to-date +#### @tootallnate/once: ^3.0.1 + +- **Reason**: Memory leak prevention and event listener security hardening in + legacy HTTP agent wrappers +- **Scope**: Dev-only / transitive dependency +- **Status**: โœ… Up-to-date + #### postcss: ^8.5.14 - **Reason**: Security hardening and dependency stability - **Scope**: Transitive dependency (used by Tailwind CSS) - **Status**: โœ… Up-to-date +#### sharp: ^0.35.0 + +- **Reason**: Native memory safety hardening and libvips security patches +- **Scope**: Production dependency (used by Next.js image optimization) +- **Status**: โœ… Up-to-date + +#### uuid: ^14.0.1 + +- **Reason**: CSPRNG generation hardening and prototype protection in v14+ +- **Scope**: Production & transitive dependency +- **Status**: โœ… Up-to-date + +### Egress Worker Overrides (workers/package.json) + +#### undici: ^8.4.2 + +- **Reason**: HTTP request smuggling and header injection protection in Worker + fetch engine +- **Scope**: Proxy worker dependency +- **Status**: โœ… Patched + +#### ws: ^8.20.1 + +- **Reason**: Resource exhaustion DoS vulnerability fix (CVE-2024-37890) +- **Scope**: Proxy worker dependency +- **Status**: โœ… Patched + ### Maintenance Policy - Overrides are reviewed during each major release @@ -146,22 +211,25 @@ GhostClass uses npm overrides to enforce minimum secure versions of transitive d ## Known Issues -No active known issues. `npm audit` reports **0 vulnerabilities** across all dependencies. +No active known issues. `npm audit` reports **0 vulnerabilities** across all +dependencies. All previously tracked issues have been resolved: -| Issue | Resolution | -| --- | --- | -| `ajv <8.18.0` ReDoS (GHSA-2g4f-4pwh-qvx6) in ESLint | Advisory resolved โ€” no longer flagged by `npm audit`. | +| Issue | Resolution | +| ----------------------------------------------------------- | ---------------------------------------------------------- | +| `ajv <8.18.0` ReDoS (GHSA-2g4f-4pwh-qvx6) in ESLint | Advisory resolved โ€” no longer flagged by `npm audit`. | | `minimatch` ReDoS (GHSA-3ppc-4f35-3m26) in `@sentry/nextjs` | Fixed via `minimatch: ^10.2.5` override in `package.json`. | -See [Dependency Security Overrides](#dependency-security-overrides) for the current override list. +See [Dependency Security Overrides](#dependency-security-overrides) for the +current override list. ## GitHub Actions Security ### Script Injection Prevention -GhostClass workflows are hardened against script injection attacks using environment variables for all untrusted inputs. +GhostClass workflows are hardened against script injection attacks using +environment variables for all untrusted inputs. #### Vulnerable Pattern (โŒ DO NOT USE) @@ -171,7 +239,9 @@ run: | git checkout "refs/tags/${VERSION_TAG}" ``` -**Risk**: Attacker-controlled inputs like branch names, tag names, or workflow inputs can contain shell metacharacters (`;`, `|`, `$()`, etc.) that execute arbitrary commands. +**Risk**: Attacker-controlled inputs like branch names, tag names, or workflow +inputs can contain shell metacharacters (`;`, `|`, `$()`, etc.) that execute +arbitrary commands. #### Secure Pattern (โœ… ALWAYS USE) @@ -183,14 +253,19 @@ run: | git checkout "refs/tags/${VERSION_TAG}" ``` -**Protection**: Environment variables treat the entire input as literal data, preventing command injection. +**Protection**: Environment variables treat the entire input as literal data, +preventing command injection. #### Protected Workflows ##### release.yml -- Dynamic versions injected from Infisical are processed via intermediate environment mapping (`env.VERSION_TAG`, `env.VERSION`) during markdown verification and release generation loops. -- `github.repository` and `github.repository_owner` are passed via localized `env:` blocks to prevent repository name manipulation during container image publishing and artifact attestation steps. +- Dynamic versions injected from Infisical are processed via intermediate + environment mapping (`env.VERSION_TAG`, `env.VERSION`) during markdown + verification and release generation loops. +- `github.repository` and `github.repository_owner` are passed via localized + `env:` blocks to prevent repository name manipulation during container image + publishing and artifact attestation steps. #### References @@ -302,9 +377,12 @@ View build provenance and security information directly in your browser: - SLSA attestation status and links - Direct links to source code, build logs, and attestations -**Footer Link**: Click the "verified" badge in the footer to access build transparency information. +**Footer Link**: Click the "verified" badge in the footer to access build +transparency information. -The web interface provides a user-friendly way to verify build provenance without requiring command-line tools, making security information accessible to all users. +The web interface provides a user-friendly way to verify build provenance +without requiring command-line tools, making security information accessible to +all users. ## Deployment Security Checklist @@ -367,15 +445,18 @@ GhostClass participates in: - **Trivy** - Container image vulnerability scanning - **Sentry** - Real-time error tracking and monitoring -View our security score: [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/devakesu/GhostClass/badge)](https://scorecard.dev/viewer/?uri=github.com/devakesu/GhostClass) +View our security score: +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/devakesu/GhostClass/badge)](https://scorecard.dev/viewer/?uri=github.com/devakesu/GhostClass) ## Additional Resources - **SLSA Framework**: [https://slsa.dev](https://slsa.dev) - **Sigstore Project**: [https://sigstore.dev](https://sigstore.dev) - **OpenSSF Scorecard**: [https://scorecard.dev](https://scorecard.dev) -- **GitHub Security**: [https://docs.github.com/en/code-security](https://docs.github.com/en/code-security) +- **GitHub Security**: + [https://docs.github.com/en/code-security](https://docs.github.com/en/code-security) --- -For development setup and contribution guidelines, see [CONTRIBUTING.md](docs/CONTRIBUTING.md). +For development setup and contribution guidelines, see +[CONTRIBUTING.md](docs/CONTRIBUTING.md). diff --git a/components.json b/components.json index 4f366042..3e7c73d1 100644 --- a/components.json +++ b/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/deno.json b/deno.json new file mode 100644 index 00000000..730f865e --- /dev/null +++ b/deno.json @@ -0,0 +1,53 @@ +{ + "exclude": [ + "mobile/**", + "**/mobile/**", + "node_modules/**", + ".next/**", + "Temp/**", + "**/__tests__/**", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test.js", + "**/*.test.jsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx" + ], + "lint": { + "include": [ + "src/lib", + "src/app/api" + ], + "exclude": [ + "**/__tests__/**", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test.js", + "**/*.test.jsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/node_modules/**", + "**/.next/**", + "**/mobile/**", + "**/Temp/**", + "**/public/sw.js" + ], + "rules": { + "exclude": [ + "no-sloppy-imports", + "no-window" + ] + } + }, + "allowScripts": [ + "npm:@firebase/util@1.15.2", + "npm:@sentry/cli@2.58.6", + "npm:esbuild@0.28.1", + "npm:protobufjs@7.6.5", + "npm:unrs-resolver@1.12.2" + ] +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 00000000..0d59504e --- /dev/null +++ b/deno.lock @@ -0,0 +1,8376 @@ +{ + "version": "5", + "specifiers": { + "npm:@eslint/js@^9.39.5": "9.39.5", + "npm:@hookform/resolvers@^5.5.7": "5.5.7_react-hook-form@7.83.0__react@19.2.8_zod@4.4.3", + "npm:@opentelemetry/context-async-hooks@^2.10.0": "2.10.0", + "npm:@playwright/test@^1.62.1": "1.62.1", + "npm:@radix-ui/react-alert-dialog@^1.1.23": "1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-avatar@^1.2.6": "1.2.6_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-checkbox@^1.3.11": "1.3.11_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-dialog@^1.1.23": "1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-dropdown-menu@^2.1.24": "2.1.24_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-label@^2.1.15": "2.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-popover@^1.1.23": "1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-progress@^1.1.16": "1.1.16_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-radio-group@^1.4.7": "1.4.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-scroll-area@^1.2.18": "1.2.18_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-select@^2.3.7": "2.3.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-separator@^1.1.15": "1.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-slot@^1.3.3": "1.3.3_@types+react@19.2.18_react@19.2.8", + "npm:@radix-ui/react-switch@^1.3.7": "1.3.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@radix-ui/react-tabs@^1.1.21": "1.1.21_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@scalar/nextjs-api-reference@~0.11.12": "0.11.12_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_@playwright+test@1.62.1", + "npm:@sentry/nextjs@^10.69.0": "10.69.0_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_@playwright+test@1.62.1", + "npm:@serwist/next@^9.5.12": "9.5.12_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_typescript@6.0.3_@playwright+test@1.62.1", + "npm:@supabase/ssr@~0.12.4": "0.12.4_@supabase+supabase-js@2.111.0", + "npm:@supabase/supabase-js@^2.111.0": "2.111.0", + "npm:@tailwindcss/postcss@4": "4.3.3", + "npm:@tanstack/react-query@^5.101.4": "5.101.4_react@19.2.8", + "npm:@tanstack/react-virtual@^3.14.9": "3.14.9_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@testing-library/dom@^10.4.1": "10.4.1", + "npm:@testing-library/jest-dom@7": "7.0.0_@testing-library+dom@10.4.1", + "npm:@testing-library/react@^16.3.2": "16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:@testing-library/user-event@^14.6.1": "14.6.1_@testing-library+dom@10.4.1", + "npm:@types/node@^26.1.2": "26.1.2", + "npm:@types/nprogress@~0.2.3": "0.2.3", + "npm:@types/react-dom@^19.2.4": "19.2.4_@types+react@19.2.18", + "npm:@types/react@^19.2.18": "19.2.18", + "npm:@types/sanitize-html@^2.16.1": "2.16.1", + "npm:@typescript-eslint/parser@^8.65.0": "8.65.0_eslint@9.39.5_typescript@6.0.3", + "npm:@upstash/ratelimit@^2.0.8": "2.0.8_@upstash+redis@1.38.1", + "npm:@upstash/redis@^1.38.1": "1.38.1", + "npm:@vitejs/plugin-react@^6.0.5": "6.0.5_@types+node@26.1.2", + "npm:@vitest/coverage-v8@^4.1.10": "4.1.10_vitest@4.1.10_@types+node@26.1.2_@vitest+ui@4.1.10_happy-dom@20.11.1_jsdom@30.0.1", + "npm:@vitest/ui@^4.1.10": "4.1.10_vitest@4.1.10_@types+node@26.1.2_@vitest+coverage-v8@4.1.10_happy-dom@20.11.1_jsdom@30.0.1_vite@8.2.0__@types+node@26.1.2", + "npm:axios@^1.19.0": "1.19.0", + "npm:class-variance-authority@~0.7.1": "0.7.1", + "npm:clsx@^2.1.1": "2.1.1", + "npm:date-fns@^4.4.0": "4.4.0", + "npm:eslint-config-next@^16.2.12": "16.2.12_eslint@9.39.5_typescript@6.0.3", + "npm:eslint-plugin-react-hooks@^7.1.1": "7.1.1_eslint@9.39.5", + "npm:eslint-plugin-react@^7.37.5": "7.37.5_eslint@9.39.5", + "npm:eslint-plugin-security@^4.0.1": "4.0.1", + "npm:eslint-plugin-sonarjs@^4.2.0": "4.2.0_eslint@9.39.5", + "npm:eslint-plugin-unused-imports@^4.4.1": "4.4.1_eslint@9.39.5", + "npm:eslint@^9.39.5": "9.39.5", + "npm:firebase-admin@^14.2.0": "14.2.0", + "npm:framer-motion@^12.43.0": "12.43.0_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:glob@^13.0.6": "13.0.6", + "npm:globals@^17.8.0": "17.8.0", + "npm:googleapis@173": "173.0.0", + "npm:happy-dom@^20.11.1": "20.11.1", + "npm:husky@^9.1.7": "9.1.7", + "npm:jose@^6.2.5": "6.2.6", + "npm:jsdom@^30.0.1": "30.0.1", + "npm:ldrs@^1.1.9": "1.1.9", + "npm:lint-staged@^17.3.0": "17.3.0", + "npm:lodash-es@^4.18.1": "4.18.1", + "npm:lru-cache@^11.5.2": "11.5.2", + "npm:lucide-react@^1.28.0": "1.28.0_react@19.2.8", + "npm:next@^16.2.12": "16.2.12_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:nextjs-toploader@^3.9.17": "3.9.17_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_react-dom@19.2.8__react@19.2.8_@playwright+test@1.62.1", + "npm:node-domexception@^2.0.2": "2.0.2", + "npm:react-day-picker@^10.0.1": "10.0.1_@types+react@19.2.18_react@19.2.8", + "npm:react-dom@^19.2.8": "19.2.8_react@19.2.8", + "npm:react-email@^6.9.1": "6.9.1_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:react-hook-form@^7.83.0": "7.83.0_react@19.2.8", + "npm:react-is@^19.2.8": "19.2.8", + "npm:react-markdown@^10.1.0": "10.1.0_@types+react@19.2.18_react@19.2.8", + "npm:react-turnstile@^1.1.5": "1.1.5_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:react@^19.2.8": "19.2.8", + "npm:recharts@^3.10.1": "3.10.1_react@19.2.8_react-dom@19.2.8__react@19.2.8_react-is@19.2.8_@types+react@19.2.18", + "npm:resend@^6.18.1": "6.18.1", + "npm:rimraf@^6.1.3": "6.1.3", + "npm:sanitize-html@^2.17.6": "2.17.6", + "npm:server-only@^0.0.1": "0.0.1", + "npm:serwist@^9.5.12": "9.5.12_typescript@6.0.3_browserslist@4.28.6", + "npm:sonner@^2.0.7": "2.0.7_react@19.2.8_react-dom@19.2.8__react@19.2.8", + "npm:source-map@0.8": "0.8.0", + "npm:supabase@^2.111.0": "2.111.0", + "npm:tailwind-merge@^3.6.0": "3.6.0", + "npm:tailwindcss@4": "4.3.3", + "npm:tw-animate-css@^1.4.0": "1.4.0", + "npm:typescript-eslint@^8.65.0": "8.65.0_eslint@9.39.5_typescript@6.0.3", + "npm:typescript@^6.0.3": "6.0.3", + "npm:uuid@^14.0.1": "14.0.1", + "npm:vitest@^4.1.10": "4.1.10_@types+node@26.1.2_@vitest+coverage-v8@4.1.10_@vitest+ui@4.1.10_happy-dom@20.11.1_jsdom@30.0.1", + "npm:zod@^4.4.3": "4.4.3" + }, + "npm": { + "@adobe/css-tools@4.5.0": { + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==" + }, + "@alloc/quick-lru@5.2.0": { + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==" + }, + "@apm-js-collab/code-transformer-bundler-plugins@0.7.3": { + "integrity": "sha512-qNbPwuMZ8f5ZuGj/ttPeB7a6C/S1bB6tNYaEL5vNiRKydSAxa4AU0gxCWgaP4fVju+AuwhcumSFjrEcGF9Dv7Q==", + "dependencies": [ + "@apm-js-collab/code-transformer", + "es-module-lexer", + "magic-string", + "module-details-from-path" + ] + }, + "@apm-js-collab/code-transformer@0.18.1": { + "integrity": "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==", + "dependencies": [ + "@types/estree", + "astring", + "esquery", + "meriyah", + "semifies", + "source-map" + ], + "bin": true + }, + "@apm-js-collab/tracing-hooks@0.13.0": { + "integrity": "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==", + "dependencies": [ + "@apm-js-collab/code-transformer", + "debug@4.4.3", + "module-details-from-path" + ] + }, + "@asamuzakjp/css-color@6.0.5": { + "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", + "dependencies": [ + "@csstools/css-calc", + "@csstools/css-color-parser", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer", + "lru-cache@11.5.2" + ] + }, + "@asamuzakjp/dom-selector@8.3.0": { + "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==", + "dependencies": [ + "bidi-js", + "css-tree", + "is-potential-custom-element-name", + "lru-cache@11.5.2" + ] + }, + "@babel/code-frame@7.29.7": { + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dependencies": [ + "@babel/helper-validator-identifier", + "js-tokens@4.0.0", + "picocolors" + ] + }, + "@babel/compat-data@7.29.7": { + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==" + }, + "@babel/core@7.29.7": { + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-compilation-targets", + "@babel/helper-module-transforms", + "@babel/helpers", + "@babel/parser@7.29.8", + "@babel/template", + "@babel/traverse@7.29.8", + "@babel/types", + "@jridgewell/remapping", + "convert-source-map", + "debug@4.4.3", + "gensync", + "json5@2.2.3", + "semver@6.3.1" + ] + }, + "@babel/generator@7.29.8": { + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dependencies": [ + "@babel/parser@7.29.8", + "@babel/types", + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping", + "jsesc" + ] + }, + "@babel/helper-compilation-targets@7.29.7": { + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dependencies": [ + "@babel/compat-data", + "@babel/helper-validator-option", + "browserslist", + "lru-cache@5.1.1", + "semver@6.3.1" + ] + }, + "@babel/helper-globals@7.29.7": { + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==" + }, + "@babel/helper-module-imports@7.29.7": { + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dependencies": [ + "@babel/traverse@7.29.8", + "@babel/types" + ] + }, + "@babel/helper-module-transforms@7.29.7_@babel+core@7.29.7": { + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dependencies": [ + "@babel/core", + "@babel/helper-module-imports", + "@babel/helper-validator-identifier", + "@babel/traverse@7.29.8" + ] + }, + "@babel/helper-string-parser@7.29.7": { + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==" + }, + "@babel/helper-validator-identifier@7.29.7": { + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==" + }, + "@babel/helper-validator-option@7.29.7": { + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==" + }, + "@babel/helpers@7.29.7": { + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dependencies": [ + "@babel/template", + "@babel/types" + ] + }, + "@babel/parser@7.29.2": { + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/parser@7.29.8": { + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/runtime@7.29.7": { + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==" + }, + "@babel/template@7.29.7": { + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dependencies": [ + "@babel/code-frame", + "@babel/parser@7.29.8", + "@babel/types" + ] + }, + "@babel/traverse@7.29.0": { + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-globals", + "@babel/parser@7.29.8", + "@babel/template", + "@babel/types", + "debug@4.4.3" + ] + }, + "@babel/traverse@7.29.8": { + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dependencies": [ + "@babel/code-frame", + "@babel/generator", + "@babel/helper-globals", + "@babel/parser@7.29.8", + "@babel/template", + "@babel/types", + "debug@4.4.3" + ] + }, + "@babel/types@7.29.8": { + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dependencies": [ + "@babel/helper-string-parser", + "@babel/helper-validator-identifier" + ] + }, + "@bcoe/v8-coverage@1.0.2": { + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==" + }, + "@bramus/specificity@2.4.2": { + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dependencies": [ + "css-tree" + ], + "bin": true + }, + "@csstools/color-helpers@6.1.0": { + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==" + }, + "@csstools/css-calc@3.3.0_@csstools+css-parser-algorithms@4.0.0__@csstools+css-tokenizer@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dependencies": [ + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-color-parser@4.1.10_@csstools+css-parser-algorithms@4.0.0__@csstools+css-tokenizer@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dependencies": [ + "@csstools/color-helpers", + "@csstools/css-calc", + "@csstools/css-parser-algorithms", + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-parser-algorithms@4.0.0_@csstools+css-tokenizer@4.0.0": { + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dependencies": [ + "@csstools/css-tokenizer" + ] + }, + "@csstools/css-syntax-patches-for-csstree@1.1.7_css-tree@3.2.1": { + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dependencies": [ + "css-tree" + ], + "optionalPeers": [ + "css-tree" + ] + }, + "@csstools/css-tokenizer@4.0.0": { + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==" + }, + "@date-fns/tz@1.5.0": { + "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==" + }, + "@ecies/ciphers@0.2.6_@noble+ciphers@1.3.0": { + "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", + "dependencies": [ + "@noble/ciphers" + ] + }, + "@emnapi/core@1.10.0": { + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dependencies": [ + "@emnapi/wasi-threads@1.2.1", + "tslib" + ] + }, + "@emnapi/core@2.0.0-alpha.3": { + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dependencies": [ + "@emnapi/wasi-threads@2.0.1", + "tslib" + ] + }, + "@emnapi/runtime@1.10.0": { + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/runtime@1.11.3": { + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/runtime@2.0.0-alpha.3": { + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/wasi-threads@1.2.1": { + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/wasi-threads@2.0.1": { + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dependencies": [ + "tslib" + ] + }, + "@esbuild/aix-ppc64@0.28.1": { + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.28.1": { + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.28.1": { + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.28.1": { + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.28.1": { + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.28.1": { + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.28.1": { + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.28.1": { + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.28.1": { + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.28.1": { + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.28.1": { + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.28.1": { + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.28.1": { + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.28.1": { + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.28.1": { + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.28.1": { + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.28.1": { + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.28.1": { + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.28.1": { + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.28.1": { + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.28.1": { + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.28.1": { + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.28.1": { + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.28.1": { + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.28.1": { + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.28.1": { + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@eslint-community/eslint-utils@4.10.1_eslint@9.39.5": { + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dependencies": [ + "eslint", + "eslint-visitor-keys@3.4.3" + ] + }, + "@eslint-community/regexpp@4.12.2": { + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==" + }, + "@eslint/config-array@0.21.2": { + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dependencies": [ + "@eslint/object-schema", + "debug@4.4.3", + "minimatch" + ] + }, + "@eslint/config-helpers@0.4.2": { + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dependencies": [ + "@eslint/core" + ] + }, + "@eslint/core@0.17.0": { + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dependencies": [ + "@types/json-schema" + ] + }, + "@eslint/eslintrc@3.3.6": { + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dependencies": [ + "ajv@6.15.0", + "debug@4.4.3", + "espree", + "globals@14.0.0", + "ignore@5.3.2", + "import-fresh", + "js-yaml", + "minimatch", + "strip-json-comments" + ] + }, + "@eslint/js@9.39.5": { + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==" + }, + "@eslint/object-schema@2.1.7": { + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==" + }, + "@eslint/plugin-kit@0.4.1": { + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dependencies": [ + "@eslint/core", + "levn" + ] + }, + "@exodus/bytes@1.15.1": { + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==" + }, + "@fastify/busboy@3.2.0": { + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==" + }, + "@firebase/app-check-interop-types@0.3.4": { + "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==" + }, + "@firebase/app-types@0.9.5": { + "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==", + "dependencies": [ + "@firebase/logger" + ] + }, + "@firebase/auth-interop-types@0.2.5": { + "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==" + }, + "@firebase/component@0.7.4": { + "integrity": "sha512-tLpOaaCol9ugUIYp2R3CbWPPA8Ajg/papX/XHEy8U52b/QXH3BbX8tTJX9aShDCjp+9sMAxMLD94i7lresdugQ==", + "dependencies": [ + "@firebase/util", + "tslib" + ] + }, + "@firebase/database-compat@2.1.5": { + "integrity": "sha512-m2KZDNXrg8DBzXWQNbbrjOhsJnM+ctsSFaDYKrqj1gEetQ8BSAwRuMUdeWLM9a6qPBgOvOA+o09j1BSEzdFqOg==", + "dependencies": [ + "@firebase/component", + "@firebase/database", + "@firebase/database-types", + "@firebase/logger", + "@firebase/util", + "tslib" + ] + }, + "@firebase/database-types@1.0.21": { + "integrity": "sha512-SX1jUqhttKgg/m9dYRTvqU9QvucBooziWfA986r4cpsbi4zlsvewe424j3Vpduwd6DG1MSAMfBVT2VqA61FnkA==", + "dependencies": [ + "@firebase/app-types", + "@firebase/util" + ] + }, + "@firebase/database@1.1.4": { + "integrity": "sha512-D+j4+8uhGtNd1tVD+X+c8JrC4ppStGJKyujSQt2NPwdN26QcCk0BeIxue+UqspHkHiFHyQOimwlzjLewGq6S+A==", + "dependencies": [ + "@firebase/app-check-interop-types", + "@firebase/auth-interop-types", + "@firebase/component", + "@firebase/logger", + "@firebase/util", + "faye-websocket", + "tslib" + ] + }, + "@firebase/logger@0.5.1": { + "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==", + "dependencies": [ + "tslib" + ] + }, + "@firebase/util@1.15.2": { + "integrity": "sha512-974pWIZVLDMc5GW5YAsj8y0XxULxIy/sPUy7tsxmWbF93KRIyh9xpuHlh0zDL+shUcf5nHDjFOg9YLiQ763eiA==", + "dependencies": [ + "tslib" + ], + "scripts": true + }, + "@floating-ui/core@1.8.0": { + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "dependencies": [ + "@floating-ui/utils" + ] + }, + "@floating-ui/dom@1.8.0": { + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "dependencies": [ + "@floating-ui/core", + "@floating-ui/utils" + ] + }, + "@floating-ui/react-dom@2.1.9_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "dependencies": [ + "@floating-ui/dom", + "react", + "react-dom" + ] + }, + "@floating-ui/utils@0.2.12": { + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==" + }, + "@google-cloud/firestore@8.7.0": { + "integrity": "sha512-EvMpZQUXkTRdweSvOu6VL6EEQwHjHAgWz2UYZR+Mj6Ao52S+TWieHbSn15jiNnEw8F8RhbZj7IGXZ1PFB1eA+A==", + "dependencies": [ + "@opentelemetry/api", + "fast-deep-equal", + "functional-red-black-tree", + "google-gax", + "protobufjs" + ] + }, + "@google-cloud/paginator@5.0.2": { + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "dependencies": [ + "arrify", + "extend" + ] + }, + "@google-cloud/projectify@4.0.0": { + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==" + }, + "@google-cloud/promisify@4.0.0": { + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==" + }, + "@google-cloud/storage@7.21.0": { + "integrity": "sha512-l+IFTkd+6Y5LoAuXyYCKNAKtw/Ci+rAMqgdTB1jv4iZiLhw0rtq+0qjIRbBizXkNzEFmXiXUW0H7sZQQvk1ffA==", + "dependencies": [ + "@google-cloud/paginator", + "@google-cloud/projectify", + "@google-cloud/promisify", + "abort-controller", + "async-retry", + "duplexify", + "fast-xml-parser", + "gaxios@6.7.1", + "google-auth-library@9.15.1", + "html-entities", + "mime", + "p-limit", + "retry-request@7.0.2", + "teeny-request@9.0.0" + ] + }, + "@grpc/grpc-js@1.14.4": { + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "dependencies": [ + "@grpc/proto-loader", + "@js-sdsl/ordered-map" + ] + }, + "@grpc/proto-loader@0.8.1": { + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "dependencies": [ + "lodash.camelcase", + "long", + "protobufjs", + "yargs" + ], + "bin": true + }, + "@hookform/resolvers@5.5.7_react-hook-form@7.83.0__react@19.2.8_zod@4.4.3": { + "integrity": "sha512-CyPCYV8/KlXfEXLWj8HHHhVsR/IZ6Ckm3b/a4fWtO/lRnRK1huqncb8LlAWrmpPRsse5glF5aVuDRMHEr3UGag==", + "dependencies": [ + "@standard-schema/utils", + "react-hook-form", + "zod" + ], + "optionalPeers": [ + "zod" + ] + }, + "@humanfs/core@0.19.2": { + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dependencies": [ + "@humanfs/types" + ] + }, + "@humanfs/node@0.16.8": { + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dependencies": [ + "@humanfs/core", + "@humanfs/types", + "@humanwhocodes/retry" + ] + }, + "@humanfs/types@0.15.0": { + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==" + }, + "@humanwhocodes/module-importer@1.0.1": { + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/retry@0.4.3": { + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" + }, + "@img/colour@1.1.0": { + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==" + }, + "@img/sharp-darwin-arm64@0.35.3": { + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "optionalDependencies": [ + "@img/sharp-libvips-darwin-arm64" + ], + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@img/sharp-darwin-x64@0.35.3": { + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "optionalDependencies": [ + "@img/sharp-libvips-darwin-x64" + ], + "os": ["darwin"], + "cpu": ["x64"] + }, + "@img/sharp-freebsd-wasm32@0.35.3": { + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dependencies": [ + "@img/sharp-wasm32" + ], + "os": ["freebsd"] + }, + "@img/sharp-libvips-darwin-arm64@1.3.2": { + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@img/sharp-libvips-darwin-x64@1.3.2": { + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@img/sharp-libvips-linux-arm64@1.3.2": { + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@img/sharp-libvips-linux-arm@1.3.2": { + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@img/sharp-libvips-linux-ppc64@1.3.2": { + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@img/sharp-libvips-linux-riscv64@1.3.2": { + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@img/sharp-libvips-linux-s390x@1.3.2": { + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@img/sharp-libvips-linux-x64@1.3.2": { + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@img/sharp-libvips-linuxmusl-arm64@1.3.2": { + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@img/sharp-libvips-linuxmusl-x64@1.3.2": { + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@img/sharp-linux-arm64@0.35.3": { + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-arm64" + ], + "os": ["linux"], + "cpu": ["arm64"] + }, + "@img/sharp-linux-arm@0.35.3": { + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-arm" + ], + "os": ["linux"], + "cpu": ["arm"] + }, + "@img/sharp-linux-ppc64@0.35.3": { + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-ppc64" + ], + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@img/sharp-linux-riscv64@0.35.3": { + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-riscv64" + ], + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@img/sharp-linux-s390x@0.35.3": { + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-s390x" + ], + "os": ["linux"], + "cpu": ["s390x"] + }, + "@img/sharp-linux-x64@0.35.3": { + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "optionalDependencies": [ + "@img/sharp-libvips-linux-x64" + ], + "os": ["linux"], + "cpu": ["x64"] + }, + "@img/sharp-linuxmusl-arm64@0.35.3": { + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "optionalDependencies": [ + "@img/sharp-libvips-linuxmusl-arm64" + ], + "os": ["linux"], + "cpu": ["arm64"] + }, + "@img/sharp-linuxmusl-x64@0.35.3": { + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "optionalDependencies": [ + "@img/sharp-libvips-linuxmusl-x64" + ], + "os": ["linux"], + "cpu": ["x64"] + }, + "@img/sharp-wasm32@0.35.3": { + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dependencies": [ + "@emnapi/runtime@1.11.3" + ] + }, + "@img/sharp-webcontainers-wasm32@0.35.3": { + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "dependencies": [ + "@img/sharp-wasm32" + ], + "cpu": ["wasm32"] + }, + "@img/sharp-win32-arm64@0.35.3": { + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@img/sharp-win32-ia32@0.35.3": { + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@img/sharp-win32-x64@0.35.3": { + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/source-map@0.3.11": { + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@js-sdsl/ordered-map@4.4.2": { + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" + }, + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dependencies": [ + "@emnapi/core@1.10.0", + "@emnapi/runtime@1.10.0", + "@tybys/wasm-util" + ] + }, + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@1.10.0_@emnapi+runtime@1.11.3": { + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dependencies": [ + "@emnapi/core@1.10.0", + "@emnapi/runtime@1.11.3", + "@tybys/wasm-util" + ] + }, + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@2.0.0-alpha.3_@emnapi+runtime@2.0.0-alpha.3": { + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dependencies": [ + "@emnapi/core@2.0.0-alpha.3", + "@emnapi/runtime@2.0.0-alpha.3", + "@tybys/wasm-util" + ] + }, + "@next/env@16.2.12": { + "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==" + }, + "@next/eslint-plugin-next@16.2.12": { + "integrity": "sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==", + "dependencies": [ + "fast-glob" + ] + }, + "@next/swc-darwin-arm64@16.2.12": { + "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@next/swc-darwin-x64@16.2.12": { + "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@next/swc-linux-arm64-gnu@16.2.12": { + "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@next/swc-linux-arm64-musl@16.2.12": { + "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@next/swc-linux-x64-gnu@16.2.12": { + "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@next/swc-linux-x64-musl@16.2.12": { + "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@next/swc-win32-arm64-msvc@16.2.12": { + "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@next/swc-win32-x64-msvc@16.2.12": { + "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@noble/ciphers@1.3.0": { + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==" + }, + "@noble/curves@1.9.7": { + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dependencies": [ + "@noble/hashes" + ] + }, + "@noble/hashes@1.8.0": { + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" + }, + "@nodable/entities@3.0.0": { + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==" + }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, + "@nolyfill/is-core-module@1.0.39": { + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==" + }, + "@opentelemetry/api-logs@0.220.0": { + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "dependencies": [ + "@opentelemetry/api" + ] + }, + "@opentelemetry/api@1.9.1": { + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==" + }, + "@opentelemetry/context-async-hooks@2.10.0": { + "integrity": "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==", + "dependencies": [ + "@opentelemetry/api" + ] + }, + "@opentelemetry/core@2.10.0_@opentelemetry+api@1.9.1": { + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/semantic-conventions" + ] + }, + "@opentelemetry/instrumentation@0.220.0_@opentelemetry+api@1.9.1": { + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/api-logs", + "import-in-the-middle", + "require-in-the-middle" + ] + }, + "@opentelemetry/resources@2.10.0_@opentelemetry+api@1.9.1": { + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/core", + "@opentelemetry/semantic-conventions" + ] + }, + "@opentelemetry/sdk-trace-base@2.10.0_@opentelemetry+api@1.9.1": { + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/core", + "@opentelemetry/resources", + "@opentelemetry/sdk-trace", + "@opentelemetry/semantic-conventions" + ] + }, + "@opentelemetry/sdk-trace@2.10.0_@opentelemetry+api@1.9.1": { + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/core", + "@opentelemetry/resources", + "@opentelemetry/semantic-conventions" + ] + }, + "@opentelemetry/semantic-conventions@1.43.0": { + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==" + }, + "@oxc-project/types@0.142.0": { + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==" + }, + "@playwright/test@1.62.1": { + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dependencies": [ + "playwright" + ], + "bin": true + }, + "@polka/url@1.0.0-next.29": { + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" + }, + "@protobufjs/aspromise@1.1.2": { + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64@1.1.2": { + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen@2.0.5": { + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==" + }, + "@protobufjs/eventemitter@1.1.1": { + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==" + }, + "@protobufjs/fetch@1.1.1": { + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dependencies": [ + "@protobufjs/aspromise" + ] + }, + "@protobufjs/float@1.0.2": { + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/path@1.1.2": { + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool@1.1.0": { + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8@1.1.2": { + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==" + }, + "@radix-ui/number@1.1.3": { + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==" + }, + "@radix-ui/primitive@1.1.7": { + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==" + }, + "@radix-ui/react-alert-dialog@1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-dialog", + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-arrow@1.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "dependencies": [ + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-avatar@1.2.6_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-context", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-is-hydrated", + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-checkbox@1.3.11_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-size", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-collection@1.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "dependencies": [ + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-primitive", + "@radix-ui/react-slot", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-compose-refs@1.1.5_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-context@1.2.2_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-dialog@1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-dismissable-layer", + "@radix-ui/react-focus-guards", + "@radix-ui/react-focus-scope", + "@radix-ui/react-id", + "@radix-ui/react-portal", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-slot", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "aria-hidden", + "react", + "react-dom", + "react-remove-scroll" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-direction@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-dismissable-layer@1.1.19_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-effect-event", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-dropdown-menu@2.1.24_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-id", + "@radix-ui/react-menu", + "@radix-ui/react-primitive", + "@radix-ui/react-use-controllable-state", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-focus-guards@1.1.6_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-focus-scope@1.1.16_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "dependencies": [ + "@radix-ui/react-compose-refs", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-id@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "dependencies": [ + "@radix-ui/react-use-layout-effect", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-label@2.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "dependencies": [ + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-menu@2.1.24_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-collection", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-dismissable-layer", + "@radix-ui/react-focus-guards", + "@radix-ui/react-focus-scope", + "@radix-ui/react-id", + "@radix-ui/react-popper", + "@radix-ui/react-portal", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-roving-focus", + "@radix-ui/react-slot", + "@radix-ui/react-use-callback-ref", + "@types/react", + "@types/react-dom", + "aria-hidden", + "react", + "react-dom", + "react-remove-scroll" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-popover@1.1.23_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-dismissable-layer", + "@radix-ui/react-focus-guards", + "@radix-ui/react-focus-scope", + "@radix-ui/react-id", + "@radix-ui/react-popper", + "@radix-ui/react-portal", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-slot", + "@radix-ui/react-use-controllable-state", + "@types/react", + "@types/react-dom", + "aria-hidden", + "react", + "react-dom", + "react-remove-scroll" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-popper@1.3.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "dependencies": [ + "@floating-ui/react-dom", + "@radix-ui/react-arrow", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-layout-effect", + "@radix-ui/react-use-rect", + "@radix-ui/react-use-size", + "@radix-ui/rect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-portal@1.1.17_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "dependencies": [ + "@radix-ui/react-primitive", + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-presence@1.1.10_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "dependencies": [ + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-primitive@2.1.10_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "dependencies": [ + "@radix-ui/react-slot", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-progress@1.1.16_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "dependencies": [ + "@radix-ui/react-context", + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-radio-group@1.4.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-roving-focus", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-size", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-roving-focus@1.1.19_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-collection", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-id", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-is-hydrated", + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-scroll-area@1.2.18_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "dependencies": [ + "@radix-ui/number", + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-layout-effect", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-select@2.3.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "dependencies": [ + "@radix-ui/number", + "@radix-ui/primitive", + "@radix-ui/react-collection", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-dismissable-layer", + "@radix-ui/react-focus-guards", + "@radix-ui/react-focus-scope", + "@radix-ui/react-id", + "@radix-ui/react-popper", + "@radix-ui/react-portal", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-slot", + "@radix-ui/react-use-callback-ref", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-layout-effect", + "@radix-ui/react-use-previous", + "@radix-ui/react-visually-hidden", + "@types/react", + "@types/react-dom", + "aria-hidden", + "react", + "react-dom", + "react-remove-scroll" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-separator@1.1.15_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "dependencies": [ + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-slot@1.3.3_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "dependencies": [ + "@radix-ui/react-compose-refs", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-switch@1.3.7_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-compose-refs", + "@radix-ui/react-context", + "@radix-ui/react-primitive", + "@radix-ui/react-use-controllable-state", + "@radix-ui/react-use-size", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-tabs@1.1.21_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-context", + "@radix-ui/react-direction", + "@radix-ui/react-id", + "@radix-ui/react-presence", + "@radix-ui/react-primitive", + "@radix-ui/react-roving-focus", + "@radix-ui/react-use-controllable-state", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/react-use-callback-ref@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-controllable-state@1.2.6_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "dependencies": [ + "@radix-ui/primitive", + "@radix-ui/react-use-effect-event", + "@radix-ui/react-use-layout-effect", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-effect-event@0.0.5_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "dependencies": [ + "@radix-ui/react-use-layout-effect", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-is-hydrated@0.1.3_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-layout-effect@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-previous@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "dependencies": [ + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-rect@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "dependencies": [ + "@radix-ui/rect", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-use-size@1.1.4_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "dependencies": [ + "@radix-ui/react-use-layout-effect", + "@types/react", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "@radix-ui/react-visually-hidden@1.2.11_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "dependencies": [ + "@radix-ui/react-primitive", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@radix-ui/rect@1.1.3": { + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==" + }, + "@react-email/render@2.1.0_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==", + "dependencies": [ + "entities@4.5.0", + "html-to-text", + "html5parser", + "prettier", + "react", + "react-dom" + ] + }, + "@reduxjs/toolkit@2.12.0_react@19.2.8_react-redux@9.3.0__@types+react@19.2.18__react@19.2.8__redux@5.0.1_@types+react@19.2.18": { + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "dependencies": [ + "@standard-schema/spec", + "@standard-schema/utils", + "immer", + "react", + "react-redux", + "redux", + "redux-thunk", + "reselect" + ], + "optionalPeers": [ + "react", + "react-redux" + ] + }, + "@rolldown/binding-android-arm64@1.2.1": { + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rolldown/binding-darwin-arm64@1.2.1": { + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rolldown/binding-darwin-x64@1.2.1": { + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rolldown/binding-freebsd-x64@1.2.1": { + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rolldown/binding-linux-arm-gnueabihf@1.2.1": { + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rolldown/binding-linux-arm64-gnu@1.2.1": { + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rolldown/binding-linux-arm64-musl@1.2.1": { + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rolldown/binding-linux-ppc64-gnu@1.2.1": { + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rolldown/binding-linux-s390x-gnu@1.2.1": { + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rolldown/binding-linux-x64-gnu@1.2.1": { + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rolldown/binding-linux-x64-musl@1.2.1": { + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rolldown/binding-openharmony-arm64@1.2.1": { + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rolldown/binding-wasm32-wasi@1.2.1": { + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", + "dependencies": [ + "@emnapi/core@2.0.0-alpha.3", + "@emnapi/runtime@2.0.0-alpha.3", + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@2.0.0-alpha.3_@emnapi+runtime@2.0.0-alpha.3" + ] + }, + "@rolldown/binding-win32-arm64-msvc@1.2.1": { + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rolldown/binding-win32-x64-msvc@1.2.1": { + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rolldown/pluginutils@1.0.1": { + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==" + }, + "@rollup/plugin-commonjs@28.0.1_rollup@4.62.3": { + "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", + "dependencies": [ + "@rollup/pluginutils", + "commondir", + "estree-walker@2.0.2", + "fdir", + "is-reference", + "magic-string", + "picomatch@4.0.5", + "rollup" + ], + "optionalPeers": [ + "rollup" + ] + }, + "@rollup/pluginutils@5.4.0_rollup@4.62.3": { + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dependencies": [ + "@types/estree", + "estree-walker@2.0.2", + "picomatch@4.0.5", + "rollup" + ], + "optionalPeers": [ + "rollup" + ] + }, + "@rollup/rollup-android-arm-eabi@4.62.3": { + "integrity": "sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.62.3": { + "integrity": "sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-arm64@4.62.3": { + "integrity": "sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-x64@4.62.3": { + "integrity": "sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rollup/rollup-freebsd-arm64@4.62.3": { + "integrity": "sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.62.3": { + "integrity": "sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-arm-gnueabihf@4.62.3": { + "integrity": "sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.62.3": { + "integrity": "sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm64-gnu@4.62.3": { + "integrity": "sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-arm64-musl@4.62.3": { + "integrity": "sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-loong64-gnu@4.62.3": { + "integrity": "sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-loong64-musl@4.62.3": { + "integrity": "sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.62.3": { + "integrity": "sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-ppc64-musl@4.62.3": { + "integrity": "sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.62.3": { + "integrity": "sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.62.3": { + "integrity": "sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.62.3": { + "integrity": "sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rollup/rollup-linux-x64-gnu@4.62.3": { + "integrity": "sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-x64-musl@4.62.3": { + "integrity": "sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-openbsd-x64@4.62.3": { + "integrity": "sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.62.3": { + "integrity": "sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-arm64-msvc@4.62.3": { + "integrity": "sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-ia32-msvc@4.62.3": { + "integrity": "sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-gnu@4.62.3": { + "integrity": "sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rollup/rollup-win32-x64-msvc@4.62.3": { + "integrity": "sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rtsao/scc@1.1.0": { + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==" + }, + "@scalar/client-side-rendering@0.3.5": { + "integrity": "sha512-0MX6HN4hNhMlBTqm+PJ1Rgi5yQy7NkMEPMa6QXdyKAO59pQVzWlBGT+MlAnyXCeVrqFULzZh90v81t5UQ/uYEw==", + "dependencies": [ + "@scalar/schemas", + "@scalar/types", + "@scalar/validation" + ] + }, + "@scalar/helpers@0.9.2": { + "integrity": "sha512-hjyMpMZjTBZQhyByZmz5oUgRKUQJO5V5AOiJxsVEGbUmgA7sJRQeTrXLB+BEwzaKS5nm2opJeNyMBYLFNK4hiQ==" + }, + "@scalar/nextjs-api-reference@0.11.12_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_@playwright+test@1.62.1": { + "integrity": "sha512-JTzoTSMCvdQGae/ECL1sOCBe9/lo50Fk87Pb93JKubFXo5SHSsPcpWX0dFWwyoamlH0UpgHqEUw3xljFpmAbNw==", + "dependencies": [ + "@scalar/client-side-rendering", + "next", + "react" + ] + }, + "@scalar/schemas@0.8.0": { + "integrity": "sha512-bwu/NCOghZI/cL6Ayti/Oeb5XEMRTncBsyQeHhkFwk4PZsR910me+kZPd5TAp6TqifMfDbAe3xyLSdlzU/KFLA==", + "dependencies": [ + "@scalar/helpers", + "@scalar/validation" + ] + }, + "@scalar/types@0.17.0": { + "integrity": "sha512-mj033MX0EFOEwfpO3ch7FGGnMbye1aLlnP6LmTC9Il2AlBCDL41rYPk67jF5ICAMsAES1RQ+8N2bcC0MJnupLQ==", + "dependencies": [ + "@scalar/helpers", + "nanoid@5.1.16", + "type-fest@5.8.0", + "zod" + ] + }, + "@scalar/validation@0.6.2": { + "integrity": "sha512-Sc1TkcwGV6aVCO51AyKeaGiP8gpwAHxEtO5d3tZzPV+KsnlC/YokQxFxwBrbIXw73k9hmcExnJyGu3k5i6n6VA==" + }, + "@selderee/plugin-htmlparser2@0.11.0": { + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": [ + "domhandler@5.0.3", + "selderee" + ] + }, + "@sentry/babel-plugin-component-annotate@5.3.0": { + "integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==" + }, + "@sentry/browser-utils@10.69.0": { + "integrity": "sha512-e/u1Abj0zRPwR/deGZAP3GOULrsx67/XXnM5Skniqs4uxTsdNtPek1Nef0tpxwaQJYxwh6pWdhswLPPbbPOgBQ==", + "dependencies": [ + "@sentry/conventions", + "@sentry/core" + ] + }, + "@sentry/browser@10.69.0": { + "integrity": "sha512-8391tnm96YbR7b8SYfEA/NEIZuyb2r3SZrtAT0bhZtjlujcYWjo7gugQvk8sWLU9cAa/euD00eJoIoJvNfpd7Q==", + "dependencies": [ + "@sentry/browser-utils", + "@sentry/conventions", + "@sentry/core", + "@sentry/feedback", + "@sentry/replay", + "@sentry/replay-canvas" + ] + }, + "@sentry/bundler-plugin-core@5.3.0": { + "integrity": "sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==", + "dependencies": [ + "@babel/core", + "@sentry/babel-plugin-component-annotate", + "@sentry/cli", + "dotenv@16.6.1", + "find-up", + "glob", + "magic-string" + ] + }, + "@sentry/bundler-plugins@10.69.0_rollup@4.62.3_webpack@5.109.2": { + "integrity": "sha512-I1otnSJIH4IOugLp+kcBbT0Kcex+J8xnHuUyzMAwCYSpZ0FMVvA723uNmkMdW0PxRRw0oEhe0qDB+t+ZjHxUiA==", + "dependencies": [ + "@babel/core", + "@sentry/cli", + "@sentry/core", + "dotenv@17.4.2", + "find-up", + "glob", + "magic-string", + "rollup", + "webpack" + ], + "optionalPeers": [ + "rollup", + "webpack" + ] + }, + "@sentry/cli-darwin@2.58.6": { + "integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==", + "os": ["darwin"] + }, + "@sentry/cli-linux-arm64@2.58.6": { + "integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==", + "os": ["linux", "freebsd", "android"], + "cpu": ["arm64"] + }, + "@sentry/cli-linux-arm@2.58.6": { + "integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==", + "os": ["linux", "freebsd", "android"], + "cpu": ["arm"] + }, + "@sentry/cli-linux-i686@2.58.6": { + "integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==", + "os": ["linux", "freebsd", "android"], + "cpu": ["x86", "ia32"] + }, + "@sentry/cli-linux-x64@2.58.6": { + "integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==", + "os": ["linux", "freebsd", "android"], + "cpu": ["x64"] + }, + "@sentry/cli-win32-arm64@2.58.6": { + "integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@sentry/cli-win32-i686@2.58.6": { + "integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==", + "os": ["win32"], + "cpu": ["x86", "ia32"] + }, + "@sentry/cli-win32-x64@2.58.6": { + "integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@sentry/cli@2.58.6": { + "integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==", + "dependencies": [ + "https-proxy-agent@5.0.1", + "node-fetch@2.7.0", + "progress", + "proxy-from-env@1.1.0", + "which" + ], + "optionalDependencies": [ + "@sentry/cli-darwin", + "@sentry/cli-linux-arm", + "@sentry/cli-linux-arm64", + "@sentry/cli-linux-i686", + "@sentry/cli-linux-x64", + "@sentry/cli-win32-arm64", + "@sentry/cli-win32-i686", + "@sentry/cli-win32-x64" + ], + "scripts": true, + "bin": true + }, + "@sentry/conventions@0.16.0": { + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==" + }, + "@sentry/core@10.69.0": { + "integrity": "sha512-+uuqVEeiDzYuAKjZLqsROKXvRTbl/QeH0gfGRtpYib1cud4rAFWRIkFmcR7Jb7JGFYwmReyQotiTj/hcDszTZg==", + "dependencies": [ + "@sentry/conventions" + ] + }, + "@sentry/feedback@10.69.0": { + "integrity": "sha512-qrGz5Qaw93/IhMjlFN6uIaXeHwgHDaKGa6FkTAP6PonpkvSbGGqan6xfsENxzj9HUVoli1lZ6tMRDnt2qtSPhg==", + "dependencies": [ + "@sentry/core" + ] + }, + "@sentry/nextjs@10.69.0_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_@playwright+test@1.62.1": { + "integrity": "sha512-48eYXezKAmSlSWtBsvwXvdHHHavXRDVIVZ3mI5vidhJwhqSs0ekwG0ZsPG3xNdaJLmnNEmavHEVupSbuuOTZZA==", + "dependencies": [ + "@opentelemetry/api", + "@rollup/plugin-commonjs", + "@sentry/browser-utils", + "@sentry/bundler-plugin-core", + "@sentry/conventions", + "@sentry/core", + "@sentry/node", + "@sentry/opentelemetry", + "@sentry/react", + "@sentry/server-utils", + "@sentry/vercel-edge", + "@sentry/webpack-plugin", + "next", + "rollup", + "stacktrace-parser" + ] + }, + "@sentry/node-core@10.69.0_@opentelemetry+api@1.9.1_@opentelemetry+instrumentation@0.220.0__@opentelemetry+api@1.9.1_@opentelemetry+sdk-trace-base@2.10.0__@opentelemetry+api@1.9.1": { + "integrity": "sha512-IgArHczrZJxkgxoffHscj0NxQrG6kCazgmGQnlf3j58J1ec21YaUu8Tu+7G4Lo5tCiW3teQnwlKW1ttMXSqWRw==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/instrumentation", + "@opentelemetry/sdk-trace-base", + "@sentry/conventions", + "@sentry/core", + "@sentry/opentelemetry", + "import-in-the-middle" + ], + "optionalPeers": [ + "@opentelemetry/api", + "@opentelemetry/instrumentation", + "@opentelemetry/sdk-trace-base" + ] + }, + "@sentry/node@10.69.0": { + "integrity": "sha512-xEXA1YGIiTZbrW6MWV34uS6JGQuQg2ijTI0zed+FsJb9JZKPYel/GZK8Km26vfTVb+yCXFmWZNBesKegNcVdzg==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/instrumentation", + "@opentelemetry/sdk-trace-base", + "@sentry/conventions", + "@sentry/core", + "@sentry/node-core", + "@sentry/opentelemetry", + "@sentry/server-utils", + "import-in-the-middle" + ] + }, + "@sentry/opentelemetry@10.69.0_@opentelemetry+api@1.9.1_@opentelemetry+sdk-trace-base@2.10.0__@opentelemetry+api@1.9.1": { + "integrity": "sha512-3FyWV6YcEJuvLrlaKGE1dHXCI+1YO0a62w7PkwlRg8yp6K6YXkmdwu9GjqaYD+Ju4tm7uC7mHIsGFQMm0M7pqQ==", + "dependencies": [ + "@opentelemetry/api", + "@opentelemetry/core", + "@opentelemetry/sdk-trace-base", + "@sentry/conventions", + "@sentry/core" + ] + }, + "@sentry/react@10.69.0_react@19.2.8": { + "integrity": "sha512-f0Il/JMteHjdWPNZQB3rtp1Pcj2Leb3p0KSZuv3rh0EUril9CbWtQVy5zJhoAppi+MWWmgRWa+6BpHbQf+ABQA==", + "dependencies": [ + "@sentry/browser", + "@sentry/conventions", + "@sentry/core", + "react" + ] + }, + "@sentry/replay-canvas@10.69.0": { + "integrity": "sha512-VF6nXvSninHcc7dC1Zme0RjkC7VgRMCixs6jKaQX5zTNeqTW3dZGSefSOVv+ZteRi3hJvVORq985VjUC9Z/0+A==", + "dependencies": [ + "@sentry/core", + "@sentry/replay" + ] + }, + "@sentry/replay@10.69.0": { + "integrity": "sha512-uRhmNhtFGPOlM0iniVmWKAX3KVXI0le41yYK/iKdPjinT9jA3ZrmykO/Fv1v/KI5znOtwa9D6eHRnDTTMRxFrg==", + "dependencies": [ + "@sentry/browser-utils", + "@sentry/core" + ] + }, + "@sentry/server-utils@10.69.0": { + "integrity": "sha512-0MwHrA8+nNvMIsqf8m3cXwCBlUjr6AS7N6CZvHJtY1DkqEvQqEbD5VIrhzEyHN/KMZIgQ8XeDCQRhjnXFQGRhg==", + "dependencies": [ + "@apm-js-collab/code-transformer-bundler-plugins", + "@apm-js-collab/tracing-hooks", + "@sentry/conventions", + "@sentry/core", + "meriyah" + ] + }, + "@sentry/vercel-edge@10.69.0": { + "integrity": "sha512-P3IsZyM3j8s4sGuxnGn1+EScVZHABsuzqv0NzIUen61ml0x+c6F6XXobvMhMcRp98fIQe1hZBpzH0VNxc7eCIA==", + "dependencies": [ + "@opentelemetry/api", + "@sentry/core" + ] + }, + "@sentry/webpack-plugin@5.4.0_rollup@4.62.3": { + "integrity": "sha512-J3a0BvUZ75Qxy+v/Ap3Hx4ZEcSjlPHZ/jDtxdRhXQCyNeEb8xq0uUBTI9VLtGk2eNeNucOxOEJ5ngqdNjnEH/A==", + "dependencies": [ + "@sentry/bundler-plugins", + "webpack" + ] + }, + "@serwist/build@9.5.12_typescript@6.0.3_browserslist@4.28.6": { + "integrity": "sha512-U2UkA9BjdpniZkXDIG6NQRBEbXccRvqewzL3hfNEQERvM2bL6ezKvsVF9359akUWP8BxV950Kkq9LKGFmOR8Uw==", + "dependencies": [ + "@serwist/utils", + "common-tags", + "glob", + "pretty-bytes", + "source-map", + "type-fest@5.8.0", + "typescript", + "zod" + ], + "optionalPeers": [ + "typescript" + ] + }, + "@serwist/next@9.5.12_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_typescript@6.0.3_@playwright+test@1.62.1": { + "integrity": "sha512-aKSDZmxC2w6mvLC1aOcO4OnZqdKIl4zrW2VndlZT12+m+nIyOWih1xqqw4LsvUGfQCx0AswtKUUQ7woRYZ2thg==", + "dependencies": [ + "@serwist/build", + "@serwist/utils", + "@serwist/webpack-plugin", + "@serwist/window", + "browserslist", + "glob", + "kolorist", + "next", + "react", + "semver@7.8.5", + "serwist", + "typescript", + "zod" + ], + "optionalPeers": [ + "typescript" + ] + }, + "@serwist/utils@9.5.12_browserslist@4.28.6": { + "integrity": "sha512-BHDwiGL7H7JS7wFvVlAWTFHGt0ffHuPgKtdkaYP0lEWJFl6fKJRCnTDZhwhusFvBhicf37XP8Y6PouTZbcLmgg==", + "dependencies": [ + "browserslist" + ], + "optionalPeers": [ + "browserslist" + ] + }, + "@serwist/webpack-plugin@9.5.12_typescript@6.0.3_browserslist@4.28.6": { + "integrity": "sha512-+a5Fap7wVR4oN5jg1GxVNbWW+xUv3LO7UWYfg2D8PkJZiTCcX8PwkcmdzMGVr39dyOtIMTSAANw0af1zMjVbOA==", + "dependencies": [ + "@serwist/build", + "@serwist/utils", + "pretty-bytes", + "typescript", + "zod" + ], + "optionalPeers": [ + "typescript" + ] + }, + "@serwist/window@9.5.12_typescript@6.0.3_browserslist@4.28.6": { + "integrity": "sha512-+fjApJme34qfwdGE2kLT8sFx+xWO+a15477MN/B/b1v4HOWBmARuA9C2xGFBCT77XzCrzQ/2DxcDePZsqEYOlw==", + "dependencies": [ + "@types/trusted-types", + "serwist", + "typescript" + ], + "optionalPeers": [ + "typescript" + ] + }, + "@socket.io/component-emitter@3.1.2": { + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "@stablelib/base64@1.0.1": { + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==" + }, + "@standard-schema/spec@1.1.0": { + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + }, + "@standard-schema/utils@0.3.0": { + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" + }, + "@supabase/auth-js@2.111.0": { + "integrity": "sha512-hbRLgyQZEX0SDyF4LYXpv94qOIQyFATfpT5SIs2V0SisHnisVRkYgiPQWzv80l4S2mO3qof78rmoH9j5zoFsYQ==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/cli-darwin-arm64@2.111.0": { + "integrity": "sha512-H1ucZ+9Z37Ha7uqYrKHfAy1vXWMVsN4gNlKaOpjKUYoHwDEbueEHVIDA1/PBIUd4HX+usJfpq+R+gWqzM/FKqQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@supabase/cli-darwin-x64@2.111.0": { + "integrity": "sha512-4iMYm/XaAZJ8YzdJ2HBRVc7i9SRIwE6VQrnSt968WTt83M1y6knC7UENCKjMtO+As4QeZkICpUk50CeathqxbA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@supabase/cli-linux-arm64-musl@2.111.0": { + "integrity": "sha512-RnvbVlJ4TX/UIwLiglBVQ2eL4GAl9SfWZmM5LENXyIaohBcRZHDva5t2OyK+BPGBtngjVGpb3QyfLdvkt9yJcw==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@supabase/cli-linux-arm64@2.111.0": { + "integrity": "sha512-2KSHITFMXe2u5yALupBWHGeQ1IY4C8GkcIWPWgNFdtAPo03pgs1hLWgL1eeqSh/a0wB/6rgUlM1fQR/hAgCyCA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@supabase/cli-linux-x64-musl@2.111.0": { + "integrity": "sha512-2QVpsy/3v+TzqE5GgTCPruoxTlF3d8QPGirjcnah8hP66nxXStB9yTvxmJq+LtO3gwMt1Kpm7is0roZVkV55Pw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@supabase/cli-linux-x64@2.111.0": { + "integrity": "sha512-NwwNhiZZT4WYEPDXAbgZL+l77z/hoJ+w5t+52WBN1VlmoxwDmf2NP+UERM8wuCGFe/tmBlUuFE1eJYZfab0/qA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@supabase/cli-windows-arm64@2.111.0": { + "integrity": "sha512-twawKY5xfU2dNOnZrKDmrudxRG/XIKcBxOb6X2lMGJU3N1Hd+8oWpI9TwM5Qv+eXehtlsKDmUvbitStXvJr/xQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@supabase/cli-windows-x64@2.111.0": { + "integrity": "sha512-1F+X5tAYxAGx93ZZoIQBnIQ2Q2NnplKqjpigAS/zpTsNaWAjNi7EnxmuQKaEoAdqOHKbLhegVVDiw1+us3ZVpA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@supabase/functions-js@2.111.0": { + "integrity": "sha512-RW/OCsd6MO592zU8ifzP8/f8XzxxIdpb+Up5XaOtE26Fw+3zTp475WX7+GuuktiD1WF8pFUDe6khUPbMp77RCw==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/phoenix@0.4.5": { + "integrity": "sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==" + }, + "@supabase/postgrest-js@2.111.0": { + "integrity": "sha512-pcqeDsnWP0lx9GawduYxNZJHeuTm53O7L0SC8RF8tniV3GWIPY6me6OTdnwzdwNUmNy1dzUVtSyIfE6+OflzPQ==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/realtime-js@2.111.0": { + "integrity": "sha512-6oRf/vZyRwg8f8GbFSJkrD2w4HAu/yTvyMViHXHS+H5hNJzdXCrUR7cP5oW7daT3YlRnzRPY9LcGSJKZAmfMSg==", + "dependencies": [ + "@supabase/phoenix", + "tslib" + ] + }, + "@supabase/ssr@0.12.4_@supabase+supabase-js@2.111.0": { + "integrity": "sha512-xHzcgI8cC1TpBKSwJcR5Yd8CCwfIq0SBc5yb4yz/YFw5tbCrEQ0QT3a+2jymCxHgQWLfzwN93HZ6eRbcoMkOlA==", + "dependencies": [ + "@supabase/supabase-js", + "cookie@1.1.1" + ] + }, + "@supabase/storage-js@2.111.0": { + "integrity": "sha512-UEViNmTzVOxE8dqUA81wls+n9xgmlvSFfhfwo6QxrO4kQOytCYyw3ciYFoi4XoD4Jl95NJ3jnndHN5iIudWzqw==", + "dependencies": [ + "iceberg-js", + "tslib" + ] + }, + "@supabase/supabase-js@2.111.0": { + "integrity": "sha512-9q0/AULthQnWeiDh1vGyjoJZbSY04bu6qHcWit70pqEYn5Kv/dkCPY62Ja1123jEnJbB9Vd2pjY7Kvk/lK3peA==", + "dependencies": [ + "@supabase/auth-js", + "@supabase/functions-js", + "@supabase/postgrest-js", + "@supabase/realtime-js", + "@supabase/storage-js" + ] + }, + "@swc/helpers@0.5.15": { + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": [ + "tslib" + ] + }, + "@tailwindcss/node@4.3.3": { + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dependencies": [ + "@jridgewell/remapping", + "enhanced-resolve", + "jiti@2.7.0", + "lightningcss@1.32.0", + "magic-string", + "source-map-js", + "tailwindcss" + ] + }, + "@tailwindcss/oxide-android-arm64@4.3.3": { + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-arm64@4.3.3": { + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-x64@4.3.3": { + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-freebsd-x64@4.3.3": { + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3": { + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@tailwindcss/oxide-linux-arm64-gnu@4.3.3": { + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-arm64-musl@4.3.3": { + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-x64-gnu@4.3.3": { + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-x64-musl@4.3.3": { + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-wasm32-wasi@4.3.3": { + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "dependencies": [ + "@emnapi/core@1.10.0", + "@emnapi/runtime@1.11.3", + "@emnapi/wasi-threads@1.2.1", + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@1.10.0_@emnapi+runtime@1.11.3", + "@tybys/wasm-util", + "tslib" + ], + "cpu": ["wasm32"] + }, + "@tailwindcss/oxide-win32-arm64-msvc@4.3.3": { + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-win32-x64-msvc@4.3.3": { + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide@4.3.3": { + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "optionalDependencies": [ + "@tailwindcss/oxide-android-arm64", + "@tailwindcss/oxide-darwin-arm64", + "@tailwindcss/oxide-darwin-x64", + "@tailwindcss/oxide-freebsd-x64", + "@tailwindcss/oxide-linux-arm-gnueabihf", + "@tailwindcss/oxide-linux-arm64-gnu", + "@tailwindcss/oxide-linux-arm64-musl", + "@tailwindcss/oxide-linux-x64-gnu", + "@tailwindcss/oxide-linux-x64-musl", + "@tailwindcss/oxide-wasm32-wasi", + "@tailwindcss/oxide-win32-arm64-msvc", + "@tailwindcss/oxide-win32-x64-msvc" + ] + }, + "@tailwindcss/postcss@4.3.3": { + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dependencies": [ + "@alloc/quick-lru", + "@tailwindcss/node", + "@tailwindcss/oxide", + "postcss", + "tailwindcss" + ] + }, + "@tanstack/query-core@5.101.4": { + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==" + }, + "@tanstack/react-query@5.101.4_react@19.2.8": { + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "dependencies": [ + "@tanstack/query-core", + "react" + ] + }, + "@tanstack/react-virtual@3.14.9_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==", + "dependencies": [ + "@tanstack/virtual-core", + "react", + "react-dom" + ] + }, + "@tanstack/virtual-core@3.17.7": { + "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==" + }, + "@testing-library/dom@10.4.1": { + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dependencies": [ + "@babel/code-frame", + "@babel/runtime", + "@types/aria-query", + "aria-query@5.3.0", + "dom-accessibility-api@0.5.16", + "lz-string", + "picocolors", + "pretty-format" + ] + }, + "@testing-library/jest-dom@7.0.0_@testing-library+dom@10.4.1": { + "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==", + "dependencies": [ + "@adobe/css-tools", + "@testing-library/dom", + "aria-query@5.3.2", + "css.escape", + "dom-accessibility-api@0.6.3", + "picocolors", + "redent" + ] + }, + "@testing-library/react@16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.18_@types+react-dom@19.2.4__@types+react@19.2.18_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dependencies": [ + "@babel/runtime", + "@testing-library/dom", + "@types/react", + "@types/react-dom", + "react", + "react-dom" + ], + "optionalPeers": [ + "@types/react", + "@types/react-dom" + ] + }, + "@testing-library/user-event@14.6.1_@testing-library+dom@10.4.1": { + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dependencies": [ + "@testing-library/dom" + ] + }, + "@tootallnate/once@3.0.1": { + "integrity": "sha512-VyMVKRrpHTT8PnotUeV8L/mDaMwD5DaAKCFLP73zAqAtvF0FCqky+Ki7BYbFCYQmqFyTe9316Ed5zS70QUR9eg==" + }, + "@tybys/wasm-util@0.10.3": { + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dependencies": [ + "tslib" + ] + }, + "@types/aria-query@5.0.4": { + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, + "@types/caseless@0.12.5": { + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, + "@types/chai@5.2.3": { + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dependencies": [ + "@types/deep-eql", + "assertion-error" + ] + }, + "@types/cors@2.8.19": { + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dependencies": [ + "@types/node" + ] + }, + "@types/d3-array@3.2.2": { + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "@types/d3-color@3.1.3": { + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "@types/d3-ease@3.0.2": { + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "@types/d3-interpolate@3.0.4": { + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": [ + "@types/d3-color" + ] + }, + "@types/d3-path@3.1.1": { + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "@types/d3-scale@4.0.9": { + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": [ + "@types/d3-time" + ] + }, + "@types/d3-shape@3.1.8": { + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dependencies": [ + "@types/d3-path" + ] + }, + "@types/d3-time@3.0.4": { + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "@types/d3-timer@3.0.2": { + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "@types/debug@4.1.13": { + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dependencies": [ + "@types/ms" + ] + }, + "@types/deep-eql@4.0.2": { + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==" + }, + "@types/estree-jsx@1.0.5": { + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": [ + "@types/estree" + ] + }, + "@types/estree@1.0.9": { + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==" + }, + "@types/hast@3.0.5": { + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "dependencies": [ + "@types/unist@3.0.3" + ] + }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "@types/json5@0.0.29": { + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "@types/jsonwebtoken@9.0.10": { + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dependencies": [ + "@types/ms", + "@types/node" + ] + }, + "@types/mdast@4.0.4": { + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": [ + "@types/unist@3.0.3" + ] + }, + "@types/ms@2.1.0": { + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "@types/node@26.1.2": { + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "dependencies": [ + "undici-types" + ] + }, + "@types/nprogress@0.2.3": { + "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==" + }, + "@types/react-dom@19.2.4_@types+react@19.2.18": { + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dependencies": [ + "@types/react" + ] + }, + "@types/react@19.2.18": { + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dependencies": [ + "csstype" + ] + }, + "@types/request@2.48.13": { + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "dependencies": [ + "@types/caseless", + "@types/node", + "@types/tough-cookie", + "form-data@2.5.6" + ] + }, + "@types/sanitize-html@2.16.1": { + "integrity": "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==", + "dependencies": [ + "htmlparser2@10.1.0" + ] + }, + "@types/tough-cookie@4.0.5": { + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, + "@types/trusted-types@2.0.7": { + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "@types/unist@2.0.11": { + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "@types/unist@3.0.3": { + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "@types/use-sync-external-store@0.0.6": { + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, + "@types/whatwg-mimetype@3.0.2": { + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==" + }, + "@types/ws@8.18.1": { + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dependencies": [ + "@types/node" + ] + }, + "@typescript-eslint/eslint-plugin@8.65.0_@typescript-eslint+parser@8.65.0__eslint@9.39.5__typescript@6.0.3_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dependencies": [ + "@eslint-community/regexpp", + "@typescript-eslint/parser", + "@typescript-eslint/scope-manager", + "@typescript-eslint/type-utils", + "@typescript-eslint/utils", + "@typescript-eslint/visitor-keys", + "eslint", + "ignore@7.0.6", + "natural-compare", + "ts-api-utils", + "typescript" + ] + }, + "@typescript-eslint/parser@8.65.0_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dependencies": [ + "@typescript-eslint/scope-manager", + "@typescript-eslint/types", + "@typescript-eslint/typescript-estree", + "@typescript-eslint/visitor-keys", + "debug@4.4.3", + "eslint", + "typescript" + ] + }, + "@typescript-eslint/project-service@8.65.0_typescript@6.0.3": { + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dependencies": [ + "@typescript-eslint/tsconfig-utils", + "@typescript-eslint/types", + "debug@4.4.3", + "typescript" + ] + }, + "@typescript-eslint/scope-manager@8.65.0": { + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dependencies": [ + "@typescript-eslint/types", + "@typescript-eslint/visitor-keys" + ] + }, + "@typescript-eslint/tsconfig-utils@8.65.0_typescript@6.0.3": { + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dependencies": [ + "typescript" + ] + }, + "@typescript-eslint/type-utils@8.65.0_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dependencies": [ + "@typescript-eslint/types", + "@typescript-eslint/typescript-estree", + "@typescript-eslint/utils", + "debug@4.4.3", + "eslint", + "ts-api-utils", + "typescript" + ] + }, + "@typescript-eslint/types@8.65.0": { + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==" + }, + "@typescript-eslint/typescript-estree@8.65.0_typescript@6.0.3": { + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dependencies": [ + "@typescript-eslint/project-service", + "@typescript-eslint/tsconfig-utils", + "@typescript-eslint/types", + "@typescript-eslint/visitor-keys", + "debug@4.4.3", + "minimatch", + "semver@7.8.5", + "tinyglobby", + "ts-api-utils", + "typescript" + ] + }, + "@typescript-eslint/utils@8.65.0_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@typescript-eslint/scope-manager", + "@typescript-eslint/types", + "@typescript-eslint/typescript-estree", + "eslint", + "typescript" + ] + }, + "@typescript-eslint/visitor-keys@8.65.0": { + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dependencies": [ + "@typescript-eslint/types", + "eslint-visitor-keys@5.0.1" + ] + }, + "@ungap/structured-clone@1.3.3": { + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==" + }, + "@unrs/resolver-binding-android-arm-eabi@1.12.2": { + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "os": ["android"], + "cpu": ["arm"] + }, + "@unrs/resolver-binding-android-arm64@1.12.2": { + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-darwin-arm64@1.12.2": { + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-darwin-x64@1.12.2": { + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@unrs/resolver-binding-freebsd-x64@1.12.2": { + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2": { + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@unrs/resolver-binding-linux-arm-musleabihf@1.12.2": { + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@unrs/resolver-binding-linux-arm64-gnu@1.12.2": { + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-linux-arm64-musl@1.12.2": { + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-linux-loong64-gnu@1.12.2": { + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@unrs/resolver-binding-linux-loong64-musl@1.12.2": { + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@unrs/resolver-binding-linux-ppc64-gnu@1.12.2": { + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@unrs/resolver-binding-linux-riscv64-gnu@1.12.2": { + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@unrs/resolver-binding-linux-riscv64-musl@1.12.2": { + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@unrs/resolver-binding-linux-s390x-gnu@1.12.2": { + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@unrs/resolver-binding-linux-x64-gnu@1.12.2": { + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@unrs/resolver-binding-linux-x64-musl@1.12.2": { + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@unrs/resolver-binding-openharmony-arm64@1.12.2": { + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-wasm32-wasi@1.12.2": { + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "dependencies": [ + "@emnapi/core@1.10.0", + "@emnapi/runtime@1.10.0", + "@napi-rs/wasm-runtime@1.2.2_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0" + ], + "cpu": ["wasm32"] + }, + "@unrs/resolver-binding-win32-arm64-msvc@1.12.2": { + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@unrs/resolver-binding-win32-ia32-msvc@1.12.2": { + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@unrs/resolver-binding-win32-x64-msvc@1.12.2": { + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@upstash/core-analytics@0.0.10": { + "integrity": "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==", + "dependencies": [ + "@upstash/redis" + ] + }, + "@upstash/ratelimit@2.0.8_@upstash+redis@1.38.1": { + "integrity": "sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w==", + "dependencies": [ + "@upstash/core-analytics", + "@upstash/redis" + ] + }, + "@upstash/redis@1.38.1": { + "integrity": "sha512-hVqkWmhqobH7hpdzSCSrOwK7gWNASOAdf85l6/yxdB+giCYNYfl8FkSKxnqW2sqCdLDP7HzRTvy/ILC1AjBMUA==", + "dependencies": [ + "uncrypto" + ] + }, + "@vitejs/plugin-react@6.0.5_@types+node@26.1.2": { + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "dependencies": [ + "@rolldown/pluginutils", + "vite" + ] + }, + "@vitest/coverage-v8@4.1.10_vitest@4.1.10_@types+node@26.1.2_@vitest+ui@4.1.10_happy-dom@20.11.1_jsdom@30.0.1": { + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dependencies": [ + "@bcoe/v8-coverage", + "@vitest/utils", + "ast-v8-to-istanbul", + "istanbul-lib-coverage", + "istanbul-lib-report", + "istanbul-reports", + "magicast", + "obug", + "std-env", + "tinyrainbow", + "vitest" + ] + }, + "@vitest/expect@4.1.10": { + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dependencies": [ + "@standard-schema/spec", + "@types/chai", + "@vitest/spy", + "@vitest/utils", + "chai", + "tinyrainbow" + ] + }, + "@vitest/mocker@4.1.10_vite@8.2.0__@types+node@26.1.2_@types+node@26.1.2": { + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dependencies": [ + "@vitest/spy", + "estree-walker@3.0.3", + "magic-string", + "vite" + ], + "optionalPeers": [ + "vite" + ] + }, + "@vitest/pretty-format@4.1.10": { + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dependencies": [ + "tinyrainbow" + ] + }, + "@vitest/runner@4.1.10": { + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dependencies": [ + "@vitest/utils", + "pathe" + ] + }, + "@vitest/snapshot@4.1.10": { + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dependencies": [ + "@vitest/pretty-format", + "@vitest/utils", + "magic-string", + "pathe" + ] + }, + "@vitest/spy@4.1.10": { + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==" + }, + "@vitest/ui@4.1.10_vitest@4.1.10_@types+node@26.1.2_@vitest+coverage-v8@4.1.10_happy-dom@20.11.1_jsdom@30.0.1_vite@8.2.0__@types+node@26.1.2": { + "integrity": "sha512-EOUqfXHTXtpSHsyLHH40ts3Ue+hRhSGwzwzMlK0dTEOLSDYyOXLyr5JDGmHQWhN2DYI30gw6dVx3cdgM9FZl+Q==", + "dependencies": [ + "@vitest/utils", + "fflate", + "flatted", + "pathe", + "sirv", + "tinyglobby", + "tinyrainbow", + "vitest" + ] + }, + "@vitest/utils@4.1.10": { + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dependencies": [ + "@vitest/pretty-format", + "convert-source-map", + "tinyrainbow" + ] + }, + "@webassemblyjs/ast@1.14.1": { + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dependencies": [ + "@webassemblyjs/helper-numbers", + "@webassemblyjs/helper-wasm-bytecode" + ] + }, + "@webassemblyjs/floating-point-hex-parser@1.13.2": { + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" + }, + "@webassemblyjs/helper-api-error@1.13.2": { + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" + }, + "@webassemblyjs/helper-buffer@1.14.1": { + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" + }, + "@webassemblyjs/helper-numbers@1.13.2": { + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dependencies": [ + "@webassemblyjs/floating-point-hex-parser", + "@webassemblyjs/helper-api-error", + "@xtuc/long" + ] + }, + "@webassemblyjs/helper-wasm-bytecode@1.13.2": { + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" + }, + "@webassemblyjs/helper-wasm-section@1.14.1": { + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dependencies": [ + "@webassemblyjs/ast", + "@webassemblyjs/helper-buffer", + "@webassemblyjs/helper-wasm-bytecode", + "@webassemblyjs/wasm-gen" + ] + }, + "@webassemblyjs/ieee754@1.13.2": { + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dependencies": [ + "@xtuc/ieee754" + ] + }, + "@webassemblyjs/leb128@1.13.2": { + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dependencies": [ + "@xtuc/long" + ] + }, + "@webassemblyjs/utf8@1.13.2": { + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" + }, + "@webassemblyjs/wasm-edit@1.14.1": { + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dependencies": [ + "@webassemblyjs/ast", + "@webassemblyjs/helper-buffer", + "@webassemblyjs/helper-wasm-bytecode", + "@webassemblyjs/helper-wasm-section", + "@webassemblyjs/wasm-gen", + "@webassemblyjs/wasm-opt", + "@webassemblyjs/wasm-parser", + "@webassemblyjs/wast-printer" + ] + }, + "@webassemblyjs/wasm-gen@1.14.1": { + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dependencies": [ + "@webassemblyjs/ast", + "@webassemblyjs/helper-wasm-bytecode", + "@webassemblyjs/ieee754", + "@webassemblyjs/leb128", + "@webassemblyjs/utf8" + ] + }, + "@webassemblyjs/wasm-opt@1.14.1": { + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dependencies": [ + "@webassemblyjs/ast", + "@webassemblyjs/helper-buffer", + "@webassemblyjs/wasm-gen", + "@webassemblyjs/wasm-parser" + ] + }, + "@webassemblyjs/wasm-parser@1.14.1": { + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dependencies": [ + "@webassemblyjs/ast", + "@webassemblyjs/helper-api-error", + "@webassemblyjs/helper-wasm-bytecode", + "@webassemblyjs/ieee754", + "@webassemblyjs/leb128", + "@webassemblyjs/utf8" + ] + }, + "@webassemblyjs/wast-printer@1.14.1": { + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dependencies": [ + "@webassemblyjs/ast", + "@xtuc/long" + ] + }, + "@xtuc/ieee754@1.2.0": { + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long@4.2.2": { + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types@2.1.35", + "negotiator" + ] + }, + "acorn-jsx@5.3.2_acorn@8.18.0": { + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dependencies": [ + "acorn" + ] + }, + "acorn@8.18.0": { + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "bin": true + }, + "agent-base@6.0.2": { + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": [ + "debug@4.4.3" + ] + }, + "agent-base@7.1.4": { + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" + }, + "ajv-formats@2.1.1_ajv@8.20.0": { + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": [ + "ajv@8.20.0" + ], + "optionalPeers": [ + "ajv@8.20.0" + ] + }, + "ajv-formats@3.0.1_ajv@8.20.0": { + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": [ + "ajv@8.20.0" + ], + "optionalPeers": [ + "ajv@8.20.0" + ] + }, + "ajv-keywords@5.1.0_ajv@8.20.0": { + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": [ + "ajv@8.20.0", + "fast-deep-equal" + ] + }, + "ajv@6.15.0": { + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dependencies": [ + "fast-deep-equal", + "fast-json-stable-stringify", + "json-schema-traverse@0.4.1", + "uri-js" + ] + }, + "ajv@8.20.0": { + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dependencies": [ + "fast-deep-equal", + "fast-uri", + "json-schema-traverse@1.0.0", + "require-from-string" + ] + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "anynum@1.0.1": { + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==" + }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "aria-hidden@1.2.6": { + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dependencies": [ + "tslib" + ] + }, + "aria-query@5.3.0": { + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": [ + "dequal" + ] + }, + "aria-query@5.3.2": { + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + }, + "array-buffer-byte-length@1.0.2": { + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": [ + "call-bound", + "is-array-buffer" + ] + }, + "array-includes@3.1.9": { + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-abstract", + "es-object-atoms", + "get-intrinsic", + "is-string", + "math-intrinsics" + ] + }, + "array.prototype.findlast@1.2.5": { + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "es-object-atoms", + "es-shim-unscopables" + ] + }, + "array.prototype.findlastindex@1.2.6": { + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-abstract", + "es-errors", + "es-object-atoms", + "es-shim-unscopables" + ] + }, + "array.prototype.flat@1.3.3": { + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-shim-unscopables" + ] + }, + "array.prototype.flatmap@1.3.3": { + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-shim-unscopables" + ] + }, + "array.prototype.tosorted@1.1.4": { + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "es-shim-unscopables" + ] + }, + "arraybuffer.prototype.slice@1.0.4": { + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": [ + "array-buffer-byte-length", + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "get-intrinsic", + "is-array-buffer" + ] + }, + "arrify@2.0.1": { + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "assertion-error@2.0.1": { + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" + }, + "ast-types-flow@0.0.8": { + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" + }, + "ast-v8-to-istanbul@1.0.5": { + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dependencies": [ + "@jridgewell/trace-mapping", + "estree-walker@3.0.3", + "js-tokens@10.0.0" + ] + }, + "astring@1.9.0": { + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "bin": true + }, + "async-function@1.0.0": { + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==" + }, + "async-retry@1.3.3": { + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": [ + "retry" + ] + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "atomically@2.1.1": { + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dependencies": [ + "stubborn-fs", + "when-exit" + ] + }, + "available-typed-arrays@1.0.7": { + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": [ + "possible-typed-array-names" + ] + }, + "axe-core@4.12.1": { + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==" + }, + "axios@1.19.0": { + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "dependencies": [ + "follow-redirects", + "form-data@4.0.6", + "https-proxy-agent@5.0.1", + "proxy-from-env@2.1.0" + ] + }, + "axobject-query@4.1.0": { + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" + }, + "bail@2.0.2": { + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, + "balanced-match@4.0.4": { + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64id@2.0.0": { + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "baseline-browser-mapping@2.11.9": { + "integrity": "sha512-cp447VUsGS07+n1Dqf7YSQ8maeJrjEhaDxTm1ZefbqDtypHBC5GzGMQbklR6IPR13Y8OAJRHZWEMtZipJLCttg==", + "bin": true + }, + "bidi-js@1.0.3": { + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dependencies": [ + "require-from-string" + ] + }, + "bignumber.js@9.3.1": { + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==" + }, + "brace-expansion@5.0.9": { + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dependencies": [ + "balanced-match" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "browserslist@4.28.6": { + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dependencies": [ + "baseline-browser-mapping", + "caniuse-lite", + "electron-to-chromium", + "node-releases", + "update-browserslist-db" + ], + "bin": true + }, + "buffer-equal-constant-time@1.0.1": { + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "buffer-from@1.1.2": { + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "buffer-image-size@0.6.4": { + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dependencies": [ + "@types/node" + ] + }, + "builtin-modules@3.3.0": { + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==" + }, + "bytes@3.1.2": { + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind-apply-helpers@1.0.2": { + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, + "call-bind@1.0.9": { + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "get-intrinsic", + "set-function-length" + ] + }, + "call-bound@1.0.4": { + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": [ + "call-bind-apply-helpers", + "get-intrinsic" + ] + }, + "callsites@3.1.0": { + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite@1.0.30001806": { + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==" + }, + "ccount@2.0.1": { + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, + "chai@6.2.2": { + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==" + }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles@4.3.0", + "supports-color@7.2.0" + ] + }, + "character-entities-html4@2.1.0": { + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy@3.0.0": { + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "character-entities@2.0.2": { + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "character-reference-invalid@2.0.1": { + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" + }, + "chokidar@4.0.3": { + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": [ + "readdirp" + ] + }, + "chrome-trace-event@1.0.4": { + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==" + }, + "citty@0.2.2": { + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==" + }, + "cjs-module-lexer@2.2.0": { + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "class-variance-authority@0.7.1": { + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": [ + "clsx" + ] + }, + "client-only@0.0.1": { + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "cliui@8.0.1": { + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": [ + "string-width", + "strip-ansi", + "wrap-ansi" + ] + }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, + "comma-separated-tokens@2.0.3": { + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "commander@13.1.0": { + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==" + }, + "commander@2.20.3": { + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "common-tags@1.8.2": { + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" + }, + "commondir@1.0.1": { + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "conf@15.1.0": { + "integrity": "sha512-Uy5YN9KEu0WWDaZAVJ5FAmZoaJt9rdK6kH+utItPyGsCqCgaTKkrmZx3zoE0/3q6S3bcp3Ihkk+ZqPxWxFK5og==", + "dependencies": [ + "ajv@8.20.0", + "ajv-formats@3.0.1_ajv@8.20.0", + "atomically", + "debounce-fn", + "dot-prop", + "env-paths", + "json-schema-typed", + "semver@7.8.5", + "uint8array-extras" + ] + }, + "convert-source-map@2.0.0": { + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "cookie@0.7.2": { + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cookie@1.1.1": { + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==" + }, + "cors@2.8.6": { + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": [ + "object-assign", + "vary" + ] + }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, + "css-tree@3.2.1": { + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dependencies": [ + "mdn-data", + "source-map-js" + ] + }, + "css.escape@1.5.1": { + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, + "csstype@3.2.3": { + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "d3-array@3.2.4": { + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": [ + "internmap" + ] + }, + "d3-color@3.1.0": { + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-ease@3.0.1": { + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-format@3.1.2": { + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==" + }, + "d3-interpolate@3.0.1": { + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": [ + "d3-color" + ] + }, + "d3-path@3.1.0": { + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-scale@4.0.2": { + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": [ + "d3-array", + "d3-format", + "d3-interpolate", + "d3-time", + "d3-time-format" + ] + }, + "d3-shape@3.2.0": { + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": [ + "d3-path" + ] + }, + "d3-time-format@4.1.0": { + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": [ + "d3-time" + ] + }, + "d3-time@3.1.0": { + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": [ + "d3-array" + ] + }, + "d3-timer@3.0.1": { + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "damerau-levenshtein@1.0.8": { + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, + "data-urls@7.0.0": { + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dependencies": [ + "whatwg-mimetype@5.0.0", + "whatwg-url@16.0.1" + ] + }, + "data-view-buffer@1.0.2": { + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": [ + "call-bound", + "es-errors", + "is-data-view" + ] + }, + "data-view-byte-length@1.0.2": { + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": [ + "call-bound", + "es-errors", + "is-data-view" + ] + }, + "data-view-byte-offset@1.0.1": { + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": [ + "call-bound", + "es-errors", + "is-data-view" + ] + }, + "date-fns@4.4.0": { + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==" + }, + "dayjs@1.11.21": { + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==" + }, + "debounce-fn@6.0.0": { + "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==", + "dependencies": [ + "mimic-function" + ] + }, + "debounce@2.2.0": { + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==" + }, + "debug@3.2.7": { + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": [ + "ms" + ] + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "decimal.js-light@2.5.1": { + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, + "decimal.js@10.6.0": { + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==" + }, + "decode-named-character-reference@1.3.0": { + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dependencies": [ + "character-entities" + ] + }, + "deep-is@0.1.4": { + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "define-data-property@1.1.4": { + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": [ + "es-define-property", + "es-errors", + "gopd" + ] + }, + "define-properties@1.2.1": { + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": [ + "define-data-property", + "has-property-descriptors", + "object-keys" + ] + }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "dequal@2.0.3": { + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "detect-libc@2.1.2": { + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "detect-node-es@1.1.0": { + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "devlop@1.1.0": { + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": [ + "dequal" + ] + }, + "doctrine@2.1.0": { + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": [ + "esutils" + ] + }, + "dom-accessibility-api@0.5.16": { + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "dom-accessibility-api@0.6.3": { + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, + "dom-serializer@2.0.0": { + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": [ + "domelementtype@2.3.0", + "domhandler@5.0.3", + "entities@4.5.0" + ] + }, + "dom-serializer@3.1.1": { + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "dependencies": [ + "domelementtype@3.0.0", + "domhandler@6.0.1", + "entities@8.0.0" + ] + }, + "domelementtype@2.3.0": { + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domelementtype@3.0.0": { + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==" + }, + "domhandler@5.0.3": { + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": [ + "domelementtype@2.3.0" + ] + }, + "domhandler@6.0.1": { + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "dependencies": [ + "domelementtype@3.0.0" + ] + }, + "domutils@3.2.2": { + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": [ + "dom-serializer@2.0.0", + "domelementtype@2.3.0", + "domhandler@5.0.3" + ] + }, + "domutils@4.0.2": { + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "dependencies": [ + "dom-serializer@3.1.1", + "domelementtype@3.0.0", + "domhandler@6.0.1" + ] + }, + "dot-prop@10.2.0": { + "integrity": "sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==", + "dependencies": [ + "type-fest@5.8.0" + ] + }, + "dotenv@16.6.1": { + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==" + }, + "dotenv@17.4.2": { + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==" + }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, + "duplexify@4.1.3": { + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": [ + "end-of-stream", + "inherits", + "readable-stream", + "stream-shift" + ] + }, + "ecdsa-sig-formatter@1.0.11": { + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": [ + "safe-buffer" + ] + }, + "eciesjs@0.5.0": { + "integrity": "sha512-s0J9SEVYAEPg7J63GFMApLYzPH9VNIQIyC6s15JpnqVc0TqcKWdbgFlnAweEBRyMmko2dcs2sfC83Hj4J43tuA==", + "dependencies": [ + "@ecies/ciphers", + "@noble/ciphers", + "@noble/curves", + "@noble/hashes" + ] + }, + "electron-to-chromium@1.5.399": { + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==" + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "emoji-regex@9.2.2": { + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "end-of-stream@1.4.5": { + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": [ + "once" + ] + }, + "engine.io-parser@5.2.3": { + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "engine.io@6.6.9": { + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", + "dependencies": [ + "@types/cors", + "@types/node", + "@types/ws", + "accepts", + "base64id", + "cookie@0.7.2", + "cors", + "debug@4.4.3", + "engine.io-parser", + "ws" + ] + }, + "enhanced-resolve@5.24.5": { + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dependencies": [ + "graceful-fs", + "tapable" + ] + }, + "entities@4.5.0": { + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "entities@7.0.1": { + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" + }, + "entities@8.0.0": { + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==" + }, + "env-paths@3.0.0": { + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==" + }, + "es-abstract-get@1.0.0": { + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dependencies": [ + "es-errors", + "es-object-atoms", + "is-callable", + "object-inspect" + ] + }, + "es-abstract@1.24.2": { + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dependencies": [ + "array-buffer-byte-length", + "arraybuffer.prototype.slice", + "available-typed-arrays", + "call-bind", + "call-bound", + "data-view-buffer", + "data-view-byte-length", + "data-view-byte-offset", + "es-define-property", + "es-errors", + "es-object-atoms", + "es-set-tostringtag", + "es-to-primitive", + "function.prototype.name", + "get-intrinsic", + "get-proto", + "get-symbol-description", + "globalthis", + "gopd", + "has-property-descriptors", + "has-proto", + "has-symbols", + "hasown", + "internal-slot", + "is-array-buffer", + "is-callable", + "is-data-view", + "is-negative-zero", + "is-regex", + "is-set", + "is-shared-array-buffer", + "is-string", + "is-typed-array", + "is-weakref", + "math-intrinsics", + "object-inspect", + "object-keys", + "object.assign", + "own-keys", + "regexp.prototype.flags", + "safe-array-concat", + "safe-push-apply", + "safe-regex-test", + "set-proto", + "stop-iteration-iterator", + "string.prototype.trim", + "string.prototype.trimend", + "string.prototype.trimstart", + "typed-array-buffer", + "typed-array-byte-length", + "typed-array-byte-offset", + "typed-array-length", + "unbox-primitive", + "which-typed-array" + ] + }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-iterator-helpers@1.4.0": { + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-abstract", + "es-errors", + "es-set-tostringtag", + "function-bind", + "get-intrinsic", + "globalthis", + "gopd", + "has-property-descriptors", + "has-proto", + "has-symbols", + "internal-slot", + "iterator.prototype", + "math-intrinsics" + ] + }, + "es-module-lexer@2.3.1": { + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==" + }, + "es-object-atoms@1.1.2": { + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": [ + "es-errors" + ] + }, + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "es-shim-unscopables@1.1.0": { + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dependencies": [ + "hasown" + ] + }, + "es-to-primitive@1.3.4": { + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dependencies": [ + "es-abstract-get", + "es-define-property", + "es-errors", + "is-callable", + "is-date-object", + "is-symbol" + ] + }, + "es-toolkit@1.50.0": { + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==" + }, + "esbuild@0.28.1": { + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "optionalDependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ], + "scripts": true, + "bin": true + }, + "escalade@3.2.0": { + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint-config-next@16.2.12_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==", + "dependencies": [ + "@next/eslint-plugin-next", + "eslint", + "eslint-import-resolver-node", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-jsx-a11y", + "eslint-plugin-react", + "eslint-plugin-react-hooks", + "globals@16.4.0", + "typescript", + "typescript-eslint" + ], + "optionalPeers": [ + "typescript" + ] + }, + "eslint-import-resolver-node@0.3.10": { + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dependencies": [ + "debug@3.2.7", + "is-core-module", + "resolve" + ] + }, + "eslint-import-resolver-typescript@3.10.1_eslint@9.39.5_eslint-plugin-import@2.32.0__eslint@9.39.5": { + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dependencies": [ + "@nolyfill/is-core-module", + "debug@4.4.3", + "eslint", + "eslint-plugin-import", + "get-tsconfig", + "is-bun-module", + "stable-hash", + "tinyglobby", + "unrs-resolver" + ], + "optionalPeers": [ + "eslint-plugin-import" + ] + }, + "eslint-module-utils@2.14.0": { + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dependencies": [ + "debug@3.2.7" + ] + }, + "eslint-plugin-import@2.32.0_eslint@9.39.5": { + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dependencies": [ + "@rtsao/scc", + "array-includes", + "array.prototype.findlastindex", + "array.prototype.flat", + "array.prototype.flatmap", + "debug@3.2.7", + "doctrine", + "eslint", + "eslint-import-resolver-node", + "eslint-module-utils", + "hasown", + "is-core-module", + "is-glob", + "minimatch", + "object.fromentries", + "object.groupby", + "object.values", + "semver@6.3.1", + "string.prototype.trimend", + "tsconfig-paths@3.15.0" + ] + }, + "eslint-plugin-jsx-a11y@6.10.2_eslint@9.39.5": { + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dependencies": [ + "aria-query@5.3.2", + "array-includes", + "array.prototype.flatmap", + "ast-types-flow", + "axe-core", + "axobject-query", + "damerau-levenshtein", + "emoji-regex@9.2.2", + "eslint", + "hasown", + "jsx-ast-utils", + "language-tags", + "minimatch", + "object.fromentries", + "safe-regex-test", + "string.prototype.includes" + ] + }, + "eslint-plugin-react-hooks@7.1.1_eslint@9.39.5": { + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dependencies": [ + "@babel/core", + "@babel/parser@7.29.8", + "eslint", + "hermes-parser", + "zod", + "zod-validation-error" + ] + }, + "eslint-plugin-react@7.37.5_eslint@9.39.5": { + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dependencies": [ + "array-includes", + "array.prototype.findlast", + "array.prototype.flatmap", + "array.prototype.tosorted", + "doctrine", + "es-iterator-helpers", + "eslint", + "estraverse@5.3.0", + "hasown", + "jsx-ast-utils", + "minimatch", + "object.entries", + "object.fromentries", + "object.values", + "prop-types", + "resolve", + "semver@6.3.1", + "string.prototype.matchall", + "string.prototype.repeat" + ] + }, + "eslint-plugin-security@4.0.1": { + "integrity": "sha512-/lZCkOxPOWaf1jXAqgICrS8St3BMBccIPvhOSUYuV6VCr1o5nFVG998FnTLt6w2Nxb8Uo0nM8fzmnhp+GY/aEg==", + "dependencies": [ + "safe-regex" + ] + }, + "eslint-plugin-sonarjs@4.2.0_eslint@9.39.5": { + "integrity": "sha512-bqADfuNtTL7VK6RU29eoiFTtaaBKIpVPuX3bOl+rBpWSBa0zIBVZlqZNZQjfP6s4iXkAJokv5IsD8OsACkwApg==", + "dependencies": [ + "@eslint-community/regexpp", + "builtin-modules", + "bytes", + "eslint", + "functional-red-black-tree", + "globals@17.8.0", + "jsx-ast-utils-x", + "lodash.merge", + "minimatch", + "scslre", + "semver@7.8.5", + "ts-api-utils", + "typescript", + "yaml" + ] + }, + "eslint-plugin-unused-imports@4.4.1_eslint@9.39.5": { + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", + "dependencies": [ + "eslint" + ] + }, + "eslint-scope@5.1.1": { + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": [ + "esrecurse", + "estraverse@4.3.0" + ] + }, + "eslint-scope@8.4.0": { + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dependencies": [ + "esrecurse", + "estraverse@5.3.0" + ] + }, + "eslint-visitor-keys@3.4.3": { + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + }, + "eslint-visitor-keys@4.2.1": { + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" + }, + "eslint-visitor-keys@5.0.1": { + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==" + }, + "eslint@9.39.5": { + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@eslint-community/regexpp", + "@eslint/config-array", + "@eslint/config-helpers", + "@eslint/core", + "@eslint/eslintrc", + "@eslint/js", + "@eslint/plugin-kit", + "@humanfs/node", + "@humanwhocodes/module-importer", + "@humanwhocodes/retry", + "@types/estree", + "ajv@6.15.0", + "chalk", + "cross-spawn", + "debug@4.4.3", + "escape-string-regexp", + "eslint-scope@8.4.0", + "eslint-visitor-keys@4.2.1", + "espree", + "esquery", + "esutils", + "fast-deep-equal", + "file-entry-cache", + "find-up", + "glob-parent@6.0.2", + "ignore@5.3.2", + "imurmurhash", + "is-glob", + "json-stable-stringify-without-jsonify", + "lodash.merge", + "minimatch", + "natural-compare", + "optionator" + ], + "bin": true + }, + "espree@10.4.0": { + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dependencies": [ + "acorn", + "acorn-jsx", + "eslint-visitor-keys@4.2.1" + ] + }, + "esquery@1.7.0": { + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dependencies": [ + "estraverse@5.3.0" + ] + }, + "esrecurse@4.3.0": { + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": [ + "estraverse@5.3.0" + ] + }, + "estraverse@4.3.0": { + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "estraverse@5.3.0": { + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "estree-util-is-identifier-name@3.0.0": { + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==" + }, + "estree-walker@2.0.2": { + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "estree-walker@3.0.3": { + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": [ + "@types/estree" + ] + }, + "esutils@2.0.3": { + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "eventemitter3@5.0.4": { + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" + }, + "events@3.3.0": { + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "expect-type@1.4.0": { + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==" + }, + "extend@3.0.2": { + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal@3.1.3": { + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob@3.3.1": { + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dependencies": [ + "@nodelib/fs.stat", + "@nodelib/fs.walk", + "glob-parent@5.1.2", + "merge2", + "micromatch" + ] + }, + "fast-json-stable-stringify@2.1.0": { + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein@2.0.6": { + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fast-sha256@1.3.0": { + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" + }, + "fast-uri@3.1.5": { + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==" + }, + "fast-xml-builder@1.3.0": { + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "dependencies": [ + "path-expression-matcher", + "xml-naming" + ] + }, + "fast-xml-parser@5.10.1": { + "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "dependencies": [ + "@nodable/entities", + "fast-xml-builder", + "is-unsafe", + "path-expression-matcher", + "strnum", + "xml-naming" + ], + "bin": true + }, + "fastq@1.20.1": { + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dependencies": [ + "reusify" + ] + }, + "faye-websocket@0.11.4": { + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": [ + "websocket-driver" + ] + }, + "fdir@6.5.0_picomatch@4.0.5": { + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dependencies": [ + "picomatch@4.0.5" + ], + "optionalPeers": [ + "picomatch@4.0.5" + ] + }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception@1.0.0", + "web-streams-polyfill" + ] + }, + "fflate@0.8.3": { + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==" + }, + "file-entry-cache@8.0.0": { + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": [ + "flat-cache" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "find-up@5.0.0": { + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "firebase-admin@14.2.0": { + "integrity": "sha512-zfs5PdccEgjX479bbMmz95Rqc7ttQ4LV3qkGFlPiqOaOu/suBZc3fG52Qzn2hQjiSHkBM4NP2AZV5r2NvAt0TQ==", + "dependencies": [ + "@fastify/busboy", + "@firebase/database-compat", + "@firebase/database-types", + "fast-deep-equal", + "google-auth-library@10.9.1", + "jsonwebtoken", + "jwks-rsa" + ], + "optionalDependencies": [ + "@google-cloud/firestore", + "@google-cloud/storage" + ] + }, + "flat-cache@4.0.1": { + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": [ + "flatted", + "keyv" + ] + }, + "flatted@3.4.4": { + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==" + }, + "follow-redirects@1.16.0": { + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" + }, + "for-each@0.3.5": { + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": [ + "is-callable" + ] + }, + "form-data@2.5.6": { + "integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "hasown", + "mime-types@2.1.35", + "safe-buffer" + ] + }, + "form-data@4.0.6": { + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "hasown", + "mime-types@2.1.35" + ] + }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, + "framer-motion@12.43.0_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==", + "dependencies": [ + "motion-dom", + "motion-utils", + "react", + "react-dom", + "tslib" + ], + "optionalPeers": [ + "react", + "react-dom" + ] + }, + "fsevents@2.3.2": { + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "os": ["darwin"], + "scripts": true + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name@1.2.0": { + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dependencies": [ + "call-bind", + "call-bound", + "es-define-property", + "es-errors", + "functions-have-names", + "has-property-descriptors", + "hasown", + "is-callable", + "is-document.all" + ] + }, + "functional-red-black-tree@1.0.1": { + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, + "functions-have-names@1.2.3": { + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "gaxios@6.7.1": { + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": [ + "extend", + "https-proxy-agent@7.0.6", + "is-stream", + "node-fetch@2.7.0", + "uuid" + ] + }, + "gaxios@7.1.3": { + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "dependencies": [ + "extend", + "https-proxy-agent@7.0.6", + "node-fetch@3.3.2", + "rimraf@5.0.10" + ] + }, + "gaxios@7.3.0": { + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", + "dependencies": [ + "extend", + "https-proxy-agent@7.0.6", + "node-fetch@3.3.2" + ] + }, + "gcp-metadata@6.1.1": { + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dependencies": [ + "gaxios@6.7.1", + "google-logging-utils@0.0.2", + "json-bigint" + ] + }, + "gcp-metadata@8.1.2": { + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dependencies": [ + "gaxios@7.3.0", + "google-logging-utils@1.1.3", + "json-bigint" + ] + }, + "generator-function@2.0.1": { + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==" + }, + "gensync@1.0.0-beta.2": { + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file@2.0.5": { + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic@1.3.0": { + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-nonce@1.0.1": { + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "get-symbol-description@1.1.0": { + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": [ + "call-bound", + "es-errors", + "get-intrinsic" + ] + }, + "get-tsconfig@4.14.0": { + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dependencies": [ + "resolve-pkg-maps" + ] + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "glob-parent@6.0.2": { + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": [ + "is-glob" + ] + }, + "glob@13.0.6": { + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dependencies": [ + "minimatch", + "minipass", + "path-scurry" + ] + }, + "globals@14.0.0": { + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" + }, + "globals@16.4.0": { + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==" + }, + "globals@17.8.0": { + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==" + }, + "globalthis@1.0.4": { + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": [ + "define-properties", + "gopd" + ] + }, + "google-auth-library@10.5.0": { + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "dependencies": [ + "base64-js", + "ecdsa-sig-formatter", + "gaxios@7.3.0", + "gcp-metadata@8.1.2", + "google-logging-utils@1.1.3", + "gtoken@8.0.0", + "jws" + ] + }, + "google-auth-library@10.9.1": { + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "dependencies": [ + "base64-js", + "ecdsa-sig-formatter", + "gaxios@7.3.0", + "gcp-metadata@8.1.2", + "google-logging-utils@1.1.3", + "jws" + ] + }, + "google-auth-library@9.15.1": { + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "dependencies": [ + "base64-js", + "ecdsa-sig-formatter", + "gaxios@6.7.1", + "gcp-metadata@6.1.1", + "gtoken@7.1.0", + "jws" + ] + }, + "google-gax@5.0.8": { + "integrity": "sha512-M4vpZcXQIC1gqIVGQ7eaU3jXQA6zecStyTXu514TYfThlgSurYJOxHZo9fzU6hAgwPWvuEynAVHWyaIk80VeEA==", + "dependencies": [ + "@grpc/grpc-js", + "@grpc/proto-loader", + "duplexify", + "google-auth-library@10.5.0", + "google-logging-utils@1.1.3", + "node-fetch@3.3.2", + "object-hash", + "proto3-json-serializer", + "protobufjs", + "retry-request@8.0.4", + "rimraf@5.0.10" + ] + }, + "google-logging-utils@0.0.2": { + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==" + }, + "google-logging-utils@1.1.3": { + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==" + }, + "googleapis-common@8.0.3": { + "integrity": "sha512-7g1yzQKx0mmNTjiK0H9dJ8eqKqDBveES9vLHeg5neb3BMQy/d1oQefIMhIpOVT8a+f+LOcixMEdRbFIW/cQUJw==", + "dependencies": [ + "extend", + "gaxios@7.1.3", + "google-auth-library@10.5.0", + "google-logging-utils@1.1.3", + "qs", + "url-template" + ] + }, + "googleapis@173.0.0": { + "integrity": "sha512-xEJJYLZ4qeenVyfzispNfRjCe9bsv7CzBv5zYFLvScOze9snJ8S9W6hjQ729CWPQt5mvn/JrcRaCHzQiukt0ng==", + "dependencies": [ + "google-auth-library@10.9.1", + "googleapis-common" + ] + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "graceful-fs@4.2.11": { + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "gtoken@7.1.0": { + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": [ + "gaxios@6.7.1", + "jws" + ] + }, + "gtoken@8.0.0": { + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "dependencies": [ + "gaxios@7.3.0", + "jws" + ] + }, + "happy-dom@20.11.1": { + "integrity": "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==", + "dependencies": [ + "@types/node", + "@types/whatwg-mimetype", + "@types/ws", + "buffer-image-size", + "entities@7.0.1", + "whatwg-mimetype@3.0.0", + "ws" + ] + }, + "has-bigints@1.1.0": { + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==" + }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors@1.0.2": { + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": [ + "es-define-property" + ] + }, + "has-proto@1.2.0": { + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": [ + "dunder-proto" + ] + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, + "hasown@2.0.4": { + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dependencies": [ + "function-bind" + ] + }, + "hast-util-to-jsx-runtime@2.3.6": { + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dependencies": [ + "@types/estree", + "@types/hast", + "@types/unist@3.0.3", + "comma-separated-tokens", + "devlop", + "estree-util-is-identifier-name", + "hast-util-whitespace", + "mdast-util-mdx-expression", + "mdast-util-mdx-jsx", + "mdast-util-mdxjs-esm", + "property-information", + "space-separated-tokens", + "style-to-js", + "unist-util-position", + "vfile-message" + ] + }, + "hast-util-whitespace@3.0.0": { + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": [ + "@types/hast" + ] + }, + "hermes-estree@0.25.1": { + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==" + }, + "hermes-parser@0.25.1": { + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dependencies": [ + "hermes-estree" + ] + }, + "html-encoding-sniffer@6.0.0": { + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dependencies": [ + "@exodus/bytes" + ] + }, + "html-entities@2.6.0": { + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==" + }, + "html-escaper@2.0.2": { + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "html-to-text@9.0.5": { + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": [ + "@selderee/plugin-htmlparser2", + "deepmerge", + "dom-serializer@2.0.0", + "htmlparser2@8.0.2", + "selderee" + ] + }, + "html-url-attributes@3.0.1": { + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" + }, + "html5parser@3.0.0": { + "integrity": "sha512-iNpSopa+4YHX50UOk825tBy7MghmXHo/ZpLskBYN0kAr1xhH8GlIMk5bLRXcZlfP3AnLUcSuFMu8C4MdOUxA8A==" + }, + "htmlparser2@10.1.0": { + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dependencies": [ + "domelementtype@2.3.0", + "domhandler@5.0.3", + "domutils@3.2.2", + "entities@7.0.1" + ] + }, + "htmlparser2@12.0.0": { + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "dependencies": [ + "domelementtype@3.0.0", + "domhandler@6.0.1", + "domutils@4.0.2", + "entities@8.0.0" + ] + }, + "htmlparser2@8.0.2": { + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dependencies": [ + "domelementtype@2.3.0", + "domhandler@5.0.3", + "domutils@3.2.2", + "entities@4.5.0" + ] + }, + "http-parser-js@0.5.10": { + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" + }, + "http-proxy-agent@5.0.0": { + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": [ + "@tootallnate/once", + "agent-base@6.0.2", + "debug@4.4.3" + ] + }, + "http-proxy-agent@7.0.2": { + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": [ + "agent-base@7.1.4", + "debug@4.4.3" + ] + }, + "https-proxy-agent@5.0.1": { + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": [ + "agent-base@6.0.2", + "debug@4.4.3" + ] + }, + "https-proxy-agent@7.0.6": { + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": [ + "agent-base@7.1.4", + "debug@4.4.3" + ] + }, + "husky@9.1.7": { + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "bin": true + }, + "iceberg-js@0.8.1": { + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==" + }, + "idb@8.0.3": { + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==" + }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "ignore@7.0.6": { + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==" + }, + "immer@11.1.15": { + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==" + }, + "import-fresh@3.3.1": { + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": [ + "parent-module", + "resolve-from" + ] + }, + "import-in-the-middle@3.3.2": { + "integrity": "sha512-jTd2FfOgOWOdgjkHuk/1Ms8VKFXkPs15ymYBETw1sAOrO/dY3XeGVRWir9qBbw7pXr0T2eTFwfCZ+N02HmiNGA==", + "dependencies": [ + "cjs-module-lexer", + "es-module-lexer", + "module-details-from-path" + ] + }, + "imurmurhash@0.1.4": { + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "indent-string@4.0.0": { + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inline-style-parser@0.2.7": { + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" + }, + "internal-slot@1.1.0": { + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": [ + "es-errors", + "hasown", + "side-channel" + ] + }, + "internmap@2.0.3": { + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, + "is-alphabetical@2.0.1": { + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" + }, + "is-alphanumerical@2.0.1": { + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": [ + "is-alphabetical", + "is-decimal" + ] + }, + "is-array-buffer@3.0.5": { + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": [ + "call-bind", + "call-bound", + "get-intrinsic" + ] + }, + "is-async-function@2.1.1": { + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": [ + "async-function", + "call-bound", + "get-proto", + "has-tostringtag", + "safe-regex-test" + ] + }, + "is-bigint@1.1.0": { + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": [ + "has-bigints" + ] + }, + "is-boolean-object@1.2.2": { + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": [ + "call-bound", + "has-tostringtag" + ] + }, + "is-bun-module@2.0.0": { + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dependencies": [ + "semver@7.8.5" + ] + }, + "is-callable@1.2.7": { + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module@2.16.2": { + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dependencies": [ + "hasown" + ] + }, + "is-data-view@1.0.2": { + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": [ + "call-bound", + "get-intrinsic", + "is-typed-array" + ] + }, + "is-date-object@1.1.0": { + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": [ + "call-bound", + "has-tostringtag" + ] + }, + "is-decimal@2.0.1": { + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" + }, + "is-document.all@1.0.0": { + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dependencies": [ + "call-bound" + ] + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-finalizationregistry@1.1.1": { + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": [ + "call-bound" + ] + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-generator-function@1.1.2": { + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dependencies": [ + "call-bound", + "generator-function", + "get-proto", + "has-tostringtag", + "safe-regex-test" + ] + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-hexadecimal@2.0.1": { + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" + }, + "is-map@2.0.3": { + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==" + }, + "is-negative-zero@2.0.3": { + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==" + }, + "is-number-object@1.1.1": { + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": [ + "call-bound", + "has-tostringtag" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-plain-obj@4.1.0": { + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + }, + "is-plain-object@5.0.0": { + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "is-potential-custom-element-name@1.0.1": { + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "is-reference@1.2.1": { + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dependencies": [ + "@types/estree" + ] + }, + "is-regex@1.2.1": { + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": [ + "call-bound", + "gopd", + "has-tostringtag", + "hasown" + ] + }, + "is-set@2.0.3": { + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==" + }, + "is-shared-array-buffer@1.0.4": { + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": [ + "call-bound" + ] + }, + "is-stream@2.0.1": { + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-string@1.1.1": { + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": [ + "call-bound", + "has-tostringtag" + ] + }, + "is-symbol@1.1.1": { + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": [ + "call-bound", + "has-symbols", + "safe-regex-test" + ] + }, + "is-typed-array@1.1.15": { + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": [ + "which-typed-array" + ] + }, + "is-unicode-supported@2.1.0": { + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==" + }, + "is-unsafe@2.0.0": { + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==" + }, + "is-weakmap@2.0.2": { + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==" + }, + "is-weakref@1.1.1": { + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": [ + "call-bound" + ] + }, + "is-weakset@2.0.4": { + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": [ + "call-bound", + "get-intrinsic" + ] + }, + "isarray@2.0.5": { + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "istanbul-lib-coverage@3.2.2": { + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==" + }, + "istanbul-lib-report@3.0.1": { + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dependencies": [ + "istanbul-lib-coverage", + "make-dir", + "supports-color@7.2.0" + ] + }, + "istanbul-reports@3.2.0": { + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dependencies": [ + "html-escaper", + "istanbul-lib-report" + ] + }, + "iterator.prototype@1.1.5": { + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dependencies": [ + "define-data-property", + "es-object-atoms", + "get-intrinsic", + "get-proto", + "has-symbols", + "set-function-name" + ] + }, + "jest-worker@27.5.1": { + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": [ + "@types/node", + "merge-stream", + "supports-color@8.1.1" + ] + }, + "jiti@2.6.1": { + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "bin": true + }, + "jiti@2.7.0": { + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "bin": true + }, + "jose@6.2.6": { + "integrity": "sha512-HwMtbJjMw8rC8dUTwCNilHJD+fxTeKM3JV1eprSmTjS41qwXSSt6exJXgyPK1QOu0jB9eDYLESRDkB3qaT3jnw==" + }, + "js-tokens@10.0.0": { + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==" + }, + "js-tokens@4.0.0": { + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml@4.3.1": { + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dependencies": [ + "argparse" + ], + "bin": true + }, + "jsdom@30.0.1": { + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dependencies": [ + "@asamuzakjp/css-color", + "@asamuzakjp/dom-selector", + "@bramus/specificity", + "@csstools/css-syntax-patches-for-csstree", + "@exodus/bytes", + "css-tree", + "data-urls", + "decimal.js", + "html-encoding-sniffer", + "is-potential-custom-element-name", + "lru-cache@11.5.2", + "parse5", + "saxes", + "symbol-tree", + "tough-cookie", + "undici", + "w3c-xmlserializer", + "webidl-conversions@8.0.1", + "whatwg-mimetype@5.0.0", + "whatwg-url@17.1.0", + "xml-name-validator" + ] + }, + "jsesc@3.1.0": { + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": true + }, + "json-bigint@1.0.0": { + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": [ + "bignumber.js" + ] + }, + "json-buffer@3.0.1": { + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema-traverse@0.4.1": { + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-schema-traverse@1.0.0": { + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "json-schema-typed@8.0.2": { + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, + "json-stable-stringify-without-jsonify@1.0.1": { + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json5@1.0.2": { + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": [ + "minimist" + ], + "bin": true + }, + "json5@2.2.3": { + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": true + }, + "jsonwebtoken@9.0.3": { + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dependencies": [ + "jws", + "lodash.includes", + "lodash.isboolean", + "lodash.isinteger", + "lodash.isnumber", + "lodash.isplainobject", + "lodash.isstring", + "lodash.once", + "ms", + "semver@7.8.5" + ] + }, + "jsx-ast-utils-x@0.1.0": { + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==" + }, + "jsx-ast-utils@3.3.5": { + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dependencies": [ + "array-includes", + "array.prototype.flat", + "object.assign", + "object.values" + ] + }, + "jwa@2.0.1": { + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": [ + "buffer-equal-constant-time", + "ecdsa-sig-formatter", + "safe-buffer" + ] + }, + "jwks-rsa@4.1.0": { + "integrity": "sha512-sbkByqyATKYJP5F4RXj03N5TUNC0QLTjCAZvwTzC4BwJZ8e0/cWxN8YROnyUth2g1/ONWi4eSFHeu6oYalrc3Q==", + "dependencies": [ + "@types/jsonwebtoken", + "debug@4.4.3", + "jose", + "limiter", + "lru-cache@11.5.2", + "lru-memoizer" + ] + }, + "jws@4.0.1": { + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dependencies": [ + "jwa", + "safe-buffer" + ] + }, + "keyv@4.5.4": { + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": [ + "json-buffer" + ] + }, + "kleur@3.0.3": { + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "kolorist@1.8.0": { + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, + "language-subtag-registry@0.3.23": { + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==" + }, + "language-tags@1.0.9": { + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dependencies": [ + "language-subtag-registry" + ] + }, + "launder@1.7.1": { + "integrity": "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==", + "dependencies": [ + "dayjs" + ] + }, + "ldrs@1.1.9": { + "integrity": "sha512-JKVdzboxeZrKecA8ovi5iIDgs59wUrtJ+LDrHCgZecNrR0AJi2cJ3ZANJ+UCNS/sHRol73/jlypNCAMCXS+Ung==" + }, + "leac@0.6.0": { + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==" + }, + "levn@0.4.1": { + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": [ + "prelude-ls", + "type-check" + ] + }, + "lightningcss-android-arm64@1.32.0": { + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "lightningcss-android-arm64@1.33.0": { + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-arm64@1.32.0": { + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-arm64@1.33.0": { + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-x64@1.32.0": { + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "lightningcss-darwin-x64@1.33.0": { + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "lightningcss-freebsd-x64@1.32.0": { + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "lightningcss-freebsd-x64@1.33.0": { + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "lightningcss-linux-arm-gnueabihf@1.32.0": { + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "lightningcss-linux-arm-gnueabihf@1.33.0": { + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "lightningcss-linux-arm64-gnu@1.32.0": { + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-gnu@1.33.0": { + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-musl@1.32.0": { + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-musl@1.33.0": { + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-x64-gnu@1.32.0": { + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-gnu@1.33.0": { + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-musl@1.32.0": { + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-musl@1.33.0": { + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-win32-arm64-msvc@1.32.0": { + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "lightningcss-win32-arm64-msvc@1.33.0": { + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "lightningcss-win32-x64-msvc@1.32.0": { + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "os": ["win32"], + "cpu": ["x64"] + }, + "lightningcss-win32-x64-msvc@1.33.0": { + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "lightningcss@1.32.0": { + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dependencies": [ + "detect-libc" + ], + "optionalDependencies": [ + "lightningcss-android-arm64@1.32.0", + "lightningcss-darwin-arm64@1.32.0", + "lightningcss-darwin-x64@1.32.0", + "lightningcss-freebsd-x64@1.32.0", + "lightningcss-linux-arm-gnueabihf@1.32.0", + "lightningcss-linux-arm64-gnu@1.32.0", + "lightningcss-linux-arm64-musl@1.32.0", + "lightningcss-linux-x64-gnu@1.32.0", + "lightningcss-linux-x64-musl@1.32.0", + "lightningcss-win32-arm64-msvc@1.32.0", + "lightningcss-win32-x64-msvc@1.32.0" + ] + }, + "lightningcss@1.33.0": { + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dependencies": [ + "detect-libc" + ], + "optionalDependencies": [ + "lightningcss-android-arm64@1.33.0", + "lightningcss-darwin-arm64@1.33.0", + "lightningcss-darwin-x64@1.33.0", + "lightningcss-freebsd-x64@1.33.0", + "lightningcss-linux-arm-gnueabihf@1.33.0", + "lightningcss-linux-arm64-gnu@1.33.0", + "lightningcss-linux-arm64-musl@1.33.0", + "lightningcss-linux-x64-gnu@1.33.0", + "lightningcss-linux-x64-musl@1.33.0", + "lightningcss-win32-arm64-msvc@1.33.0", + "lightningcss-win32-x64-msvc@1.33.0" + ] + }, + "limiter@1.1.5": { + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "lint-staged@17.3.0": { + "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", + "dependencies": [ + "picomatch@4.0.5", + "string-argv", + "tinyexec" + ], + "optionalDependencies": [ + "yaml" + ], + "bin": true + }, + "locate-path@6.0.0": { + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": [ + "p-locate" + ] + }, + "lodash-es@4.18.1": { + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" + }, + "lodash.camelcase@4.3.0": { + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "lodash.clonedeep@4.5.0": { + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "lodash.includes@4.3.0": { + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean@3.0.3": { + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger@4.0.4": { + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber@3.0.3": { + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject@4.0.6": { + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring@4.0.1": { + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.merge@4.6.2": { + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.once@4.1.1": { + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "log-symbols@7.0.1": { + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dependencies": [ + "is-unicode-supported", + "yoctocolors" + ] + }, + "long@5.3.2": { + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "longest-streak@3.1.0": { + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" + }, + "loose-envify@1.4.0": { + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": [ + "js-tokens@4.0.0" + ], + "bin": true + }, + "lru-cache@11.5.2": { + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==" + }, + "lru-cache@5.1.1": { + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": [ + "yallist" + ] + }, + "lru-memoizer@3.0.0": { + "integrity": "sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==", + "dependencies": [ + "lodash.clonedeep", + "lru-cache@11.5.2" + ] + }, + "lucide-react@1.28.0_react@19.2.8": { + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", + "dependencies": [ + "react" + ] + }, + "lz-string@1.5.0": { + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": true + }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "magicast@0.5.4": { + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dependencies": [ + "@babel/parser@7.29.8", + "@babel/types", + "source-map-js" + ] + }, + "make-dir@4.0.0": { + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dependencies": [ + "semver@7.8.5" + ] + }, + "marked@15.0.12": { + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "bin": true + }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mdast-util-from-markdown@2.0.3": { + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "dependencies": [ + "@types/mdast", + "@types/unist@3.0.3", + "decode-named-character-reference", + "devlop", + "mdast-util-to-string", + "micromark", + "micromark-util-decode-numeric-character-reference", + "micromark-util-decode-string", + "micromark-util-normalize-identifier", + "micromark-util-symbol", + "micromark-util-types", + "unist-util-stringify-position" + ] + }, + "mdast-util-mdx-expression@2.0.1": { + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": [ + "@types/estree-jsx", + "@types/hast", + "@types/mdast", + "devlop", + "mdast-util-from-markdown", + "mdast-util-to-markdown" + ] + }, + "mdast-util-mdx-jsx@3.2.0": { + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dependencies": [ + "@types/estree-jsx", + "@types/hast", + "@types/mdast", + "@types/unist@3.0.3", + "ccount", + "devlop", + "mdast-util-from-markdown", + "mdast-util-to-markdown", + "parse-entities", + "stringify-entities", + "unist-util-stringify-position", + "vfile-message" + ] + }, + "mdast-util-mdxjs-esm@2.0.1": { + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": [ + "@types/estree-jsx", + "@types/hast", + "@types/mdast", + "devlop", + "mdast-util-from-markdown", + "mdast-util-to-markdown" + ] + }, + "mdast-util-phrasing@4.1.0": { + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": [ + "@types/mdast", + "unist-util-is" + ] + }, + "mdast-util-to-hast@13.2.1": { + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dependencies": [ + "@types/hast", + "@types/mdast", + "@ungap/structured-clone", + "devlop", + "micromark-util-sanitize-uri", + "trim-lines", + "unist-util-position", + "unist-util-visit", + "vfile" + ] + }, + "mdast-util-to-markdown@2.1.2": { + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": [ + "@types/mdast", + "@types/unist@3.0.3", + "longest-streak", + "mdast-util-phrasing", + "mdast-util-to-string", + "micromark-util-classify-character", + "micromark-util-decode-string", + "unist-util-visit", + "zwitch" + ] + }, + "mdast-util-to-string@4.0.0": { + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": [ + "@types/mdast" + ] + }, + "mdn-data@2.27.1": { + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==" + }, + "merge-stream@2.0.0": { + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "meriyah@6.1.4": { + "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==" + }, + "micromark-core-commonmark@2.0.3": { + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dependencies": [ + "decode-named-character-reference", + "devlop", + "micromark-factory-destination", + "micromark-factory-label", + "micromark-factory-space", + "micromark-factory-title", + "micromark-factory-whitespace", + "micromark-util-character", + "micromark-util-chunked", + "micromark-util-classify-character", + "micromark-util-html-tag-name", + "micromark-util-normalize-identifier", + "micromark-util-resolve-all", + "micromark-util-subtokenize", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-factory-destination@2.0.1": { + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dependencies": [ + "micromark-util-character", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-factory-label@2.0.1": { + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dependencies": [ + "devlop", + "micromark-util-character", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-factory-space@2.0.1": { + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dependencies": [ + "micromark-util-character", + "micromark-util-types" + ] + }, + "micromark-factory-title@2.0.1": { + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dependencies": [ + "micromark-factory-space", + "micromark-util-character", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-factory-whitespace@2.0.1": { + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dependencies": [ + "micromark-factory-space", + "micromark-util-character", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-util-character@2.1.1": { + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dependencies": [ + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-util-chunked@2.0.1": { + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dependencies": [ + "micromark-util-symbol" + ] + }, + "micromark-util-classify-character@2.0.1": { + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dependencies": [ + "micromark-util-character", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-util-combine-extensions@2.0.1": { + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dependencies": [ + "micromark-util-chunked", + "micromark-util-types" + ] + }, + "micromark-util-decode-numeric-character-reference@2.0.2": { + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dependencies": [ + "micromark-util-symbol" + ] + }, + "micromark-util-decode-string@2.0.1": { + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dependencies": [ + "decode-named-character-reference", + "micromark-util-character", + "micromark-util-decode-numeric-character-reference", + "micromark-util-symbol" + ] + }, + "micromark-util-encode@2.0.1": { + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" + }, + "micromark-util-html-tag-name@2.0.1": { + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==" + }, + "micromark-util-normalize-identifier@2.0.1": { + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dependencies": [ + "micromark-util-symbol" + ] + }, + "micromark-util-resolve-all@2.0.1": { + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dependencies": [ + "micromark-util-types" + ] + }, + "micromark-util-sanitize-uri@2.0.1": { + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dependencies": [ + "micromark-util-character", + "micromark-util-encode", + "micromark-util-symbol" + ] + }, + "micromark-util-subtokenize@2.1.0": { + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dependencies": [ + "devlop", + "micromark-util-chunked", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-util-symbol@2.0.1": { + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" + }, + "micromark-util-types@2.0.2": { + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" + }, + "micromark@4.0.2": { + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dependencies": [ + "@types/debug", + "debug@4.4.3", + "decode-named-character-reference", + "devlop", + "micromark-core-commonmark", + "micromark-factory-space", + "micromark-util-character", + "micromark-util-chunked", + "micromark-util-combine-extensions", + "micromark-util-decode-numeric-character-reference", + "micromark-util-encode", + "micromark-util-normalize-identifier", + "micromark-util-resolve-all", + "micromark-util-sanitize-uri", + "micromark-util-subtokenize", + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch@2.3.2" + ] + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-db@1.54.0": { + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db@1.52.0" + ] + }, + "mime-types@3.0.2": { + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": [ + "mime-db@1.54.0" + ] + }, + "mime@3.0.0": { + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": true + }, + "mimic-function@5.0.1": { + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==" + }, + "min-indent@1.0.1": { + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + }, + "minimatch@10.2.6": { + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dependencies": [ + "brace-expansion" + ] + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "minimizer-webpack-plugin@5.6.1_webpack@5.109.2": { + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "dependencies": [ + "@jridgewell/trace-mapping", + "jest-worker", + "schema-utils", + "terser", + "webpack" + ] + }, + "minipass@7.1.3": { + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" + }, + "module-details-from-path@1.0.4": { + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" + }, + "motion-dom@12.43.0": { + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==", + "dependencies": [ + "motion-utils" + ] + }, + "motion-utils@12.39.0": { + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==" + }, + "mrmime@2.0.1": { + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid@3.3.16": { + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "bin": true + }, + "nanoid@5.1.16": { + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "bin": true + }, + "napi-postinstall@0.3.4": { + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "bin": true + }, + "natural-compare@1.4.0": { + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "neo-async@2.6.2": { + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "next@16.2.12_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==", + "dependencies": [ + "@next/env", + "@opentelemetry/api", + "@playwright/test", + "@swc/helpers", + "baseline-browser-mapping", + "caniuse-lite", + "postcss", + "react", + "react-dom", + "styled-jsx" + ], + "optionalDependencies": [ + "@next/swc-darwin-arm64", + "@next/swc-darwin-x64", + "@next/swc-linux-arm64-gnu", + "@next/swc-linux-arm64-musl", + "@next/swc-linux-x64-gnu", + "@next/swc-linux-x64-musl", + "@next/swc-win32-arm64-msvc", + "@next/swc-win32-x64-msvc", + "sharp" + ], + "optionalPeers": [ + "@opentelemetry/api", + "@playwright/test" + ], + "bin": true + }, + "nextjs-toploader@3.9.17_next@16.2.12__@opentelemetry+api@1.9.1__@playwright+test@1.62.1__react@19.2.8__react-dom@19.2.8___react@19.2.8_react@19.2.8_react-dom@19.2.8__react@19.2.8_@playwright+test@1.62.1": { + "integrity": "sha512-9OF0KSSLtoSAuNg2LZ3aTl4hR9mBDj5L9s9DZiFCbMlXehyICGjkIz5dVGzuATU2bheJZoBdFgq9w07AKSuQQw==", + "dependencies": [ + "next", + "nprogress", + "prop-types", + "react", + "react-dom" + ] + }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true + }, + "node-domexception@2.0.2": { + "integrity": "sha512-Qf9vHK9c5MGgUXj8SnucCIS4oEPuUstjRaMplLGeZpbWMfNV1rvEcXuwoXfN51dUfD1b4muPHPQtCx/5Dj/QAA==", + "deprecated": true + }, + "node-exports-info@1.6.2": { + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dependencies": [ + "array.prototype.flatmap", + "es-errors", + "object.entries", + "semver@6.3.1" + ] + }, + "node-fetch@2.7.0": { + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": [ + "whatwg-url@5.0.0" + ] + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, + "node-releases@2.0.51": { + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==" + }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "nprogress@0.2.0": { + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, + "nypm@0.6.6": { + "integrity": "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==", + "dependencies": [ + "citty", + "pathe", + "tinyexec" + ], + "bin": true + }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-hash@3.0.0": { + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" + }, + "object-inspect@1.13.4": { + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "object-keys@1.1.1": { + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign@4.1.7": { + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-object-atoms", + "has-symbols", + "object-keys" + ] + }, + "object.entries@1.1.9": { + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-object-atoms" + ] + }, + "object.fromentries@2.0.8": { + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-object-atoms" + ] + }, + "object.groupby@1.0.3": { + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract" + ] + }, + "object.values@1.2.1": { + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-object-atoms" + ] + }, + "obug@2.1.4": { + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==" + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, + "optionator@0.9.4": { + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": [ + "deep-is", + "fast-levenshtein", + "levn", + "prelude-ls", + "type-check", + "word-wrap" + ] + }, + "own-keys@1.0.2": { + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dependencies": [ + "call-bound", + "get-intrinsic", + "object-keys", + "safe-push-apply" + ] + }, + "p-limit@3.1.0": { + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": [ + "yocto-queue" + ] + }, + "p-locate@5.0.0": { + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": [ + "p-limit" + ] + }, + "package-json-from-dist@1.0.1": { + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "parent-module@1.0.1": { + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": [ + "callsites" + ] + }, + "parse-entities@4.0.2": { + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": [ + "@types/unist@2.0.11", + "character-entities-legacy", + "character-reference-invalid", + "decode-named-character-reference", + "is-alphanumerical", + "is-decimal", + "is-hexadecimal" + ] + }, + "parse-srcset@1.0.2": { + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "parse5@8.0.1": { + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dependencies": [ + "entities@8.0.0" + ] + }, + "parseley@0.12.1": { + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": [ + "leac", + "peberminta" + ] + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-expression-matcher@1.6.2": { + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==" + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse@1.0.7": { + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry@2.0.2": { + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dependencies": [ + "lru-cache@11.5.2", + "minipass" + ] + }, + "pathe@2.0.3": { + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, + "peberminta@0.9.0": { + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch@2.3.2": { + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" + }, + "picomatch@4.0.5": { + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==" + }, + "picospinner@3.1.2": { + "integrity": "sha512-0Z++uU8mvB0EvCPAEUq9BGiPcSravzJ+RPdXfjYlzXffqsogisiKRQqI6ctrSSJ6n985vxrgKtQ5n4sRINcJZg==" + }, + "playwright-core@1.62.1": { + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "bin": true + }, + "playwright@1.62.1": { + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dependencies": [ + "playwright-core" + ], + "optionalDependencies": [ + "fsevents@2.3.2" + ], + "bin": true + }, + "possible-typed-array-names@1.1.0": { + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" + }, + "postal-mime@2.7.5": { + "integrity": "sha512-GNEXKvWFQnbgO5NlrGzVa0FmWzBZ24PersAWErttSg1Hjpf0ATxTwS5DOMGaOpTG6bUh5cTr7xi0jAD942wCJA==" + }, + "postcss@8.5.25": { + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dependencies": [ + "nanoid@3.3.16", + "picocolors", + "source-map-js" + ] + }, + "prelude-ls@1.2.1": { + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prettier@3.9.6": { + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "bin": true + }, + "pretty-bytes@6.1.1": { + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==" + }, + "pretty-format@27.5.1": { + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": [ + "ansi-regex", + "ansi-styles@5.2.0", + "react-is@17.0.2" + ] + }, + "prismjs@1.30.0": { + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==" + }, + "progress@2.0.3": { + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "prompts@2.4.2": { + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": [ + "kleur", + "sisteransi" + ] + }, + "prop-types@15.8.1": { + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": [ + "loose-envify", + "object-assign", + "react-is@16.13.1" + ] + }, + "property-information@7.2.0": { + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==" + }, + "proto3-json-serializer@3.0.4": { + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", + "dependencies": [ + "protobufjs" + ] + }, + "protobufjs@7.6.5": { + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "dependencies": [ + "@protobufjs/aspromise", + "@protobufjs/base64", + "@protobufjs/codegen", + "@protobufjs/eventemitter", + "@protobufjs/fetch", + "@protobufjs/float", + "@protobufjs/path", + "@protobufjs/pool", + "@protobufjs/utf8", + "@types/node", + "long" + ], + "scripts": true + }, + "proxy-from-env@1.1.0": { + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "proxy-from-env@2.1.0": { + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==" + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "qs@6.15.3": { + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dependencies": [ + "es-define-property", + "side-channel" + ] + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "react-day-picker@10.0.1_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==", + "dependencies": [ + "@date-fns/tz", + "@types/react", + "date-fns", + "react" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "react-dom@19.2.8_react@19.2.8": { + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "dependencies": [ + "react", + "scheduler" + ] + }, + "react-email@6.9.1_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-uUDRgFukMUXRlrsCNGlA0PZuUlQ44faI9hT/D7uMjozdumLBdHjfQttQswRYLjyLW8fFtg2HBrxAESbFU4ZKKA==", + "dependencies": [ + "@babel/parser@7.29.2", + "@babel/traverse@7.29.0", + "@react-email/render", + "chokidar", + "commander@13.1.0", + "conf", + "css-tree", + "debounce", + "esbuild", + "glob", + "jiti@2.6.1", + "log-symbols", + "marked", + "mime-types@3.0.2", + "normalize-path", + "nypm", + "picospinner", + "prismjs", + "prompts", + "react", + "react-dom", + "socket.io", + "tailwindcss", + "tsconfig-paths@4.2.0" + ], + "bin": true + }, + "react-hook-form@7.83.0_react@19.2.8": { + "integrity": "sha512-AXt8cMCmx5a7u4uvpb2uRFVrWQhllI4pV+LSykxIac/hjt44TnQkmX9BKuQi2i+LDC62esmiLpilkav+kjVf/A==", + "dependencies": [ + "react" + ] + }, + "react-is@16.13.1": { + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-is@17.0.2": { + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "react-is@19.2.8": { + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==" + }, + "react-markdown@10.1.0_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "dependencies": [ + "@types/hast", + "@types/mdast", + "@types/react", + "devlop", + "hast-util-to-jsx-runtime", + "html-url-attributes", + "mdast-util-to-hast", + "react", + "remark-parse", + "remark-rehype", + "unified", + "unist-util-visit", + "vfile" + ] + }, + "react-redux@9.3.0_@types+react@19.2.18_react@19.2.8_redux@5.0.1": { + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "dependencies": [ + "@types/react", + "@types/use-sync-external-store", + "react", + "redux", + "use-sync-external-store" + ], + "optionalPeers": [ + "@types/react", + "redux" + ] + }, + "react-remove-scroll-bar@2.3.8_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "dependencies": [ + "@types/react", + "react", + "react-style-singleton", + "tslib" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "react-remove-scroll@2.7.2_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "dependencies": [ + "@types/react", + "react", + "react-remove-scroll-bar", + "react-style-singleton", + "tslib", + "use-callback-ref", + "use-sidecar" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "react-style-singleton@2.2.3_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "dependencies": [ + "@types/react", + "get-nonce", + "react", + "tslib" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "react-turnstile@1.1.5_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-VTL5OeHAatzCEVQxAZox70/TPmhKxEbNgtr++dg+8zm9QrWKuoU9E0+7gqmycOSCDZuJFzvMMLKQb5PVUPLV6w==", + "dependencies": [ + "react", + "react-dom" + ] + }, + "react@19.2.8": { + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==" + }, + "readable-stream@3.6.2": { + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": [ + "inherits", + "string_decoder", + "util-deprecate" + ] + }, + "readdirp@4.1.2": { + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" + }, + "recharts@3.10.1_react@19.2.8_react-dom@19.2.8__react@19.2.8_react-is@19.2.8_@types+react@19.2.18": { + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "dependencies": [ + "@reduxjs/toolkit", + "clsx", + "decimal.js-light", + "es-toolkit", + "eventemitter3", + "immer", + "react", + "react-dom", + "react-is@19.2.8", + "react-redux", + "reselect", + "tiny-invariant", + "use-sync-external-store", + "victory-vendor" + ] + }, + "redent@3.0.0": { + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": [ + "indent-string", + "strip-indent" + ] + }, + "redux-thunk@3.1.0_redux@5.0.1": { + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "dependencies": [ + "redux" + ] + }, + "redux@5.0.1": { + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "refa@0.12.1": { + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dependencies": [ + "@eslint-community/regexpp" + ] + }, + "reflect.getprototypeof@1.0.10": { + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract", + "es-errors", + "es-object-atoms", + "get-intrinsic", + "get-proto", + "which-builtin-type" + ] + }, + "regexp-ast-analysis@0.7.1": { + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dependencies": [ + "@eslint-community/regexpp", + "refa" + ] + }, + "regexp-tree@0.1.27": { + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "bin": true + }, + "regexp.prototype.flags@1.5.4": { + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": [ + "call-bind", + "define-properties", + "es-errors", + "get-proto", + "gopd", + "set-function-name" + ] + }, + "remark-parse@11.0.0": { + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": [ + "@types/mdast", + "mdast-util-from-markdown", + "micromark-util-types", + "unified" + ] + }, + "remark-rehype@11.1.2": { + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dependencies": [ + "@types/hast", + "@types/mdast", + "mdast-util-to-hast", + "unified", + "vfile" + ] + }, + "require-directory@2.1.1": { + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-from-string@2.0.2": { + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "require-in-the-middle@8.0.1": { + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "dependencies": [ + "debug@4.4.3", + "module-details-from-path" + ] + }, + "reselect@5.2.0": { + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==" + }, + "resend@6.18.1": { + "integrity": "sha512-XN8XIaDdKF+ziSQ3K23ndUcyhP7U3ze2gky6SPgYkuAOq54mH4Wdhwm7QylEQ3zlz0NzdX7/l1AgmJUZbdPI/Q==", + "dependencies": [ + "postal-mime", + "standardwebhooks" + ] + }, + "resolve-from@4.0.0": { + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-pkg-maps@1.0.0": { + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" + }, + "resolve@2.0.0-next.7": { + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dependencies": [ + "es-errors", + "is-core-module", + "node-exports-info", + "object-keys", + "path-parse", + "supports-preserve-symlinks-flag" + ], + "bin": true + }, + "retry-request@7.0.2": { + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "dependencies": [ + "@types/request", + "extend", + "teeny-request@9.0.0" + ] + }, + "retry-request@8.0.4": { + "integrity": "sha512-pI6/7eabUYkZxamkOq0g0uMxKLLGnjzhefY+vL8bVXag5rto4OU2YBTPytWLuHH7aEKD6fn7kJycQfid0Mwnkw==", + "dependencies": [ + "extend", + "teeny-request@10.1.4" + ] + }, + "retry@0.13.1": { + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, + "reusify@1.1.0": { + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" + }, + "rimraf@5.0.10": { + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dependencies": [ + "glob" + ], + "bin": true + }, + "rimraf@6.1.3": { + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dependencies": [ + "glob", + "package-json-from-dist" + ], + "bin": true + }, + "rolldown@1.2.1": { + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", + "dependencies": [ + "@oxc-project/types", + "@rolldown/pluginutils" + ], + "optionalDependencies": [ + "@rolldown/binding-android-arm64", + "@rolldown/binding-darwin-arm64", + "@rolldown/binding-darwin-x64", + "@rolldown/binding-freebsd-x64", + "@rolldown/binding-linux-arm-gnueabihf", + "@rolldown/binding-linux-arm64-gnu", + "@rolldown/binding-linux-arm64-musl", + "@rolldown/binding-linux-ppc64-gnu", + "@rolldown/binding-linux-s390x-gnu", + "@rolldown/binding-linux-x64-gnu", + "@rolldown/binding-linux-x64-musl", + "@rolldown/binding-openharmony-arm64", + "@rolldown/binding-wasm32-wasi", + "@rolldown/binding-win32-arm64-msvc", + "@rolldown/binding-win32-x64-msvc" + ], + "bin": true + }, + "rollup@4.62.3": { + "integrity": "sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==", + "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", + "@rollup/rollup-openharmony-arm64", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-gnu", + "@rollup/rollup-win32-x64-msvc", + "fsevents@2.3.3" + ], + "bin": true + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" + ] + }, + "safe-array-concat@1.1.4": { + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dependencies": [ + "call-bind", + "call-bound", + "get-intrinsic", + "has-symbols", + "isarray" + ] + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-push-apply@1.0.0": { + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": [ + "es-errors", + "isarray" + ] + }, + "safe-regex-test@1.1.0": { + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": [ + "call-bound", + "es-errors", + "is-regex" + ] + }, + "safe-regex@2.1.1": { + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dependencies": [ + "regexp-tree" + ] + }, + "sanitize-html@2.17.6": { + "integrity": "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==", + "dependencies": [ + "deepmerge", + "escape-string-regexp", + "htmlparser2@12.0.0", + "is-plain-object", + "launder", + "parse-srcset", + "postcss" + ] + }, + "saxes@6.0.0": { + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": [ + "xmlchars" + ] + }, + "scheduler@0.27.0": { + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "schema-utils@4.3.3": { + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dependencies": [ + "@types/json-schema", + "ajv@8.20.0", + "ajv-formats@2.1.1_ajv@8.20.0", + "ajv-keywords" + ] + }, + "scslre@0.3.0": { + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dependencies": [ + "@eslint-community/regexpp", + "refa", + "regexp-ast-analysis" + ] + }, + "selderee@0.11.0": { + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": [ + "parseley" + ] + }, + "semifies@1.0.0": { + "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==" + }, + "semver@6.3.1": { + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": true + }, + "semver@7.8.5": { + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "bin": true + }, + "server-only@0.0.1": { + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" + }, + "serwist@9.5.12_typescript@6.0.3_browserslist@4.28.6": { + "integrity": "sha512-PwREKJrSb3ja40XJyBntPOm7okcYZJCJPd++jUVz8tzxY1VYO9dyKSm0MimZ8LtUk0pIUTN0q2cfGe7R0wQsaQ==", + "dependencies": [ + "@serwist/utils", + "idb", + "typescript" + ], + "optionalPeers": [ + "typescript" + ] + }, + "set-function-length@1.2.2": { + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": [ + "define-data-property", + "es-errors", + "function-bind", + "get-intrinsic", + "gopd", + "has-property-descriptors" + ] + }, + "set-function-name@2.0.2": { + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": [ + "define-data-property", + "es-errors", + "functions-have-names", + "has-property-descriptors" + ] + }, + "set-proto@1.0.0": { + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": [ + "dunder-proto", + "es-errors", + "es-object-atoms" + ] + }, + "sharp@0.35.3": { + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dependencies": [ + "@img/colour", + "detect-libc", + "semver@7.8.5" + ], + "optionalDependencies": [ + "@img/sharp-darwin-arm64", + "@img/sharp-darwin-x64", + "@img/sharp-freebsd-wasm32", + "@img/sharp-libvips-darwin-arm64", + "@img/sharp-libvips-darwin-x64", + "@img/sharp-libvips-linux-arm", + "@img/sharp-libvips-linux-arm64", + "@img/sharp-libvips-linux-ppc64", + "@img/sharp-libvips-linux-riscv64", + "@img/sharp-libvips-linux-s390x", + "@img/sharp-libvips-linux-x64", + "@img/sharp-libvips-linuxmusl-arm64", + "@img/sharp-libvips-linuxmusl-x64", + "@img/sharp-linux-arm", + "@img/sharp-linux-arm64", + "@img/sharp-linux-ppc64", + "@img/sharp-linux-riscv64", + "@img/sharp-linux-s390x", + "@img/sharp-linux-x64", + "@img/sharp-linuxmusl-arm64", + "@img/sharp-linuxmusl-x64", + "@img/sharp-webcontainers-wasm32", + "@img/sharp-win32-arm64", + "@img/sharp-win32-ia32", + "@img/sharp-win32-x64" + ] + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "side-channel-list@1.0.1": { + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dependencies": [ + "es-errors", + "object-inspect" + ] + }, + "side-channel-map@1.0.1": { + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": [ + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect" + ] + }, + "side-channel-weakmap@1.0.2": { + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": [ + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect", + "side-channel-map" + ] + }, + "side-channel@1.1.1": { + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dependencies": [ + "es-errors", + "object-inspect", + "side-channel-list", + "side-channel-map", + "side-channel-weakmap" + ] + }, + "siginfo@2.0.0": { + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, + "sirv@3.0.2": { + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dependencies": [ + "@polka/url", + "mrmime", + "totalist" + ] + }, + "sisteransi@1.0.5": { + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "socket.io-adapter@2.5.8": { + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "dependencies": [ + "debug@4.4.3", + "ws" + ] + }, + "socket.io-parser@4.2.7": { + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.4.3" + ] + }, + "socket.io@4.8.3": { + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dependencies": [ + "accepts", + "base64id", + "cors", + "debug@4.4.3", + "engine.io", + "socket.io-adapter", + "socket.io-parser" + ] + }, + "sonner@2.0.7_react@19.2.8_react-dom@19.2.8__react@19.2.8": { + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "dependencies": [ + "react", + "react-dom" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "source-map-support@0.5.21": { + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": [ + "buffer-from", + "source-map" + ] + }, + "source-map@0.8.0": { + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==" + }, + "space-separated-tokens@2.0.2": { + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, + "stable-hash@0.0.5": { + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==" + }, + "stackback@0.0.2": { + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "stacktrace-parser@0.1.11": { + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "dependencies": [ + "type-fest@0.7.1" + ] + }, + "standardwebhooks@1.0.0": { + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "dependencies": [ + "@stablelib/base64", + "fast-sha256" + ] + }, + "std-env@4.2.0": { + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==" + }, + "stop-iteration-iterator@1.1.0": { + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": [ + "es-errors", + "internal-slot" + ] + }, + "stream-events@1.0.5": { + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": [ + "stubs" + ] + }, + "stream-shift@1.0.3": { + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "string-argv@0.3.2": { + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex@8.0.0", + "is-fullwidth-code-point", + "strip-ansi" + ] + }, + "string.prototype.includes@2.0.1": { + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-abstract" + ] + }, + "string.prototype.matchall@4.0.12": { + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-abstract", + "es-errors", + "es-object-atoms", + "get-intrinsic", + "gopd", + "has-symbols", + "internal-slot", + "regexp.prototype.flags", + "set-function-name", + "side-channel" + ] + }, + "string.prototype.repeat@1.0.0": { + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dependencies": [ + "define-properties", + "es-abstract" + ] + }, + "string.prototype.trim@1.2.11": { + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dependencies": [ + "call-bind", + "call-bound", + "define-data-property", + "define-properties", + "es-abstract", + "es-object-atoms", + "has-property-descriptors", + "safe-regex-test" + ] + }, + "string.prototype.trimend@1.0.10": { + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-object-atoms" + ] + }, + "string.prototype.trimstart@1.0.8": { + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": [ + "call-bind", + "define-properties", + "es-object-atoms" + ] + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": [ + "safe-buffer" + ] + }, + "stringify-entities@4.0.4": { + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": [ + "character-entities-html4", + "character-entities-legacy" + ] + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex" + ] + }, + "strip-bom@3.0.0": { + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + }, + "strip-indent@3.0.0": { + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": [ + "min-indent" + ] + }, + "strip-json-comments@3.1.1": { + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "strnum@2.4.1": { + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "dependencies": [ + "anynum" + ] + }, + "stubborn-fs@2.0.0": { + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dependencies": [ + "stubborn-utils" + ] + }, + "stubborn-utils@1.0.2": { + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==" + }, + "stubs@3.0.0": { + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "style-to-js@1.1.21": { + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "dependencies": [ + "style-to-object" + ] + }, + "style-to-object@1.0.14": { + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dependencies": [ + "inline-style-parser" + ] + }, + "styled-jsx@5.1.6_react@19.2.8": { + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dependencies": [ + "client-only", + "react" + ] + }, + "supabase@2.111.0": { + "integrity": "sha512-0cjCRdYNV1h2XXa0wm04mdct7QuDU7sMul/NwETRJmN3+HCsdEu6u5n0oygL97fdf6sDrGmnAdBh8E4wsv1ayg==", + "dependencies": [ + "eciesjs", + "jose" + ], + "optionalDependencies": [ + "@supabase/cli-darwin-arm64", + "@supabase/cli-darwin-x64", + "@supabase/cli-linux-arm64", + "@supabase/cli-linux-arm64-musl", + "@supabase/cli-linux-x64", + "@supabase/cli-linux-x64-musl", + "@supabase/cli-windows-arm64", + "@supabase/cli-windows-x64" + ], + "bin": true + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, + "supports-color@8.1.1": { + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": [ + "has-flag" + ] + }, + "supports-preserve-symlinks-flag@1.0.0": { + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "symbol-tree@3.2.4": { + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "tagged-tag@1.0.0": { + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + }, + "tailwind-merge@3.6.0": { + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==" + }, + "tailwindcss@4.3.3": { + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==" + }, + "tapable@2.3.3": { + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==" + }, + "teeny-request@10.1.4": { + "integrity": "sha512-R1Cg4Vu0UULeDfHL/kjABLaTW++9yD/B6n2g48y5dJ04hsEaxcfmAqbNDzNsbqAYJyIpZafjklLG9YxRu9uzOg==", + "dependencies": [ + "http-proxy-agent@7.0.2", + "https-proxy-agent@7.0.6", + "node-fetch@3.3.2", + "stream-events" + ] + }, + "teeny-request@9.0.0": { + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "dependencies": [ + "http-proxy-agent@5.0.0", + "https-proxy-agent@5.0.1", + "node-fetch@2.7.0", + "stream-events", + "uuid" + ] + }, + "terser@5.49.0": { + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "dependencies": [ + "@jridgewell/source-map", + "acorn", + "commander@2.20.3", + "source-map-support" + ], + "bin": true + }, + "tiny-invariant@1.3.3": { + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "tinybench@2.9.0": { + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" + }, + "tinyexec@1.2.4": { + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==" + }, + "tinyglobby@0.2.17": { + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dependencies": [ + "fdir", + "picomatch@4.0.5" + ] + }, + "tinyrainbow@3.1.1": { + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==" + }, + "tldts-core@7.4.10": { + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==" + }, + "tldts@7.4.10": { + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dependencies": [ + "tldts-core" + ], + "bin": true + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, + "totalist@3.0.1": { + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" + }, + "tough-cookie@6.0.2": { + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dependencies": [ + "tldts" + ] + }, + "tr46@0.0.3": { + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tr46@6.0.0": { + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dependencies": [ + "punycode" + ] + }, + "trim-lines@3.0.1": { + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "trough@2.2.0": { + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" + }, + "ts-api-utils@2.5.0_typescript@6.0.3": { + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dependencies": [ + "typescript" + ] + }, + "tsconfig-paths@3.15.0": { + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dependencies": [ + "@types/json5", + "json5@1.0.2", + "minimist", + "strip-bom" + ] + }, + "tsconfig-paths@4.2.0": { + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dependencies": [ + "json5@2.2.3", + "minimist", + "strip-bom" + ] + }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "tw-animate-css@1.4.0": { + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==" + }, + "type-check@0.4.0": { + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": [ + "prelude-ls" + ] + }, + "type-fest@0.7.1": { + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" + }, + "type-fest@5.8.0": { + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "dependencies": [ + "tagged-tag" + ] + }, + "typed-array-buffer@1.0.3": { + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": [ + "call-bound", + "es-errors", + "is-typed-array" + ] + }, + "typed-array-byte-length@1.0.3": { + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": [ + "call-bind", + "for-each", + "gopd", + "has-proto", + "is-typed-array" + ] + }, + "typed-array-byte-offset@1.0.4": { + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dependencies": [ + "available-typed-arrays", + "call-bind", + "for-each", + "gopd", + "has-proto", + "is-typed-array", + "reflect.getprototypeof" + ] + }, + "typed-array-length@1.0.8": { + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dependencies": [ + "call-bind", + "for-each", + "gopd", + "is-typed-array", + "possible-typed-array-names", + "reflect.getprototypeof" + ] + }, + "typescript-eslint@8.65.0_eslint@9.39.5_typescript@6.0.3": { + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dependencies": [ + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + "@typescript-eslint/typescript-estree", + "@typescript-eslint/utils", + "eslint", + "typescript" + ] + }, + "typescript@6.0.3": { + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "bin": true + }, + "uint8array-extras@1.5.0": { + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==" + }, + "unbox-primitive@1.1.0": { + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": [ + "call-bound", + "has-bigints", + "has-symbols", + "which-boxed-primitive" + ] + }, + "uncrypto@0.1.3": { + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==" + }, + "undici-types@8.3.0": { + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" + }, + "undici@8.9.0": { + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==" + }, + "unified@11.0.5": { + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": [ + "@types/unist@3.0.3", + "bail", + "devlop", + "extend", + "is-plain-obj", + "trough", + "vfile" + ] + }, + "unist-util-is@6.0.1": { + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dependencies": [ + "@types/unist@3.0.3" + ] + }, + "unist-util-position@5.0.0": { + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": [ + "@types/unist@3.0.3" + ] + }, + "unist-util-stringify-position@4.0.0": { + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": [ + "@types/unist@3.0.3" + ] + }, + "unist-util-visit-parents@6.0.2": { + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dependencies": [ + "@types/unist@3.0.3", + "unist-util-is" + ] + }, + "unist-util-visit@5.1.0": { + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dependencies": [ + "@types/unist@3.0.3", + "unist-util-is", + "unist-util-visit-parents" + ] + }, + "unrs-resolver@1.12.2": { + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dependencies": [ + "napi-postinstall" + ], + "optionalDependencies": [ + "@unrs/resolver-binding-android-arm-eabi", + "@unrs/resolver-binding-android-arm64", + "@unrs/resolver-binding-darwin-arm64", + "@unrs/resolver-binding-darwin-x64", + "@unrs/resolver-binding-freebsd-x64", + "@unrs/resolver-binding-linux-arm-gnueabihf", + "@unrs/resolver-binding-linux-arm-musleabihf", + "@unrs/resolver-binding-linux-arm64-gnu", + "@unrs/resolver-binding-linux-arm64-musl", + "@unrs/resolver-binding-linux-loong64-gnu", + "@unrs/resolver-binding-linux-loong64-musl", + "@unrs/resolver-binding-linux-ppc64-gnu", + "@unrs/resolver-binding-linux-riscv64-gnu", + "@unrs/resolver-binding-linux-riscv64-musl", + "@unrs/resolver-binding-linux-s390x-gnu", + "@unrs/resolver-binding-linux-x64-gnu", + "@unrs/resolver-binding-linux-x64-musl", + "@unrs/resolver-binding-openharmony-arm64", + "@unrs/resolver-binding-wasm32-wasi", + "@unrs/resolver-binding-win32-arm64-msvc", + "@unrs/resolver-binding-win32-ia32-msvc", + "@unrs/resolver-binding-win32-x64-msvc" + ], + "scripts": true + }, + "update-browserslist-db@1.2.3_browserslist@4.28.6": { + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dependencies": [ + "browserslist", + "escalade", + "picocolors" + ], + "bin": true + }, + "uri-js@4.4.1": { + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": [ + "punycode" + ] + }, + "url-template@2.0.8": { + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "use-callback-ref@1.3.3_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "dependencies": [ + "@types/react", + "react", + "tslib" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "use-sidecar@1.1.3_@types+react@19.2.18_react@19.2.8": { + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "dependencies": [ + "@types/react", + "detect-node-es", + "react", + "tslib" + ], + "optionalPeers": [ + "@types/react" + ] + }, + "use-sync-external-store@1.6.0_react@19.2.8": { + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dependencies": [ + "react" + ] + }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "uuid@14.0.1": { + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "bin": true + }, + "vary@1.1.2": { + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "vfile-message@4.0.3": { + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dependencies": [ + "@types/unist@3.0.3", + "unist-util-stringify-position" + ] + }, + "vfile@6.0.3": { + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": [ + "@types/unist@3.0.3", + "vfile-message" + ] + }, + "victory-vendor@37.3.6": { + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "dependencies": [ + "@types/d3-array", + "@types/d3-ease", + "@types/d3-interpolate", + "@types/d3-scale", + "@types/d3-shape", + "@types/d3-time", + "@types/d3-timer", + "d3-array", + "d3-ease", + "d3-interpolate", + "d3-scale", + "d3-shape", + "d3-time", + "d3-timer" + ] + }, + "vite@8.2.0_@types+node@26.1.2": { + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", + "dependencies": [ + "@types/node", + "lightningcss@1.33.0", + "picomatch@4.0.5", + "postcss", + "rolldown", + "tinyglobby" + ], + "optionalDependencies": [ + "fsevents@2.3.3" + ], + "optionalPeers": [ + "@types/node" + ], + "bin": true + }, + "vitest@4.1.10_@types+node@26.1.2_@vitest+coverage-v8@4.1.10_@vitest+ui@4.1.10_happy-dom@20.11.1_jsdom@30.0.1": { + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dependencies": [ + "@types/node", + "@vitest/coverage-v8", + "@vitest/expect", + "@vitest/mocker", + "@vitest/pretty-format", + "@vitest/runner", + "@vitest/snapshot", + "@vitest/spy", + "@vitest/ui", + "@vitest/utils", + "es-module-lexer", + "expect-type", + "happy-dom", + "jsdom", + "magic-string", + "obug", + "pathe", + "picomatch@4.0.5", + "std-env", + "tinybench", + "tinyexec", + "tinyglobby", + "tinyrainbow", + "vite", + "why-is-node-running" + ], + "optionalPeers": [ + "@types/node", + "@vitest/coverage-v8", + "@vitest/ui", + "happy-dom", + "jsdom" + ], + "bin": true + }, + "w3c-xmlserializer@5.0.0": { + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": [ + "xml-name-validator" + ] + }, + "watchpack@2.5.2": { + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dependencies": [ + "graceful-fs" + ] + }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, + "webidl-conversions@3.0.1": { + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "webidl-conversions@8.0.1": { + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==" + }, + "webpack-sources@3.5.1": { + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==" + }, + "webpack@5.109.2": { + "integrity": "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==", + "dependencies": [ + "@types/estree", + "@types/json-schema", + "@webassemblyjs/ast", + "@webassemblyjs/wasm-edit", + "@webassemblyjs/wasm-parser", + "acorn", + "chrome-trace-event", + "enhanced-resolve", + "es-module-lexer", + "eslint-scope@5.1.1", + "events", + "graceful-fs", + "mime-db@1.54.0", + "minimizer-webpack-plugin", + "neo-async", + "schema-utils", + "tapable", + "watchpack", + "webpack-sources" + ], + "bin": true + }, + "websocket-driver@0.7.5": { + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "dependencies": [ + "http-parser-js", + "safe-buffer", + "websocket-extensions" + ] + }, + "websocket-extensions@0.1.4": { + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-mimetype@3.0.0": { + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" + }, + "whatwg-mimetype@5.0.0": { + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==" + }, + "whatwg-url@16.0.1": { + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dependencies": [ + "@exodus/bytes", + "tr46@6.0.0", + "webidl-conversions@8.0.1" + ] + }, + "whatwg-url@17.1.0": { + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dependencies": [ + "@exodus/bytes", + "tr46@6.0.0", + "webidl-conversions@8.0.1" + ] + }, + "whatwg-url@5.0.0": { + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": [ + "tr46@0.0.3", + "webidl-conversions@3.0.1" + ] + }, + "when-exit@2.1.5": { + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==" + }, + "which-boxed-primitive@1.1.1": { + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": [ + "is-bigint", + "is-boolean-object", + "is-number-object", + "is-string", + "is-symbol" + ] + }, + "which-builtin-type@1.2.1": { + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": [ + "call-bound", + "function.prototype.name", + "has-tostringtag", + "is-async-function", + "is-date-object", + "is-finalizationregistry", + "is-generator-function", + "is-regex", + "is-weakref", + "isarray", + "which-boxed-primitive", + "which-collection", + "which-typed-array" + ] + }, + "which-collection@1.0.2": { + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": [ + "is-map", + "is-set", + "is-weakmap", + "is-weakset" + ] + }, + "which-typed-array@1.1.22": { + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dependencies": [ + "available-typed-arrays", + "call-bind", + "call-bound", + "for-each", + "get-proto", + "gopd", + "has-tostringtag" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ], + "bin": true + }, + "why-is-node-running@2.3.0": { + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dependencies": [ + "siginfo", + "stackback" + ], + "bin": true + }, + "word-wrap@1.2.5": { + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + }, + "wrap-ansi@7.0.0": { + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": [ + "ansi-styles@4.3.0", + "string-width", + "strip-ansi" + ] + }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws@8.21.1": { + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==" + }, + "xml-name-validator@5.0.0": { + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==" + }, + "xml-naming@0.3.0": { + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==" + }, + "xmlchars@2.2.0": { + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "y18n@5.0.8": { + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist@3.1.1": { + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yaml@2.9.0": { + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "bin": true + }, + "yargs-parser@21.1.1": { + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yargs@17.7.3": { + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dependencies": [ + "cliui", + "escalade", + "get-caller-file", + "require-directory", + "string-width", + "y18n", + "yargs-parser" + ] + }, + "yocto-queue@0.1.0": { + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "yoctocolors@2.2.0": { + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==" + }, + "zod-validation-error@4.0.2_zod@4.4.3": { + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dependencies": [ + "zod" + ] + }, + "zod@4.4.3": { + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" + }, + "zwitch@2.0.4": { + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" + } + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@eslint/js@^9.39.5", + "npm:@hookform/resolvers@^5.5.7", + "npm:@opentelemetry/context-async-hooks@^2.10.0", + "npm:@playwright/test@^1.62.1", + "npm:@radix-ui/react-alert-dialog@^1.1.23", + "npm:@radix-ui/react-avatar@^1.2.6", + "npm:@radix-ui/react-checkbox@^1.3.11", + "npm:@radix-ui/react-dialog@^1.1.23", + "npm:@radix-ui/react-dropdown-menu@^2.1.24", + "npm:@radix-ui/react-label@^2.1.15", + "npm:@radix-ui/react-popover@^1.1.23", + "npm:@radix-ui/react-progress@^1.1.16", + "npm:@radix-ui/react-radio-group@^1.4.7", + "npm:@radix-ui/react-scroll-area@^1.2.18", + "npm:@radix-ui/react-select@^2.3.7", + "npm:@radix-ui/react-separator@^1.1.15", + "npm:@radix-ui/react-slot@^1.3.3", + "npm:@radix-ui/react-switch@^1.3.7", + "npm:@radix-ui/react-tabs@^1.1.21", + "npm:@scalar/nextjs-api-reference@~0.11.12", + "npm:@sentry/nextjs@^10.69.0", + "npm:@serwist/next@^9.5.12", + "npm:@supabase/ssr@~0.12.4", + "npm:@supabase/supabase-js@^2.111.0", + "npm:@tailwindcss/postcss@4", + "npm:@tanstack/react-query@^5.101.4", + "npm:@tanstack/react-virtual@^3.14.9", + "npm:@testing-library/dom@^10.4.1", + "npm:@testing-library/jest-dom@7", + "npm:@testing-library/react@^16.3.2", + "npm:@testing-library/user-event@^14.6.1", + "npm:@types/node@^26.1.2", + "npm:@types/nprogress@~0.2.3", + "npm:@types/react-dom@^19.2.4", + "npm:@types/react@^19.2.18", + "npm:@types/sanitize-html@^2.16.1", + "npm:@typescript-eslint/parser@^8.65.0", + "npm:@upstash/ratelimit@^2.0.8", + "npm:@upstash/redis@^1.38.1", + "npm:@vitejs/plugin-react@^6.0.5", + "npm:@vitest/coverage-v8@^4.1.10", + "npm:@vitest/ui@^4.1.10", + "npm:axios@^1.19.0", + "npm:class-variance-authority@~0.7.1", + "npm:clsx@^2.1.1", + "npm:date-fns@^4.4.0", + "npm:eslint-config-next@^16.2.12", + "npm:eslint-plugin-react-hooks@^7.1.1", + "npm:eslint-plugin-react@^7.37.5", + "npm:eslint-plugin-security@^4.0.1", + "npm:eslint-plugin-sonarjs@^4.2.0", + "npm:eslint-plugin-unused-imports@^4.4.1", + "npm:eslint@^9.39.5", + "npm:firebase-admin@^14.2.0", + "npm:framer-motion@^12.43.0", + "npm:glob@^13.0.6", + "npm:globals@^17.8.0", + "npm:googleapis@173", + "npm:happy-dom@^20.11.1", + "npm:husky@^9.1.7", + "npm:jsdom@^30.0.1", + "npm:ldrs@^1.1.9", + "npm:lint-staged@^17.3.0", + "npm:lodash-es@^4.18.1", + "npm:lru-cache@^11.5.2", + "npm:lucide-react@^1.28.0", + "npm:next@^16.2.12", + "npm:nextjs-toploader@^3.9.17", + "npm:node-domexception@^2.0.2", + "npm:react-day-picker@^10.0.1", + "npm:react-dom@^19.2.8", + "npm:react-email@^6.9.1", + "npm:react-hook-form@^7.83.0", + "npm:react-is@^19.2.8", + "npm:react-markdown@^10.1.0", + "npm:react-turnstile@^1.1.5", + "npm:react@^19.2.8", + "npm:recharts@^3.10.1", + "npm:resend@^6.18.1", + "npm:rimraf@^6.1.3", + "npm:sanitize-html@^2.17.6", + "npm:server-only@^0.0.1", + "npm:serwist@^9.5.12", + "npm:sonner@^2.0.7", + "npm:source-map@0.8", + "npm:supabase@^2.111.0", + "npm:tailwind-merge@^3.6.0", + "npm:tailwindcss@4", + "npm:tw-animate-css@^1.4.0", + "npm:typescript-eslint@^8.65.0", + "npm:typescript@^6.0.3", + "npm:uuid@^14.0.1", + "npm:vitest@^4.1.10", + "npm:zod@^4.4.3" + ], + "overrides": { + "serialize-javascript": "^7.0.5", + "tar": "^7.5.15", + "js-yaml": "^4.1.1", + "rollup": "^4.60.3", + "glob": "^13.0.6", + "source-map": "^0.8.0", + "minimatch": "^10.2.5", + "flatted": "^3.4.2", + "@tootallnate/once": "^3.0.1", + "postcss": "^8.5.14", + "sharp": "^0.35.0", + "uuid": "^14.0.1" + } + } + } +} diff --git a/docs/ALGORITHM.md b/docs/ALGORITHM.md index 220f4d94..6610951f 100644 --- a/docs/ALGORITHM.md +++ b/docs/ALGORITHM.md @@ -1,10 +1,13 @@ # Attendance Calculation Algorithm -Detailed documentation of the core attendance calculation and bunk logic used in GhostClass (Web and Mobile). +Detailed documentation of the core attendance calculation and bunk logic used in +GhostClass (Web and Mobile). ## Core Algorithm -The algorithm is implemented with parity in both [bunk.ts](src/lib/logic/bunk.ts) (Web) and [bunk.dart](mobile/lib/logic/bunk.dart) (Mobile). +The algorithm is implemented with parity in both +[bunk.ts](src/lib/logic/bunk.ts) (Web) and +[bunk.dart](mobile/lib/logic/bunk.dart) (Mobile). ### Calculation Flow @@ -55,10 +58,13 @@ x = (target*total - 100*present) / (100 - target) The calculation combines official data with user-added modifiers: -1. **Official Data**: Fetched from EzyGo API (`realPresent`, `realTotal`, `realAbsent`). +1. **Official Data**: Fetched from EzyGo API (`realPresent`, `realTotal`, + `realAbsent`). 2. **Manual Modifiers**: - - `extraPresent/extraAbsent`: Additional classes marked by user (adds to total). - - `correctionPresent`: Wrongly marked absences corrected to present (status swap only). + - `extraPresent/extraAbsent`: Additional classes marked by user (adds to + total). + - `correctionPresent`: Wrongly marked absences corrected to present (status + swap only). ### Final Calculation Formula @@ -70,16 +76,18 @@ displayPercentage = (finalPresent / finalTotal) * 100 ## Duty Leave Rules -**Attendance Code 225 Limit**: Maximum 5 duty leave entries per course per semester. +**Attendance Code 225 Limit**: Maximum 5 duty leave entries per course per +semester. -- Enforced via database trigger `check_225_attendance_limit()` in the `tracker` table. +- Enforced via database trigger `check_225_attendance_limit()` in the `tracker` + table. - Raises an exception if exceeded to maintain data integrity. ## Example Scenarios -| Scenario | Present | Total | Target | Result | -| :--- | :--- | :--- | :--- | :--- | -| **At Target** | 45 | 60 | 75% | `isExact = true` | -| **Can Bunk** | 50 | 60 | 75% | `canBunk = 6` | -| **Need to Attend** | 40 | 60 | 75% | `requiredToAttend = 6` | +| Scenario | Present | Total | Target | Result | +| :----------------- | :------ | :---- | :----- | :--------------------- | +| **At Target** | 45 | 60 | 75% | `isExact = true` | +| **Can Bunk** | 50 | 60 | 75% | `canBunk = 6` | +| **Need to Attend** | 40 | 60 | 75% | `requiredToAttend = 6` | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index da6705c2..62dec9f0 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,8 +1,7 @@ # Contributing to GhostClass -Thank you for your interest in contributing to GhostClass! This guide will help you understand our development workflow and contribution process. - -> **๐Ÿ‘‹ For External Contributors**: You don't need GPG keys, PAT tokens, or any special setup! Just fork, code, and submit a PR. The version bump workflow will guide you through a simple script. See [Quick Setup](#quick-setup) below. +Thank you for your interest in contributing to GhostClass! This guide will help +you understand our development workflow and contribution process. ## Table of Contents @@ -19,33 +18,12 @@ Thank you for your interest in contributing to GhostClass! This guide will help ### Prerequisites -- **Node.js**: 24.14.1+ -- **npm**: 11.11.0+ -- **Flutter SDK**: 3.27+ (for mobile development) -- **Dart SDK**: ^3.11.4 (bundled with Flutter) -- **Git**: Latest version - -**That's it!** External contributors don't need GPG keys, GitHub PAT tokens, or access to secrets. - -### Quick Setup - -```bash -# 1. Fork and clone -git clone https://github.com/YOUR_USERNAME/GhostClass.git -cd GhostClass - -# 2. Install dependencies -npm install --legacy-peer-deps - -# 3. Create feature branch -git checkout -b feature/your-feature-name - -# 4. Start development -npm run dev # Web development -cd mobile && flutter run # Mobile development -``` +- **Docker Desktop** (with WSL2 backend enabled) +- **WSL2** (Linux distribution such as Ubuntu/Debian) +- **VS Code or Antigravity IDE / any IDE with WSL/Docker Support** -**That's all you need to start developing!** For advanced maintainer setup (GPG, PAT tokens, deployment), see [For Maintainers Only](#for-maintainers-only) at the bottom of this guide. +Complete step-by-step dev container environment setup: +**[Getting Started / Dev Container Setup](../README.md#-getting-started)**. ## Development Workflow @@ -88,11 +66,16 @@ flutter build apk # Build Android APK 5. Commit with clear messages (see [Commit Messages](#commit-messages)) 6. Push and create a Pull Request -**Important**: Centralized version values apply automatically! See [Versioning System](#versioning-system) below. +**Important**: Centralized version values apply automatically! See +[Versioning System](#versioning-system) below. ## Versioning System -GhostClass derives its build versions dynamically via centralized Infisical runtime and CI configurations (`NEXT_PUBLIC_APP_VERSION`). Contributors do not need to manually compute or inject git semantic rollover tags when proposing features. Maintainers synchronize version thresholds directly in the project dashboard prior to production releases. +GhostClass derives its build versions dynamically via centralized Infisical +runtime and CI configurations (`NEXT_PUBLIC_APP_VERSION`). Contributors do not +need to manually compute or inject git semantic rollover tags when proposing +features. Maintainers synchronize version thresholds directly in the project +dashboard prior to production releases. ## Pull Request Process @@ -173,11 +156,11 @@ docker build \ ### CI/CD Build Times -| Build Type | Platforms | Time | Use Case | -| --- | --- | --- | --- | -| Cached build | AMD64 | ~3-5 min | Incremental changes | -| Cold build | AMD64 | ~6-8 min | Fresh build | -| Multi-arch | AMD64 + ARM64 | ~10-15 min | Production releases | +| Build Type | Platforms | Time | Use Case | +| ------------ | ------------- | ---------- | ------------------- | +| Cached build | AMD64 | ~3-5 min | Incremental changes | +| Cold build | AMD64 | ~6-8 min | Fresh build | +| Multi-arch | AMD64 + ARM64 | ~10-15 min | Production releases | ### Performance Features @@ -238,35 +221,42 @@ Closes #123 - **Bug Reports**: Use [bug report template](.github/ISSUE_TEMPLATE) - **Feature Requests**: Use [feature request template](.github/ISSUE_TEMPLATE) -- **Questions**: Open a [Discussion](https://github.com/devakesu/GhostClass/discussions) +- **Questions**: Open a + [Discussion](https://github.com/devakesu/GhostClass/discussions) - **Setup Issues**: Check [SECURITY.md](../SECURITY.md) and `.example.env` --- ## For Maintainers Only -> **โš ๏ธ This section is for repository maintainers with write access only.** +> **โš ๏ธ This section is for repository maintainers with write access only.**\ > External contributors can skip this section entirely. ### Maintainer Tools #### Infisical Secret Orchestration -- Centralized management via Infisical Dashboard acts as the single source of truth, organized into `/build-time`, `/runtime`, and `/ci` folders. -- While GitHub Actions (`/build-time` and `/ci`) use Native Integrations, Coolify production runtime environments inject `/runtime` secrets dynamically into memory at boot time using the Infisical CLI wrapper. +- Centralized management via Infisical Dashboard acts as the single source of + truth, organized into `/build-time`, `/runtime`, and `/ci` folders. +- While GitHub Actions (`/build-time` and `/ci`) use Native Integrations, + Coolify production runtime environments inject `/runtime` secrets dynamically + into memory at boot time using the Infisical CLI wrapper. - Eliminates manual script execution and plaintext storage on disk. - External contributors don't need access to Infisical to submit code. ### Version Management -- App Versioning: Controlled directly via `NEXT_PUBLIC_APP_VERSION` injected dynamically at runtime/compile-time. -- Release Automation: Dynamic multi-arch bundles and attestation manual updates are published synchronously upon successful merges to the primary main trunk. +- App Versioning: Controlled directly via `NEXT_PUBLIC_APP_VERSION` injected + dynamically at runtime/compile-time. +- Release Automation: Dynamic multi-arch bundles and attestation manual updates + are published synchronously upon successful merges to the primary main trunk. --- ## License -By contributing, you agree that your contributions will be licensed under the project's GPLv3 license. +By contributing, you agree that your contributions will be licensed under the +project's GPLv3 license. --- diff --git a/docs/EDGE_CASES_TESTS.md b/docs/EDGE_CASES_TESTS.md index d92dff28..3fb01515 100644 --- a/docs/EDGE_CASES_TESTS.md +++ b/docs/EDGE_CASES_TESTS.md @@ -1,6 +1,7 @@ # Edge Case Analysis & Test Scenarios for EzyGo Rate Limiting -This file documents edge cases and provides test scenarios to verify the rate limiting implementation works correctly. +This file documents edge cases and provides test scenarios to verify the rate +limiting implementation works correctly. ## Edge Case 1: User Refreshes Dashboard Rapidly @@ -9,13 +10,16 @@ This file documents edge cases and provides test scenarios to verify the rate li **Expected Behavior:** - First request: Triggers API calls -- Subsequent requests (within 60s): Share in-flight promises and use cached responses +- Subsequent requests (within 60s): Share in-flight promises and use cached + responses - Result: Only 3 API calls total, not 9 (3 per refresh) **Implementation:** -- LRU cache with 60s TTL deduplicates in-flight requests and caches resolved responses -- Cache key structure: `${method}:${hashedToken}:${normalizedEndpoint}:${hashedBody}` +- LRU cache with 60s TTL deduplicates in-flight requests and caches resolved + responses +- Cache key structure: + `${method}:${hashedToken}:${normalizedEndpoint}:${hashedBody}` - Token and body are SHA-256 hashed for security - Endpoint is normalized (leading slashes removed) - Body uses sentinel value `__SENTINEL_NO_BODY_VALUE__` when undefined @@ -66,14 +70,15 @@ This file documents edge cases and provides test scenarios to verify the rate li **Scenario:** Multiple users need same public data -**Cache Key Structure:** `${method}:${hashedToken}:${normalizedEndpoint}:${hashedBody}` +**Cache Key Structure:** +`${method}:${hashedToken}:${normalizedEndpoint}:${hashedBody}` - Token and body are SHA-256 hashed - Endpoint is normalized (leading slashes removed) - Body uses sentinel value `__SENTINEL_NO_BODY_VALUE__` when undefined -Different users = Different token hashes = Different cache keys -Result: NO deduplication across users +Different users = Different token hashes = Different cache keys Result: NO +deduplication across users **Reasoning:** @@ -126,18 +131,23 @@ Result: NO deduplication across users - EzyGo returns 401 Unauthorized - Circuit breaker treats 401 (and most 4xx) as NonBreakerError -- 401 responses do NOT increment the circuit breaker failure count or OPEN the circuit -- User gets logged out or re-authenticated via existing auth logic (independent of the circuit breaker) +- 401 responses do NOT increment the circuit breaker failure count or OPEN the + circuit +- User gets logged out or re-authenticated via existing auth logic (independent + of the circuit breaker) **Risk:** -- Auth failures (401) are handled outside the circuit breaker and won't by themselves OPEN the circuit -- Breaker state changes remain reserved for true API reliability issues (e.g. 5xx/429) +- Auth failures (401) are handled outside the circuit breaker and won't by + themselves OPEN the circuit +- Breaker state changes remain reserved for true API reliability issues (e.g. + 5xx/429) **Mitigation:** - Token refresh or re-authentication mechanisms handle expired tokens -- Auth errors (401/other 4xx) are handled separately from breaker-worthy API errors (5xx/429), and only 5xx/429 increment breaker failure counts +- Auth errors (401/other 4xx) are handled separately from breaker-worthy API + errors (5xx/429), and only 5xx/429 increment breaker failure counts ## Edge Case 8: Slow Network + Timeout @@ -327,22 +337,12 @@ After 3 timeouts โ†’ Circuit OPENS - Status 200 when healthy - Status 503 when circuit open -> **Note:** Detailed telemetry is only exposed in `NODE_ENV=development` or `NODE_ENV=test` environments for security reasons. +> **Note:** Detailed telemetry is only exposed in `NODE_ENV=development` or +> `NODE_ENV=test` environments for security reasons. --- -## Mobile Edge Case 11: JWE Decryption Failure - -**Scenario:** Client receives a payload encrypted with a stale or mismatched RSA key - -**Expected Behavior:** - -- Client fails to decrypt the payload -- `JweService` throws a `DecryptionException` -- `ApiService` interceptor catches the error -- User sees a "Security mismatch" error and is prompted to refresh or re-login - -## Mobile Edge Case 12: Play Integrity / App Check Failure +## Mobile Edge Case 11: Play Integrity / App Check Failure **Scenario:** User runs the app on a rooted device or an unauthorized emulator @@ -354,9 +354,10 @@ After 3 timeouts โ†’ Circuit OPENS - User sees a "Device not supported" or "Security violation" dialog - App blocks further access to sensitive attendance data -## Mobile Edge Case 13: Biometric/Credential Storage Timeout +## Mobile Edge Case 12: Biometric/Credential Storage Timeout -**Scenario:** `flutter_secure_storage` takes too long to respond (common on some Android devices) +**Scenario:** `flutter_secure_storage` takes too long to respond (common on some +Android devices) **Expected Behavior:** @@ -368,19 +369,6 @@ After 3 timeouts โ†’ Circuit OPENS ## Additional Test Scenarios -### Test 9: Mobile JWE Round-trip - -**Setup:** - -- Mock the Next.js backend to return a JWE-encrypted response -- Ensure the mobile app has the corresponding private key - -**Assertions:** - -- `ApiService` successfully decrypts the response -- Data is correctly hydrated into the UI -- No cleartext attendance data is visible in the network logs (only JWE tokens) - ### Test 10: App Check Enforcement **Setup:** diff --git a/docs/EZYGO_INTEGRATION.md b/docs/EZYGO_INTEGRATION.md index 7ff2986b..25ea4d8b 100644 --- a/docs/EZYGO_INTEGRATION.md +++ b/docs/EZYGO_INTEGRATION.md @@ -1,6 +1,7 @@ # EzyGo API Integration Guide -Complete documentation for EzyGo API rate limiting, batch fetcher implementation, and verification. +Complete documentation for EzyGo API rate limiting, batch fetcher +implementation, and verification. ## Table of Contents @@ -18,7 +19,11 @@ Complete documentation for EzyGo API rate limiting, batch fetcher implementation ## Overview -The EzyGo API integration uses a sophisticated three-layer protection system to prevent rate limiting and ensure reliable access to attendance data. This system combines request deduplication, rate limiting, and circuit breaker patterns to optimize concurrent user access while protecting both the EzyGo API and our application. +The EzyGo API integration uses a sophisticated three-layer protection system to +prevent rate limiting and ensure reliable access to attendance data. This system +combines request deduplication, rate limiting, and circuit breaker patterns to +optimize concurrent user access while protecting both the EzyGo API and our +application. **Key Features:** @@ -32,7 +37,8 @@ The EzyGo API integration uses a sophisticated three-layer protection system to ## Problem Statement -When multiple users access the dashboard simultaneously, the application makes 6 API calls per user to the EzyGo backend: +When multiple users access the dashboard simultaneously, the application makes 6 +API calls per user to the EzyGo backend: - `/myprofile` (profile data) - `/institutionuser/courses/withusers` (courses) @@ -50,7 +56,8 @@ When multiple users access the dashboard simultaneously, the application makes 6 ## Solution Architecture -The implementation uses a hybrid approach combining server-side rendering, request deduplication, rate limiting, and circuit breaker patterns. +The implementation uses a hybrid approach combining server-side rendering, +request deduplication, rate limiting, and circuit breaker patterns. ### Components @@ -127,13 +134,13 @@ Client-side hydration: **20 users hit /dashboard simultaneously:** -| Metric | Before Optimization | After Optimization | -| --- | --- | --- | -| Peak concurrent requests | 120 | 3 | -| First user load time | ~2s | ~2s (same) | -| 20th user load time | ~2s | ~6s (queued) | -| Rate limit risk | High ๐Ÿ”ด | Low ๐ŸŸข | -| Circuit breaker protection | None | Full | +| Metric | Before Optimization | After Optimization | +| -------------------------- | ------------------- | ------------------ | +| Peak concurrent requests | 120 | 3 | +| First user load time | ~2s | ~2s (same) | +| 20th user load time | ~2s | ~6s (queued) | +| Rate limit risk | High ๐Ÿ”ด | Low ๐ŸŸข | +| Circuit breaker protection | None | Full | **Result (with MAX_CONCURRENT = 3):** @@ -169,7 +176,7 @@ EzyGo API ### Fetching Dashboard Data ```typescript -import { fetchDashboardData } from '@/lib/ezygo-batch-fetcher'; +import { fetchDashboardData } from "@/lib/ezygo-batch-fetcher"; // Server component (SSR) const data = await fetchDashboardData(accessToken); @@ -179,13 +186,13 @@ const data = await fetchDashboardData(accessToken); ### Individual API Calls ```typescript -import { circuitBreaker } from '@/lib/circuit-breaker'; -import axios from '@/lib/axios'; +import { circuitBreaker } from "@/lib/circuit-breaker"; +import axios from "@/lib/axios"; // Wrap individual calls with circuit breaker const response = await circuitBreaker.execute(async () => { - return axios.get('/myprofile', { - headers: { Authorization: `Bearer ${token}` } + return axios.get("/myprofile", { + headers: { Authorization: `Bearer ${token}` }, }); }); ``` @@ -195,17 +202,17 @@ const response = await circuitBreaker.execute(async () => { Circuit breaker provides state monitoring: ```typescript -import { circuitBreaker } from '@/lib/circuit-breaker'; +import { circuitBreaker } from "@/lib/circuit-breaker"; // Check circuit state -console.log('Circuit state:', circuitBreaker.getState()); +console.log("Circuit state:", circuitBreaker.getState()); // Output: 'CLOSED' | 'OPEN' | 'HALF_OPEN' // Get failure count -console.log('Failures:', circuitBreaker['failureCount']); +console.log("Failures:", circuitBreaker["failureCount"]); // Get last failure time -console.log('Last failure:', circuitBreaker['lastFailureTime']); +console.log("Last failure:", circuitBreaker["lastFailureTime"]); ``` --- @@ -217,7 +224,8 @@ console.log('Last failure:', circuitBreaker['lastFailureTime']); **Dashboard Page** (`src/app/(protected)/dashboard/page.tsx`): - Uses `fetchDashboardData()` with full protection -- Fetches: `/institutionuser/courses/withusers`, `/attendancereports/student/detailed` +- Fetches: `/institutionuser/courses/withusers`, + `/attendancereports/student/detailed` - โœ… Request deduplication - โœ… Circuit breaker protection - โœ… Rate limited to 3 concurrent requests @@ -265,7 +273,8 @@ All client-side hooks use `axios` which routes through `/api/backend/*` proxy: #### Production Cron Setup -To configure automated daily/hourly synchronization in production, set up a cron job on your server executing the following command: +To configure automated daily/hourly synchronization in production, set up a cron +job on your server executing the following command: ```bash infisical run --path /runtime --projectId $INFISICAL_PROJECT_ID --env prod -- sh -c 'curl -sS -H "Authorization: Bearer $CRON_SECRET" "http://localhost:80/api/cron/sync"' @@ -295,9 +304,9 @@ Modify thresholds in `src/lib/circuit-breaker.ts`: ```typescript export class CircuitBreaker { - private failureThreshold = 3; // Open after 3 failures - private resetTimeout = 60000; // Stay open for 60 seconds - private halfOpenRequests = 2; // Test with 2 requests + private failureThreshold = 3; // Open after 3 failures + private resetTimeout = 60000; // Stay open for 60 seconds + private halfOpenRequests = 2; // Test with 2 requests } ``` @@ -319,11 +328,13 @@ const CACHE_TTL = 30 * 1000; // 30 seconds ## Optimization for Single-IP Deployment -For deployments where all users share a single public IP (common in institutional networks, NATs, or proxies), consider: +For deployments where all users share a single public IP (common in +institutional networks, NATs, or proxies), consider: ### Recommendation: Increase MAX_CONCURRENT -Single-IP deployments can handle more concurrent requests without triggering rate limits: +Single-IP deployments can handle more concurrent requests without triggering +rate limits: ```typescript // Default (conservative) @@ -348,8 +359,8 @@ const MAX_CONCURRENT = 15; 1. **Monitor circuit breaker state:** ```typescript - import { circuitBreaker } from '@/lib/circuit-breaker'; - console.log('State:', circuitBreaker.getState()); + import { circuitBreaker } from "@/lib/circuit-breaker"; + console.log("State:", circuitBreaker.getState()); ``` 2. **Check for rate limit errors:** @@ -438,80 +449,124 @@ const MAX_CONCURRENT = 15; ### Why calls route through the server instead of directly from the browser -The original [Bunkr](https://github.com/ABHAY-100/Bunkr/) fork sent the EzyGo bearer token directly from client-side JavaScript, making browser โ†’ EzyGo API calls. This is fastโ€”one fewer network hopโ€”but it exposes the token in the browser's Network tab and in JavaScript memory, where it can be trivially extracted by any script running on the page (XSS, browser extensions, or even a user inspecting DevTools). - -GhostClass stores the EzyGo token in an **httpOnly cookie** (AES-256-GCM encrypted at rest in the database). All EzyGo requests flow through the Next.js server at `/api/backend/*`, so the raw token never appears in browser-visible traffic. - -| | Original fork (direct client calls) | GhostClass (server proxy) | -| --- | --- | --- | -| Token visible in browser DevTools | โœ… Yes | โŒ No | -| Vulnerable to XSS token theft | โœ… Yes | โŒ No | -| Extra network hop per request | โŒ No | โœ… Yes (~10โ€“50 ms) | -| EzyGo sees one IP for all users | โŒ No (each user's IP) | โš ๏ธ Server IP (original forwarded via headers) | -| Rate limit scope | Per-user IP | Entire deployment (mitigated by proxy headers) | +The original [Bunkr](https://github.com/ABHAY-100/Bunkr/) fork sent the EzyGo +bearer token directly from client-side JavaScript, making browser โ†’ EzyGo API +calls. This is fastโ€”one fewer network hopโ€”but it exposes the token in the +browser's Network tab and in JavaScript memory, where it can be trivially +extracted by any script running on the page (XSS, browser extensions, or even a +user inspecting DevTools). + +GhostClass stores the EzyGo token in an **httpOnly cookie** (AES-256-GCM +encrypted at rest in the database). All EzyGo requests flow through the Next.js +server at `/api/backend/*`, so the raw token never appears in browser-visible +traffic. + +| | Original fork (direct client calls) | GhostClass (server proxy) | +| --------------------------------- | ----------------------------------- | ---------------------------------------------- | +| Token visible in browser DevTools | โœ… Yes | โŒ No | +| Vulnerable to XSS token theft | โœ… Yes | โŒ No | +| Extra network hop per request | โŒ No | โœ… Yes (~10โ€“50 ms) | +| EzyGo sees one IP for all users | โŒ No (each user's IP) | โš ๏ธ Server IP (original forwarded via headers) | +| Rate limit scope | Per-user IP | Entire deployment (mitigated by proxy headers) | ### Shared outbound IP & rate limit risk -Because every user's EzyGo request originates from the same server IP, the deployment acts as a single client from EzyGo's perspective. If many users load the dashboard simultaneously, GhostClass could collectively hit EzyGo's rate limits even though each individual user generates only 6 calls. +Because every user's EzyGo request originates from the same server IP, the +deployment acts as a single client from EzyGo's perspective. If many users load +the dashboard simultaneously, GhostClass could collectively hit EzyGo's rate +limits even though each individual user generates only 6 calls. -The three-layer protection system (LRU cache โ†’ rate limiter โ†’ circuit breaker) exists specifically to manage this constraint: +The three-layer protection system (LRU cache โ†’ rate limiter โ†’ circuit breaker) +exists specifically to manage this constraint: -- The **LRU cache** deduplicates identical requests within the TTL window โ€” common for users in the same institution. -- The **`MAX_CONCURRENT` cap** (default: 3) throttles outbound requests to EzyGo to a predictable rate. -- The **circuit breaker** stops all requests if EzyGo starts returning errors, preventing a thundering-herd retry storm. +- The **LRU cache** deduplicates identical requests within the TTL window โ€” + common for users in the same institution. +- The **`MAX_CONCURRENT` cap** (default: 3) throttles outbound requests to EzyGo + to a predictable rate. +- The **circuit breaker** stops all requests if EzyGo starts returning errors, + preventing a thundering-herd retry storm. ### Proxy header forwarding -To help EzyGo's rate limiter distinguish between users even when all requests share the same server outbound IP, the proxy layer (`src/app/api/backend/[...path]/route.ts`) extracts the original client identity from the incoming Next.js request and injects it into every outbound EzyGo request: +To help EzyGo's rate limiter distinguish between users even when all requests +share the same server outbound IP, the proxy layer +(`src/app/api/backend/[...path]/route.ts`) extracts the original client identity +from the incoming Next.js request and injects it into every outbound EzyGo +request: -| Outgoing header | Source (priority order via `getClientIp()`) | -| --- | --- | +| Outgoing header | Source (priority order via `getClientIp()`) | +| ----------------- | ------------------------------------------------------------------ | | `X-Forwarded-For` | `cf-connecting-ip` โ†’ `X-Real-IP` โ†’ `X-Forwarded-For` (first entry) | -| `X-Real-IP` | same value as `X-Forwarded-For` above | -| `User-Agent` | `User-Agent` from the browser request | - -These headers are omitted when the corresponding value cannot be determined (e.g., no forwarding headers set by the reverse proxy). If EzyGo respects these headers for per-IP rate limiting, each user's requests are counted against their own IP instead of the shared server IP. - -> **Security note:** `X-Forwarded-For` and `X-Real-IP` **must be treated as trusted-only headers**. They are trivially spoofable by clients unless a reverse proxy (e.g., Traefik, nginx, Cloudflare) is configured to **strip any incoming `X-Forwarded-For` / `X-Real-IP` from the client request and rebuild them based on the actual connection**. Do not assume that the leftโ€‘most entry in `X-Forwarded-For` is authentic unless it was populated by a trusted proxy; otherwise an attacker can control the value you forward to EzyGo and defeat the purpose of "original client identity". +| `X-Real-IP` | same value as `X-Forwarded-For` above | +| `User-Agent` | `User-Agent` from the browser request | + +These headers are omitted when the corresponding value cannot be determined +(e.g., no forwarding headers set by the reverse proxy). If EzyGo respects these +headers for per-IP rate limiting, each user's requests are counted against their +own IP instead of the shared server IP. + +> **Security note:** `X-Forwarded-For` and `X-Real-IP` **must be treated as +> trusted-only headers**. They are trivially spoofable by clients unless a +> reverse proxy (e.g., Traefik, nginx, Cloudflare) is configured to **strip any +> incoming `X-Forwarded-For` / `X-Real-IP` from the client request and rebuild +> them based on the actual connection**. Do not assume that the leftโ€‘most entry +> in `X-Forwarded-For` is authentic unless it was populated by a trusted proxy; +> otherwise an attacker can control the value you forward to EzyGo and defeat +> the purpose of "original client identity". > > In practice, you should: > -> - Run GhostClass behind a trusted reverse proxy that normalizes `X-Forwarded-For` / `X-Real-IP`. -> - Configure that proxy to overwrite these headers on ingress rather than passing client-supplied values through. -> - Disable or ignore this forwarding mechanism if the app is exposed directly to the internet without such a proxy. +> - Run GhostClass behind a trusted reverse proxy that normalizes +> `X-Forwarded-For` / `X-Real-IP`. +> - Configure that proxy to overwrite these headers on ingress rather than +> passing client-supplied values through. +> - Disable or ignore this forwarding mechanism if the app is exposed directly +> to the internet without such a proxy. > -> **Note:** Whether EzyGo actually uses `X-Forwarded-For` / `X-Real-IP` for rate limiting is unverified. If EzyGo ignores these headers, the shared-IP constraint remains and the three-layer protection system is the primary mitigation. +> **Note:** Whether EzyGo actually uses `X-Forwarded-For` / `X-Real-IP` for rate +> limiting is unverified. If EzyGo ignores these headers, the shared-IP +> constraint remains and the three-layer protection system is the primary +> mitigation. ### Latency impact -The server-proxy adds one extra round-trip per API call. On a well-hosted server co-located with users (e.g., a regional VPS or edge deployment), this is typically **10โ€“50 ms** per call. On a distant server, it can reach 100โ€“200 ms. SSR mitigates this for the initial dashboard load โ€” data is fetched server-side and streamed as HTML before the client hydrates. +The server-proxy adds one extra round-trip per API call. On a well-hosted server +co-located with users (e.g., a regional VPS or edge deployment), this is +typically **10โ€“50 ms** per call. On a distant server, it can reach 100โ€“200 ms. +SSR mitigates this for the initial dashboard load โ€” data is fetched server-side +and streamed as HTML before the client hydrates. If latency is unacceptable for your deployment region: 1. Deploy the Next.js server closer to your institution's geography. -2. Increase the cache TTL (`CACHE_TTL` in `ezygo-batch-fetcher.ts`) to serve more requests from cache. -3. Increase `MAX_CONCURRENT` cautiously โ€” higher values reduce queue wait time but increase rate-limit risk. +2. Increase the cache TTL (`CACHE_TTL` in `ezygo-batch-fetcher.ts`) to serve + more requests from cache. +3. Increase `MAX_CONCURRENT` cautiously โ€” higher values reduce queue wait time + but increase rate-limit risk. --- ## Egress Helpers (`src/lib/utils.server.ts`) -All server-side EzyGo API calls are routed through a tiered egress system that automatically selects the highest-priority available proxy: +All server-side EzyGo API calls are routed through a tiered egress system that +automatically selects the highest-priority available proxy: -| Priority | Env Var | Description | Secret Header | -| --- | --- | --- | --- | -| Tier 1 | `CF_PROXY_URL` | Cloudflare Worker proxy | `x-proxy-secret` via `CF_PROXY_SECRET` | -| Tier 2 | `AWS_SECONDARY_URL` | AWS Lambda + API Gateway proxy | `x-proxy-secret` via `AWS_SECONDARY_SECRET` | -| Tier 3 | `NEXT_PUBLIC_BACKEND_URL` | Direct EzyGo API (fallback) | None | +| Priority | Env Var | Description | Secret Header | +| -------- | ------------------------- | ------------------------------ | ------------------------------------------- | +| Tier 1 | `CF_PROXY_URL` | Cloudflare Worker proxy | `x-proxy-secret` via `CF_PROXY_SECRET` | +| Tier 2 | `AWS_SECONDARY_URL` | AWS Lambda + API Gateway proxy | `x-proxy-secret` via `AWS_SECONDARY_SECRET` | +| Tier 3 | `NEXT_PUBLIC_BACKEND_URL` | Direct EzyGo API (fallback) | None | Three helpers in `src/lib/utils.server.ts` implement this: ### `getEgressConfig()` -Resolves the highest-priority configured tier at call time. Returns `{ baseUrl, proxyHeaders }`. Used internally by the other two helpers and by the batch fetcher. +Resolves the highest-priority configured tier at call time. Returns +`{ baseUrl, proxyHeaders }`. Used internally by the other two helpers and by the +batch fetcher. ```typescript -import { getEgressConfig } from '@/lib/utils.server'; +import { getEgressConfig } from "@/lib/utils.server"; const { baseUrl, proxyHeaders } = getEgressConfig(); // baseUrl: "https://ezygo-proxy.user.workers.dev/api/v1/salt" (tier 1) @@ -520,12 +575,14 @@ const { baseUrl, proxyHeaders } = getEgressConfig(); ### `egressFetch(endpoint, init?)` -Thin `fetch` wrapper. Resolves the egress tier, builds the full URL, and injects the proxy secret header automatically. Use for API routes that call EzyGo via `fetch`. +Thin `fetch` wrapper. Resolves the egress tier, builds the full URL, and injects +the proxy secret header automatically. Use for API routes that call EzyGo via +`fetch`. ```typescript -import { egressFetch } from '@/lib/utils.server'; +import { egressFetch } from "@/lib/utils.server"; -const res = await egressFetch('myprofile', { +const res = await egressFetch("myprofile", { headers: { Authorization: `Bearer ${token}` }, }); ``` @@ -534,12 +591,14 @@ const res = await egressFetch('myprofile', { ### `egressAxios` -Server-only Axios instance with a request interceptor that resolves the egress tier per-request. Use for API routes that prefer Axios (error handling, response transforms, etc.). +Server-only Axios instance with a request interceptor that resolves the egress +tier per-request. Use for API routes that prefer Axios (error handling, response +transforms, etc.). ```typescript -import { egressAxios } from '@/lib/utils.server'; +import { egressAxios } from "@/lib/utils.server"; -const { data } = await egressAxios.get('user', { +const { data } = await egressAxios.get("user", { headers: { Authorization: `Bearer ${token}` }, }); ``` @@ -548,33 +607,40 @@ const { data } = await egressAxios.get('user', { ### Client-facing backend proxy -The client-facing proxy route (`src/app/api/backend/[...path]/route.ts`) implements its own CF โ†’ AWS โ†’ Direct failover chain with retry semantics. It does **not** use the shared helpers because it needs per-tier error handling and automatic fallback between tiers within a single request. +The client-facing proxy route (`src/app/api/backend/[...path]/route.ts`) +implements its own CF โ†’ AWS โ†’ Direct failover chain with retry semantics. It +does **not** use the shared helpers because it needs per-tier error handling and +automatic fallback between tiers within a single request. ### Batch fetcher -The batch fetcher (`src/lib/ezygo-batch-fetcher.ts`) calls `getEgressConfig()` directly to resolve the egress tier before making rate-limited fetch calls. +The batch fetcher (`src/lib/ezygo-batch-fetcher.ts`) calls `getEgressConfig()` +directly to resolve the egress tier before making rate-limited fetch calls. --- ## Mobile App Integration -GhostClass Mobile implements the same three-layer protection system as the web application to ensure parity and reliability. +GhostClass Mobile implements the same three-layer protection system as the web +application to ensure parity and reliability. ### Implementation Details -| Layer | Web Implementation | Mobile Implementation | -| :--- | :--- | :--- | -| **1. Deduplication** | LRU Cache (`lru-cache`) | In-memory map caching & `AsyncValue` (Riverpod) | -| **2. Rate Limiting** | `MAX_CONCURRENT` Queue | `EzygoBatchFetcher` (3 concurrent max) | -| **3. Circuit Breaker** | `CircuitBreaker` class | `OutageProvider` + `CircuitBreaker` mixin | +| Layer | Web Implementation | Mobile Implementation | +| :--------------------- | :---------------------- | :---------------------------------------------- | +| **1. Deduplication** | LRU Cache (`lru-cache`) | In-memory map caching & `AsyncValue` (Riverpod) | +| **2. Rate Limiting** | `MAX_CONCURRENT` Queue | `EzygoBatchFetcher` (3 concurrent max) | +| **3. Circuit Breaker** | `CircuitBreaker` class | `OutageProvider` + `CircuitBreaker` mixin | ### Security Differences Unlike the web app which uses `httpOnly` cookies, the mobile app: -1. **Authenticates** with a `MOBILE_API_SECRET` and JWE-encrypted payload. -2. **Stores tokens** in the hardware-backed **SecureStorage** (Keystore/Keychain). -3. **Calls EzyGo directly** for attendance data to minimize latency, while using the GhostClass backend for security nonces and session provisioning. +1. **Authenticates** with Firebase App Check. +2. **Stores tokens** in the hardware-backed **SecureStorage** + (Keystore/Keychain). +3. **Calls EzyGo directly** for attendance data to minimize latency, while using + the GhostClass backend for security nonces and session provisioning. --- @@ -585,4 +651,4 @@ For implementation details and code examples, see: - `src/lib/ezygo-batch-fetcher.ts` - `src/app/(protected)/dashboard/page.tsx` - `mobile/lib/logic/ezygo_batch_fetcher.dart` (Mobile implementation) -- `mobile/lib/services/api_service.dart` (Mobile JWE proxying) +- `mobile/lib/services/api_service.dart` (Mobile API proxying) diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index 65bc165d..9067753e 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -1,19 +1,23 @@ # Versioning System -GhostClass follows a modified [Semantic Versioning 2.0.0](https://semver.org/) system with a **rollover mechanism** for patch and minor versions. +GhostClass follows a modified [Semantic Versioning 2.0.0](https://semver.org/) +system with a **rollover mechanism** for patch and minor versions. ## Version Format: `X.Y.Z` -- **X (Major)**: Significant structural changes, breaking API changes, or major project milestones. Can exceed `9`. -- **Y (Minor)**: New features or significant enhancements. Rollover at `9` (increments X and resets Y to 0). -- **Z (Patch)**: Bug fixes, security patches, or minor documentation updates. Rollover at `9` (increments Y and resets Z to 0). +- **X (Major)**: Significant structural changes, breaking API changes, or major + project milestones. Can exceed `9`. +- **Y (Minor)**: New features or significant enhancements. Rollover at `9` + (increments X and resets Y to 0). +- **Z (Patch)**: Bug fixes, security patches, or minor documentation updates. + Rollover at `9` (increments Y and resets Z to 0). ### Rollover Examples -| Current Version | Bump Type | New Version | Reason | -| --- | --- | --- | --- | -| `1.5.5` | Patch | `1.5.6` | Normal increment | -| `1.5.9` | Patch | `1.6.0` | Patch rollover (Z=9 โ†’ Y+1, Z=0) | -| `1.9.9` | Patch | `2.0.0` | Minor rollover (Y=9, Z=9 โ†’ X+1, Y=0, Z=0) | -| `10.3.5` | Minor | `10.4.0` | Normal increment | -| `10.9.2` | Minor | `11.0.0` | Minor rollover (Y=9 โ†’ X+1, Y=0) | +| Current Version | Bump Type | New Version | Reason | +| --------------- | --------- | ----------- | ----------------------------------------- | +| `1.5.5` | Patch | `1.5.6` | Normal increment | +| `1.5.9` | Patch | `1.6.0` | Patch rollover (Z=9 โ†’ Y+1, Z=0) | +| `1.9.9` | Patch | `2.0.0` | Minor rollover (Y=9, Z=9 โ†’ X+1, Y=0, Z=0) | +| `10.3.5` | Minor | `10.4.0` | Normal increment | +| `10.9.2` | Minor | `11.0.0` | Minor rollover (Y=9 โ†’ X+1, Y=0) | diff --git a/e2e/homepage.spec.ts b/e2e/homepage.spec.ts index 1dfa20c2..ca1a1890 100644 --- a/e2e/homepage.spec.ts +++ b/e2e/homepage.spec.ts @@ -1,24 +1,24 @@ -import { test, expect } from '@playwright/test' +import { expect, test } from "@playwright/test"; + +test.describe("Homepage", () => { + test("should load homepage successfully", async ({ page }) => { + const response = await page.goto("/"); -test.describe('Homepage', () => { - test('should load homepage successfully', async ({ page }) => { - const response = await page.goto('/') - // Check that the page loads with a successful status code - expect(response).not.toBeNull() - expect(response!.status()).toBeLessThan(400) - + expect(response).not.toBeNull(); + expect(response!.status()).toBeLessThan(400); + // Verify page has content - const bodyContent = await page.locator('body').textContent() - expect(bodyContent).toBeTruthy() - expect(bodyContent!.length).toBeGreaterThan(0) - }) + const bodyContent = await page.locator("body").textContent(); + expect(bodyContent).toBeTruthy(); + expect(bodyContent!.length).toBeGreaterThan(0); + }); + + test("should have a valid HTML structure", async ({ page }) => { + await page.goto("/"); - test('should have a valid HTML structure', async ({ page }) => { - await page.goto('/') - // Verify basic HTML structure exists - await expect(page.locator('html')).toBeVisible() - await expect(page.locator('body')).toBeVisible() - }) -}) + await expect(page.locator("html")).toBeVisible(); + await expect(page.locator("body")).toBeVisible(); + }); +}); diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 0de6616d..6df83186 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -1,27 +1,27 @@ -import { test, expect } from '@playwright/test' +import { expect, test } from "@playwright/test"; + +test.describe("Smoke Tests", () => { + test("should respond to requests", async ({ page }) => { + const response = await page.goto("/"); -test.describe('Smoke Tests', () => { - test('should respond to requests', async ({ page }) => { - const response = await page.goto('/') - // Check that the server responds successfully - expect(response).not.toBeNull() - expect(response!.status()).toBeLessThan(400) - }) + expect(response).not.toBeNull(); + expect(response!.status()).toBeLessThan(400); + }); + + test("should render HTML content", async ({ page }) => { + await page.goto("/"); - test('should render HTML content', async ({ page }) => { - await page.goto('/') - // Verify the page has HTML content - const bodyContent = await page.locator('body').textContent() - expect(bodyContent).toBeTruthy() - }) + const bodyContent = await page.locator("body").textContent(); + expect(bodyContent).toBeTruthy(); + }); + + test("should have a non-empty page title", async ({ page }) => { + await page.goto("/"); - test('should have a non-empty page title', async ({ page }) => { - await page.goto('/') - // Verify the page has a title - const title = await page.title() - expect(title.length).toBeGreaterThan(0) - }) -}) + const title = await page.title(); + expect(title.length).toBeGreaterThan(0); + }); +}); diff --git a/eslint.config.mjs b/eslint.config.mjs index fc55809e..21f0b10f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,46 +11,67 @@ import unusedImports from "eslint-plugin-unused-imports"; export default [ { - ignores: [".next/*", "node_modules/*", "lint-staged.config.js", "public/sw.js", "Temp/*", "mobile/*"] + ignores: [ + ".next/*", + "node_modules/*", + "lint-staged.config.js", + "public/sw.js", + "Temp/*", + "mobile/*", + "**/__tests__/**", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test.js", + "**/*.test.jsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + ], }, js.configs.recommended, ...tseslint.configs.recommended, security.configs.recommended, // Injects security rules - sonarjs.configs.recommended, // Injects complexity/quality rules + sonarjs.configs.recommended, // Injects complexity/quality rules { ...pluginReact.configs.flat.recommended, languageOptions: { ...pluginReact.configs.flat.recommended.languageOptions, globals: { ...globals.browser, - ...globals.node - } - } + ...globals.node, + }, + }, }, { plugins: { "@next/next": pluginNext, "react-hooks": pluginReactHooks, - "unused-imports": unusedImports // Registers unused-imports plugin + "unused-imports": unusedImports, // Registers unused-imports plugin }, rules: { ...pluginNext.configs.recommended.rules, ...pluginReactHooks.configs.recommended.rules, - + // QUALITY & SECURITY LOCKDOWN "react/react-in-jsx-scope": "off", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/ban-ts-comment": "warn", - + // DEAD CODE REMOVAL "no-unused-vars": "off", // Handled by unused-imports "@typescript-eslint/no-unused-vars": "warn", "unused-imports/no-unused-imports": "warn", "unused-imports/no-unused-vars": [ "warn", - { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_", + }, ], - + // SONARJS TWEAKS "sonarjs/cognitive-complexity": ["warn", 15], "sonarjs/assertions-in-tests": "warn", @@ -78,13 +99,13 @@ export default [ "sonarjs/table-header": "warn", "sonarjs/unused-import": "warn", "sonarjs/use-type-alias": "warn", - "sonarjs/void-use": "warn" + "sonarjs/void-use": "warn", }, settings: { react: { - version: "detect" - } - } + version: "detect", + }, + }, }, // --- FIX FOR SCRIPTS --- { @@ -94,32 +115,32 @@ export default [ "@typescript-eslint/no-var-requires": "off", "no-console": "off", "security/detect-non-literal-require": "off", // Usually safe in build scripts - "security/detect-non-literal-fs-filename": "off" - } + "security/detect-non-literal-fs-filename": "off", + }, }, // --- TEST FILE AUDIT SETTINGS --- { files: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], rules: { // RELAX: Tests often have similar structures for different cases - "sonarjs/no-identical-functions": "off", + "sonarjs/no-identical-functions": "off", "sonarjs/no-clear-text-protocols": "off", "sonarjs/no-hardcoded-ip": "off", - + // TIGHTEN: Ensure tests are actually testing something "sonarjs/assertions-in-tests": "error", "sonarjs/no-exclusive-tests": "error", // Prevents pushing 'it.only' or 'describe.only' - + // RELAX: Tests often use 'any' for quick mocking "@typescript-eslint/no-explicit-any": "off", - + // SECURITY: Ensure no real secrets are being used in mocks "security/detect-no-csrf-before-method": "off", "security/detect-object-injection": "off", - + // RELAX: Structural test nesting and inline aliases "sonarjs/no-nested-functions": "off", - "sonarjs/use-type-alias": "off" - } - } -]; \ No newline at end of file + "sonarjs/use-type-alias": "off", + }, + }, +]; diff --git a/lint-staged.config.js b/lint-staged.config.js index 82b01a89..9af252ec 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,9 +1,9 @@ module.exports = { // 1. Type Check: Runs on the whole project (function form = ignore file args, run once) - '**/*.{ts,tsx}': () => 'node node_modules/typescript/bin/tsc --noEmit', + "**/*.{ts,tsx}": () => "node node_modules/typescript/bin/tsc --noEmit", // 2. Linting: Runs on the whole project (function form = ignore file args, run once) - '**/*.{js,jsx,ts,tsx}': () => { - return 'node node_modules/eslint/bin/eslint.js . --fix'; + "**/*.{js,jsx,ts,tsx}": () => { + return "node node_modules/eslint/bin/eslint.js . --fix"; }, -}; \ No newline at end of file +}; diff --git a/mobile/README.md b/mobile/README.md index bb613176..5dd007b8 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,14 +1,17 @@ # GhostClass Mobile ![Flutter](https://img.shields.io/badge/Flutter-3.44.0-02569B?style=for-the-badge&logo=flutter&logoColor=white) -![Dart](https://img.shields.io/badge/Dart-3.12.0-0175C2?style=for-the-badge&logo=dart&logoColor=white) +![Dart](https://img.shields.io/badge/Dart-3.11.4-0175C2?style=for-the-badge&logo=dart&logoColor=white) ![Android](https://img.shields.io/badge/Android-10+-3DDC84?style=for-the-badge&logo=android&logoColor=black) ![iOS](https://img.shields.io/badge/iOS-13+-000000?style=for-the-badge&logo=apple&logoColor=white) ![License](https://img.shields.io/badge/License-GPL%20v3-blue?style=for-the-badge) ## Overview -GhostClass Mobile is a secure, zero-trust Flutter application that communicates with the GhostClass backend API. Every network request is encrypted with JWE (JSON Web Encryption), device integrity is attested by Firebase App Check with Play Integrity (Android) and DeviceCheck (iOS), and all credentials are stored in hardware-backed secure storage โ€” never in plain SharedPreferences. +GhostClass Mobile is a secure, zero-trust Flutter application that communicates +with the GhostClass backend API. Device integrity is attested by Firebase App +Check with Play Integrity (Android) and DeviceCheck (iOS), and all credentials +are stored in hardware-backed secure storage โ€” never in plain SharedPreferences. ## ๐Ÿ“ฒ Download @@ -22,80 +25,85 @@ GhostClass Mobile is a secure, zero-trust Flutter application that communicates ## โœจ Features -- **Dashboard** ๐Ÿ“Š โ€” Attendance overview with stats grid, progress ring, trend chart, and per-course bunk calculator -- **Course Cards** ๐Ÿƒ โ€” Same hatched-pattern modified attendance visualization as the web, with disable/enable toggle +- **Dashboard** ๐Ÿ“Š โ€” Attendance overview with stats grid, progress ring, trend + chart, and per-course bunk calculator +- **Course Cards** ๐Ÿƒ โ€” Same hatched-pattern modified attendance visualization + as the web, with disable/enable toggle - **Attendance Calendar** ๐Ÿ“… โ€” Day-by-day attendance history calendar - **Manual Tracker** ๐Ÿ‘ป โ€” Track wrongly marked absences until they're corrected -- **Scores** ๐Ÿ“‹ โ€” Exam and assignment results grouped by course, with per-question breakdown +- **Scores** ๐Ÿ“‹ โ€” Exam and assignment results grouped by course, with + per-question breakdown - **Leave Applications** ๐Ÿ“ โ€” View leave application status (sourced from EzyGo) - **Notifications** ๐Ÿ”” โ€” In-app notification center -- **Help & Contact** ๐Ÿ“š โ€” Built-in help docs (rendered Markdown) and contact form +- **Help & Contact** ๐Ÿ“š โ€” Built-in help docs (rendered Markdown) and contact + form - **Dark / Light Theme** ๐ŸŒ“ โ€” System-aware theme with manual override -- **Zero-Trust Security** ๐Ÿ” โ€” App Check, Play Integrity, JWE encryption, SecureStorage, anti-tapjacking +- **Zero-Trust Security** ๐Ÿ” โ€” App Check, Play Integrity, SecureStorage, + anti-tapjacking ## ๐Ÿ› ๏ธ Tech Stack ### Framework & Language -| Package | Version | Purpose | -| :--- | :--- | :--- | -| **Flutter** | 3.44.0 | Cross-platform UI framework | -| **Dart** | 3.12.0 | Language | +| Package | Version | Purpose | +| :---------- | :------ | :-------------------------- | +| **Flutter** | 3.44.0 | Cross-platform UI framework | +| **Dart** | 3.11.4 | Language | ### State Management -| Package | Version | Purpose | -| :--- | :--- | :--- | -| `flutter_riverpod` | ^3.3.1 | Reactive state management | -| `riverpod_annotation` + `build_runner` | ^4.0.2 | Code-gen providers | +| Package | Version | Purpose | +| :------------------------------------- | :------ | :------------------------ | +| `flutter_riverpod` | ^3.3.1 | Reactive state management | +| `riverpod_annotation` + `build_runner` | ^4.0.2 | Code-gen providers | ### Networking & Backend -| Package | Version | Purpose | -| :--- | :--- | :--- | -| `dio` | ^5.9.2 | HTTP client with interceptors | -| `supabase_flutter` | ^2.12.2 | Supabase auth + realtime | +| Package | Version | Purpose | +| :----------------- | :------ | :---------------------------- | +| `dio` | ^5.9.2 | HTTP client with interceptors | +| `supabase_flutter` | ^2.12.2 | Supabase auth + realtime | ### Navigation -| Package | Version | Purpose | -| :--- | :--- | :--- | +| Package | Version | Purpose | +| :---------- | :------ | :------------------ | | `go_router` | ^17.1.0 | Declarative routing | ### Security -| Package | Version | Purpose | -| :--- | :--- | :--- | -| `firebase_core` | ^4.7.0 | Firebase SDK | -| `firebase_app_check` | ^0.4.3 | Device integrity & API protection | +| Package | Version | Purpose | +| :----------------------- | :------ | :--------------------------------- | +| `firebase_core` | ^4.7.0 | Firebase SDK | +| `firebase_app_check` | ^0.4.3 | Device integrity & API protection | | `flutter_secure_storage` | ^10.0.0 | Hardware-backed credential storage | -| `jose` + `pointycastle` | ^0.3.5 / ^3.9.1 | JWE key parsing + RSA operations | -| `encrypt` | ^5.0.3 | AES-256 symmetric encryption | +| `pointycastle` | ^3.9.1 | Certificate & ASN1 parsing | +| `encrypt` | ^5.0.3 | AES-256 symmetric encryption | ### UI & Charts -| Package | Version | Purpose | -| :--- | :--- | :--- | -| `lucide_icons` | ^0.257.0 | Icon set (matches web) | -| `google_fonts` | ^8.0.2 | Typography | -| `flutter_animate` | ^4.5.2 | Declarative animations | -| `fl_chart` | ^1.2.0 | Attendance trend charts | -| `flutter_markdown_plus` | ^1.0.7 | Help page Markdown renderer | +| Package | Version | Purpose | +| :---------------------- | :-------- | :-------------------------- | +| `lucide_icons_flutter` | ^3.1.14+1 | Icon set (matches web) | +| `google_fonts` | ^8.0.2 | Typography | +| `flutter_animate` | ^4.5.2 | Declarative animations | +| `fl_chart` | ^1.2.0 | Attendance trend charts | +| `flutter_markdown_plus` | ^1.0.7 | Help page Markdown renderer | ### Monitoring -| Package | Version | Purpose | -| :--- | :--- | :--- | +| Package | Version | Purpose | +| :------------------------------ | :------ | :--------------------------- | | `sentry_flutter` + `sentry_dio` | ^9.18.0 | Error tracking + performance | ### Utilities -| Package | Version | Purpose | -| :--- | :--- | :--- | -| `shared_preferences` | ^2.5.5 | Non-sensitive local preferences | -| `intl` | ^0.20.2 | Date/number formatting | -| `url_launcher` | ^6.3.2 | Open external links | -| `image_picker` | ^1.1.2 | Profile photo selection | +| Package | Version | Purpose | +| :------------------- | :------ | :------------------------------ | +| `shared_preferences` | ^2.5.5 | Non-sensitive local preferences | +| `intl` | ^0.20.2 | Date/number formatting | +| `url_launcher` | ^6.3.2 | Open external links | +| `image_picker` | ^1.1.2 | Profile photo selection | ## ๐Ÿ“ Project Structure @@ -168,9 +176,7 @@ mobile/ โ”‚ โ”‚ โ”œโ”€โ”€ ghostclass_screen.dart # GhostClass info screen โ”‚ โ”‚ โ””โ”€โ”€ static_screen.dart # Legal/static content โ”‚ โ”œโ”€โ”€ services/ -โ”‚ โ”‚ โ”œโ”€โ”€ api_service.dart # Dio HTTP client + JWE interceptor -โ”‚ โ”‚ โ”œโ”€โ”€ jwe_service.dart # JWE key fetch + encrypt/decrypt -โ”‚ โ”‚ โ”œโ”€โ”€ jwe_interceptor.dart # Dio interceptor for JWE wrapping +โ”‚ โ”‚ โ”œโ”€โ”€ api_service.dart # Dio HTTP client & API egress โ”‚ โ”‚ โ”œโ”€โ”€ secure_storage.dart # flutter_secure_storage wrapper โ”‚ โ”‚ โ”œโ”€โ”€ security_guard.dart # App Check + Play Integrity check โ”‚ โ”‚ โ”œโ”€โ”€ stealth_headers_service.dart # Anti-fingerprinting header injection @@ -210,8 +216,9 @@ mobile/ ### Prerequisites -- **Flutter SDK** โ€” 3.44.0 ([install](https://docs.flutter.dev/get-started/install)) -- **Dart SDK** โ€” 3.12.0 (bundled with Flutter) +- **Flutter SDK** โ€” 3.44.0 + ([install](https://docs.flutter.dev/get-started/install)) +- **Dart SDK** โ€” 3.11.4 (bundled with Flutter) - **Android Studio / Xcode** โ€” for emulator/simulator - **Firebase CLI** โ€” for App Check configuration - **A GhostClass backend** โ€” see the [root README](../README.md) for web setup @@ -232,12 +239,13 @@ flutter pub get flutter run ``` -> **Note:** The app will not build without `lib/config/app_secrets.dart`. -> See the **Secrets Setup** section below. +> **Note:** The app will not build without `lib/config/app_secrets.dart`. See +> the **Secrets Setup** section below. ### Secrets Setup -`lib/config/app_secrets.dart` is gitignored because it contains sensitive keys. Create it by copying the example: +`lib/config/app_secrets.dart` is gitignored because it contains sensitive keys. +Create it by copying the example: ```bash cp lib/config/app_secrets.dart.example lib/config/app_secrets.dart @@ -258,24 +266,35 @@ class AppSecrets { } ``` -> **Tip:** Securely manage and inject these values into CI builds via Infisical Native Integrations from the `/ci` folder without executing local sync scripts. +> **Tip:** Securely manage and inject these values into CI builds via Infisical +> Native Integrations from the `/ci` folder without executing local sync +> scripts. ### Firebase Setup -1. Create a Firebase project at [console.firebase.google.com](https://console.firebase.google.com) +1. Create a Firebase project at + [console.firebase.google.com](https://console.firebase.google.com) 2. Enable **App Check** with: - - Android: **Play Integrity** provider (production) / **Debug** provider (dev) + - Android: **Play Integrity** provider (production) / **Debug** provider + (dev) - iOS: **DeviceCheck** provider (production) / **Debug** provider (dev) 3. Download and place the config files: - `android/app/google-services.json` โ† gitignored - `ios/Runner/GoogleService-Info.plist` โ† gitignored 4. Run `flutterfire configure` if regenerating `firebase_options.dart` -> **๐Ÿ”’ Production CI/CD Injection Note**: Because `google-services.json` and `GoogleService-Info.plist` contain project identifiers and configurations, they are strictly excluded from version control to protect production infrastructure. For automated builds, store these files as Base64 repository secrets and dynamically decode/inject them during the CI/CD pipeline initialization phase. +> **๐Ÿ”’ Production CI/CD Injection Note**: Because `google-services.json` and +> `GoogleService-Info.plist` contain project identifiers and configurations, +> they are strictly excluded from version control to protect production +> infrastructure. For automated builds, store these files as Base64 repository +> secrets and dynamically decode/inject them during the CI/CD pipeline +> initialization phase. ### Running Tests -GhostClass Mobile maintains a comprehensive automated testing suite covering logic parity, cryptographic operations, state management providers, and UI interactions. +GhostClass Mobile maintains a comprehensive automated testing suite covering +logic parity, cryptographic operations, state management providers, and UI +interactions. ```bash # Execute unit and widget tests @@ -287,9 +306,16 @@ flutter test --coverage #### ๐Ÿ›ก๏ธ Testing & CI/CD Strategy -- **Minimum Module Coverage**: All core logic and data model files enforce a minimum **50% test coverage threshold**, with mission-critical modules (such as the bunk calculation algorithm and encryption services) maintained at **100% coverage**. -- **Automated CI/CD Quality Gates**: Mandatory GitHub Actions workflows validate coverage metrics on all pull requests and pushes, requiring an aggregate **80% total code coverage** before code can be merged. -- **Resilient Exception Simulations**: Tests actively simulate extreme network dropouts, plugin failures, and asynchronous edge cases using `mocktail` to verify robustness. +- **Minimum Module Coverage**: All core logic and data model files enforce a + minimum **50% test coverage threshold**, with mission-critical modules (such + as the bunk calculation algorithm and encryption services) maintained at + **100% coverage**. +- **Automated CI/CD Quality Gates**: Mandatory GitHub Actions workflows validate + coverage metrics on all pull requests and pushes, requiring an aggregate **80% + total code coverage** before code can be merged. +- **Resilient Exception Simulations**: Tests actively simulate extreme network + dropouts, plugin failures, and asynchronous edge cases using `mocktail` to + verify robustness. ### Building @@ -304,29 +330,33 @@ flutter build appbundle --release flutter build ios --release ``` -> **๐Ÿ”‘ Production Signing Note**: Release builds require valid cryptographically secure production keys. Ensure `android/key.properties` and your associated keystore (`.jks` / `.keystore`) fileโ€”both of which are **gitignored**โ€”are placed in the `android/` directory before assembling production artifacts. For automated builds, these credentials must be injected dynamically via secure CI/CD environment secrets. +> **๐Ÿ”‘ Production Signing Note**: Release builds require valid cryptographically +> secure production keys. Ensure `android/key.properties` and your associated +> keystore (`.jks` / `.keystore`) fileโ€”both of which are **gitignored**โ€”are +> placed in the `android/` directory before assembling production artifacts. For +> automated builds, these credentials must be injected dynamically via secure +> CI/CD environment secrets. ## ๐Ÿ”’ Security Architecture GhostClass Mobile implements a zero-trust security model: -| Layer | Mechanism | -| :--- | :--- | -| **Device Attestation** | Firebase App Check โ†’ Play Integrity (Android) / DeviceCheck (iOS) | -| **Network Encryption** | Every API request/response wrapped in JWE (RSA-OAEP + AES-256-GCM) | -| **Credential Storage** | `flutter_secure_storage` (Android Keystore / iOS Keychain) | -| **Anti-Tapjacking** | `FLAG_SECURE` on Android `MainActivity` | -| **Stealth Headers** | Custom header injection to reduce fingerprinting during upstream data fetches | -| **Token Lifecycle** | Upstream bearer token (EzyGo) encrypted at rest; auto-refreshed on expiry | -| **Unified Auth** | Firebase App Check used in place of cookie-based CSRF for API requests | +| Layer | Mechanism | +| :--------------------- | :---------------------------------------------------------------------------- | +| **Device Attestation** | Firebase App Check โ†’ Play Integrity (Android) / DeviceCheck (iOS) | +| **Credential Storage** | `flutter_secure_storage` (Android Keystore / iOS Keychain) | +| **Anti-Tapjacking** | `FLAG_SECURE` on Android `MainActivity` | +| **Stealth Headers** | Custom header injection to reduce fingerprinting during upstream data fetches | +| **Token Lifecycle** | Upstream bearer token (EzyGo) encrypted at rest; auto-refreshed on expiry | +| **Unified Auth** | Firebase App Check used in place of cookie-based CSRF for API requests | ## ๐Ÿ“ฑ Platform Requirements -| Platform | Minimum | Target | Compile | -| :--- | :--- | :--- | :--- | -| Android | API 29 (Android 10) | API 35 (Android 15) | API 36 | -| iOS | iOS 13 | latest | latest Xcode | +| Platform | Minimum | Target | Compile | +| :------- | :------------------ | :------------------ | :----------- | +| Android | API 29 (Android 10) | API 35 (Android 15) | API 36 | +| iOS | iOS 13 | latest | latest Xcode | --- -*Part of the [GhostClass](../README.md) monorepo.* +_Part of the [GhostClass](../README.md) monorepo._ diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts index c0fedf1d..3bd4a4f9 100644 --- a/mobile/android/app/build.gradle.kts +++ b/mobile/android/app/build.gradle.kts @@ -17,7 +17,7 @@ if (keystorePropertiesFile.exists()) { android { namespace = "com.devakesu.apps.ghostclass" - compileSdk = flutter.compileSdkVersion + compileSdk = 37 ndkVersion = flutter.ndkVersion compileOptions { diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml index d234ded7..29256cac 100644 --- a/mobile/android/app/src/debug/AndroidManifest.xml +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -1,10 +1,10 @@ - - + xmlns:tools="http://schemas.android.com/tools"> + + - + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 0d13629d..fccfe552 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -1,74 +1,74 @@ - - - - - - + xmlns:tools="http://schemas.android.com/tools"> + + + - - - - - - - - - - - - - + - In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f3..6d869b1a 100644 --- a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - + - - + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml index 304732f8..f9c251d3 100644 --- a/mobile/android/app/src/main/res/drawable/launch_background.xml +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,12 @@ - + - - + + diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml index 06952be7..9e107b7a 100644 --- a/mobile/android/app/src/main/res/values-night/styles.xml +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -1,18 +1,18 @@ - - - + + - + This Theme is only used starting with V2 of Flutter's Android embedding. --> + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml index cb1ef880..c52005fa 100644 --- a/mobile/android/app/src/main/res/values/styles.xml +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - + + - + This Theme is only used starting with V2 of Flutter's Android embedding. --> + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml index 399f6981..7f4b94bc 100644 --- a/mobile/android/app/src/profile/AndroidManifest.xml +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -1,7 +1,7 @@ - - + + diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index e4ef43fb..a9db1155 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/mobile/android/gradlew b/mobile/android/gradlew index 9d82f789..249efbb0 100644 --- a/mobile/android/gradlew +++ b/mobile/android/gradlew @@ -1,74 +1,128 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright ยฉ 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions ยซ$varยป, ยซ${var}ยป, ยซ${var:-default}ยป, ยซ${var+SET}ยป, +# ยซ${var#prefix}ยป, ยซ${var%suffix}ยป, and ยซ$( cmd )ยป; +# * compound commands having a testable exit status, especially ยซcaseยป; +# * various built-in commands including ยซcommandยป, ยซsetยป, and ยซulimitยป. +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -77,84 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/mobile/android/gradlew.bat b/mobile/android/gradlew.bat index aec99730..8508ef68 100644 --- a/mobile/android/gradlew.bat +++ b/mobile/android/gradlew.bat @@ -1,90 +1,82 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants +if exist "%JAVA_EXE%" goto execute -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/mobile/android/settings.gradle.kts b/mobile/android/settings.gradle.kts index 87ee0386..c17b18f9 100644 --- a/mobile/android/settings.gradle.kts +++ b/mobile/android/settings.gradle.kts @@ -23,9 +23,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.2" apply false + id("com.android.application") version "9.3.1" apply false + id("org.jetbrains.kotlin.android") version "2.4.10" apply false + id("com.google.gms.google-services") version "4.5.0" apply false } include(":app") diff --git a/mobile/firebase.json b/mobile/firebase.json deleted file mode 100644 index 4bf464fc..00000000 --- a/mobile/firebase.json +++ /dev/null @@ -1 +0,0 @@ -{"flutter":{"platforms":{"android":{"default":{"projectId":"devakesu-ghostclass","appId":"1:424804867878:android:df401041d564c22b21abe7","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"devakesu-ghostclass","configurations":{"android":"1:424804867878:android:df401041d564c22b21abe7","ios":"1:424804867878:ios:d132bb8be987f52d21abe7"}}}}}} \ No newline at end of file diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d0d98aa1..ac6e3b13 100644 --- a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1 +1,155 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@3x.png", + "scale": "3x" + }, + { + "size": "57x57", + "idiom": "iphone", + "filename": "Icon-App-57x57@1x.png", + "scale": "1x" + }, + { + "size": "57x57", + "idiom": "iphone", + "filename": "Icon-App-57x57@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@1x.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@1x.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "50x50", + "idiom": "ipad", + "filename": "Icon-App-50x50@1x.png", + "scale": "1x" + }, + { + "size": "50x50", + "idiom": "ipad", + "filename": "Icon-App-50x50@2x.png", + "scale": "2x" + }, + { + "size": "72x72", + "idiom": "ipad", + "filename": "Icon-App-72x72@1x.png", + "scale": "1x" + }, + { + "size": "72x72", + "idiom": "ipad", + "filename": "Icon-App-72x72@2x.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@1x.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon-App-83.5x83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon-App-1024x1024@1x.png", + "scale": "1x" + } + ], + "info": { "version": 1, "author": "xcode" } +} diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2f..781d7cdc 100644 --- a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,23 @@ { - "images" : [ + "images": [ { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" + "idiom": "universal", + "filename": "LaunchImage.png", + "scale": "1x" }, { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" + "idiom": "universal", + "filename": "LaunchImage@2x.png", + "scale": "2x" }, { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" + "idiom": "universal", + "filename": "LaunchImage@3x.png", + "scale": "3x" } ], - "info" : { - "version" : 1, - "author" : "xcode" + "info": { + "version": 1, + "author": "xcode" } } diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md index 45593126..09c4940e 100644 --- a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -1,5 +1,8 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing +the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with +`open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project +Navigator and dropping in the desired images. diff --git a/mobile/lib/config/app_config.dart b/mobile/lib/config/app_config.dart index e90aa4d6..2ceca6e2 100644 --- a/mobile/lib/config/app_config.dart +++ b/mobile/lib/config/app_config.dart @@ -55,6 +55,10 @@ class AppConfig { : AppSecrets.ghostclassApiUrlProd, ); + /// Default network timeout duration (45s debug / 30s release). + static Duration get defaultTimeout => + kDebugMode ? const Duration(seconds: 45) : const Duration(seconds: 30); + /// The EzyGo authentication root. static String get ezygoAuthUrl => _d(AppSecrets.ezygoAuthUrl); @@ -70,13 +74,14 @@ class AppConfig { static String get sentryDsn => _d(AppSecrets.sentryDsn); /// Firebase Cloud Project Number for Play Integrity - static String get firebaseCloudProjectNumber => '424804867878'; + static String get firebaseCloudProjectNumber => + const String.fromEnvironment('FIREBASE_PROJECT_ID'); // โ”€โ”€โ”€ App Metadata โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ /// Current application version (derived from Infisical compilation injection). static String get appVersion => - const String.fromEnvironment('APP_VERSION', defaultValue: '4.4.9'); + const String.fromEnvironment('APP_VERSION', defaultValue: '4.5.0'); /// Commit SHA injected by CI for release builds. static String get appCommitSha => @@ -143,7 +148,6 @@ class AppConfig { static String get appStoreUrl { const appId = String.fromEnvironment( 'IOS_APP_ID', - defaultValue: '6478952324', ); return 'https://apps.apple.com/app/id$appId'; } diff --git a/mobile/lib/config/app_secrets.dart.example b/mobile/lib/config/app_secrets.dart.example index 3487e9e2..8f120b40 100644 --- a/mobile/lib/config/app_secrets.dart.example +++ b/mobile/lib/config/app_secrets.dart.example @@ -40,7 +40,7 @@ class AppSecrets { /// flutter run --debug --dart-define=GHOSTCLASS_DEV_URL=https://your-ip:port/api static const String ghostclassApiUrlDev = String.fromEnvironment( 'GHOSTCLASS_DEV_URL', - defaultValue: 'https://localhost:3000/api', // Safe default for debug builds + defaultValue: 'https://10.0.2.2:3000/api', // Safe default for debug builds ); // โ”€โ”€โ”€ Backend & Bridge Config (Prod) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ diff --git a/mobile/lib/firebase_options.dart b/mobile/lib/firebase_options.dart index 43504cad..e24bc74a 100644 --- a/mobile/lib/firebase_options.dart +++ b/mobile/lib/firebase_options.dart @@ -1,19 +1,10 @@ -// File generated by FlutterFire CLI. +// File generated dynamically during build workflow. // ignore_for_file: type=lint import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; /// Default [FirebaseOptions] for use with your Firebase apps. -/// -/// Example: -/// ```dart -/// import 'firebase_options.dart'; -/// // ... -/// await Firebase.initializeApp( -/// options: DefaultFirebaseOptions.currentPlatform, -/// ); -/// ``` class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { @@ -29,18 +20,15 @@ class DefaultFirebaseOptions { return ios; case TargetPlatform.macOS: throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for macos - ' - 'you can reconfigure this by running the FlutterFire CLI again.', + 'DefaultFirebaseOptions have not been configured for macos.', ); case TargetPlatform.windows: throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for windows - ' - 'you can reconfigure this by running the FlutterFire CLI again.', + 'DefaultFirebaseOptions have not been configured for windows.', ); case TargetPlatform.linux: throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for linux - ' - 'you can reconfigure this by running the FlutterFire CLI again.', + 'DefaultFirebaseOptions are not supported for this platform.', ); default: throw UnsupportedError( @@ -51,18 +39,18 @@ class DefaultFirebaseOptions { static const FirebaseOptions android = FirebaseOptions( apiKey: String.fromEnvironment('FIREBASE_API_KEY_ANDROID'), - appId: '1:424804867878:android:df401041d564c22b21abe7', - messagingSenderId: '424804867878', - projectId: 'devakesu-ghostclass', - storageBucket: 'devakesu-ghostclass.firebasestorage.app', + appId: String.fromEnvironment('FIREBASE_ANDROID_APP_ID'), + messagingSenderId: String.fromEnvironment('FIREBASE_MESSAGING_SENDER_ID'), + projectId: String.fromEnvironment('FIREBASE_PROJECT_ID'), + storageBucket: String.fromEnvironment('FIREBASE_STORAGE_BUCKET'), ); static const FirebaseOptions ios = FirebaseOptions( apiKey: String.fromEnvironment('FIREBASE_API_KEY_IOS'), - appId: '1:424804867878:ios:d132bb8be987f52d21abe7', - messagingSenderId: '424804867878', - projectId: 'devakesu-ghostclass', - storageBucket: 'devakesu-ghostclass.firebasestorage.app', - iosBundleId: 'com.devakesu.apps.ghostclass', + appId: String.fromEnvironment('FIREBASE_IOS_APP_ID'), + messagingSenderId: String.fromEnvironment('FIREBASE_MESSAGING_SENDER_ID'), + projectId: String.fromEnvironment('FIREBASE_PROJECT_ID'), + storageBucket: String.fromEnvironment('FIREBASE_STORAGE_BUCKET'), + iosBundleId: String.fromEnvironment('FIREBASE_IOS_BUNDLE_ID'), ); } diff --git a/mobile/lib/logic/attendance_utils.dart b/mobile/lib/logic/attendance_utils.dart index 70e42805..3502c8a4 100644 --- a/mobile/lib/logic/attendance_utils.dart +++ b/mobile/lib/logic/attendance_utils.dart @@ -3,28 +3,44 @@ import 'package:ghostclass/models/attendance.dart'; import 'package:ghostclass/models/course_details.dart'; import 'package:ghostclass/services/logger.dart'; +const romanNumerals = [ + 'I', + 'II', + 'III', + 'IV', + 'V', + 'VI', + 'VII', + 'VIII', + 'IX', + 'X', + 'XI', + 'XII', +]; + +const Map romanToNumberMap = { + 'i': 1, + 'ii': 2, + 'iii': 3, + 'iv': 4, + 'v': 5, + 'vi': 6, + 'vii': 7, + 'viii': 8, + 'ix': 9, + 'x': 10, + 'xi': 11, + 'xii': 12, +}; + /// Converts a numeric value (1, 2, 3...) to Roman numerals (I, II, III...). String toRoman(dynamic value) { final n = (value is String) ? int.tryParse(value) ?? 0 : (value is num ? value.toInt() : 0); if (n < 1) return n.toString(); - const romans = [ - 'I', - 'II', - 'III', - 'IV', - 'V', - 'VI', - 'VII', - 'VIII', - 'IX', - 'X', - 'XI', - 'XII', - ]; - if (n > 0 && n <= romans.length) { - return romans[n - 1]; + if (n > 0 && n <= romanNumerals.length) { + return romanNumerals[n - 1]; } return n.toString(); } @@ -104,12 +120,15 @@ String normalizeDate(dynamic date) { } AppLogger.e( - 'attendance_utils.normalizeDate: Unrecognized date format. Returning empty string to avoid invalid slot keys.', + 'attendance_utils.normalizeDate: Unrecognized date format. Preserving raw string to prevent key collision.', {'raw': s}, ); - return ''; + return s; } +/// Parses date strings in expected EzyGo formats: +/// 1. YYYY-MM-DD (e.g. 2024-01-15) +/// 2. DD-MM-YYYY or DD-MM-YY (e.g. 15-01-2024 or 15-01-24) String? _parseSeparatedDate(String base, String sep) { final parts = base.split(sep); if (parts.length != 3) return null; @@ -124,12 +143,25 @@ String? _parseSeparatedDate(String base, String sep) { return null; } - var year = (a.length == 4) ? int.parse(a) : int.parse(c); - if (year < 100) year += 2000; - final month = int.parse(b); - final day = (a.length == 4) ? int.parse(c) : int.parse(a); + int year; + int month; + int day; + if (a.length == 4) { + // Format: YYYY-MM-DD + year = int.parse(a); + month = int.parse(b); + day = int.parse(c); + } else if (c.length == 4 || c.length == 2) { + // Format: DD-MM-YYYY or DD-MM-YY + day = int.parse(a); + month = int.parse(b); + year = int.parse(c); + if (year < 100) year += 2000; + } else { + return null; + } - if (month < 1 || month > 12 || day < 1 || day > 31) return ''; + if (month < 1 || month > 12 || day < 1 || day > 31) return null; final parsed = DateTime.tryParse( "${year.toString().padLeft(4, '0')}-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}", @@ -138,7 +170,7 @@ String? _parseSeparatedDate(String base, String sep) { parsed.year != year || parsed.month != month || parsed.day != day) { - return ''; + return null; } final yearStr = year.toString().padLeft(4, '0'); @@ -161,22 +193,7 @@ String normalizeSession(dynamic session) { if (s.contains(' ')) s = s.split(' ')[0]; // 2. Roman to Number Map - const romans = { - 'viii': '8', - 'vii': '7', - 'vi': '6', - 'v': '5', - 'iv': '4', - 'iii': '3', - 'ii': '2', - 'i': '1', - 'ix': '9', - 'x': '10', - 'xi': '11', - 'xii': '12', - }; - - if (romans.containsKey(s)) return romans[s]!; + if (romanToNumberMap.containsKey(s)) return romanToNumberMap[s]!.toString(); // 3. Parse Integer final num = int.tryParse(s); @@ -196,22 +213,15 @@ String formatSessionName(String sessionName) { .trim(); final lower = clean.toLowerCase(); - const romanMap = { - 'i': '1st Hour', - 'ii': '2nd Hour', - 'iii': '3rd Hour', - 'iv': '4th Hour', - 'v': '5th Hour', - 'vi': '6th Hour', - 'vii': '7th Hour', - 'viii': '8th Hour', - 'ix': '9th Hour', - 'x': '10th Hour', - 'xi': '11th Hour', - 'xii': '12th Hour', - }; - - if (romanMap.containsKey(lower)) return romanMap[lower]!; + final romanVal = romanToNumberMap[lower]; + if (romanVal != null) { + final j = romanVal % 10; + final k = romanVal % 100; + if (j == 1 && k != 11) return '${romanVal}st Hour'; + if (j == 2 && k != 12) return '${romanVal}nd Hour'; + if (j == 3 && k != 13) return '${romanVal}rd Hour'; + return '${romanVal}th Hour'; + } final num = int.tryParse(clean); if (num != null && num > 0) { @@ -236,22 +246,7 @@ int getSessionNumber(String name) { .replaceAll(RegExp('session|hour'), '') .trim(); - const romanMap = { - 'i': 1, - 'ii': 2, - 'iii': 3, - 'iv': 4, - 'v': 5, - 'vi': 6, - 'vii': 7, - 'viii': 8, - 'ix': 9, - 'x': 10, - 'xi': 11, - 'xii': 12, - }; - - if (romanMap.containsKey(clean)) return romanMap[clean]!; + if (romanToNumberMap.containsKey(clean)) return romanToNumberMap[clean]!; final match = RegExp(r'\d+').firstMatch(clean); if (match != null) { diff --git a/mobile/lib/logic/bunk.dart b/mobile/lib/logic/bunk.dart index 5f35e226..3b448fe6 100644 --- a/mobile/lib/logic/bunk.dart +++ b/mobile/lib/logic/bunk.dart @@ -58,7 +58,7 @@ AttendanceResult calculateAttendance( // Impossible to reach 100% if missed any class return AttendanceResult( canBunk: 0, - requiredToAttend: 999, + requiredToAttend: 0x7FFFFFFF, // int.maxValue / unreachable targetPercentage: safeTarget, isExact: false, isBorderline: false, diff --git a/mobile/lib/logic/encrypted_value.dart b/mobile/lib/logic/encrypted_value.dart index 7d991b82..22e49f2d 100644 --- a/mobile/lib/logic/encrypted_value.dart +++ b/mobile/lib/logic/encrypted_value.dart @@ -12,16 +12,31 @@ import 'package:ghostclass/services/logger.dart'; /// being easily identifiable in RAM dumps. @immutable class EncryptedValue { - const EncryptedValue._(this._encryptedBase64); + const EncryptedValue._( + this._encryptedBase64, + this._entropyA, + this._entropyB, + this._generation, + ); @visibleForTesting - factory EncryptedValue.forTesting(String base64) => EncryptedValue._(base64); + factory EncryptedValue.forTesting(String base64) => + EncryptedValue._(base64, Uint8List(0), Uint8List(0), _globalGeneration); /// Creates an encrypted wrapper for a plaintext string. factory EncryptedValue.fromPlaintext(String plaintext) { - if (plaintext.isEmpty) return const EncryptedValue._(''); + if (plaintext.isEmpty) { + return EncryptedValue._( + '', + Uint8List(0), + Uint8List(0), + _globalGeneration, + ); + } - final key = _reconstructKey(); + final entropyA = _generateRandomBytes(32); + final entropyB = _generateRandomBytes(32); + final key = _reconstructKey(entropyA, entropyB); final encrypter = Encrypter(AES(key, mode: AESMode.gcm)); // Generate a fresh random IV for each encryption (prevents GCM nonce-reuse) @@ -32,13 +47,20 @@ class EncryptedValue { final combined = Uint8List.fromList([...iv.bytes, ...encrypted.bytes]); final combinedBase64 = base64.encode(combined); - return EncryptedValue._(combinedBase64); + return EncryptedValue._( + combinedBase64, + entropyA, + entropyB, + _globalGeneration, + ); } - // We store entropy in two separate buffers. XORing them reconstructs the key. - static final Uint8List _entropyA = _generateRandomBytes(32); - static final Uint8List _entropyB = _generateRandomBytes(32); + + static int _globalGeneration = 0; final String _encryptedBase64; + final Uint8List _entropyA; + final Uint8List _entropyB; + final int _generation; static Uint8List _generateRandomBytes(int length) { final random = Random.secure(); @@ -49,32 +71,29 @@ class EncryptedValue { /// Reconstructs the 32-byte AES key from masked entropy. /// The full key only exists in this local scope during execution. - static Key _reconstructKey() { + static Key _reconstructKey(Uint8List entropyA, Uint8List entropyB) { + if (entropyA.length != 32 || entropyB.length != 32) { + return Key(Uint8List(32)); + } final keyBytes = Uint8List(32); for (var i = 0; i < 32; i++) { - keyBytes[i] = _entropyA[i] ^ _entropyB[i]; + keyBytes[i] = entropyA[i] ^ entropyB[i]; } return Key(keyBytes); } - /// Overwrite entropy buffers to reduce risk of key reconstruction after logout. - /// Call this when the app is performing a full logout or memory wipe. + /// Invalidate session entropy generation counter when performing a logout or memory wipe. static void clearEntropy() { - final random = Random.secure(); - for (var i = 0; i < _entropyA.length; i++) { - _entropyA[i] = random.nextInt(256); - } - for (var i = 0; i < _entropyB.length; i++) { - _entropyB[i] = random.nextInt(256); - } + _globalGeneration++; } /// Decrypts and returns the plaintext value. String get value { if (_encryptedBase64.isEmpty) return ''; + if (_generation != _globalGeneration) return ''; try { - final key = _reconstructKey(); + final key = _reconstructKey(_entropyA, _entropyB); final encrypter = Encrypter(AES(key, mode: AESMode.gcm)); // Decode the base64 to get IV + ciphertext diff --git a/mobile/lib/logic/error_handler.dart b/mobile/lib/logic/error_handler.dart index 8e5ca278..da943438 100644 --- a/mobile/lib/logic/error_handler.dart +++ b/mobile/lib/logic/error_handler.dart @@ -54,12 +54,10 @@ mixin ErrorHandlerMixin on State { title: 'Security Attestation Failed', message: dialogMessage, technicalDetails: error.message, - retryLabel: isCritical ? 'Close App' : 'Restart App', + retryLabel: isCritical ? 'Acknowledge' : 'Restart App', onRetry: () async { if (Platform.isAndroid) { await SystemNavigator.pop(); - } else { - exit(0); } }, ); @@ -89,8 +87,6 @@ mixin ErrorHandlerMixin on State { onRetry: () async { if (Platform.isAndroid) { await SystemNavigator.pop(); - } else { - exit(0); } }, ); diff --git a/mobile/lib/logic/error_utils.dart b/mobile/lib/logic/error_utils.dart index 5c20cda8..b90d99ae 100644 --- a/mobile/lib/logic/error_utils.dart +++ b/mobile/lib/logic/error_utils.dart @@ -2,6 +2,16 @@ import 'package:dio/dio.dart'; import 'package:ghostclass/logic/app_exception.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; +/// Error Utility & Catch Pattern Guidance +/// --------------------------------------- +/// Note on exception handling across the codebase: +/// Errors are intentionally caught using `on Object catch (e, st)` rather than +/// `on Exception catch (e)`. In Dart, asynchronous operations, Riverpod provider +/// builds, and third-party native bridge calls can throw non-Exception objects +/// or standard `Error` types (e.g. `TypeError`, `ArgumentError`, `StateError`). +/// Catching `Object` ensures no unhandled runtime faults bypass defensive fallback +/// blocks or loggers. + String formatApiError(dynamic response, String context) { if (response == null) { if (context == 'ApiService.Dio') { @@ -161,3 +171,33 @@ String sanitizeTechnicalDetails(String error) { return sanitized; } + +/// Consolidated detector for transient App Check network / quota / server failures. +bool isTransientAppCheckFailure(dynamic input) { + if (input == null) return false; + final msg = input.toString().toLowerCase(); + if (msg.isEmpty) return false; + return msg.contains('quota') || + msg.contains('connection') || + msg.contains('timeout') || + msg.contains('too_many_attempts') || + msg.contains('network') || + msg.contains('rate limit') || + msg.contains('server') || + msg.contains('internal error') || + msg.contains('internal google server error') || + msg.contains('google_server_unavailable') || + msg.contains('-12') || + msg.contains('unavailable'); +} + +/// Evaluates if a structured security error payload represents a transient failure. +bool isTransientSecurityPayload(Map? data) { + if (data == null) return false; + final reason = data['reason'] as String?; + final error = data['error'] as String?; + final appCheckError = data['appCheckError'] as String?; + return isTransientAppCheckFailure(reason) || + isTransientAppCheckFailure(error) || + isTransientAppCheckFailure(appCheckError); +} diff --git a/mobile/lib/logic/ezygo_batch_fetcher.dart b/mobile/lib/logic/ezygo_batch_fetcher.dart index e4395853..c0b799f7 100644 --- a/mobile/lib/logic/ezygo_batch_fetcher.dart +++ b/mobile/lib/logic/ezygo_batch_fetcher.dart @@ -42,10 +42,24 @@ class EzygoBatchFetcher { final List> _queue = []; // Local result cache + static const int _maxCacheSize = 50; final Map _cache = {}; + void _putCache(String key, _CacheEntry entry) { + if (_cache.length >= _maxCacheSize) { + final now = DateTime.now(); + _cache.removeWhere((_, value) => now.isAfter(value.expiry)); + if (_cache.length >= _maxCacheSize) { + _cache.remove(_cache.keys.first); + } + } + _cache[key] = entry; + } + // Tracker for log throttling DateTime? _lastCircuitBreakerLog; + final Map _lastEndpointCall = {}; + static const Duration _perEndpointThrottle = Duration(milliseconds: 100); int _generation = 0; @@ -165,6 +179,16 @@ class EzygoBatchFetcher { return postSlotInFlight; } + // 3.6 Per-endpoint Throttling + final lastCall = _lastEndpointCall[path]; + if (lastCall != null) { + final elapsed = DateTime.now().difference(lastCall); + if (elapsed < _perEndpointThrottle) { + await Future.delayed(_perEndpointThrottle - elapsed); + } + } + _lastEndpointCall[path] = DateTime.now(); + // 4. Execute the network request final requestFuture = _executeRequest( path: path, @@ -183,18 +207,24 @@ class EzygoBatchFetcher { if (_generation == startGeneration) { if (response.statusCode == 200) { // Success cache (Longer) - _cache[cacheKey] = _CacheEntry( - response: response, - expiry: DateTime.now().add(_cacheTtl), + _putCache( + cacheKey, + _CacheEntry( + response: response, + expiry: DateTime.now().add(_cacheTtl), + ), ); } else if (response.statusCode != null && response.statusCode! >= 500) { // NEGATIVE CACHE (Circuit Breaker): // Remember 5xx failures briefly to prevent Request Storms. _setOutage(true); - _cache[cacheKey] = _CacheEntry( - response: response, - expiry: DateTime.now().add(const Duration(seconds: 15)), + _putCache( + cacheKey, + _CacheEntry( + response: response, + expiry: DateTime.now().add(const Duration(seconds: 15)), + ), ); } } else { @@ -212,9 +242,12 @@ class EzygoBatchFetcher { _setOutage(true); // Short error TTL for transient network issues to recover faster if (e.response != null && _generation == startGeneration) { - _cache[cacheKey] = _CacheEntry( - response: e.response!, - expiry: DateTime.now().add(const Duration(seconds: 5)), + _putCache( + cacheKey, + _CacheEntry( + response: e.response!, + expiry: DateTime.now().add(const Duration(seconds: 5)), + ), ); } } diff --git a/mobile/lib/logic/security_initializer.dart b/mobile/lib/logic/security_initializer.dart index e2f9e541..1f01a7e7 100644 --- a/mobile/lib/logic/security_initializer.dart +++ b/mobile/lib/logic/security_initializer.dart @@ -22,8 +22,7 @@ typedef ActivateFn = class SecurityInitializer { SecurityInitializer._(); - // Exposed for tests to exercise the private constructor and improve - // coverage. Kept minimal and intended only for test use. + @visibleForTesting static void invokePrivateConstructorForTest() => SecurityInitializer._(); /// Initializes and activates App Check based on the current build mode. diff --git a/mobile/lib/logic/security_utils.dart b/mobile/lib/logic/security_utils.dart index fc7393f3..c63254a9 100644 --- a/mobile/lib/logic/security_utils.dart +++ b/mobile/lib/logic/security_utils.dart @@ -43,10 +43,10 @@ class SecurityUtils { await showGeneralDialog( context: context, barrierLabel: 'Security Error', - barrierColor: Colors.black.withValues(alpha: 0.5), + barrierColor: Colors.black.withValues(alpha: 0.75), transitionDuration: const Duration(milliseconds: 300), pageBuilder: (ctx, anim1, anim2) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: FadeTransition( opacity: anim1, child: SecurityErrorDialog( diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 03aee25a..2de88985 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -12,7 +12,6 @@ import 'package:ghostclass/logic/security_initializer.dart'; import 'package:ghostclass/providers/theme_provider.dart'; import 'package:ghostclass/router/app_router.dart'; import 'package:ghostclass/services/analytics_service.dart'; -import 'package:ghostclass/services/jwe_service.dart'; import 'package:ghostclass/services/logger.dart'; import 'package:ghostclass/theme/app_theme.dart'; import 'package:ghostclass/widgets/security_lockdown_listener.dart'; @@ -28,9 +27,7 @@ class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { final client = super.createHttpClient(context) - ..connectionTimeout = kDebugMode - ? const Duration(seconds: 45) - : const Duration(seconds: 30); + ..connectionTimeout = AppConfig.defaultTimeout; // In debug mode, we allow untrusted certificates ONLY if they match our expected hostname. // In release mode, standard certificate validation is enforced. @@ -60,7 +57,11 @@ Future _initializeFirebase() async { } } -Future? firebaseInitFuture; +class FirebaseInitializer { + FirebaseInitializer._(); + static Future? _initFuture; + static Future? get initFuture => _initFuture; +} void main() async { SentryWidgetsFlutterBinding.ensureInitialized(); @@ -91,7 +92,7 @@ void main() async { }; // Initialize Firebase & App Check asynchronously in the background - firebaseInitFuture = () async { + FirebaseInitializer._initFuture = () async { try { await _initializeFirebase(); @@ -119,7 +120,7 @@ void main() async { await Supabase.initialize( url: sUrl, - anonKey: sKey, + publishableKey: sKey, headers: { 'Origin': sOrigin, }, @@ -127,9 +128,6 @@ void main() async { await ThemeNotifier.preload(); - // Eagerly pre-warm cryptographic services concurrently while other SDKs/Fonts initialize - AppLogger.safeUnawait(JweService.instance.preWarm(), 'JWE pre-warm'); - // Defer font pre-warm so UI can render faster AppLogger.safeUnawait( GoogleFonts.pendingFonts([ @@ -150,11 +148,9 @@ void main() async { message: 'Supabase Config', category: 'auth.config', data: { - 'url': sUrl, - 'origin': sOrigin, - 'key_masked': sKey.length > 8 - ? '${sKey.substring(0, 4)}...${sKey.substring(sKey.length - 4)}' - : '[TOO SHORT]', + 'url_configured': sUrl.isNotEmpty, + 'key_length': sKey.length, + 'has_origin': sOrigin.isNotEmpty, }, ), ); @@ -186,7 +182,10 @@ class _MyAppState extends ConsumerState { routerConfig: router, debugShowCheckedModeBanner: false, builder: (context, child) { - return child!; + return Semantics( + label: 'GhostClass', + child: child, + ); }, ), ); diff --git a/mobile/lib/models/dashboard_stats.dart b/mobile/lib/models/dashboard_stats.dart index d4561823..0f2b3b3d 100644 --- a/mobile/lib/models/dashboard_stats.dart +++ b/mobile/lib/models/dashboard_stats.dart @@ -132,6 +132,10 @@ class DashboardStats { course.finalPresent++; } + if (status == AttendanceStatus.dutyLeave.code) { + course.officialDL++; + } + if (catalogCodesSet.contains(stdCourseCode) && !courseDisabled) { officialTotal++; final statusObj = AttendanceStatus.fromCode(status); @@ -185,12 +189,16 @@ class DashboardStats { if (isTrulyExtra) { course.finalTotal++; if (trackerPositive) course.finalPresent++; + if (trackerDL) course.extraDL++; } else { if (!officialPositive && trackerPositive) { course.finalPresent++; } else if (officialPositive && !trackerPositive) { course.finalPresent--; } + if (!officialDLStatus && trackerDL) { + course.corrDL++; + } } if (item.status == 'extra') { @@ -331,9 +339,7 @@ class DashboardStats { } static String standardize(String input) { - // Note: the '-' is placed last in the character class to avoid forming an - // ambiguous or reversed range (\u00A0 > '-' in code-point order). - return input.trim().toUpperCase().replaceAll(RegExp(r'[\s\u00A0\-]'), ''); + return utils.standardizeCourseCode(input); } } @@ -355,9 +361,14 @@ class CourseStat { int extraPresent = 0; int extraAbsent = 0; + int officialDL = 0; + int corrDL = 0; + int extraDL = 0; + int get officialAbsent => officialTotal - officialPresent; int get finalAbsent => finalTotal - finalPresent; int get manualTotalGain => extraPresent + extraAbsent; + int get dlCount => officialDL + corrDL + extraDL; double get percentage => finalTotal > 0 ? (finalPresent / finalTotal) * 100 : 0.0; diff --git a/mobile/lib/models/user.dart b/mobile/lib/models/user.dart index 91896db6..85637e2c 100644 --- a/mobile/lib/models/user.dart +++ b/mobile/lib/models/user.dart @@ -78,32 +78,32 @@ class UserProfile { UserProfile copyWith({ String? firstName, String? lastName, - String? avatarUrl, + String? Function()? avatarUrl, String? email, - String? phone, - String? birthDate, - String? gender, - String? lastSyncedAt, + String? Function()? phone, + String? Function()? birthDate, + String? Function()? gender, + String? Function()? lastSyncedAt, String? currentSemester, String? currentYear, String? createdAt, String? ezygoCreatedAt, - UserClass? classField, + UserClass? Function()? classField, }) { return UserProfile( firstName: firstName ?? this.firstName, lastName: lastName ?? this.lastName, - avatarUrl: avatarUrl ?? this.avatarUrl, + avatarUrl: avatarUrl != null ? avatarUrl() : this.avatarUrl, email: email ?? this.email, - phone: phone ?? this.phone, - birthDate: birthDate ?? this.birthDate, - gender: gender ?? this.gender, - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + phone: phone != null ? phone() : this.phone, + birthDate: birthDate != null ? birthDate() : this.birthDate, + gender: gender != null ? gender() : this.gender, + lastSyncedAt: lastSyncedAt != null ? lastSyncedAt() : this.lastSyncedAt, currentSemester: currentSemester ?? this.currentSemester, currentYear: currentYear ?? this.currentYear, createdAt: createdAt ?? this.createdAt, ezygoCreatedAt: ezygoCreatedAt ?? this.ezygoCreatedAt, - classField: classField ?? this.classField, + classField: classField != null ? classField() : this.classField, ); } @@ -182,6 +182,7 @@ class UserSettings { required this.bunkCalculatorEnabled, required this.targetPercentage, required this.disabledCourses, + this.courseTargets = const {}, this.semester, this.academicYear, }); @@ -208,12 +209,24 @@ class UserSettings { } }); + final rawTargetsSource = json['course_targets']; + final rawTargets = rawTargetsSource is Map + ? Map.from(rawTargetsSource) + : {}; + final targets = {}; + rawTargets.forEach((key, val) { + if (val is num) { + targets[key] = val.toInt(); + } + }); + return UserSettings( bunkCalculatorEnabled: json['bunk_calculator_enabled'] as bool? ?? true, targetPercentage: (json['target_percentage'] as num?)?.toInt() ?? 75, semester: json['semester'] as String?, academicYear: json['academic_year'] as String?, disabledCourses: disabled, + courseTargets: targets, ); } final bool bunkCalculatorEnabled; @@ -221,6 +234,7 @@ class UserSettings { final String? semester; final String? academicYear; final Map> disabledCourses; + final Map courseTargets; UserSettings copyWith({ bool? bunkCalculatorEnabled, @@ -228,6 +242,7 @@ class UserSettings { String? semester, String? academicYear, Map>? disabledCourses, + Map? courseTargets, }) { return UserSettings( bunkCalculatorEnabled: @@ -236,6 +251,7 @@ class UserSettings { semester: semester ?? this.semester, academicYear: academicYear ?? this.academicYear, disabledCourses: disabledCourses ?? this.disabledCourses, + courseTargets: courseTargets ?? this.courseTargets, ); } @@ -260,6 +276,7 @@ class UserSettings { 'semester': semester, 'academic_year': academicYear, 'disabled_courses': disabledCourses, + 'course_targets': courseTargets, }; @override @@ -271,7 +288,8 @@ class UserSettings { targetPercentage == other.targetPercentage && semester == other.semester && academicYear == other.academicYear && - _mapsEqual(disabledCourses, other.disabledCourses); + _mapsEqual(disabledCourses, other.disabledCourses) && + _mapsEqual(courseTargets, other.courseTargets); @override int get hashCode => Object.hash( @@ -280,6 +298,7 @@ class UserSettings { semester, academicYear, disabledCourses, + courseTargets, ); bool _mapsEqual(Map m1, Map m2) { diff --git a/mobile/lib/providers/academic_context_service.dart b/mobile/lib/providers/academic_context_service.dart new file mode 100644 index 00000000..7cdbc470 --- /dev/null +++ b/mobile/lib/providers/academic_context_service.dart @@ -0,0 +1,179 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ghostclass/logic/app_exception.dart'; +import 'package:ghostclass/logic/attendance_utils.dart'; +import 'package:ghostclass/logic/error_utils.dart'; +import 'package:ghostclass/models/institution.dart'; +import 'package:ghostclass/providers/academic_provider.dart'; +import 'package:ghostclass/providers/auth_provider.dart'; +import 'package:ghostclass/providers/profile_hydration_service.dart'; +import 'package:ghostclass/services/api_service.dart'; +import 'package:ghostclass/services/logger.dart'; +import 'package:ghostclass/services/secure_storage.dart'; + +final academicContextServiceProvider = + NotifierProvider( + AcademicContextService.new, + ); + +class AcademicContextService extends Notifier { + @override + void build() { + // No-op state + } + + Future updateAcademicContext(String? sem, String? year) async { + final authNotifier = ref.read(authProvider.notifier); + final user = ref.read(authProvider).value; + if (user == null) return; + + final api = ref.read(apiServiceProvider); + final storage = ref.read(secureStorageProvider); + + try { + if (year != null) { + final yearResponse = await api.updateAcademicYear(year, storage); + if (yearResponse.statusCode != 200 && yearResponse.statusCode != 201) { + final resData = yearResponse.data as Map?; + throw Exception(formatApiError(resData, 'Auth.AcademicUpdate')); + } + } + + if (sem != null) { + final semesterResponse = await api.updateSemester(sem, storage); + if (semesterResponse.statusCode != 200 && + semesterResponse.statusCode != 201) { + final resData = semesterResponse.data as Map?; + throw Exception(formatApiError(resData, 'Auth.AcademicUpdate')); + } + } + + AcademicState? nextAcademic; + if (sem != null || year != null) { + final currentAcademic = await storage.getAcademicState(); + nextAcademic = AcademicState( + semester: + sem ?? + currentAcademic?.semester ?? + calculateCurrentAcademicInfo()['current_semester']!, + year: + year ?? + currentAcademic?.year ?? + calculateCurrentAcademicInfo()['current_year']!, + ); + await storage.saveAcademicState(nextAcademic); + } + + final syncingUser = user.copyWith(isSyncing: true); + authNotifier.updateState(syncingUser); + + final token = await authNotifier.getFreshSupabaseToken(); + if (token == null) { + await authNotifier.logout(); + return; + } + + try { + api.clearCaches(); + + final response = await api.refreshProfile( + token, + sync: true, + force: true, + ); + if (response.statusCode == 401) { + final data = response.data as Map?; + throw AppException( + message: formatApiError(data, 'Security Verification'), + type: AppExceptionType.unauthorized, + statusCode: 401, + details: data, + ); + } + + if (response.statusCode != 200 || response.data == null) { + if (response.statusCode != null && response.statusCode! >= 500) { + throw const AppException( + message: 'Ezygo issues (5xx)', + type: AppExceptionType.server, + ); + } + throw const AppException( + message: 'Profile sync failed', + type: AppExceptionType.server, + ); + } + + await ref + .read(profileHydrationServiceProvider.notifier) + .applyProfileResponseData( + currentUser: syncingUser, + data: response.data as Map, + ); + } finally { + final finalUser = ref.read(authProvider).value; + if (finalUser != null) { + authNotifier.updateState(finalUser.copyWith(isSyncing: false)); + } + } + + AppLogger.i( + 'AuthNotifier: Academic context updated successfully ($sem, $year)', + ); + } on Object catch (e) { + AppLogger.e('AuthNotifier: Failed to update academic context', e); + if (e is AppException && e.isAuthError) { + final isSecurityError = e.details?['type'] == 'security'; + final isCritical = e.details?['criticalRisk'] == true; + + if (isSecurityError && !isCritical) { + AppLogger.e( + 'AuthNotifier: Non-critical security block. Skipping logout.', + ); + } else { + if (isCritical) { + AppLogger.e('AuthNotifier: CRITICAL SECURITY RISK. Logging out.'); + } + await authNotifier.logout(); + } + } + rethrow; + } + } + + Future updateDefaultInstitution(int institutionId) async { + final api = ref.read(apiServiceProvider); + final storage = ref.read(secureStorageProvider); + + try { + final res = await api.updateDefaultInstitution(institutionId, storage); + if (res.statusCode != 200 && res.statusCode != 201) { + throw Exception(formatApiError(res.data, 'Auth.Institution')); + } + + await ref + .read(profileHydrationServiceProvider.notifier) + .refreshProfile(force: true); + } on Object catch (e) { + AppLogger.e('AuthNotifier: Institution update failed', e); + rethrow; + } + } + + Future> fetchInstitutions() async { + final api = ref.read(apiServiceProvider); + final storage = ref.read(secureStorageProvider); + final response = await api.getInstitutions(storage); + + if (response.statusCode != 200) { + throw Exception(formatApiError(response.data, 'Institution Fetch')); + } + + final all = (response.data as List) + .map((i) => Institution.fromJson(i as Map)) + .toList(); + + return all.where((i) => i.role.toLowerCase() == 'student').toList(); + } +} diff --git a/mobile/lib/providers/academic_provider.dart b/mobile/lib/providers/academic_provider.dart index 035c5a71..fac1cbe7 100644 --- a/mobile/lib/providers/academic_provider.dart +++ b/mobile/lib/providers/academic_provider.dart @@ -247,6 +247,10 @@ class AcademicNotifier extends AsyncNotifier { ref.invalidateSelf(); } } + + void updateState(AcademicState? newState) { + state = AsyncValue.data(newState); + } } (int, int) _parseAcademicYear(String year) { diff --git a/mobile/lib/providers/auth_provider.dart b/mobile/lib/providers/auth_provider.dart index f7ebf9d0..d706a261 100644 --- a/mobile/lib/providers/auth_provider.dart +++ b/mobile/lib/providers/auth_provider.dart @@ -1,18 +1,19 @@ import 'dart:async'; -import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/config/app_config.dart'; import 'package:ghostclass/logic/app_exception.dart'; -import 'package:ghostclass/logic/attendance_utils.dart'; import 'package:ghostclass/logic/encrypted_value.dart'; import 'package:ghostclass/logic/error_utils.dart'; import 'package:ghostclass/models/institution.dart'; import 'package:ghostclass/models/user.dart'; +import 'package:ghostclass/providers/academic_context_service.dart'; import 'package:ghostclass/providers/academic_provider.dart'; +import 'package:ghostclass/providers/profile_hydration_service.dart'; import 'package:ghostclass/providers/security_provider.dart'; +import 'package:ghostclass/providers/session_healing_service.dart'; import 'package:ghostclass/services/analytics_service.dart'; import 'package:ghostclass/services/api_service.dart'; import 'package:ghostclass/services/cache_manager.dart'; @@ -21,6 +22,7 @@ import 'package:ghostclass/services/profile_service.dart'; import 'package:ghostclass/services/secure_storage.dart'; import 'package:ghostclass/services/settings_service.dart'; import 'package:ghostclass/services/startup_flow_service.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class LoginException implements Exception { @@ -33,10 +35,13 @@ class LoginException implements Exception { // โ”€โ”€โ”€ Providers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ final Provider profileServiceProvider = Provider( - (ref) => ProfileService(), + (ref) => ProfileService(ref.watch(supabaseClientProvider)), ); final Provider settingsServiceProvider = Provider( - (ref) => SettingsService(ref.watch(secureStorageProvider)), + (ref) => SettingsService( + ref.watch(secureStorageProvider), + ref.watch(supabaseClientProvider), + ), ); final supabaseClientProvider = Provider( @@ -82,8 +87,8 @@ class AuthenticatedUser { String get maskedToken { final token = ezygoToken.value; - if (token.length <= 8) return 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข'; - return 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข${token.substring(token.length - 8)}'; + if (token.length <= 4) return 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข'; + return 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข${token.substring(token.length - 4)}'; } AuthenticatedUser copyWith({ @@ -142,36 +147,35 @@ class AuthenticatedUser { /// AuthNotifier /// ------------ -/// A complex notifier that manages the authenticated user session, -/// self-healing token logic, periodic background refreshes, and -/// security lockdown procedures. +/// A simplified notifier that delegates complex session/hydration/healing +/// operations to dedicated services: SessionHealingService, +/// ProfileHydrationService, and AcademicContextService. class AuthNotifier extends AsyncNotifier with WidgetsBindingObserver { + bool _isInitializing = false; + bool get isInitializing => _isInitializing; + Timer? _refreshTimer; - DateTime? _lastRefresh; DateTime? _lastBackgroundedAt; - bool _isRefreshing = false; - bool _isInitializing = false; - int _consecutiveHealFailures = 0; - int _profileRefreshGeneration = 0; - Future? _refreshProfileInFlight; - Future? _profileRefreshInFlight; @override FutureOr build() async { + WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.addObserver(this); final apiService = ref.read(apiServiceProvider); final unauthorizedSub = apiService.onUnauthorized.listen((_) { AppLogger.safeUnawait( - _handleUnauthorized(), + ref.read(sessionHealingServiceProvider.notifier).handleUnauthorized(), 'AuthNotifier: handleUnauthorized', ); }); final lockdownSub = apiService.onSecurityLockdown.listen((data) { AppLogger.safeUnawait( - _handleSecurityLockdown(data), + ref + .read(sessionHealingServiceProvider.notifier) + .handleSecurityLockdown(data), 'AuthNotifier: securityLockdown', ); }); @@ -197,7 +201,9 @@ class AuthNotifier extends AsyncNotifier _isInitializing = true; try { - final user = await _buildFromCurrentSession(); + final user = await ref + .read(profileHydrationServiceProvider.notifier) + .buildFromCurrentSession(); AppLogger.i('AuthNotifier: Core hydration complete'); return user; } finally { @@ -209,7 +215,10 @@ class AuthNotifier extends AsyncNotifier void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { _lastBackgroundedAt = DateTime.now(); + _refreshTimer?.cancel(); + _refreshTimer = null; } else if (state == AppLifecycleState.resumed) { + _startPeriodicRefresh(); final now = DateTime.now(); if (_lastBackgroundedAt != null) { final backgroundDuration = now.difference(_lastBackgroundedAt!); @@ -229,422 +238,73 @@ class AuthNotifier extends AsyncNotifier }); } - Future _handleUnauthorized() async { - if (_isRefreshing || _isInitializing) return; - _isRefreshing = true; - final healAttemptId = DateTime.now().microsecondsSinceEpoch.toString(); - - final api = ref.read(apiServiceProvider)..suppress401 = true; - AppLogger.e('AuthNotifier: 401 DETECTED. Attempting self-healing...'); - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Starting heal with $_consecutiveHealFailures prior failures', - ); + // โ”€โ”€โ”€ Public Setters / Internal Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - try { - // Adaptive backoff based on consecutive failures: 0ms, 500ms, 1s, 2s, 4s (capped at 5s) - final backoffMs = _consecutiveHealFailures > 0 - ? (500 * (1 << (_consecutiveHealFailures - 1))).clamp(500, 5000) - : 0; - if (backoffMs > 0) { - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Waiting ${backoffMs}ms before retry (attempt ${_consecutiveHealFailures + 1})', - ); - await Future.delayed(Duration(milliseconds: backoffMs)); - } + void updateState(AuthenticatedUser? user) { + state = AsyncValue.data(user); + } - final oldToken = state.value?.ezygoToken; - if (state.value == null) { - final recoveredUser = await _buildFromCurrentSession(); - if (recoveredUser != null) { - state = AsyncValue.data(recoveredUser); - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Recovered user from session', - ); - } - } + void setAuthLoading() { + state = const AsyncValue.loading(); + } - final supabaseToken = await _getFreshSupabaseToken(); - if (supabaseToken == null) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Supabase token unavailable, logging out', - ); - await logout(); - return; - } + void setAuthError(Object error, StackTrace stackTrace) { + state = AsyncValue.error(error, stackTrace); + } - Response? syncRes; - try { - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Calling syncMobileAuth (attempt 1/2)', - ); - syncRes = await api - .syncMobileAuth(supabaseToken) + Future getFreshSupabaseToken() async { + try { + final session = ref.read(supabaseClientProvider).auth.currentSession; + if (session == null) return null; + if (session.isExpired) { + final res = await ref + .read(supabaseClientProvider) + .auth + .refreshSession() .timeout( kDebugMode ? const Duration(seconds: 45) : const Duration(seconds: 30), ); - } on TimeoutException catch (e, st) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth timed out (attempt 1)', - e, - st, - ); - syncRes = null; - } - - // If the first attempt failed, do a single retry with a short backoff - if (syncRes == null || syncRes.statusCode != 200) { - try { - await Future.delayed(const Duration(milliseconds: 500)); - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Calling syncMobileAuth (attempt 2/2)', - ); - syncRes = await api - .syncMobileAuth(supabaseToken) - .timeout( - kDebugMode - ? const Duration(seconds: 45) - : const Duration(seconds: 30), - ); - } on Object catch (e, st) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth retry failed', - e, - st, - ); - syncRes = null; - } - } - - if (syncRes != null && - syncRes.statusCode == 200 && - syncRes.data is Map) { - final syncData = syncRes.data as Map; - final syncedToken = (syncData['ezygo_token'] as String?)?.trim(); - - if (syncedToken != null && syncedToken.isNotEmpty) { - try { - await ref.read(secureStorageProvider).saveEzygoToken(syncedToken); - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Persisted synced ezygo token', - ); - } on Object catch (e, st) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Failed to persist synced ezygo token', - e, - st, - ); - } - - final current = state.value; - if (current != null) { - final syncedTermsVersion = syncData['terms_version'] as String?; - final syncedEzygoId = syncData['id']?.toString(); - state = AsyncValue.data( - current.copyWith( - ezygoToken: EncryptedValue.fromPlaintext(syncedToken), - termsVersion: syncedTermsVersion, - ezygoId: syncedEzygoId, - ), - ); - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Updated state with synced token', - ); - } - } - } - - AppLogger.d('AuthNotifier [HEAL-$healAttemptId]: Refreshing profile'); - await refreshProfile(force: true); - final newToken = state.value?.ezygoToken; - - if (newToken != null && newToken != oldToken) { - AppLogger.i( - 'AuthNotifier [HEAL-$healAttemptId]: SELF-HEALING SUCCESSFUL. Token changed', - ); - _consecutiveHealFailures = 0; - } else { - _consecutiveHealFailures++; - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Self-healing did not produce a new token. Consecutive failures: $_consecutiveHealFailures', - ); - - if (_consecutiveHealFailures >= 3) { - final lastError = state.error; - final isSecurityError = - lastError is AppException && - lastError.details?['type'] == 'security'; - - if (isSecurityError) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Terminal security block detected. Not logging out.', - ); - _consecutiveHealFailures = 0; // Reset to allow more attempts later - } else { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Terminal 401 loop detected after $_consecutiveHealFailures attempts. Logging out to protect state.', - ); - await logout(); - } - } - } - } on Object catch (e) { - AppLogger.e('AuthNotifier [HEAL-$healAttemptId]: Self-healing error', e); - if (e is AppException && e.isAuthError) { - final isSecurityError = e.details?['type'] == 'security'; - final isCritical = e.details?['criticalRisk'] == true; - - if (isSecurityError && !isCritical) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: Non-critical security block. Skipping logout.', - ); - } else { - if (isCritical) { - AppLogger.e( - 'AuthNotifier [HEAL-$healAttemptId]: CRITICAL SECURITY RISK. Logging out.', - ); - } - await logout(); - } - } - } finally { - // Cooldown before allowing next 401 triggers to prevent tight cascades. - // Cooldown increases with consecutive failures (1s baseline, up to 5s). - final cooldownMs = _consecutiveHealFailures > 0 - ? (500 * (1 << (_consecutiveHealFailures - 1))).clamp(500, 5000) - : 1000; - AppLogger.d( - 'AuthNotifier [HEAL-$healAttemptId]: Cooldown for ${cooldownMs}ms before next 401 can trigger', - ); - await Future.delayed(Duration(milliseconds: cooldownMs)); - api.suppress401 = false; - _isRefreshing = false; - } - } - - bool _isTransientAppCheckFailureText(String? text) { - final msg = (text ?? '').toLowerCase(); - if (msg.isEmpty) return false; - return msg.contains('too_many_attempts') || - msg.contains('timeout') || - msg.contains('network') || - msg.contains('connection') || - msg.contains('unavailable') || - msg.contains('rate limit') || - msg.contains('internal google server error') || - msg.contains('google_server_unavailable') || - msg.contains('-12'); - } - - bool _isTransientSecurityPayload(Map? data) { - if (data == null) return false; - final reason = data['reason'] as String?; - final error = data['error'] as String?; - final appCheckError = data['appCheckError'] as String?; - return _isTransientAppCheckFailureText(reason) || - _isTransientAppCheckFailureText(error) || - _isTransientAppCheckFailureText(appCheckError); - } - - Future _handleSecurityLockdown(Map data) async { - AppLogger.e('AuthNotifier: SECURITY LOCKDOWN TRIGGERED'); - - // 1. Set failure state immediately to block UI - ref - .read(securityFailureProvider.notifier) - .setFailure( - data['title'], - criticalRisk: true, - reason: data['reason'], - action: data['action'], - source: data['technicalDetails'], - ); - - // 2. Perform forced logout and data wipe - await logout(force: true); - } - - Future refreshProfile({ - bool force = false, - }) async { - final inFlight = _refreshProfileInFlight; - if (inFlight != null) return inFlight; - - final future = _refreshProfileInternal( - force: force, - ); - _refreshProfileInFlight = future; - return future.whenComplete(() { - if (identical(_refreshProfileInFlight, future)) { - _refreshProfileInFlight = null; - } - }); - } - - Future _refreshProfileInternal({ - bool force = false, - }) async { - final currentUser = state.value; - if (currentUser == null) return; - - if (!force && - _lastRefresh != null && - DateTime.now().difference(_lastRefresh!) < const Duration(minutes: 5)) { - return; - } - - if (force && - _lastRefresh != null && - DateTime.now().difference(_lastRefresh!) < const Duration(seconds: 5)) { - return; - } - - try { - final token = await _getFreshSupabaseToken(); - if (token == null) { - await logout(); - return; - } - - await _fetchAndApplyServerProfile( - currentUser, - supabaseToken: token, - sync: force, - force: force, - ); - } on Object catch (e) { - if (e is AppException && e.isAuthError) { - final isSecurityError = e.details?['type'] == 'security'; - final isCritical = e.details?['criticalRisk'] == true; - - if (isSecurityError && !isCritical) { - AppLogger.e( - 'AuthNotifier: Non-critical security block. Skipping logout.', - ); - } else { - if (isCritical) { - AppLogger.e('AuthNotifier: CRITICAL SECURITY RISK. Logging out.'); - } - await logout(); - } + return res.session?.accessToken; } - } - } - - Future syncProfile() => refreshProfile(force: true); - - Future acceptTerms() async { - final user = state.value; - if (user == null) return; - - final token = await _getFreshSupabaseToken(); - if (token == null) return; - - final api = ref.read(apiServiceProvider); - final storage = ref.read(secureStorageProvider); - final version = AppConfig.termsVersion; - - try { - await api.acceptTerms(token, version); - await storage.saveTermsVersion(version); - state = AsyncValue.data(user.copyWith(termsVersion: version)); - try { - await AnalyticsService.instance.logAcceptTerms(version); - } on Object catch (_) {} - } on Object catch (e) { - AppLogger.e('AuthNotifier: Terms acceptance failed', e); - rethrow; - } - } - - Future _buildFromCurrentSession() async { - final session = ref.read(supabaseClientProvider).auth.currentSession; - if (session == null) return null; - - final storage = ref.read(secureStorageProvider); - final ezygoToken = await storage.getNormalizedEzygoToken(); - - final user = await _buildStoredUserForIdentity( - supabaseUserId: session.user.id, - ezygoToken: ezygoToken ?? '', - ); - - // Trigger profile sync in parallel without blocking startup/splash screen - AppLogger.safeUnawait( - _runBackgroundStartupHydration(user), - 'AuthNotifier: background startup hydration', - ); - - return user.copyWith(isSyncing: true); - } + return session.accessToken; + } on AuthException catch (e) { + final isTerminal = + e.statusCode == '400' || + e.message.contains('refresh_token_not_found') || + e.message.contains('Invalid Refresh Token') || + e.message.contains('not found'); - Future _runBackgroundStartupHydration( - AuthenticatedUser cachedUser, { - bool silent = false, - }) async { - final api = ref.read(apiServiceProvider)..suppress401 = true; - try { - final token = await _getFreshSupabaseToken(); - if (token == null) { - throw const AppException( - message: 'Auth session dead', - type: AppExceptionType.unauthorized, - ); + if (isTerminal) { + AppLogger.e('AuthNotifier: Supabase session terminal failure', e); + return null; } - // 1. Fetch Profile and trigger backend full EzyGo sync synchronously - await _runProfileRefresh( - cachedUser, - supabaseToken: token, - sync: true, - force: true, + AppLogger.e( + 'AuthNotifier: Supabase transient auth error. Preventing logout.', + e, ); - _lastRefresh = DateTime.now(); - - // Pre-fetch institutions so they are ready in settings - AppLogger.safeUnawait( - ref.read(institutionsProvider.future).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e('AuthNotifier: prefetch institutions failed', e, st); - return []; - }), - 'AuthNotifier: prefetch institutions', + throw AppException( + message: 'Supabase service issues: ${e.message}', + type: AppExceptionType.network, + originalError: e, ); - - // If we are not running silently, clear the syncing status to unlock the UI - if (!silent) { - final finalUser = state.value; - if (finalUser != null && - finalUser.supabaseUserId == cachedUser.supabaseUserId) { - state = AsyncValue.data(finalUser.copyWith(isSyncing: false)); - } - } } on Object catch (e) { - if (e is AppException && e.isAuthError) { - AppLogger.e('AuthNotifier: Background auth error, logging out', e); - await logout(); - return; - } - AppLogger.e( - 'AuthNotifier: Background startup hydration failed. Using cached data.', + 'AuthNotifier: Network error during token refresh. Preventing logout.', e, ); - if (!silent) { - final currentUser = state.value; - if (currentUser != null && - currentUser.supabaseUserId == cachedUser.supabaseUserId) { - state = AsyncValue.data(currentUser.copyWith(isSyncing: false)); - } - } - } finally { - api.suppress401 = false; + throw AppException( + message: 'Could not refresh session due to network failure.', + type: AppExceptionType.network, + originalError: e, + ); } } + // โ”€โ”€โ”€ Session Methods (delegated or handled directly) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Future login(String username, String password) async { ref.invalidate(institutionsProvider); state = const AsyncValue.loading(); @@ -660,7 +320,7 @@ class AuthNotifier extends AsyncNotifier if (bridgeResponse.statusCode != 200 && bridgeResponse.statusCode != 201) { final data = bridgeResponse.data as Map?; - final isTransientSecurity = _isTransientSecurityPayload(data); + final isTransientSecurity = isTransientSecurityPayload(data); final errorMsg = formatApiError(data, 'Secure Session'); throw AppException( message: isTransientSecurity @@ -714,12 +374,9 @@ class AuthNotifier extends AsyncNotifier : UserSettings.defaults(); final ezygoId = (bridgeData['id'] ?? bridgeData['user_id'])?.toString(); - final termsVersion = _extractTermsVersion( - bridgeData, - ); + final termsVersion = _extractTermsVersion(bridgeData); final ezygoToken = (bridgeData['ezygo_token'] as String?) ?? ''; - // Extract initial academic context from bridge response final initialSem = bridgeData['current_semester'] ?? bridgeData['semester']; final initialYear = @@ -793,14 +450,16 @@ class AuthNotifier extends AsyncNotifier ]; await Future.wait(saves); - final cachedUser = await _buildStoredUserForIdentity( - supabaseUserId: supabaseUser.id, - ezygoToken: ezygoToken, - usernameOverride: username, - ezygoIdOverride: ezygoId, - termsVersionOverride: termsVersion, - settingsFallback: settingsWithAcademic, - ); + final cachedUser = await ref + .read(profileHydrationServiceProvider.notifier) + .buildStoredUserForIdentity( + supabaseUserId: supabaseUser.id, + ezygoToken: ezygoToken, + usernameOverride: username, + ezygoIdOverride: ezygoId, + termsVersionOverride: termsVersion, + settingsFallback: settingsWithAcademic, + ); final profileService = ref.read(profileServiceProvider); if (profileService.hasRenderableLocalProfile(cachedUser.profile)) { @@ -817,27 +476,36 @@ class AuthNotifier extends AsyncNotifier try { await AnalyticsService.instance.logLogin(method: 'ezygo'); } on Object catch (_) {} + unawaited( + Sentry.addBreadcrumb( + Breadcrumb( + message: 'Login successful', + category: 'auth', + data: {'supabase_user_id': supabaseUser.id}, + ), + ), + ); ref .read(startupFlowServiceProvider) .markPostLoginFastPath(supabaseUser.id); return; } - final token = await _getFreshSupabaseToken(); + final token = await getFreshSupabaseToken(); if (token != null) { - // Mark as syncing and trigger backend full EzyGo sync - final profiledUser = await _runProfileRefresh( - cachedUser, - supabaseToken: token, - updateState: false, - sync: true, // Wait for backend to heal semester and fetch courses - ); + final profiledUser = await ref + .read(profileHydrationServiceProvider.notifier) + .runProfileRefresh( + cachedUser, + supabaseToken: token, + updateState: false, + sync: true, + ); final syncingUser = profiledUser.copyWith(isSyncing: true); state = AsyncValue.data(syncingUser); AppLogger.safeUnawait( Future.microtask(() async { try { - // Pre-fetch institutions so they are ready in settings AppLogger.safeUnawait( ref.read(institutionsProvider.future).catchError(( Object e, @@ -865,16 +533,14 @@ class AuthNotifier extends AsyncNotifier }), 'AuthNotifier: post-login microtask', ); - // Ensure any errors in the background microtask are logged - // (the microtask itself contains its own try/catch, but attach - // a top-level catcher to be defensive). - // Note: we purposely keep this fire-and-forget behavior. try { await AnalyticsService.instance.logLogin(method: 'ezygo'); } on Object catch (_) {} } else { - await _runProfileRefresh(cachedUser); + await ref + .read(profileHydrationServiceProvider.notifier) + .runProfileRefresh(cachedUser); try { await AnalyticsService.instance.logLogin(method: 'ezygo'); } on Object catch (_) {} @@ -895,7 +561,6 @@ class AuthNotifier extends AsyncNotifier } Future logout({bool force = false}) async { - // Reset security failure state on normal logout so the next login starts clean. if (!force) { ref.read(securityFailureProvider.notifier).clearFailure(); } @@ -906,17 +571,13 @@ class AuthNotifier extends AsyncNotifier ..invalidate(startupFlowServiceProvider); state = const AsyncValue.data(null); - // Stop periodic refreshes and prevent in-flight refresh continuations _refreshTimer?.cancel(); - _profileRefreshGeneration++; - _refreshProfileInFlight = null; - _profileRefreshInFlight = null; - // Wipe any in-memory entropy used by `EncryptedValue` to prevent - // reconstruction of plaintext tokens after logout. + + ref.read(sessionHealingServiceProvider.notifier).reset(); + ref.read(profileHydrationServiceProvider.notifier).reset(); + EncryptedValue.clearEntropy(); try { - // On forced logout (e.g. security lockdown), also wipe sensitive - // secure storage entries such as EzyGo and FCM tokens. final storage = ref.read(secureStorageProvider); final ops = >[ ref.read(supabaseClientProvider).auth.signOut(), @@ -936,46 +597,22 @@ class AuthNotifier extends AsyncNotifier } on Object catch (_) {} } - Future updateAvatar(String publicUrl) async { - final user = state.value; - if (user == null) return; - await ref - .read(profileServiceProvider) - .updateAvatar(user.supabaseUserId, publicUrl); - final updatedProfile = user.profile?.copyWith(avatarUrl: publicUrl); - if (updatedProfile != null) { - await ref.read(secureStorageProvider).saveUserProfile(updatedProfile); - } - state = AsyncValue.data(user.copyWith(profile: updatedProfile)); - } - - Future deleteAccount() async { - final user = state.value; - if (user == null) return; - try { - await ref.read(profileServiceProvider).deleteAccount(user.supabaseUserId); - await logout(); - } on Object catch (e) { - AppLogger.e('AuthNotifier: Account deletion failed', e); - rethrow; - } - } - Future updateSettings({ bool? bunkEnabled, int? targetPercentage, Map>? disabledCourses, + Map? courseTargets, }) async { final user = state.value; if (user == null) return; final previousSettings = user.settings; - // 1. Optimistic UI Update + Visual Feedback (isUpdatingSettings = true) final updatedSettings = user.settings.copyWith( bunkCalculatorEnabled: bunkEnabled ?? user.settings.bunkCalculatorEnabled, targetPercentage: targetPercentage ?? user.settings.targetPercentage, disabledCourses: disabledCourses ?? user.settings.disabledCourses, + courseTargets: courseTargets ?? user.settings.courseTargets, ); state = AsyncValue.data( @@ -985,7 +622,6 @@ class AuthNotifier extends AsyncNotifier ), ); - // 2. Persist final service = ref.read(settingsServiceProvider); try { await service.saveSettingsLocally(updatedSettings); @@ -994,8 +630,8 @@ class AuthNotifier extends AsyncNotifier bunkEnabled: bunkEnabled, targetPercentage: targetPercentage, disabledCourses: disabledCourses, + courseTargets: courseTargets, ); - // Analytics: settings updated try { final changes = {}; if (bunkEnabled != null) { @@ -1007,6 +643,9 @@ class AuthNotifier extends AsyncNotifier if (disabledCourses != null) { changes['disabledCoursesCount'] = disabledCourses.length; } + if (courseTargets != null) { + changes['courseTargetsCount'] = courseTargets.length; + } if (changes.isNotEmpty) { await AnalyticsService.instance.logSettingsUpdated(changes); } @@ -1016,7 +655,6 @@ class AuthNotifier extends AsyncNotifier 'AuthNotifier: Settings persistence failed, rolling back.', e, ); - // Rollback to previous settings state = AsyncValue.data( user.copyWith( settings: previousSettings, @@ -1025,7 +663,6 @@ class AuthNotifier extends AsyncNotifier ); rethrow; } finally { - // Clear updating flag if not already cleared by rollback final currentUser = state.value; if (currentUser != null && currentUser.isUpdatingSettings) { state = AsyncValue.data( @@ -1035,566 +672,37 @@ class AuthNotifier extends AsyncNotifier } } - Future updateAcademicContext(String? sem, String? year) async { - final user = state.value; - if (user == null) return; - - final api = ref.read(apiServiceProvider); - final storage = ref.read(secureStorageProvider); - - try { - // 1. Inform Ezygo of the change first (matches web parity) - if (year != null) { - final yearResponse = await api.updateAcademicYear(year, storage); - if (yearResponse.statusCode != 200 && yearResponse.statusCode != 201) { - final resData = yearResponse.data as Map?; - throw Exception(formatApiError(resData, 'Auth.AcademicUpdate')); - } - } - - if (sem != null) { - final semesterResponse = await api.updateSemester(sem, storage); - if (semesterResponse.statusCode != 200 && - semesterResponse.statusCode != 201) { - final resData = semesterResponse.data as Map?; - throw Exception(formatApiError(resData, 'Auth.AcademicUpdate')); - } - } - - // 2. Update the dedicated academic state in storage immediately so providers see it - AcademicState? nextAcademic; - if (sem != null || year != null) { - final currentAcademic = await storage.getAcademicState(); - nextAcademic = AcademicState( - semester: - sem ?? - currentAcademic?.semester ?? - calculateCurrentAcademicInfo()['current_semester']!, - year: - year ?? - currentAcademic?.year ?? - calculateCurrentAcademicInfo()['current_year']!, - ); - await storage.saveAcademicState(nextAcademic); - } - - // 3. Set isSyncing=true here to show the syncing overlay while backend sync runs. - final syncingUser = user.copyWith(isSyncing: true); - state = AsyncValue.data(syncingUser); - - final token = await _getFreshSupabaseToken(); - if (token == null) { - await logout(); - return; - } - - // Sequential Clean Sync Flow: - try { - api.clearCaches(); - - // 1. Fetch the fresh profile from Supabase with sync: true - // This forces the backend to fetch the NEW courses for the updated semester and populate the database! - final response = await api.refreshProfile( - token, - sync: true, - force: true, - ); - if (response.statusCode == 401) { - final data = response.data as Map?; - throw AppException( - message: formatApiError(data, 'Security Verification'), - type: AppExceptionType.unauthorized, - statusCode: 401, - details: data, - ); - } - - if (response.statusCode != 200 || response.data == null) { - if (response.statusCode != null && response.statusCode! >= 500) { - throw const AppException( - message: 'Ezygo issues (5xx)', - type: AppExceptionType.server, - ); - } - throw const AppException( - message: 'Profile sync failed', - type: AppExceptionType.server, - ); - } - - await _applyProfileResponseData( - currentUser: syncingUser, - data: response.data as Map, - ); - } finally { - final finalUser = state.value; - if (finalUser != null) { - state = AsyncValue.data(finalUser.copyWith(isSyncing: false)); - } - } - - AppLogger.i( - 'AuthNotifier: Academic context updated successfully ($sem, $year)', - ); - } on Object catch (e) { - AppLogger.e('AuthNotifier: Failed to update academic context', e); - if (e is AppException && e.isAuthError) { - final isSecurityError = e.details?['type'] == 'security'; - final isCritical = e.details?['criticalRisk'] == true; - - if (isSecurityError && !isCritical) { - AppLogger.e( - 'AuthNotifier: Non-critical security block. Skipping logout.', - ); - } else { - if (isCritical) { - AppLogger.e('AuthNotifier: CRITICAL SECURITY RISK. Logging out.'); - } - await logout(); - } - } - rethrow; - } - } - - Future updateDefaultInstitution(int institutionId) async { - final user = state.value; - if (user == null) return; - - final api = ref.read(apiServiceProvider); - final storage = ref.read(secureStorageProvider); - - try { - final res = await api.updateDefaultInstitution(institutionId, storage); - if (res.statusCode != 200 && res.statusCode != 201) { - throw Exception(formatApiError(res.data, 'Auth.Institution')); - } - - // Update local state by re-fetching profile (this ensures ID and other fields sync) - await refreshProfile(force: true); - } on Object catch (e) { - AppLogger.e('AuthNotifier: Institution update failed', e); - rethrow; - } - } - - Future> fetchInstitutions() async { - final api = ref.read(apiServiceProvider); - final storage = ref.read(secureStorageProvider); - final response = await api.getInstitutions(storage); - - if (response.statusCode != 200) { - throw Exception(formatApiError(response.data, 'Institution Fetch')); - } - - final all = (response.data as List) - .map((i) => Institution.fromJson(i as Map)) - .toList(); - - // Achieve parity with web app: Only show institutions where user is a student - return all.where((i) => i.role.toLowerCase() == 'student').toList(); - } - - // โ”€โ”€โ”€ Private Handlers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - Future _buildStoredUserForIdentity({ - required String supabaseUserId, - required String ezygoToken, - String? usernameOverride, - String? ezygoIdOverride, - String? termsVersionOverride, - UserSettings? settingsFallback, - }) async { - final storage = ref.read(secureStorageProvider); - - final identityReads = await Future.wait([ - storage.getSupabaseUserId(), - storage.getEzygoUserId(), - ]); - final storedSupabaseUserId = identityReads[0]; - final storedEzygoUserId = identityReads[1]; - - final matchesIdentity = - storedSupabaseUserId == null || - storedSupabaseUserId == supabaseUserId || - (ezygoIdOverride != null && storedEzygoUserId == ezygoIdOverride); - - Future usernameFuture() async => - matchesIdentity ? storage.getUsername() : null; - - Future termsVersionFuture() async => - matchesIdentity ? storage.getTermsVersion() : null; - - Future settingsFuture() async { - if (!matchesIdentity) { - return settingsFallback ?? UserSettings.defaults(); - } - return await storage.getSettings() ?? - settingsFallback ?? - UserSettings.defaults(); - } - - Future profileFuture() async => - matchesIdentity ? storage.getUserProfile() : null; - - final hydrationReads = await Future.wait([ - usernameFuture(), - termsVersionFuture(), - settingsFuture(), - profileFuture(), - ]); - final storedUsername = hydrationReads[0] as String?; - final storedTermsVersion = hydrationReads[1] as String?; - final hydratedSettings = hydrationReads[2] as UserSettings; - final hydratedProfile = hydrationReads[3] as UserProfile?; - - return AuthenticatedUser( - supabaseUserId: supabaseUserId, - ezygoToken: EncryptedValue.fromPlaintext(ezygoToken), - ezygoId: ezygoIdOverride ?? (matchesIdentity ? storedEzygoUserId : null), - username: usernameOverride ?? storedUsername, - termsVersion: termsVersionOverride ?? storedTermsVersion, - settings: hydratedSettings, - profile: hydratedProfile, - ); - } - - Future _fetchAndApplyServerProfile( - AuthenticatedUser user, { - String? supabaseToken, - bool updateState = true, - bool sync = false, - bool force = false, - }) async { - final refreshGeneration = _profileRefreshGeneration; - final token = supabaseToken ?? await _getFreshSupabaseToken(); - if (token == null) { - throw const AppException( - message: 'Session dead', - type: AppExceptionType.unauthorized, - ); - } - - final api = ref.read(apiServiceProvider); - final response = await api.refreshProfile( - token, - sync: sync, - force: force, - ); - - if (response.statusCode == 401) { - final data = response.data as Map?; - final isTransientSecurity = _isTransientSecurityPayload(data); - throw AppException( - message: isTransientSecurity - ? 'Device verification is temporarily unavailable. Please retry in a few moments.' - : formatApiError(data, 'Security Verification'), - type: isTransientSecurity - ? AppExceptionType.network - : AppExceptionType.unauthorized, - statusCode: 401, - details: data, - ); - } - - if (response.statusCode != 200 || response.data == null) { - if (response.statusCode != null && response.statusCode! >= 500) { - throw const AppException( - message: 'Ezygo issues (5xx)', - type: AppExceptionType.server, - ); - } - throw const AppException( - message: 'Profile sync failed', - type: AppExceptionType.server, - ); - } - - final updatedUser = await _applyProfileResponseData( - currentUser: user, - data: response.data as Map, - updateState: false, - ); - - if (updateState && refreshGeneration == _profileRefreshGeneration) { - final currentState = state.value; - if (currentState == null || - currentState.supabaseUserId == user.supabaseUserId) { - state = AsyncValue.data(updatedUser); - } - } - - return updatedUser; - } - - Future _runProfileRefresh( - AuthenticatedUser user, { - String? supabaseToken, - bool updateState = true, - bool sync = false, - bool force = false, - }) { - final inFlight = _profileRefreshInFlight; - if (inFlight != null) return inFlight; - - final future = _fetchAndApplyServerProfile( - user, - supabaseToken: supabaseToken, - updateState: updateState, - sync: sync, - force: force, - ); - _profileRefreshInFlight = future; + // โ”€โ”€โ”€ Delegation Facades โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - return future.whenComplete(() { - if (identical(_profileRefreshInFlight, future)) { - _profileRefreshInFlight = null; - } - }); - } + Future refreshProfile({bool force = false}) => ref + .read(profileHydrationServiceProvider.notifier) + .refreshProfile(force: force); - Future _applyProfileResponseData({ - required AuthenticatedUser currentUser, - required Map data, - bool updateState = true, - }) async { - final storage = ref.read(secureStorageProvider); - final rawSettings = data['settings'] as Map?; - final baseSettings = rawSettings != null - ? UserSettings.fromJson(rawSettings) - : currentUser.settings; - - final settings = baseSettings; - - final rawProfile = data.containsKey('profile') - ? Map.from(data['profile'] as Map) - : Map.from(data); - - // Ensure current_semester/year are explicitly included in the map passed to fromJson - rawProfile['current_semester'] = - data['current_semester'] ?? rawProfile['current_semester']; - rawProfile['current_year'] = - data['current_year'] ?? rawProfile['current_year']; - - final profile = UserProfile.fromJson(rawProfile); - - final mergedUser = currentUser.copyWith( - settings: settings, - profile: profile, - ezygoToken: EncryptedValue.fromPlaintext( - (data['ezygo_token'] as String?) ?? currentUser.ezygoToken.value, - ), - ezygoId: - (data['id'] ?? - data['user_id'] ?? - data['ezygo_user_id'] ?? - data['ezygo_id']) - ?.toString() ?? - currentUser.ezygoId, - termsVersion: _extractTermsVersion(data) ?? currentUser.termsVersion, - username: data['username'] as String? ?? currentUser.username, - ); + Future syncProfile() => + ref.read(profileHydrationServiceProvider.notifier).syncProfile(); - final nextAcademic = - (data['current_semester'] != null && data['current_year'] != null) - ? AcademicState( - semester: data['current_semester']! as String, - year: data['current_year']! as String, - ) - : null; - - // If the user has logged out while this refresh was in-flight, skip - // persisting any profile or token changes to avoid reintroducing - // sensitive data after a forced logout. - final currentSession = ref.read(supabaseClientProvider).auth.currentSession; - if ((state.value == null && !state.isLoading) || currentSession == null) { - AppLogger.i( - 'AuthNotifier: Skipping profile apply because user logged out during refresh', - ); - _lastRefresh = DateTime.now(); - return mergedUser; - } + Future acceptTerms() => + ref.read(profileHydrationServiceProvider.notifier).acceptTerms(); - final saves = >[ - storage.saveEzygoToken(mergedUser.ezygoToken.value).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist ezygo token (profile apply)', - e, - st, - ); - }), - storage.saveSupabaseUserId(mergedUser.supabaseUserId).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist supabase id (profile apply)', - e, - st, - ); - }), - storage.saveSettings(settings).catchError((Object e, StackTrace st) { - AppLogger.e( - 'AuthNotifier: Failed to persist settings (profile apply)', - e, - st, - ); - }), - storage.saveUserProfile(profile).catchError((Object e, StackTrace st) { - AppLogger.e( - 'AuthNotifier: Failed to persist profile (profile apply)', - e, - st, - ); - }), - if (mergedUser.ezygoId != null) - storage.saveEzygoUserId(mergedUser.ezygoId!).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist ezygo id (profile apply)', - e, - st, - ); - }), - if (mergedUser.username != null) - storage.saveUsername(mergedUser.username!).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist username (profile apply)', - e, - st, - ); - }), - if (mergedUser.termsVersion != null) - storage.saveTermsVersion(mergedUser.termsVersion!).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist terms version (profile apply)', - e, - st, - ); - }), - if (nextAcademic != null) - storage.saveAcademicState(nextAcademic).catchError(( - Object e, - StackTrace st, - ) { - AppLogger.e( - 'AuthNotifier: Failed to persist academic state (profile apply)', - e, - st, - ); - }), - ]; - await Future.wait(saves); - - final newSem = profile.currentSemester; - final newYear = profile.currentYear; - final newClassLabel = profile.classField?.name; - - final oldSem = currentUser.profile?.currentSemester; - final oldYear = currentUser.profile?.currentYear; - final oldClassLabel = currentUser.profile?.classField?.name; - - final classChanged = - oldClassLabel != null && oldClassLabel != newClassLabel; - final academicChanged = - (oldSem != null && oldSem != newSem) || - (oldYear != null && oldYear != newYear); - - if (academicChanged || classChanged) { - AppLogger.i( - 'AuthNotifier: Academic context or class changed (sem: $oldSem->$newSem, year: $oldYear->$newYear, class: $oldClassLabel->$newClassLabel). ' - 'Purging caches and invalidating page providers.', - ); - ref.read(apiServiceProvider).clearCaches(); - await storage.clearAllCachedData(); + Future updateAvatar(String publicUrl) => ref + .read(profileHydrationServiceProvider.notifier) + .updateAvatar(publicUrl); - ref.invalidate(academicProvider); - } else { - if (nextAcademic != null) { - AppLogger.safeUnawait( - Future.delayed(Duration.zero, () { - ref.read(academicProvider.notifier).state = AsyncValue.data( - nextAcademic, - ); - }).catchError((Object e, StackTrace st) { - AppLogger.e('AuthNotifier: Deferred academic set failed', e, st); - }), - 'AuthNotifier: deferred academic set', - ); - } - } + Future deleteAccount() => + ref.read(profileHydrationServiceProvider.notifier).deleteAccount(); - if (updateState) state = AsyncValue.data(mergedUser); - _lastRefresh = DateTime.now(); - return mergedUser; - } + Future updateAcademicContext(String? sem, String? year) => ref + .read(academicContextServiceProvider.notifier) + .updateAcademicContext(sem, year); - Future _getFreshSupabaseToken() async { - try { - final session = ref.read(supabaseClientProvider).auth.currentSession; - if (session == null) return null; - if (session.isExpired) { - final res = await ref - .read(supabaseClientProvider) - .auth - .refreshSession() - .timeout( - kDebugMode - ? const Duration(seconds: 45) - : const Duration(seconds: 30), - ); - return res.session?.accessToken; - } - return session.accessToken; - } on AuthException catch (e) { - // User Request (Verification): Match proxy.ts logic from web app - // Only return null (which triggers logout) if the session is definitively dead. - // Codes like 'refresh_token_not_found' or 400 with 'Invalid Refresh Token' are terminal. - final isTerminal = - e.statusCode == '400' || - e.message.contains('refresh_token_not_found') || - e.message.contains('Invalid Refresh Token') || - e.message.contains('not found'); + Future updateDefaultInstitution(int institutionId) => ref + .read(academicContextServiceProvider.notifier) + .updateDefaultInstitution(institutionId); - if (isTerminal) { - AppLogger.e('AuthNotifier: Supabase session terminal failure', e); - return null; - } + Future> fetchInstitutions() => + ref.read(academicContextServiceProvider.notifier).fetchInstitutions(); - // For other AuthExceptions (e.g. 500s, rate limits), treat as transient - // network errors to avoid logging out the user prematurely. - AppLogger.e( - 'AuthNotifier: Supabase transient auth error. Preventing logout.', - e, - ); - throw AppException( - message: 'Supabase service issues: ${e.message}', - type: AppExceptionType.network, - originalError: e, - ); - } on Object catch (e) { - AppLogger.e( - 'AuthNotifier: Network error during token refresh. Preventing logout.', - e, - ); - throw AppException( - message: 'Could not refresh session due to network failure.', - type: AppExceptionType.network, - originalError: e, - ); - } - } + // โ”€โ”€โ”€ Private Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ String? _extractTermsVersion(Map data) { if (data['terms_version'] != null) return data['terms_version'].toString(); diff --git a/mobile/lib/providers/dashboard_provider.dart b/mobile/lib/providers/dashboard_provider.dart index 2c2b0be3..1650c2c4 100644 --- a/mobile/lib/providers/dashboard_provider.dart +++ b/mobile/lib/providers/dashboard_provider.dart @@ -215,15 +215,21 @@ class DashboardNotifier extends AsyncNotifier { var sharedCourses = []; var sharedInstructors = []; - await Future.wait([ + Future resolveAttendance() async { + final existing = + attendanceToUse ?? + _cachedAttendance ?? + ref.read(trackingProvider).value?.officialReport; + if (existing != null) return existing; + return _fetchAttendanceOnce(api: api, storage: storage); + } + + await Future.wait([ api.fetchCourses(storage).then((res) => coursesResponse = res), - (attendanceToUse != null - ? Future.value(attendanceToUse) - : _fetchAttendanceOnce(api: api, storage: storage)) - .then((res) { - if (res == null) throw Exception('No attendance data'); - attendance = res; - }), + resolveAttendance().then((res) { + if (res == null) throw Exception('No attendance data'); + attendance = res; + }), if (classId != null) ...[ // Fetch Class Courses api.fetchClassCourses(classId).then((coursesRes) { @@ -412,7 +418,8 @@ class DashboardNotifier extends AsyncNotifier { // --- SORTING LOGIC (WEBSITE PARITY) --- // Pre-calculate sorting criteria to avoid redundant math during sort - final target = (auth?.settings.targetPercentage ?? 75).toDouble(); + final defaultTarget = (auth?.settings.targetPercentage ?? 75).toDouble(); + final courseTargets = auth?.settings.courseTargets ?? const {}; final metaMap = < @@ -424,7 +431,14 @@ class DashboardNotifier extends AsyncNotifier { course: c, stats: stats, disabledCodes: disabledCodes, - targetPercentage: target, + targetPercentage: () { + final stdCode = utils.standardizeCourseCode(c.code ?? c.safeId); + final val = + courseTargets[stdCode] ?? + courseTargets[c.code] ?? + courseTargets[c.safeId]; + return (val ?? defaultTarget).toDouble(); + }(), ), }; diff --git a/mobile/lib/providers/profile_hydration_service.dart b/mobile/lib/providers/profile_hydration_service.dart new file mode 100644 index 00000000..8ff2deb4 --- /dev/null +++ b/mobile/lib/providers/profile_hydration_service.dart @@ -0,0 +1,603 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ghostclass/config/app_config.dart'; +import 'package:ghostclass/logic/app_exception.dart'; +import 'package:ghostclass/logic/encrypted_value.dart'; +import 'package:ghostclass/logic/error_utils.dart'; +import 'package:ghostclass/models/institution.dart'; +import 'package:ghostclass/models/user.dart'; +import 'package:ghostclass/providers/academic_provider.dart'; +import 'package:ghostclass/providers/auth_provider.dart'; +import 'package:ghostclass/services/analytics_service.dart'; +import 'package:ghostclass/services/api_service.dart'; +import 'package:ghostclass/services/logger.dart'; +import 'package:ghostclass/services/secure_storage.dart'; + +final profileHydrationServiceProvider = + NotifierProvider( + ProfileHydrationService.new, + ); + +class ProfileHydrationService extends Notifier { + Future? _refreshProfileInFlight; + Future? _profileRefreshInFlight; + int _profileRefreshGeneration = 0; + DateTime? _lastRefresh; + + @override + void build() { + // No-op state + } + + void reset() { + _profileRefreshGeneration++; + _refreshProfileInFlight = null; + _profileRefreshInFlight = null; + _lastRefresh = null; + } + + Future refreshProfile({ + bool force = false, + }) async { + final inFlight = _refreshProfileInFlight; + if (inFlight != null) return inFlight; + + final future = _refreshProfileInternal( + force: force, + ); + _refreshProfileInFlight = future; + return future.whenComplete(() { + if (identical(_refreshProfileInFlight, future)) { + _refreshProfileInFlight = null; + } + }); + } + + Future syncProfile() => refreshProfile(force: true); + + Future _refreshProfileInternal({ + bool force = false, + }) async { + final authNotifier = ref.read(authProvider.notifier); + final currentUser = ref.read(authProvider).value; + if (currentUser == null) return; + + if (!force && + _lastRefresh != null && + DateTime.now().difference(_lastRefresh!) < const Duration(minutes: 5)) { + return; + } + + if (force && + _lastRefresh != null && + DateTime.now().difference(_lastRefresh!) < const Duration(seconds: 5)) { + return; + } + + try { + final token = await authNotifier.getFreshSupabaseToken(); + if (token == null) { + await authNotifier.logout(); + return; + } + + await _fetchAndApplyServerProfile( + currentUser, + supabaseToken: token, + sync: force, + force: force, + ); + } on Object catch (e) { + if (e is AppException && e.isAuthError) { + final isSecurityError = e.details?['type'] == 'security'; + final isCritical = e.details?['criticalRisk'] == true; + + if (isSecurityError && !isCritical) { + AppLogger.e( + 'AuthNotifier: Non-critical security block. Skipping logout.', + ); + } else { + if (isCritical) { + AppLogger.e('AuthNotifier: CRITICAL SECURITY RISK. Logging out.'); + } + await authNotifier.logout(); + } + } + } + } + + Future acceptTerms() async { + final authNotifier = ref.read(authProvider.notifier); + final user = ref.read(authProvider).value; + if (user == null) return; + + final token = await authNotifier.getFreshSupabaseToken(); + if (token == null) return; + + final api = ref.read(apiServiceProvider); + final storage = ref.read(secureStorageProvider); + final version = AppConfig.termsVersion; + + try { + await api.acceptTerms(token, version); + await storage.saveTermsVersion(version); + authNotifier.updateState(user.copyWith(termsVersion: version)); + try { + await AnalyticsService.instance.logAcceptTerms(version); + } on Object catch (_) {} + } on Object catch (e) { + AppLogger.e('AuthNotifier: Terms acceptance failed', e); + rethrow; + } + } + + Future buildFromCurrentSession() async { + final session = ref.read(supabaseClientProvider).auth.currentSession; + if (session == null) return null; + + final storage = ref.read(secureStorageProvider); + final ezygoToken = await storage.getNormalizedEzygoToken(); + + final user = await buildStoredUserForIdentity( + supabaseUserId: session.user.id, + ezygoToken: ezygoToken ?? '', + ); + + // Trigger profile sync in parallel without blocking startup/splash screen + AppLogger.safeUnawait( + runBackgroundStartupHydration(user), + 'AuthNotifier: background startup hydration', + ); + + return user.copyWith(isSyncing: true); + } + + Future runBackgroundStartupHydration( + AuthenticatedUser cachedUser, { + bool silent = false, + }) async { + final api = ref.read(apiServiceProvider)..suppress401 = true; + final authNotifier = ref.read(authProvider.notifier); + try { + final token = await authNotifier.getFreshSupabaseToken(); + if (token == null) { + throw const AppException( + message: 'Auth session dead', + type: AppExceptionType.unauthorized, + ); + } + + // 1. Fetch Profile and trigger backend full EzyGo sync synchronously + await runProfileRefresh( + cachedUser, + supabaseToken: token, + sync: true, + force: true, + ); + _lastRefresh = DateTime.now(); + + // Pre-fetch institutions so they are ready in settings + AppLogger.safeUnawait( + ref.read(institutionsProvider.future).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e('AuthNotifier: prefetch institutions failed', e, st); + return []; + }), + 'AuthNotifier: prefetch institutions', + ); + + // If we are not running silently, clear the syncing status to unlock the UI + if (!silent) { + final finalUser = ref.read(authProvider).value; + if (finalUser != null && + finalUser.supabaseUserId == cachedUser.supabaseUserId) { + authNotifier.updateState(finalUser.copyWith(isSyncing: false)); + } + } + } on Object catch (e) { + if (e is AppException && e.isAuthError) { + AppLogger.e('AuthNotifier: Background auth error, logging out', e); + await authNotifier.logout(); + return; + } + + AppLogger.e( + 'AuthNotifier: Background startup hydration failed. Using cached data.', + e, + ); + if (!silent) { + final currentUser = ref.read(authProvider).value; + if (currentUser != null && + currentUser.supabaseUserId == cachedUser.supabaseUserId) { + authNotifier.updateState(currentUser.copyWith(isSyncing: false)); + } + } + } finally { + api.suppress401 = false; + } + } + + Future updateAvatar(String publicUrl) async { + final authNotifier = ref.read(authProvider.notifier); + final user = ref.read(authProvider).value; + if (user == null) return; + await ref + .read(profileServiceProvider) + .updateAvatar(user.supabaseUserId, publicUrl); + final updatedProfile = user.profile?.copyWith(avatarUrl: () => publicUrl); + if (updatedProfile != null) { + await ref.read(secureStorageProvider).saveUserProfile(updatedProfile); + } + authNotifier.updateState(user.copyWith(profile: updatedProfile)); + } + + Future deleteAccount() async { + final authNotifier = ref.read(authProvider.notifier); + final user = ref.read(authProvider).value; + if (user == null) return; + try { + await ref.read(profileServiceProvider).deleteAccount(user.supabaseUserId); + await authNotifier.logout(); + } on Object catch (e) { + AppLogger.e('AuthNotifier: Account deletion failed', e); + rethrow; + } + } + + Future buildStoredUserForIdentity({ + required String supabaseUserId, + required String ezygoToken, + String? usernameOverride, + String? ezygoIdOverride, + String? termsVersionOverride, + UserSettings? settingsFallback, + }) async { + final storage = ref.read(secureStorageProvider); + + final identityReads = await Future.wait([ + storage.getSupabaseUserId(), + storage.getEzygoUserId(), + ]); + final storedSupabaseUserId = identityReads[0]; + final storedEzygoUserId = identityReads[1]; + + final matchesIdentity = + storedSupabaseUserId == null || + storedSupabaseUserId == supabaseUserId || + (ezygoIdOverride != null && storedEzygoUserId == ezygoIdOverride); + + Future usernameFuture() async => + matchesIdentity ? storage.getUsername() : null; + + Future termsVersionFuture() async => + matchesIdentity ? storage.getTermsVersion() : null; + + Future settingsFuture() async { + if (!matchesIdentity) { + return settingsFallback ?? UserSettings.defaults(); + } + return await storage.getSettings() ?? + settingsFallback ?? + UserSettings.defaults(); + } + + Future profileFuture() async => + matchesIdentity ? storage.getUserProfile() : null; + + final hydrationReads = await Future.wait([ + usernameFuture(), + termsVersionFuture(), + settingsFuture(), + profileFuture(), + ]); + final storedUsername = hydrationReads[0] as String?; + final storedTermsVersion = hydrationReads[1] as String?; + final hydratedSettings = hydrationReads[2] as UserSettings; + final hydratedProfile = hydrationReads[3] as UserProfile?; + + return AuthenticatedUser( + supabaseUserId: supabaseUserId, + ezygoToken: EncryptedValue.fromPlaintext(ezygoToken), + ezygoId: ezygoIdOverride ?? (matchesIdentity ? storedEzygoUserId : null), + username: usernameOverride ?? storedUsername, + termsVersion: termsVersionOverride ?? storedTermsVersion, + settings: hydratedSettings, + profile: hydratedProfile, + ); + } + + Future _fetchAndApplyServerProfile( + AuthenticatedUser user, { + String? supabaseToken, + bool updateState = true, + bool sync = false, + bool force = false, + }) async { + final authNotifier = ref.read(authProvider.notifier); + final refreshGeneration = _profileRefreshGeneration; + final token = supabaseToken ?? await authNotifier.getFreshSupabaseToken(); + if (token == null) { + throw const AppException( + message: 'Session dead', + type: AppExceptionType.unauthorized, + ); + } + + final api = ref.read(apiServiceProvider); + final response = await api.refreshProfile( + token, + sync: sync, + force: force, + ); + + if (response.statusCode == 401) { + final data = response.data as Map?; + final isTransientSecurity = isTransientSecurityPayload(data); + throw AppException( + message: isTransientSecurity + ? 'Device verification is temporarily unavailable. Please retry in a few moments.' + : formatApiError(data, 'Security Verification'), + type: isTransientSecurity + ? AppExceptionType.network + : AppExceptionType.unauthorized, + statusCode: 401, + details: data, + ); + } + + if (response.statusCode != 200 || response.data == null) { + if (response.statusCode != null && response.statusCode! >= 500) { + throw const AppException( + message: 'Ezygo issues (5xx)', + type: AppExceptionType.server, + ); + } + throw const AppException( + message: 'Profile sync failed', + type: AppExceptionType.server, + ); + } + + final updatedUser = await applyProfileResponseData( + currentUser: user, + data: response.data as Map, + updateState: false, + ); + + if (updateState && refreshGeneration == _profileRefreshGeneration) { + final currentState = ref.read(authProvider).value; + if (currentState == null || + currentState.supabaseUserId == user.supabaseUserId) { + authNotifier.updateState(updatedUser); + } + } + + return updatedUser; + } + + Future runProfileRefresh( + AuthenticatedUser user, { + String? supabaseToken, + bool updateState = true, + bool sync = false, + bool force = false, + }) { + final inFlight = _profileRefreshInFlight; + if (inFlight != null) return inFlight; + + final future = _fetchAndApplyServerProfile( + user, + supabaseToken: supabaseToken, + updateState: updateState, + sync: sync, + force: force, + ); + _profileRefreshInFlight = future; + + return future.whenComplete(() { + if (identical(_profileRefreshInFlight, future)) { + _profileRefreshInFlight = null; + } + }); + } + + Future applyProfileResponseData({ + required AuthenticatedUser currentUser, + required Map data, + bool updateState = true, + }) async { + final authNotifier = ref.read(authProvider.notifier); + final storage = ref.read(secureStorageProvider); + final rawSettings = data['settings'] as Map?; + final baseSettings = rawSettings != null + ? UserSettings.fromJson(rawSettings) + : currentUser.settings; + + final settings = baseSettings; + + final rawProfile = data.containsKey('profile') + ? Map.from(data['profile'] as Map) + : Map.from(data); + + rawProfile['current_semester'] = + data['current_semester'] ?? rawProfile['current_semester']; + rawProfile['current_year'] = + data['current_year'] ?? rawProfile['current_year']; + + final profile = UserProfile.fromJson(rawProfile); + + final mergedUser = currentUser.copyWith( + settings: settings, + profile: profile, + ezygoToken: EncryptedValue.fromPlaintext( + (data['ezygo_token'] as String?) ?? currentUser.ezygoToken.value, + ), + ezygoId: + (data['id'] ?? + data['user_id'] ?? + data['ezygo_user_id'] ?? + data['ezygo_id']) + ?.toString() ?? + currentUser.ezygoId, + termsVersion: _extractTermsVersion(data) ?? currentUser.termsVersion, + username: data['username'] as String? ?? currentUser.username, + ); + + final nextAcademic = + (data['current_semester'] != null && data['current_year'] != null) + ? AcademicState( + semester: data['current_semester']! as String, + year: data['current_year']! as String, + ) + : null; + + final currentSession = ref.read(supabaseClientProvider).auth.currentSession; + if ((ref.read(authProvider).value == null && + !ref.read(authProvider).isLoading) || + currentSession == null) { + AppLogger.i( + 'AuthNotifier: Skipping profile apply because user logged out during refresh', + ); + _lastRefresh = DateTime.now(); + return mergedUser; + } + + final saves = >[ + storage.saveEzygoToken(mergedUser.ezygoToken.value).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist ezygo token (profile apply)', + e, + st, + ); + }), + storage.saveSupabaseUserId(mergedUser.supabaseUserId).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist supabase id (profile apply)', + e, + st, + ); + }), + storage.saveSettings(settings).catchError((Object e, StackTrace st) { + AppLogger.e( + 'AuthNotifier: Failed to persist settings (profile apply)', + e, + st, + ); + }), + storage.saveUserProfile(profile).catchError((Object e, StackTrace st) { + AppLogger.e( + 'AuthNotifier: Failed to persist profile (profile apply)', + e, + st, + ); + }), + if (mergedUser.ezygoId != null) + storage.saveEzygoUserId(mergedUser.ezygoId!).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist ezygo id (profile apply)', + e, + st, + ); + }), + if (mergedUser.username != null) + storage.saveUsername(mergedUser.username!).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist username (profile apply)', + e, + st, + ); + }), + if (mergedUser.termsVersion != null) + storage.saveTermsVersion(mergedUser.termsVersion!).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist terms version (profile apply)', + e, + st, + ); + }), + if (nextAcademic != null) + storage.saveAcademicState(nextAcademic).catchError(( + Object e, + StackTrace st, + ) { + AppLogger.e( + 'AuthNotifier: Failed to persist academic state (profile apply)', + e, + st, + ); + }), + ]; + await Future.wait(saves); + + final newSem = profile.currentSemester; + final newYear = profile.currentYear; + final newClassLabel = profile.classField?.name; + + final oldSem = currentUser.profile?.currentSemester; + final oldYear = currentUser.profile?.currentYear; + final oldClassLabel = currentUser.profile?.classField?.name; + + final classChanged = + oldClassLabel != null && oldClassLabel != newClassLabel; + final academicChanged = + (oldSem != null && oldSem != newSem) || + (oldYear != null && oldYear != newYear); + + if (academicChanged || classChanged) { + AppLogger.i( + 'AuthNotifier: Academic context or class changed (sem: $oldSem->$newSem, year: $oldYear->$newYear, class: $oldClassLabel->$newClassLabel). ' + 'Purging caches and invalidating page providers.', + ); + ref.read(apiServiceProvider).clearCaches(); + await storage.clearAllCachedData(); + + ref.invalidate(academicProvider); + } else { + if (nextAcademic != null) { + AppLogger.safeUnawait( + Future.delayed(Duration.zero, () { + ref + .read(academicProvider.notifier) + .updateState( + nextAcademic, + ); + }).catchError((Object e, StackTrace st) { + AppLogger.e('AuthNotifier: Deferred academic set failed', e, st); + }), + 'AuthNotifier: deferred academic set', + ); + } + } + + if (updateState) authNotifier.updateState(mergedUser); + _lastRefresh = DateTime.now(); + return mergedUser; + } + + String? _extractTermsVersion(Map data) { + if (data['terms_version'] != null) return data['terms_version'].toString(); + final profile = data['profile'] as Map?; + if (profile != null && profile['terms_version'] != null) { + return profile['terms_version'].toString(); + } + return null; + } +} diff --git a/mobile/lib/providers/session_healing_service.dart b/mobile/lib/providers/session_healing_service.dart new file mode 100644 index 00000000..61004536 --- /dev/null +++ b/mobile/lib/providers/session_healing_service.dart @@ -0,0 +1,243 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ghostclass/config/app_config.dart'; +import 'package:ghostclass/logic/app_exception.dart'; +import 'package:ghostclass/logic/encrypted_value.dart'; +import 'package:ghostclass/providers/auth_provider.dart'; +import 'package:ghostclass/providers/profile_hydration_service.dart'; +import 'package:ghostclass/providers/security_provider.dart'; +import 'package:ghostclass/services/api_service.dart'; +import 'package:ghostclass/services/logger.dart'; +import 'package:ghostclass/services/secure_storage.dart'; + +final sessionHealingServiceProvider = + NotifierProvider( + SessionHealingService.new, + ); + +class SessionHealingService extends Notifier { + int _consecutiveHealFailures = 0; + bool _isRefreshing = false; + + bool get isRefreshing => _isRefreshing; + + @override + void build() { + // No-op state + } + + void reset() { + _consecutiveHealFailures = 0; + _isRefreshing = false; + } + + Future handleUnauthorized() async { + final authNotifier = ref.read(authProvider.notifier); + if (_isRefreshing || authNotifier.isInitializing) return; + _isRefreshing = true; + final healAttemptId = DateTime.now().microsecondsSinceEpoch.toString(); + + final api = ref.read(apiServiceProvider)..suppress401 = true; + AppLogger.e('AuthNotifier: 401 DETECTED. Attempting self-healing...'); + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Starting heal with $_consecutiveHealFailures prior failures', + ); + + try { + final backoffMs = _consecutiveHealFailures > 0 + ? (500 * (1 << (_consecutiveHealFailures - 1))).clamp(500, 5000) + : 0; + if (backoffMs > 0) { + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Waiting ${backoffMs}ms before retry (attempt ${_consecutiveHealFailures + 1})', + ); + await Future.delayed(Duration(milliseconds: backoffMs)); + } + + final oldToken = ref.read(authProvider).value?.ezygoToken; + if (ref.read(authProvider).value == null) { + final recoveredUser = await ref + .read(profileHydrationServiceProvider.notifier) + .buildFromCurrentSession(); + if (recoveredUser != null) { + authNotifier.updateState(recoveredUser); + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Recovered user from session', + ); + } + } + + final supabaseToken = await authNotifier.getFreshSupabaseToken(); + if (supabaseToken == null) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Supabase token unavailable, logging out', + ); + await authNotifier.logout(); + return; + } + + Response? syncRes; + try { + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Calling syncMobileAuth (attempt 1/2)', + ); + syncRes = await api + .syncMobileAuth(supabaseToken) + .timeout(AppConfig.defaultTimeout); + } on TimeoutException catch (e, st) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth timed out (attempt 1)', + e, + st, + ); + syncRes = null; + } + + if (syncRes == null || syncRes.statusCode != 200) { + try { + await Future.delayed(const Duration(milliseconds: 500)); + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Calling syncMobileAuth (attempt 2/2)', + ); + syncRes = await api + .syncMobileAuth(supabaseToken) + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); + } on Object catch (e, st) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth retry failed', + e, + st, + ); + syncRes = null; + } + } + + if (syncRes != null && + syncRes.statusCode == 200 && + syncRes.data is Map) { + final syncData = syncRes.data as Map; + final syncedToken = (syncData['ezygo_token'] as String?)?.trim(); + + if (syncedToken != null && syncedToken.isNotEmpty) { + try { + await ref.read(secureStorageProvider).saveEzygoToken(syncedToken); + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Persisted synced ezygo token', + ); + } on Object catch (e, st) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Failed to persist synced ezygo token', + e, + st, + ); + } + + final current = ref.read(authProvider).value; + if (current != null) { + final syncedTermsVersion = syncData['terms_version'] as String?; + final syncedEzygoId = syncData['id']?.toString(); + authNotifier.updateState( + current.copyWith( + ezygoToken: EncryptedValue.fromPlaintext(syncedToken), + termsVersion: syncedTermsVersion, + ezygoId: syncedEzygoId, + ), + ); + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Updated state with synced token', + ); + } + } + } + + AppLogger.d('AuthNotifier [HEAL-$healAttemptId]: Refreshing profile'); + await ref + .read(profileHydrationServiceProvider.notifier) + .refreshProfile(force: true); + final newToken = ref.read(authProvider).value?.ezygoToken; + + if (newToken != null && newToken != oldToken) { + AppLogger.i( + 'AuthNotifier [HEAL-$healAttemptId]: SELF-HEALING SUCCESSFUL. Token changed', + ); + _consecutiveHealFailures = 0; + } else { + _consecutiveHealFailures++; + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Self-healing did not produce a new token. Consecutive failures: $_consecutiveHealFailures', + ); + + if (_consecutiveHealFailures >= 3) { + final lastError = ref.read(authProvider).error; + final isSecurityError = + lastError is AppException && + lastError.details?['type'] == 'security'; + + if (isSecurityError) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Terminal security block detected. Not logging out.', + ); + _consecutiveHealFailures = 0; + } else { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Terminal 401 loop detected after $_consecutiveHealFailures attempts. Logging out to protect state.', + ); + await authNotifier.logout(); + } + } + } + } on Object catch (e) { + AppLogger.e('AuthNotifier [HEAL-$healAttemptId]: Self-healing error', e); + if (e is AppException && e.isAuthError) { + final isSecurityError = e.details?['type'] == 'security'; + final isCritical = e.details?['criticalRisk'] == true; + + if (isSecurityError && !isCritical) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: Non-critical security block. Skipping logout.', + ); + } else { + if (isCritical) { + AppLogger.e( + 'AuthNotifier [HEAL-$healAttemptId]: CRITICAL SECURITY RISK. Logging out.', + ); + } + await authNotifier.logout(); + } + } + } finally { + final cooldownMs = _consecutiveHealFailures > 0 + ? (500 * (1 << (_consecutiveHealFailures - 1))).clamp(500, 5000) + : 1000; + AppLogger.d( + 'AuthNotifier [HEAL-$healAttemptId]: Cooldown for ${cooldownMs}ms before next 401 can trigger', + ); + await Future.delayed(Duration(milliseconds: cooldownMs)); + api.suppress401 = false; + _isRefreshing = false; + } + } + + Future handleSecurityLockdown(Map data) async { + AppLogger.e('AuthNotifier: SECURITY LOCKDOWN TRIGGERED'); + + ref + .read(securityFailureProvider.notifier) + .setFailure( + data['title'], + criticalRisk: true, + reason: data['reason'], + action: data['action'], + source: data['technicalDetails'], + ); + + await ref.read(authProvider.notifier).logout(force: true); + } +} diff --git a/mobile/lib/screens/dashboard_screen.dart b/mobile/lib/screens/dashboard_screen.dart index aff1256a..b63f99c0 100644 --- a/mobile/lib/screens/dashboard_screen.dart +++ b/mobile/lib/screens/dashboard_screen.dart @@ -139,6 +139,7 @@ class _DashboardContent extends ConsumerWidget { stats: data.stats, targetPercentage: targetValue, disabledCodes: data.disabledCodes, + courseTargets: userSettings?.courseTargets ?? const {}, ), StatsGridSection(stats: data.stats, activeCount: data.courses.length), const CourseLineupHeader(), diff --git a/mobile/lib/screens/ghostclass_screen.dart b/mobile/lib/screens/ghostclass_screen.dart index 52b6b1dc..19dbb88b 100644 --- a/mobile/lib/screens/ghostclass_screen.dart +++ b/mobile/lib/screens/ghostclass_screen.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:ghostclass/models/dashboard_stats.dart'; import 'package:ghostclass/models/institution.dart'; import 'package:ghostclass/providers/auth_provider.dart'; +import 'package:ghostclass/providers/dashboard_provider.dart'; import 'package:ghostclass/providers/theme_provider.dart'; import 'package:ghostclass/providers/ui_state_provider.dart'; import 'package:ghostclass/services/logger.dart'; @@ -552,6 +554,9 @@ class GhostClassScreen extends ConsumerWidget { final primary = ghostColors?.brandPrimary ?? Theme.of(context).colorScheme.primary; + final dashboardData = ref.read(dashboardProvider).value; + final courses = dashboardData?.courses ?? []; + ref.read(uiModalOpenProvider.notifier).setOpen(true); await showModalBottomSheet( context: context, @@ -563,28 +568,39 @@ class GhostClassScreen extends ConsumerWidget { ), builder: (context) { var localTarget = user.settings.targetPercentage; + final localCourseTargets = Map.from( + user.settings.courseTargets, + ); + var isSaving = false; + return StatefulBuilder( builder: (context, setModalState) => Container( - padding: const EdgeInsets.all(32), + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.85, + ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.onSurface.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(2), + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(2), + ), ), ), - const SizedBox(height: 24), + const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Target Percentage', + 'Attendance Targets', style: GoogleFonts.manrope( fontSize: 20, fontWeight: FontWeight.w800, @@ -592,19 +608,29 @@ class GhostClassScreen extends ConsumerWidget { ), ), Text( - '$localTarget%', + 'Default: $localTarget%', style: GoogleFonts.manrope( - fontSize: 28, - fontWeight: FontWeight.w900, + fontSize: 16, + fontWeight: FontWeight.w800, color: primary, ), ), ], ), - const SizedBox(height: 40), - // Custom labels above slider + const SizedBox(height: 16), + Text( + 'Default Universal Target', + style: GoogleFonts.manrope( + fontSize: 12, + fontWeight: FontWeight.w700, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 8), Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [75, 80, 85, 90, 95].map((val) { @@ -621,7 +647,6 @@ class GhostClassScreen extends ConsumerWidget { : Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.6), - letterSpacing: 1, ), ); }).toList(), @@ -640,10 +665,6 @@ class GhostClassScreen extends ConsumerWidget { thumbColor: primary, overlayColor: primary.withValues(alpha: 0.1), valueIndicatorColor: primary, - valueIndicatorTextStyle: GoogleFonts.manrope( - color: Colors.white, - fontWeight: FontWeight.bold, - ), ), child: Slider( value: localTarget.clamp(75, 95).toDouble(), @@ -654,38 +675,258 @@ class GhostClassScreen extends ConsumerWidget { setModalState(() => localTarget = val.toInt()), ), ), - const SizedBox(height: 40), + const SizedBox(height: 16), + if (courses.isNotEmpty) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Course-Specific Targets', + style: GoogleFonts.manrope( + fontSize: 13, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + if (localCourseTargets.isNotEmpty) + TextButton( + onPressed: () { + setModalState(localCourseTargets.clear); + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + 'Reset All', + style: GoogleFonts.manrope( + fontSize: 12, + fontWeight: FontWeight.w700, + color: primary, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: courses.length, + separatorBuilder: (_, _) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final course = courses[index]; + final code = course.code ?? course.safeId; + final stdCode = DashboardStats.standardize(code); + final customVal = + localCourseTargets[stdCode] ?? + localCourseTargets[code] ?? + localCourseTargets[course.id.toString()]; + final activeVal = customVal ?? localTarget; + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: customVal != null + ? primary.withValues(alpha: 0.4) + : Theme.of(context).colorScheme.outlineVariant + .withValues(alpha: 0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + code.toUpperCase(), + style: GoogleFonts.manrope( + fontSize: 12, + fontWeight: FontWeight.w800, + color: primary, + ), + ), + Text( + course.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.manrope( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Theme.of( + context, + ).colorScheme.onSurface, + ), + ), + ], + ), + ), + if (customVal != null) + IconButton( + icon: const Icon( + Icons.rotate_left, + size: 18, + ), + onPressed: () { + setModalState(() { + localCourseTargets + ..remove(stdCode) + ..remove(code) + ..remove( + course.id.toString(), + ); + }); + }, + tooltip: 'Reset to default', + ), + ], + ), + const SizedBox(height: 6), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [75, 80, 85, 90, 95].map((val) { + final isSel = activeVal == val; + return InkWell( + onTap: () { + setModalState(() { + localCourseTargets[stdCode] = val; + }); + }, + borderRadius: BorderRadius.circular(10), + child: AnimatedContainer( + duration: const Duration( + milliseconds: 150, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: BoxDecoration( + color: isSel + ? primary + : Theme.of( + context, + ).colorScheme.surface, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: isSel + ? primary + : Theme.of(context) + .colorScheme + .outlineVariant + .withValues(alpha: 0.4), + ), + boxShadow: isSel + ? [ + BoxShadow( + color: primary.withValues( + alpha: 0.25, + ), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ] + : null, + ), + child: Text( + '$val%', + style: GoogleFonts.manrope( + fontSize: 11, + fontWeight: isSel + ? FontWeight.w800 + : FontWeight.w600, + color: isSel + ? Colors.white + : Theme.of( + context, + ).colorScheme.onSurface, + ), + ), + ), + ); + }).toList(), + ), + ], + ), + ); + }, + ), + ), + ], + const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: () async { - try { - await ref - .read(authProvider.notifier) - .updateSettings(targetPercentage: localTarget); - if (context.mounted) Navigator.pop(context); - } on Object catch (_) { - if (context.mounted) { - ServiceToast.show( - context, - 'Failed to update target', - isError: true, - ); - } - } - }, + onPressed: isSaving + ? null + : () async { + setModalState(() => isSaving = true); + try { + await ref + .read(authProvider.notifier) + .updateSettings( + targetPercentage: localTarget, + courseTargets: localCourseTargets, + ); + if (context.mounted) { + Navigator.pop(context); + ServiceToast.show( + context, + 'Attendance targets updated', + ); + } + } on Object catch (_) { + if (context.mounted) { + setModalState(() => isSaving = false); + ServiceToast.show( + context, + 'Failed to update targets', + isError: true, + ); + } + } + }, style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, + disabledBackgroundColor: primary.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), - child: Text( - 'Done', - style: GoogleFonts.manrope(fontWeight: FontWeight.bold), - ), + child: isSaving + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2.5, + color: Colors.white, + ), + ) + : Text( + 'Save Settings', + style: GoogleFonts.manrope( + fontSize: 16, + fontWeight: FontWeight.w800, + ), + ), ), ), ], diff --git a/mobile/lib/screens/login_screen.dart b/mobile/lib/screens/login_screen.dart index 3cc7c55c..096f390e 100644 --- a/mobile/lib/screens/login_screen.dart +++ b/mobile/lib/screens/login_screen.dart @@ -43,7 +43,12 @@ class _LoginScreenState extends ConsumerState late final SecurityGuard _securityGuard; void _startCooldown() { - _cooldownSecondsRemaining = 30; + // Exponential backoff cooldown: 30s -> 60s -> 120s -> 300s (max 5 minutes) + final attemptCount = (_consecutiveFailures - 2).clamp(1, 10); + var cooldown = 30 * (1 << (attemptCount - 1)); + if (cooldown > 300) cooldown = 300; + + _cooldownSecondsRemaining = cooldown; _cooldownTimer?.cancel(); _cooldownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (!mounted) { @@ -55,7 +60,6 @@ class _LoginScreenState extends ConsumerState _cooldownSecondsRemaining--; } else { _cooldownSecondsRemaining = 0; - _consecutiveFailures = 0; _cooldownTimer?.cancel(); _cooldownTimer = null; } diff --git a/mobile/lib/screens/navigation_shell.dart b/mobile/lib/screens/navigation_shell.dart index 7ed95c40..a5976259 100644 --- a/mobile/lib/screens/navigation_shell.dart +++ b/mobile/lib/screens/navigation_shell.dart @@ -838,8 +838,6 @@ class _NavigationShellState extends ConsumerState { if (isCriticalSecurityFailure) { if (Platform.isAndroid) { await SystemNavigator.pop(); - } else { - exit(0); } return; } diff --git a/mobile/lib/screens/splash_screen.dart b/mobile/lib/screens/splash_screen.dart index 10e0fb98..1fcbc1ce 100644 --- a/mobile/lib/screens/splash_screen.dart +++ b/mobile/lib/screens/splash_screen.dart @@ -21,7 +21,6 @@ import 'package:ghostclass/providers/notification_provider.dart'; import 'package:ghostclass/providers/score_provider.dart'; import 'package:ghostclass/providers/tracking_provider.dart'; import 'package:ghostclass/services/api_service.dart'; -import 'package:ghostclass/services/jwe_service.dart'; import 'package:ghostclass/services/logger.dart'; import 'package:ghostclass/services/push_notification_service.dart'; import 'package:ghostclass/services/secure_storage.dart'; @@ -139,26 +138,11 @@ class _SplashScreenState extends ConsumerState { authStack = st; }); - Object? jweError; - StackTrace? jweStack; - - final jweTask = JweService.instance - .preWarm() - .then((_) { - AppLogger.i('SplashScreen: jweTask completed'); - }) - .catchError((Object e, StackTrace st) { - AppLogger.e('SplashScreen: jweTask failed', e, st); - jweError = e; - jweStack = st; - }); - api.clearCaches(); AppLogger.i('SplashScreen: Awaiting Future.wait...'); await Future.wait([ integrityTask, authTask, - jweTask, ]); AppLogger.i('SplashScreen: Future.wait completed'); @@ -171,9 +155,6 @@ class _SplashScreenState extends ConsumerState { if (authError != null) { Error.throwWithStackTrace(authError!, authStack ?? StackTrace.current); } - if (jweError != null) { - Error.throwWithStackTrace(jweError!, jweStack ?? StackTrace.current); - } return _StartupSnapshot(user: user, versionResult: versionResult); }(); @@ -256,14 +237,6 @@ class _SplashScreenState extends ConsumerState { void _startPostNavigationPreloads({ required ApiService apiService, }) { - AppLogger.safeUnawait( - JweService.instance.preWarm().catchError( - (Object e, StackTrace st) => - AppLogger.e('SplashScreen: post-nav JWE pre-warm failed', e, st), - ), - 'SplashScreen: post-nav JWE pre-warm', - ); - AppLogger.safeUnawait( apiService.preWarm().catchError((Object e, StackTrace st) { AppLogger.e('SplashScreen: post-nav API pre-warm failed', e, st); @@ -283,12 +256,12 @@ class _SplashScreenState extends ConsumerState { // 2. Critical Security Check First try { - if (firebaseInitFuture != null) { + if (FirebaseInitializer.initFuture != null) { AppLogger.i( 'SplashScreen: Awaiting Firebase & App Check initialization...', ); try { - await firebaseInitFuture!; + await FirebaseInitializer.initFuture!; AppLogger.i( 'SplashScreen: Firebase & App Check initialization completed.', ); diff --git a/mobile/lib/screens/tracking_screen.dart b/mobile/lib/screens/tracking_screen.dart index d6dfca4f..db0fb9d8 100644 --- a/mobile/lib/screens/tracking_screen.dart +++ b/mobile/lib/screens/tracking_screen.dart @@ -38,6 +38,7 @@ class TrackingScreen extends ConsumerStatefulWidget { class _TrackingScreenState extends ConsumerState with ErrorHandlerMixin { String _selectedCourse = 'all'; + bool _onlyDutyLeave = false; @override Widget build(BuildContext context) { @@ -107,8 +108,24 @@ class _TrackingScreenState extends ConsumerState .toSet() ?? {}; + final processedGroupedByCourse = >{}; + if (_onlyDutyLeave) { + data.groupedByCourse.forEach((key, list) { + final filtered = list.where((r) { + final att = r.attendance; + return att == 225 || att == '225'; + }).toList(); + if (filtered.isNotEmpty) { + processedGroupedByCourse[key] = filtered; + } + }); + } else { + processedGroupedByCourse.addAll(data.groupedByCourse); + } + final filteredCourseKeys = _selectedCourse == 'all' ? sortedCourseKeys.where((k) { + if (!processedGroupedByCourse.containsKey(k)) return false; final mergedCourse = (dashboard?.courses ?? []) .cast() .firstWhere((c) => c?.safeId == k, orElse: () => null); @@ -119,7 +136,13 @@ class _TrackingScreenState extends ConsumerState ); return !disabledCodes.contains((displayCode ?? '').toUpperCase()); }).toList() - : sortedCourseKeys.where((k) => k == _selectedCourse).toList(); + : sortedCourseKeys + .where( + (k) => + k == _selectedCourse && + processedGroupedByCourse.containsKey(k), + ) + .toList(); return ServiceRefreshIndicator( onRefresh: () async { @@ -218,21 +241,32 @@ class _TrackingScreenState extends ConsumerState ), const SizedBox(height: 16), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( - child: TrackingFilterChip( - selectedCourse: _selectedCourse, - officialReport: data.officialReport, - allCourses: dashboard?.courses, - onTap: () => _showSubjectPicker( - data.groupedByCourse, - data.officialReport, - dashboard?.courses, - sortedCourseKeys, - ), - onClear: () => - setState(() => _selectedCourse = 'all'), + Expanded( + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + TrackingFilterChip( + selectedCourse: _selectedCourse, + officialReport: data.officialReport, + allCourses: dashboard?.courses, + onTap: () => _showSubjectPicker( + data.groupedByCourse, + data.officialReport, + dashboard?.courses, + sortedCourseKeys, + ), + onClear: () => + setState(() => _selectedCourse = 'all'), + ), + _DutyLeaveFilterChip( + isActive: _onlyDutyLeave, + onTap: () => setState( + () => _onlyDutyLeave = !_onlyDutyLeave, + ), + ), + ], ), ), ], @@ -246,6 +280,49 @@ class _TrackingScreenState extends ConsumerState const SliverFillRemaining( hasScrollBody: false, child: EmptyTrackingState(), + ) + else if (filteredCourseKeys.isEmpty) + SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + LucideIcons.filter, + size: 48, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.3), + ), + const SizedBox(height: 16), + Text( + 'No matching records', + style: GoogleFonts.manrope( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 8), + Text( + 'No tracking records match your selected filters.', + style: GoogleFonts.manrope( + fontSize: 13, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.4), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), ), // --- Sync Indicator --- @@ -314,7 +391,7 @@ class _TrackingScreenState extends ConsumerState for (final courseKey in filteredCourseKeys) TrackingCourseSection( courseKey: courseKey, - records: data.groupedByCourse[courseKey] ?? [], + records: processedGroupedByCourse[courseKey] ?? [], officialReport: data.officialReport, allCourses: dashboard?.courses, onDelete: _showDeleteRecordConfirm, @@ -711,3 +788,72 @@ class _ModalHeaderDelegate extends SliverPersistentHeaderDelegate { return oldDelegate.onClose != onClose; } } + +class _DutyLeaveFilterChip extends StatelessWidget { + const _DutyLeaveFilterChip({ + required this.isActive, + required this.onTap, + }); + final bool isActive; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final ghostColors = Theme.of(context).extension(); + final dutyColor = ghostColors?.accentOrange ?? Colors.orange; + + return Semantics( + button: true, + label: 'Filter by Duty Leave ${isActive ? "active" : "inactive"}', + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isActive + ? dutyColor.withValues(alpha: 0.1) + : Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isActive + ? dutyColor.withValues(alpha: 0.45) + : Theme.of( + context, + ).colorScheme.outlineVariant.withValues(alpha: 0.35), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isActive ? LucideIcons.check : LucideIcons.circle, + size: 14, + color: isActive + ? dutyColor + : Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.4), + ), + const SizedBox(width: 8), + Text( + 'Duty Leave Only', + style: GoogleFonts.manrope( + fontSize: 12, + fontWeight: FontWeight.w800, + color: isActive + ? Theme.of(context).colorScheme.onSurface + : Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/services/analytics_service.dart b/mobile/lib/services/analytics_service.dart index 1b863e74..c2174b6d 100644 --- a/mobile/lib/services/analytics_service.dart +++ b/mobile/lib/services/analytics_service.dart @@ -1,6 +1,7 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:ghostclass/services/logger.dart'; /// AnalyticsService /// Centralized wrapper around `FirebaseAnalytics` that exposes common @@ -15,6 +16,14 @@ class AnalyticsService { FirebaseAnalyticsObserver? _observer; bool get isInitialized => _analytics != null && _observer != null; + static bool _hasLoggedFailure = false; + + void _handleAnalyticsError(Object e, String context) { + if (!_hasLoggedFailure) { + _hasLoggedFailure = true; + AppLogger.w('AnalyticsService failure ($context): $e'); + } + } static Future initialize({FirebaseAnalytics? analyticsInstance}) async { final svc = AnalyticsService.instance; @@ -24,12 +33,16 @@ class AnalyticsService { .._env = kDebugMode ? 'development' : 'production'; try { await svc.analytics.setUserProperty(name: 'env', value: svc._env); - } on Object catch (_) {} + } on Object catch (e) { + svc._handleAnalyticsError(e, 'setUserProperty'); + } // Log an app_open event on cold start (includes env param) try { await svc.analytics.logAppOpen(parameters: svc._withEnvParams()); - } on Object catch (_) {} + } on Object catch (e) { + svc._handleAnalyticsError(e, 'logAppOpen'); + } } Future logScreenView(String screenName) async { @@ -38,7 +51,9 @@ class AnalyticsService { screenName: screenName, parameters: _withEnvParams(), ); - } on Object catch (_) {} + } on Object catch (e) { + _handleAnalyticsError(e, 'logScreenView'); + } } Future logLogin({String method = 'unknown'}) async { @@ -47,7 +62,9 @@ class AnalyticsService { name: 'login', parameters: _withEnvParams({'method': method}), ); - } on Object catch (_) {} + } on Object catch (e) { + _handleAnalyticsError(e, 'logLogin'); + } } Future logLogout() async { diff --git a/mobile/lib/services/api_service.dart b/mobile/lib/services/api_service.dart index 057ce313..8d4b7192 100644 --- a/mobile/lib/services/api_service.dart +++ b/mobile/lib/services/api_service.dart @@ -247,12 +247,15 @@ class ApiService { required String courseCode, required String instructorName, required String supabaseToken, + String? courseName, }) async { return client.post( '${AppConfig.ghostclassApiUrl}/instructors/upsert', data: { 'courseCode': courseCode, 'instructorName': instructorName, + if (courseName != null && courseName.isNotEmpty) + 'courseName': courseName, }, options: Options(headers: {'Authorization': 'Bearer $supabaseToken'}), ); @@ -269,19 +272,8 @@ class ApiService { } // --- Error Handling --- - bool _isTransientAppCheckFailure(String? text) { - final msg = (text ?? '').toLowerCase(); - if (msg.isEmpty) return false; - return msg.contains('too_many_attempts') || - msg.contains('timeout') || - msg.contains('network') || - msg.contains('connection') || - msg.contains('unavailable') || - msg.contains('rate limit') || - msg.contains('internal google server error') || - msg.contains('google_server_unavailable') || - msg.contains('-12'); - } + bool _isTransientAppCheckFailure(String? text) => + isTransientAppCheckFailure(text); AppException mapDioError(DioException e) { final status = e.response?.statusCode; diff --git a/mobile/lib/services/auth_service.dart b/mobile/lib/services/auth_service.dart index bb23bfd0..fa76ad60 100644 --- a/mobile/lib/services/auth_service.dart +++ b/mobile/lib/services/auth_service.dart @@ -1,5 +1,4 @@ import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/config/app_config.dart'; import 'package:ghostclass/logic/app_exception.dart'; @@ -16,9 +15,7 @@ class AuthService { final Ref _ref; static final String _ghostclassBaseUrl = AppConfig.ghostclassApiUrl; static final String _ezygoAuthUrl = AppConfig.ezygoAuthUrl; - static const Duration _loginTimeout = kDebugMode - ? Duration(seconds: 45) - : Duration(seconds: 30); + static final Duration _loginTimeout = AppConfig.defaultTimeout; static const Duration _provisionRetryBackoff = Duration(milliseconds: 500); Dio get _dio => _ref.read(dioServiceProvider).dio; @@ -190,6 +187,9 @@ class AuthService { ); } + static DateTime? _lastContactSubmission; + static const Duration _contactCooldown = Duration(seconds: 60); + Future> submitContact({ required String name, required String email, @@ -197,6 +197,21 @@ class AuthService { required String message, String? supabaseToken, }) async { + final now = DateTime.now(); + if (_lastContactSubmission != null && + now.difference(_lastContactSubmission!) < _contactCooldown) { + final remaining = + _contactCooldown.inSeconds - + now.difference(_lastContactSubmission!).inSeconds; + throw AppException( + message: + 'Please wait $remaining seconds before submitting another message.', + type: AppExceptionType.rateLimit, + statusCode: 429, + ); + } + _lastContactSubmission = now; + return _dio.post( '$_ghostclassBaseUrl/contact', data: { diff --git a/mobile/lib/services/dio_service.dart b/mobile/lib/services/dio_service.dart index 91bc9ef5..6e581de8 100644 --- a/mobile/lib/services/dio_service.dart +++ b/mobile/lib/services/dio_service.dart @@ -1,10 +1,11 @@ import 'dart:async'; + import 'package:dio/dio.dart'; import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/config/app_config.dart'; -import 'package:ghostclass/services/jwe_interceptor.dart'; +import 'package:ghostclass/logic/error_utils.dart'; import 'package:ghostclass/services/logger.dart'; import 'package:ghostclass/services/stealth_headers_service.dart'; import 'package:sentry_dio/sentry_dio.dart'; @@ -13,10 +14,10 @@ import 'package:sentry_dio/sentry_dio.dart'; /// ---------- /// Centralized network client for the application. /// -/// Configures interceptors for JWE, Sentry, and authentication headers. +/// Configures interceptors for Sentry and authentication headers. class DioService { DioService(this._ref) { - const timeout = kDebugMode ? Duration(seconds: 45) : Duration(seconds: 30); + final timeout = AppConfig.defaultTimeout; dio = Dio( BaseOptions( @@ -29,9 +30,6 @@ class DioService { dio.addSentry(); - // Attach JWE Layer first - dio.interceptors.add(_ref.read(jweInterceptorProvider)); - // Auth & Security Interceptor dio.interceptors.add( InterceptorsWrapper( @@ -133,18 +131,8 @@ class DioService { _unauthorizedController.add(null); } - bool _isTransientAppCheckFailure(Object error) { - final msg = error.toString().toLowerCase(); - return msg.contains('too_many_attempts') || - msg.contains('timeout') || - msg.contains('network') || - msg.contains('connection') || - msg.contains('unavailable') || - msg.contains('rate limit') || - msg.contains('internal google server error') || - msg.contains('google_server_unavailable') || - msg.contains('-12'); - } + bool _isTransientAppCheckFailure(Object error) => + isTransientAppCheckFailure(error); Duration _retryDelayForAttempt(int attempt) { switch (attempt) { @@ -292,12 +280,12 @@ class DioService { } } -final dioServiceProvider = Provider(DioService.new); +final dioServiceProvider = Provider((ref) { + final service = DioService(ref); + ref.onDispose(service.close); + return service; +}); final appCheckProvider = Provider( (ref) => FirebaseAppCheck.instance, ); - -final jweInterceptorProvider = Provider( - (ref) => JweInterceptor(), -); diff --git a/mobile/lib/services/jwe_interceptor.dart b/mobile/lib/services/jwe_interceptor.dart deleted file mode 100644 index a9cda053..00000000 --- a/mobile/lib/services/jwe_interceptor.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:ghostclass/config/app_config.dart'; -import 'package:ghostclass/services/jwe_service.dart'; -import 'package:ghostclass/services/logger.dart'; -import 'package:uuid/uuid.dart'; - -/// Interceptor that handles JSON Web Encryption (JWE) for all GhostClass API requests. -/// -/// 1. [onRequest]: Encrypts outgoing POST/PUT request bodies and attaches the RCEK -/// (Response Content Encryption Key) to the headers for server-side use. -/// 2. [onResponse]: Decrypts incoming encrypted responses using the stored RCEK. -class JweInterceptor extends Interceptor { - JweInterceptor([this._serviceOverride]); - final JweService? _serviceOverride; - - JweService get _jweService => _serviceOverride ?? JweService.instance; - - // Use a map to store RCEKs for concurrent requests with self-pruning timestamps - final Map _rcekMap = {}; - - void _pruneExpiredRceks() { - final now = DateTime.now(); - // Prune entries older than 2 minutes (120 seconds) to prevent any unbounded leak - _rcekMap.removeWhere( - (key, entry) => now.difference(entry.timestamp).inSeconds > 120, - ); - // Defensive cap: if an attacker floods requests, prevent the map from - // growing without bound by removing the oldest entries when exceeding - // a reasonable limit. - const maxEntries = 256; - if (_rcekMap.length > maxEntries) { - final entries = _rcekMap.entries.toList() - ..sort((a, b) => a.value.timestamp.compareTo(b.value.timestamp)); - final toRemove = _rcekMap.length - maxEntries; - for (var i = 0; i < toRemove; i++) { - _rcekMap.remove(entries[i].key); - } - } - } - - @override - Future onRequest( - RequestOptions options, - RequestInterceptorHandler handler, - ) async { - final baseUrl = AppConfig.ghostclassApiUrl; - // Ensure we only encrypt requests targeting our own backend. - // Check both absolute paths and relative paths combined with baseUrl. - final fullUrl = options.path.startsWith('http') - ? options.path - : '${options.baseUrl}${options.path}'; - final isGhostClassApi = fullUrl.startsWith(baseUrl); - final isWrite = - options.method == 'POST' || - options.method == 'PUT' || - options.method == 'PATCH'; - - if (isGhostClassApi && isWrite && options.data is Map) { - try { - final jweService = _jweService; - final result = await jweService.encryptRequest( - options.data as Map, - ); - - // Store the RCEK for this request's response decryption using a unique request ID - final requestId = const Uuid().v4(); - _pruneExpiredRceks(); - _rcekMap[requestId] = _RcekEntry( - rcek: result.rcek, - timestamp: DateTime.now(), - ); - options.headers['X-GhostClass-Request-ID'] = requestId; - - options.data = result.jwe; - options.headers['x-jwe'] = 'true'; - options.headers['Content-Type'] = 'application/jose'; - // The server needs the RCEK to decrypt the request and to encrypt the response - // In the GhostClass protocol, we send the RCEK encrypted with the server's public key - final keyResult = await jweService.encryptHeaderKey(); - options.headers['x-jwe-key'] = keyResult.jwe; - - AppLogger.d('JweInterceptor: Request encrypted for ${options.path}'); - } on Object catch (e) { - AppLogger.e('JweInterceptor: Encryption failed', e); - // Fail the request if encryption for our backend fails to avoid sending - // sensitive payloads unencrypted. - return handler.reject( - DioException( - requestOptions: options, - error: 'JWE encryption failed', - ), - ); - } - } else if (isGhostClassApi && options.method == 'GET') { - // For GET requests, we still need to send a JWE key if we want the response to be encrypted - try { - final jweService = _jweService; - final keyResult = await jweService.encryptHeaderKey(); - - final requestId = const Uuid().v4(); - _pruneExpiredRceks(); - _rcekMap[requestId] = _RcekEntry( - rcek: keyResult.rcek, - timestamp: DateTime.now(), - ); - options.headers['X-GhostClass-Request-ID'] = requestId; - - options.headers['x-jwe-key'] = keyResult.jwe; - } on Object catch (e) { - AppLogger.e('JweInterceptor: GET Key setup failed', e); - // Fail GET if we cannot establish a header key for our backend. - return handler.reject( - DioException( - requestOptions: options, - error: 'JWE header key setup failed', - ), - ); - } - } - - return handler.next(options); - } - - @override - Future onResponse( - Response response, - ResponseInterceptorHandler handler, - ) async { - final contentType = response.headers.value('content-type') ?? ''; - final isEncrypted = - contentType.contains('application/jose') || - response.headers.value('x-jwe') == 'true'; - // Header casing can vary depending on platform/transport. Try common - // variants to robustly retrieve the request ID used to store the RCEK. - String? requestId; - final headers = response.requestOptions.headers; - if (headers.containsKey('X-GhostClass-Request-ID')) { - requestId = headers['X-GhostClass-Request-ID'] as String?; - } else if (headers.containsKey('x-ghostclass-request-id')) { - requestId = headers['x-ghostclass-request-id'] as String?; - } else if (headers.containsKey('X-Ghostclass-Request-Id')) { - requestId = headers['X-Ghostclass-Request-Id'] as String?; - } - final entry = requestId == null ? null : _rcekMap.remove(requestId); - final rcek = entry?.rcek; - - if (isEncrypted && rcek != null) { - String? jwe; - if (response.data is String) { - jwe = response.data as String; - } - - if (jwe != null) { - try { - final jweService = _jweService; - final decryptedData = await jweService.decryptResponse(jwe, rcek); - response.data = decryptedData; - AppLogger.d( - 'JweInterceptor: Response decrypted for ${response.requestOptions.path}', - ); - } on Object catch (e) { - AppLogger.e('JweInterceptor: Decryption failed', e); - // If decryption fails, reject the response so callers don't process - // potentially tampered or unreadable data. - return handler.reject( - DioException( - requestOptions: response.requestOptions, - error: 'JWE response decryption failed', - type: DioExceptionType.badResponse, - ), - ); - } - } - } - - return handler.next(response); - } - - @override - void onError(DioException err, ErrorInterceptorHandler handler) { - // Clean up RCEK on error to prevent memory leaks - final headers = err.requestOptions.headers; - String? requestId; - if (headers.containsKey('X-GhostClass-Request-ID')) { - requestId = headers['X-GhostClass-Request-ID'] as String?; - } else if (headers.containsKey('x-ghostclass-request-id')) { - requestId = headers['x-ghostclass-request-id'] as String?; - } - if (requestId != null) _rcekMap.remove(requestId); - return handler.next(err); - } -} - -class _RcekEntry { - _RcekEntry({required this.rcek, required this.timestamp}); - final String rcek; - final DateTime timestamp; -} diff --git a/mobile/lib/services/jwe_service.dart b/mobile/lib/services/jwe_service.dart deleted file mode 100644 index 752d6e05..00000000 --- a/mobile/lib/services/jwe_service.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; -// Preserved for potential platform-specific overrides -// ignore: unnecessary_import -import 'dart:typed_data'; - -import 'package:dio/dio.dart'; -import 'package:dio/io.dart'; -import 'package:flutter/foundation.dart'; -import 'package:ghostclass/config/app_config.dart'; -import 'package:ghostclass/logic/network_utils.dart'; -import 'package:ghostclass/services/logger.dart'; -import 'package:jose/jose.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -/// JweService -/// ---------- -/// Handles Bi-directional End-to-End Encryption (E2EE) for GhostClass. -class JweService { - JweService._internal() { - const networkTimeout = kDebugMode - ? Duration(seconds: 45) - : Duration(seconds: 30); - - _dio = Dio( - BaseOptions( - connectTimeout: networkTimeout, - receiveTimeout: networkTimeout, - sendTimeout: networkTimeout, - ), - ); - - if (kDebugMode) { - (_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { - return HttpClient() - ..badCertificateCallback = NetworkUtils.validateCertificateHostname; - }; - } - } - static final JweService _instance = JweService._internal(); - static JweService get instance => _instance; - - late final Dio _dio; - - JsonWebKeySet? _cachedJwks; - DateTime? _lastFetch; - - final String _ghostclassApiUrl = AppConfig.ghostclassApiUrl; - - /// No longer needed internally, delegated to NetworkUtils. - - Future? _inFlightFetch; - static const String _jwksCacheKey = 'ghostclass_jwks_cache'; - static const String _jwksTimeKey = 'ghostclass_jwks_time'; - - Future _fetchJwks() async { - // 1. In-memory cache check (1 hour) - if (_cachedJwks != null && - _lastFetch != null && - DateTime.now().difference(_lastFetch!).inHours < 1) { - return; - } - - // 2. Return in-flight fetch if exists to deduplicate concurrent calls - if (_inFlightFetch != null) { - return _inFlightFetch!; - } - - _inFlightFetch = _performFetch(); - try { - await _inFlightFetch; - } finally { - _inFlightFetch = null; - } - } - - Future _performFetch() async { - try { - final prefs = await SharedPreferences.getInstance(); - - // 3. Persistent cache check - final cachedJson = prefs.getString(_jwksCacheKey); - final cachedTimeStr = prefs.getString(_jwksTimeKey); - if (cachedJson != null && cachedTimeStr != null) { - try { - final cachedTime = DateTime.parse(cachedTimeStr); - _cachedJwks = JsonWebKeySet.fromJson( - json.decode(cachedJson) as Map, - ); - _lastFetch = cachedTime; - AppLogger.d('JweService: Loaded JWKS from persistent cache.'); - - // Stale-While-Revalidate: if cached keys are older than 24 hours, - // refresh from network in the background without blocking startup. - if (DateTime.now().difference(cachedTime).inHours >= 24) { - AppLogger.safeUnawait( - _refreshJwksFromNetwork(prefs).catchError( - (Object e, StackTrace st) => AppLogger.e( - 'JweService: Background JWKS refresh failed', - e, - st, - ), - ), - 'JweService: background JWKS refresh', - ); - } - return; - } on Object catch (e) { - AppLogger.e('JweService: Failed to parse cached JWKS time', e); - } - } - - // 4. Cache miss: Blocking network fetch - await _refreshJwksFromNetwork(prefs); - } on Object catch (e) { - AppLogger.e('JweService: JWKS Fetch Error', e); - rethrow; - } - } - - Future _refreshJwksFromNetwork(SharedPreferences prefs) async { - try { - final url = '$_ghostclassApiUrl/.well-known/jwks.json'; - final response = await _dio.get(url); - - if (response.statusCode == 200) { - final data = response.data; - _cachedJwks = JsonWebKeySet.fromJson(data as Map); - _lastFetch = DateTime.now(); - - // Update persistent cache - await prefs.setString(_jwksCacheKey, json.encode(data)); - await prefs.setString(_jwksTimeKey, _lastFetch!.toIso8601String()); - - AppLogger.i('JweService: Fetched server JWKS successfully.'); - } else { - throw Exception('Failed to fetch JWKS: ${response.statusCode}'); - } - } on Object catch (e) { - AppLogger.e('JweService: Failed to refresh JWKS from network', e); - // If we already have a cached copy, don't bubble up background errors - if (_cachedJwks == null) { - rethrow; - } - } - } - - Future preWarm() async { - try { - await _fetchJwks(); - } on Object catch (e) { - AppLogger.d('JweService: Pre-warm skipped.', e); - } - } - - /// Selects a usable server key from JWKS. - /// - /// Prefer a key with a kid so the resulting JWE preserves key-rotation hints. - JsonWebKey _getPreferredServerKey() { - final keys = _cachedJwks?.keys ?? const []; - if (keys.isEmpty) { - throw Exception('Server public key not available.'); - } - - return keys.firstWhere( - (key) => key.keyId != null && key.keyId!.isNotEmpty, - orElse: () => keys.first, - ); - } - - /// Forces the JsonWebKey to explicitly support wrapKey while preserving kid. - JsonWebKey _getSanitizedServerKey(JsonWebKey key) { - final rawJson = key.toJson(); - - // Ensure the server key has the required RSA parameters. If the server - // provides an unexpected key shape, fail fast instead of constructing a - // potentially invalid JWK which could cause subtle crypto errors later. - final n = rawJson['n'] as String?; - final e = rawJson['e'] as String?; - if (n == null || n.isEmpty || e == null || e.isEmpty) { - throw Exception('Server JWK missing RSA modulus or exponent.'); - } - - return JsonWebKey.fromJson({ - 'kty': 'RSA', - 'n': n, - 'e': e, - if (rawJson['kid'] != null) 'kid': rawJson['kid'], - // Notice: We deliberately omit 'alg', 'use', and 'key_ops' to avoid - // rejecting the key for operation mismatches while preserving the kid. - }); - } - - Future<({String jwe, String rcek})> encryptRequest( - Map data, - ) async { - await _fetchJwks(); - - if (_cachedJwks == null || _cachedJwks!.keys.isEmpty) { - throw Exception('Server public key not available.'); - } - - final random = Random.secure(); - final rcekBytes = Uint8List.fromList( - List.generate(32, (_) => random.nextInt(256)), - ); - final rcekBase64 = base64Url.encode(rcekBytes).replaceAll('=', ''); - - final enrichedData = {...data, 'rcek': rcekBase64}; - - // Use the sanitized key while preserving kid for rotation-aware servers - final serverKey = _getSanitizedServerKey(_getPreferredServerKey()); - - final builder = JsonWebEncryptionBuilder() - ..jsonContent = enrichedData - ..encryptionAlgorithm = 'A256GCM' - ..addRecipient(serverKey, algorithm: 'RSA-OAEP-256'); - - final jwe = builder.build().toCompactSerialization(); - - return (jwe: jwe, rcek: rcekBase64); - } - - Future<({String jwe, String rcek})> encryptHeaderKey() async { - await _fetchJwks(); - - if (_cachedJwks == null || _cachedJwks!.keys.isEmpty) { - throw Exception('Server public key not available.'); - } - - final random = Random.secure(); - final rcekBytes = Uint8List.fromList( - List.generate(32, (_) => random.nextInt(256)), - ); - final rcekBase64 = base64Url.encode(rcekBytes).replaceAll('=', ''); - - // Use the sanitized key while preserving kid for rotation-aware servers - final serverKey = _getSanitizedServerKey(_getPreferredServerKey()); - - final builder = JsonWebEncryptionBuilder() - ..jsonContent = {'rcek': rcekBase64} - ..encryptionAlgorithm = 'A256GCM' - ..addRecipient(serverKey, algorithm: 'RSA-OAEP-256'); - - return (jwe: builder.build().toCompactSerialization(), rcek: rcekBase64); - } - - Future decryptResponse(String jwe, String rcekBase64) async { - try { - final rcekBytes = base64Url.decode(base64.normalize(rcekBase64)); - - final jwk = JsonWebKey.fromJson({ - 'kty': 'oct', - 'k': base64Url.encode(rcekBytes).replaceAll('=', ''), - 'alg': 'A256GCM', - 'use': 'enc', - }); - - final jweObj = JsonWebEncryption.fromCompactSerialization(jwe); - final keyStore = JsonWebKeyStore()..addKey(jwk); - final payload = await jweObj.getPayload(keyStore); - - return json.decode(utf8.decode(payload.data)); - } on Object catch (e) { - AppLogger.e('JweService: Response Decryption Error', e); - throw Exception('Security sync failed: Response could not be verified.'); - } - } -} diff --git a/mobile/lib/services/logger.dart b/mobile/lib/services/logger.dart index 42321e43..a515defe 100644 --- a/mobile/lib/services/logger.dart +++ b/mobile/lib/services/logger.dart @@ -86,8 +86,8 @@ class AppLogger { /// Logs a debug message. static void d(String message, [Object? error, StackTrace? stackTrace]) { - _addToBuffer('DEBUG', message); if (kDebugMode) { + _addToBuffer('DEBUG', message); debugPrint('[DEBUG] $message'); if (error != null) debugPrint('Error: $error'); if (stackTrace != null) debugPrint('StackTrace: $stackTrace'); @@ -154,6 +154,16 @@ class AppLogger { eWithContext(message, error: error, stackTrace: stackTrace); } + /// Logs a handled/expected error to the buffer and debug console without sending to Sentry. + static void eLocal(String message, [Object? error, StackTrace? stackTrace]) { + _addToBuffer('ERROR_LOCAL', message); + if (kDebugMode) { + debugPrint('[ERROR_LOCAL] $message'); + if (error != null) debugPrint('Error: $error'); + if (stackTrace != null) debugPrint('StackTrace: $stackTrace'); + } + } + /// Logs an error message with optional tags/extras and sends a sanitized /// payload to Sentry. PII is redacted and any UUIDs are hashed. static void eWithContext( diff --git a/mobile/lib/services/profile_service.dart b/mobile/lib/services/profile_service.dart index 165c6bea..8d8cf770 100644 --- a/mobile/lib/services/profile_service.dart +++ b/mobile/lib/services/profile_service.dart @@ -2,7 +2,10 @@ import 'package:ghostclass/models/user.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class ProfileService { - final SupabaseClient _client = Supabase.instance.client; + ProfileService([SupabaseClient? client]) + : _client = client ?? Supabase.instance.client; + + final SupabaseClient _client; bool hasRenderableLocalProfile(UserProfile? profile) { return profile?.fullName != null || profile?.avatarUrl != null; diff --git a/mobile/lib/services/push_notification_service.dart b/mobile/lib/services/push_notification_service.dart index e9a7bdf9..d6665089 100644 --- a/mobile/lib/services/push_notification_service.dart +++ b/mobile/lib/services/push_notification_service.dart @@ -1,5 +1,4 @@ // Service is dynamically resolved or used in background isolates -// ignore_for_file: unreachable_from_main import 'dart:async'; import 'dart:io'; diff --git a/mobile/lib/services/security_service.dart b/mobile/lib/services/security_service.dart index 130ee835..5949368f 100644 --- a/mobile/lib/services/security_service.dart +++ b/mobile/lib/services/security_service.dart @@ -5,6 +5,7 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/config/app_config.dart'; import 'package:ghostclass/logic/app_exception.dart'; +import 'package:ghostclass/logic/error_utils.dart'; import 'package:ghostclass/providers/app_update_provider.dart'; import 'package:ghostclass/providers/auth_provider.dart'; import 'package:ghostclass/providers/security_provider.dart'; @@ -72,20 +73,8 @@ class SecurityService { return false; } - static bool isTransientAppCheckFailureText(String? text) { - if (text == null) return false; - final msg = text.toLowerCase(); - return msg.contains('quota') || - msg.contains('connection') || - msg.contains('timeout') || - msg.contains('too_many_attempts') || - msg.contains('network') || - msg.contains('rate limit') || - msg.contains('server') || - msg.contains('internal error') || - msg.contains('-12') || - msg.contains('unavailable'); - } + static bool isTransientAppCheckFailureText(String? text) => + isTransientAppCheckFailure(text); bool _isTransientErrorForFallback(Object e) { if (e is DioException) { diff --git a/mobile/lib/services/settings_service.dart b/mobile/lib/services/settings_service.dart index 66aaf41d..6affa279 100644 --- a/mobile/lib/services/settings_service.dart +++ b/mobile/lib/services/settings_service.dart @@ -3,15 +3,18 @@ import 'package:ghostclass/services/secure_storage.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class SettingsService { - SettingsService(this.storage); + SettingsService(this.storage, [SupabaseClient? client]) + : _client = client ?? Supabase.instance.client; + final SecureStorageService storage; - final SupabaseClient _client = Supabase.instance.client; + final SupabaseClient _client; Future updateSettings( String userId, { bool? bunkEnabled, int? targetPercentage, Map>? disabledCourses, + Map? courseTargets, }) async { final updates = {}; if (bunkEnabled != null) { @@ -23,6 +26,9 @@ class SettingsService { if (disabledCourses != null) { updates['disabled_courses'] = disabledCourses; } + if (courseTargets != null) { + updates['course_targets'] = courseTargets; + } if (updates.isEmpty) { return; diff --git a/mobile/lib/widgets/add_attendance_dialog.dart b/mobile/lib/widgets/add_attendance_dialog.dart index 95f666cc..535a4493 100644 --- a/mobile/lib/widgets/add_attendance_dialog.dart +++ b/mobile/lib/widgets/add_attendance_dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -283,31 +284,82 @@ class _AddAttendanceDialogState extends ConsumerState { const SizedBox(height: 16), const AttendanceDialogLabel(text: 'Session'), _buildSessionSelector(primary), - if (_isBlocked) - Padding( - padding: const EdgeInsets.only(top: 6, left: 4), - child: Text( - 'Session occupied', - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 11, - fontWeight: FontWeight.w600, + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: Stack( + alignment: Alignment.center, + children: [ + AbsorbPointer( + absorbing: _isBlocked, + child: ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: _isBlocked ? 3 : 0, + sigmaY: _isBlocked ? 3 : 0, + ), + child: Opacity( + opacity: _isBlocked ? 0.4 : 1.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + const AttendanceDialogLabel(text: 'Subject'), + _buildSubjectSelectorButton(data, primary), + const SizedBox(height: 20), + const AttendanceDialogLabel(text: 'Status'), + _buildStatusButtons(ghostColors), + const SizedBox(height: 16), + AttendanceDialogLabel( + text: _status == AttendanceStatus.dutyLeave + ? 'Reason (Optional)' + : 'Remarks (Optional)', + ), + _buildRemarksField(primary), + ], + ), + ), + ), ), - ), + if (_isBlocked) + Positioned.fill( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + LucideIcons.alertCircle, + size: 24, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(height: 8), + Text( + 'Session occupied', + style: GoogleFonts.manrope( + fontSize: 14, + fontWeight: FontWeight.w800, + color: Theme.of(context).colorScheme.error, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + Text( + 'Please select another period/hour', + style: GoogleFonts.manrope( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface + .withValues(alpha: 0.7), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], ), - const SizedBox(height: 16), - const AttendanceDialogLabel(text: 'Subject'), - _buildSubjectSelectorButton(data, primary), - const SizedBox(height: 20), - const AttendanceDialogLabel(text: 'Status'), - _buildStatusButtons(ghostColors), - const SizedBox(height: 16), - AttendanceDialogLabel( - text: _status == AttendanceStatus.dutyLeave - ? 'Reason (Optional)' - : 'Remarks (Optional)', ), - _buildRemarksField(primary), const SizedBox(height: 24), _buildSubmitButton(primary), ], @@ -392,11 +444,14 @@ class _AddAttendanceDialogState extends ConsumerState { children: [ Icon(LucideIcons.calendar, size: 18, color: primary), const SizedBox(width: 12), - Text( - DateFormat('MMMM d, yyyy').format(_selectedDate), - style: GoogleFonts.manrope( - fontSize: 13, - fontWeight: FontWeight.w600, + Expanded( + child: Text( + DateFormat('MMMM d, yyyy').format(_selectedDate), + style: GoogleFonts.manrope( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + overflow: TextOverflow.ellipsis, ), ), ], @@ -851,9 +906,9 @@ class _AddAttendanceDialogState extends ConsumerState { ), child: _isSubmitting ? const CircularProgressIndicator(color: Colors.white) - : const Text( - 'Add Record', - style: TextStyle(fontWeight: FontWeight.bold), + : Text( + _isBlocked ? 'Session occupied' : 'Add Record', + style: const TextStyle(fontWeight: FontWeight.bold), ), ), ); diff --git a/mobile/lib/widgets/attendance/edit_instructor_dialog.dart b/mobile/lib/widgets/attendance/edit_instructor_dialog.dart index 35d0bc04..66b1e51e 100644 --- a/mobile/lib/widgets/attendance/edit_instructor_dialog.dart +++ b/mobile/lib/widgets/attendance/edit_instructor_dialog.dart @@ -65,6 +65,7 @@ class _EditInstructorDialogState extends ConsumerState { await apiService.upsertInstructor( courseCode: widget.courseCode, instructorName: name, + courseName: widget.courseName, supabaseToken: supabaseToken, ); diff --git a/mobile/lib/widgets/dashboard/course_card.dart b/mobile/lib/widgets/dashboard/course_card.dart index de74503d..80f97953 100644 --- a/mobile/lib/widgets/dashboard/course_card.dart +++ b/mobile/lib/widgets/dashboard/course_card.dart @@ -338,6 +338,60 @@ class CourseCard extends StatelessWidget { ); }, ), + const SizedBox(height: 8), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: primary.withValues(alpha: 0.3), + ), + ), + child: Text( + 'Target: ${bunkResult.targetPercentage.toInt()}%', + style: GoogleFonts.manrope( + fontSize: 10, + fontWeight: FontWeight.w700, + color: primary, + ), + ), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: isDark + ? Colors.amber.withValues(alpha: 0.15) + : const Color(0xFFFEF3C7), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: isDark + ? Colors.amber.withValues(alpha: 0.3) + : const Color(0xFFFDE68A), + ), + ), + child: Text( + '${stat.dlCount} ${stat.dlCount == 1 ? "Duty Leave" : "Duty Leaves"}', + style: GoogleFonts.manrope( + fontSize: 10, + fontWeight: FontWeight.w800, + color: isDark + ? const Color(0xFFFBBF24) + : const Color(0xFFB45309), + ), + ), + ), + ], + ), ], ), ), diff --git a/mobile/lib/widgets/dashboard/course_card_widgets.dart b/mobile/lib/widgets/dashboard/course_card_widgets.dart index af64e8d9..8f89d5e5 100644 --- a/mobile/lib/widgets/dashboard/course_card_widgets.dart +++ b/mobile/lib/widgets/dashboard/course_card_widgets.dart @@ -118,6 +118,8 @@ class SimpleBunkPanel extends StatelessWidget { final message = result.canBunk > 0 ? 'You can safely bunk ${result.canBunk} ${result.canBunk == 1 ? 'class' : 'classes'}' + : result.requiredToAttend >= 999 + ? 'Cannot reach target โ€” classes have been missed' : result.requiredToAttend > 0 ? 'You need to attend ${result.requiredToAttend} more ${result.requiredToAttend == 1 ? 'class' : 'classes'}' : "You are on the edge. Skipping now's risky"; @@ -135,6 +137,8 @@ class SimpleBunkPanel extends StatelessWidget { child: Text( result.canBunk > 0 ? 'You can safely bunk ${result.canBunk} ${result.canBunk == 1 ? 'class ๐Ÿฅณ' : 'classes ๐Ÿฅณ๐Ÿฅณ'}' + : result.requiredToAttend >= 999 + ? 'Cannot reach target ๐Ÿ’€' : result.requiredToAttend > 0 ? 'You need to attend ${result.requiredToAttend} more ${result.requiredToAttend == 1 ? 'class ๐Ÿ’€' : 'classes ๐Ÿ’€๐Ÿ’€'}' : "You are on the edge. Skipping now's risky ๐Ÿ’€๐Ÿ’€", diff --git a/mobile/lib/widgets/dashboard/disable_aware_course_card.dart b/mobile/lib/widgets/dashboard/disable_aware_course_card.dart index 35074c78..32459a4d 100644 --- a/mobile/lib/widgets/dashboard/disable_aware_course_card.dart +++ b/mobile/lib/widgets/dashboard/disable_aware_course_card.dart @@ -277,13 +277,34 @@ class _DisableAwareCourseCardState @override Widget build(BuildContext context) { - ref.watch(authProvider); + final user = ref.watch(authProvider).value; + final userSettings = user?.settings; + final courseTargets = userSettings?.courseTargets ?? {}; + final stdCode = DashboardStats.standardize( + widget.course.code ?? widget.course.safeId, + ); + final courseTargetVal = + courseTargets[stdCode] ?? + courseTargets[widget.course.code] ?? + courseTargets[widget.course.id.toString()]; + final effectiveTarget = + (courseTargetVal ?? + userSettings?.targetPercentage ?? + widget.bunkResult.targetPercentage) + .toDouble(); + + final courseBunkResult = utils.calculateAttendance( + widget.stat.finalPresent, + widget.stat.finalTotal, + targetPercentage: effectiveTarget, + ); + return Opacity( opacity: _isDisabled ? 0.62 : 1, child: CourseCard( course: widget.course, stat: widget.stat, - bunkResult: widget.bunkResult, + bunkResult: courseBunkResult, bunkEnabled: widget.bunkEnabled, isEnabled: !_isDisabled, onToggleTap: _courseCode == null diff --git a/mobile/lib/widgets/dashboard/trend_chart.dart b/mobile/lib/widgets/dashboard/trend_chart.dart index 12608bed..a75d5a97 100644 --- a/mobile/lib/widgets/dashboard/trend_chart.dart +++ b/mobile/lib/widgets/dashboard/trend_chart.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:ghostclass/models/dashboard_stats.dart'; @@ -10,10 +12,12 @@ class TrendChartSection extends StatefulWidget { required this.targetPercentage, super.key, this.disabledCodes = const {}, + this.courseTargets = const {}, }); final DashboardStats stats; final double targetPercentage; final Set disabledCodes; + final Map courseTargets; @override State createState() => _TrendChartSectionState(); @@ -39,6 +43,11 @@ class _TrendChartSectionState extends State { : widget.targetPercentage; } + // Also account for any custom course-target lines so they are always visible + for (final v in widget.courseTargets.values) { + if (v < minRef) minRef = v.toDouble(); + } + return ((minRef / 5).floor() * 5.0 - 5.0).clamp(0, 95); } @@ -98,7 +107,8 @@ class _TrendChartSectionState extends State { void didUpdateWidget(TrendChartSection oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.stats != widget.stats || - oldWidget.disabledCodes != widget.disabledCodes) { + oldWidget.disabledCodes != widget.disabledCodes || + oldWidget.courseTargets != widget.courseTargets) { _updateCourses(); } } @@ -268,6 +278,7 @@ class _TrendChartSectionState extends State { borderData: FlBorderData(show: false), extraLinesData: ExtraLinesData( horizontalLines: [ + // Global target line (amber) HorizontalLine( y: widget.targetPercentage, color: Colors.amber.shade700, @@ -295,8 +306,17 @@ class _TrendChartSectionState extends State { barGroups: _courses.asMap().entries.map((entry) { final i = entry.key; final s = entry.value; - final isSafe = - s.percentage >= widget.targetPercentage; + final stdCode = DashboardStats.standardize( + s.code, + ); + final targetVal = + widget.courseTargets[stdCode] ?? + widget.courseTargets[s.code] ?? + widget.courseTargets[s.id]; + final effectiveTarget = + (targetVal ?? widget.targetPercentage) + .toDouble(); + final isSafe = s.percentage >= effectiveTarget; final isLoss = s.percentage < s.officialPercentage; final displayedBase = isLoss @@ -347,11 +367,31 @@ class _TrendChartSectionState extends State { hatchStops.addAll([s0, mid, mid, s1]); } + // Whether this bar has a custom target distinct from the global one + final hasCustomTarget = + targetVal != null && + effectiveTarget != widget.targetPercentage; + + // Height of the purple target band in data-units (~0.4 percentage unit) + const targetBandHalf = 0.2; + final targetBandBottom = + (effectiveTarget - targetBandHalf).clamp( + 0.0, + double.infinity, + ); + final targetBandTop = + effectiveTarget + targetBandHalf; + + // Extend the rod to reach the target band when it's above the bar + final rodToY = hasCustomTarget + ? math.max(totalVal, targetBandTop) + : totalVal; + return BarChartGroupData( x: i, barRods: [ BarChartRodData( - toY: totalVal, + toY: rodToY, width: 18, color: Colors.transparent, borderRadius: const BorderRadius.vertical( @@ -364,6 +404,12 @@ class _TrendChartSectionState extends State { displayedBase, baseColor, ), + if (hasCustomTarget) + BarChartRodStackItem( + targetBandBottom, + targetBandTop, + Colors.amber.shade700, + ), ], backDrawRodData: BackgroundBarChartRodData( show: totalVal > 0, @@ -388,7 +434,15 @@ class _TrendChartSectionState extends State { if (_touchedIndex != -1 && _touchedOffset != null) _LocalChartTooltip( stat: _courses[_touchedIndex], - targetPercentage: widget.targetPercentage, + targetPercentage: () { + final s = _courses[_touchedIndex]; + final stdCode = DashboardStats.standardize(s.code); + final targetVal = + widget.courseTargets[stdCode] ?? + widget.courseTargets[s.code] ?? + widget.courseTargets[s.id]; + return (targetVal ?? widget.targetPercentage).toDouble(); + }(), chartOffset: _touchedOffset!, // Offset of chart inside the card (padding is 12, 12, 20, 12) chartOriginInCard: const Offset(12, 12), diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d655893f..991205a4 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c url: "https://pub.dev" source: hosted - version: "92.0.0" + version: "99.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: bda3b7b55958bfd867addc40d067b4b11f7b8846d57671f5b5a6e7f9a56fe3ad + sha256: "460e9e684edb461d85498fc166ff8416f303f22216838d302d80676b348c6a4c" url: "https://pub.dev" source: hosted - version: "1.3.69" + version: "1.3.75" adaptive_number: dependency: transitive description: @@ -29,18 +29,18 @@ packages: dependency: transitive description: name: analyzer - sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "12.1.0" analyzer_buffer: dependency: transitive description: name: analyzer_buffer - sha256: ff4bd291778c7417fe53fe24ee0d0a1f1ffe281a2d4ea887e7094f16e36eace7 + sha256: "445b77e2054fa3e8c8a8ef1b5e9e6b23bb8028fffd34b5e60eaef315b7750674" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.3" ansicolor: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: app_links - sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" + sha256: f8db46d2ea9ff6f3a37191a7fd5b7813da1253ace8c32c1b9eadace41d1188ea url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.2.1" app_links_linux: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: app_links_platform_interface - sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.4" app_links_web: dependency: transitive description: @@ -125,34 +125,34 @@ packages: dependency: transitive description: name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.7" build_config: dependency: transitive description: name: build_config - sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + sha256: "94eaf6708fe64408c632ef2689ca3777b112f9421306ccf4f8c84d7c5c9f83f8" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + sha256: "8c0535c3b2f625619f4dd1036ef1127f2e77bfedf89ed2eb2676ef076e0b6712" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "4425a87d87d0d1303540f867994303f5b141ad2f6ecac7ac2cf8851f41c0cef1" + sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" url: "https://pub.dev" source: hosted - version: "2.14.0" + version: "2.15.1" built_collection: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: built_value - sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.5" + version: "8.12.6" characters: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.1" code_builder: dependency: transitive description: @@ -245,18 +245,18 @@ packages: dependency: transitive description: name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.15.1" cross_file: dependency: transitive description: name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+2" + version: "0.3.5+4" crypto: dependency: "direct main" description: @@ -265,14 +265,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" - crypto_keys: - dependency: transitive - description: - name: crypto_keys - sha256: acc19abf34623d990a0e8aec69463d74a824c31f137128f42e2810befc509ad0 - url: "https://pub.dev" - source: hosted - version: "0.3.0+1" cupertino_icons: dependency: "direct main" description: @@ -293,42 +285,42 @@ packages: dependency: transitive description: name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.8" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd + sha256: "0891702f96b2e465fe567b7ec448380e6b1c14f60af552a8536d9f583b6b8442" url: "https://pub.dev" source: hosted - version: "12.4.0" + version: "13.2.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46" url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "8.1.0" dio: dependency: "direct main" description: name: dio - sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" url: "https://pub.dev" source: hosted - version: "5.9.2" + version: "5.11.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.1" ed25519_edwards: dependency: transitive description: @@ -349,10 +341,10 @@ packages: dependency: transitive description: name: equatable - sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.1.0" fake_async: dependency: transitive description: @@ -369,6 +361,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -413,98 +413,98 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "6993e54441e96b4de1dd85a159b236bbcd2c2487215c9d02b12c1685ddfded73" + sha256: "47b68927bc4abd6d7b4e2ddfe7c64fe087cca5f96d0c2b8d9d6deb6120c10ad6" url: "https://pub.dev" source: hosted - version: "12.3.0" + version: "12.4.5" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: ac2a17484daf380b2247b9874e675ff9e4ac611d839a10b91b7445f764756760 + sha256: "36ce5feb5a996381e6411792eaa00e89178e75943e54ac8f14a8296b6e9a3e88" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.5" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "167a3115e71501ba6706235f00e370072920ee8d26b44fbfb9dc8e63c98a8d9b" + sha256: c12358983c927093cd40d42feb0cb05c8594d3a9b56ab5ad303bec2b8838d252 url: "https://pub.dev" source: hosted - version: "0.6.1+5" + version: "0.6.1+11" firebase_app_check: dependency: "direct main" description: name: firebase_app_check - sha256: "893cf0f921f76a3eeb288a6fb4ba821d1fdb08f8967d71a429648aea0c172570" + sha256: "28c1cc87389132c710236a008ee6688df510d3ce8b542ec377274ed8777006d6" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "0.4.5+2" firebase_app_check_platform_interface: dependency: transitive description: name: firebase_app_check_platform_interface - sha256: d46dc041aca21e7659e928a03900e0e60f168349523a00d9773618a27d562bc4 + sha256: "1cccf087884425f595f6ae7159c568bb0bcb8aaff3acb6dd150bde9b421b06d2" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.4.1+2" firebase_app_check_web: dependency: transitive description: name: firebase_app_check_web - sha256: "2b5904c7dc72366306f69c0b31a7ebed4eb56b7c7170220cd18ba06628abad39" + sha256: "1049ce1073c43f8e3efc97190206132182ed2f71f4686efe9ada54a2bba27442" url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.2.5+2" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: d5a94b884dcb1e6d3430298e94bfe002238094cdfd5e29202d536ee2120f9158 + sha256: "6f22d1c62e0c20976f02cd842c7b7cd3c0f561cc2052586411871045c08860c9" url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.12.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + sha256: f74d1d6fabccf7743b0144c2ed363d81049e258e22428ebb6fb0faec2fa7d938 url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "8.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: dc5096257cd67292d34d78ceeb90836f02a4be921b5f3934311a02bb2376118c + sha256: ddab99d709b8c27dd47576eb05a1e719e07a2aa45c009a49ae92ac4d2a8ca555 url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.9.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: e5c93e8e7a9b0513f94bb684d2cf100e32e7dcdf2949574386b1955fc9a9b96a + sha256: "30ad2d59bcd86117dc49d278c8998a0fb390c5a3202f6e43e4bd215d3f1d0556" url: "https://pub.dev" source: hosted - version: "16.2.0" + version: "16.4.3" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "8cbb7d842e5071bba836452aff262f7db4b14bb3a0d00c1896cf176df886d65a" + sha256: "4d144cb42b9a5a42855596be2d7682d32c50169f42e7df2cb3278ecf935e7d63" url: "https://pub.dev" source: hosted - version: "4.7.9" + version: "4.9.2" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "8750bacf50573c0383535fc3f9c58c6a2f9dff5320a16a82c30631b9dad894f1" + sha256: fcd25d0b9da55766ef4d28ae05a7460f5189635d6b42607adcd9f08818fd35f0 url: "https://pub.dev" source: hosted - version: "4.1.5" + version: "4.2.3" fixnum: dependency: transitive description: @@ -554,74 +554,74 @@ packages: dependency: "direct main" description: name: flutter_markdown_plus - sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" + sha256: fce641d6c2106cc495de1cd603f97a4f9d615a97a64011928ded380dbdade935 url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.0.12" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" url: "https://pub.dev" source: hosted - version: "2.0.34" + version: "2.0.35" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2" + sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.2" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.3.1" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.3.2" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" + sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + sha256: "06686df417f34fe9f963ed217b7fdce250e2f637ddd6c374d7eb054549770633" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.2" flutter_shaders: dependency: transitive description: @@ -660,10 +660,10 @@ packages: dependency: transitive description: name: functions_client - sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" + sha256: e9685e9ab852a8b8e0579c867f6c9155da27317b294b3df796a536b8b8e0d253 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.4" glob: dependency: transitive description: @@ -684,26 +684,26 @@ packages: dependency: "direct main" description: name: go_router - sha256: "08b742eef4f71c9df5af543751cd0b7f1c679c4088488f4223ecaddc1a813b79" + sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a" url: "https://pub.dev" source: hosted - version: "17.2.2" + version: "17.3.0" google_fonts: dependency: "direct main" description: name: google_fonts - sha256: db9df7a5898d894eeda4c78143f35c30a243558be439518972366880b80bf88e + sha256: d3f251381df389f7418d4dff416bd27c0a23138df22969aafd28c4a4b1bb3cd6 url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.2.0" gotrue: dependency: transitive description: name: gotrue - sha256: "7a4172601553e61716f5c3dd243aa3297e13308e07eb85b7853c941ba585dcf5" + sha256: "360bba9606e58d5acc59a033ab64ae3cf571a6868f7a7267cb8e9a204b7bf1b2" url: "https://pub.dev" source: hosted - version: "2.20.0" + version: "2.26.0" graphs: dependency: transitive description: @@ -716,18 +716,18 @@ packages: dependency: transitive description: name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" hooks: dependency: transitive description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.2" http: dependency: transitive description: @@ -756,26 +756,26 @@ packages: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.1" image_picker: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622" + sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" url: "https://pub.dev" source: hosted - version: "0.8.13+16" + version: "0.8.13+19" image_picker_for_web: dependency: transitive description: @@ -836,10 +836,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" io: dependency: transitive description: @@ -856,14 +856,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.14.2" - jose: - dependency: "direct main" - description: - name: jose - sha256: a0a339d0a0652dc1bd89f8b92d38479e07e16db83858fb55fa57212479f323f7 - url: "https://pub.dev" - source: hosted - version: "0.3.5+2" js: dependency: transitive description: @@ -876,10 +868,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.12.0" jwt_decode: dependency: transitive description: @@ -932,10 +924,10 @@ packages: dependency: "direct main" description: name: lucide_icons_flutter - sha256: f9fd5d49b93bf14b89e0ff4818658a74ab16899bdaf7aa745358ee1a34f54eed + sha256: "234155b10641b8ef7bab8a077b93ea6c92fe849c06740cf89dcd86dcf350934f" url: "https://pub.dev" source: hosted - version: "3.1.14+1" + version: "3.1.15" markdown: dependency: "direct main" description: @@ -992,14 +984,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" - source: hosted - version: "0.17.6" node_preamble: dependency: transitive description: @@ -1012,10 +996,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.5.0" package_config: dependency: transitive description: @@ -1028,18 +1012,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" + sha256: "127e1751e37ffb2ff4658beeaca77bad0c27bf5f932bd3a501c2296926d4b481" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "10.2.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + passkeys_platform_interface: + dependency: transitive + description: + name: passkeys_platform_interface + sha256: e810520c7b79dca629fdc266958564eedd77dc85a955f5e94430dfccb92eef68 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "2.9.0" path: dependency: "direct main" description: @@ -1052,10 +1044,10 @@ packages: dependency: transitive description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: @@ -1076,18 +1068,18 @@ packages: dependency: transitive description: name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_windows: dependency: transitive description: @@ -1100,50 +1092,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + sha256: "06149cba29bc46206f8b54b56065fe74b07602d2454a281287685ae66b5ab7b5" url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "13.0.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242 url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "14.0.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: "11b7e94a9d2fbee23c27f0cae0105c6266c03fd83b9a2eda6cf09141fc82624b" url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.5.0" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + sha256: "6b74008020d08eb95751d4935f3f07ef4658c03a43a51e6148b40e8412edce1b" url: "https://pub.dev" source: hosted - version: "0.1.3+5" + version: "0.1.4+0" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.2" petitparser: dependency: transitive description: @@ -1188,18 +1180,18 @@ packages: dependency: transitive description: name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.5.2" postgrest: dependency: transitive description: name: postgrest - sha256: "9d61b3d4a88fcf9424d400127c54d49ed1b56ec30838fc0a33a64f31d4e694cc" + sha256: "10e3f195e131eec944fa872644aed89b33b5be998f3fc77c87325db3927bc646" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.8.0" process: dependency: transitive description: @@ -1232,22 +1224,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" realtime_client: dependency: transitive description: name: realtime_client - sha256: "7dfccf372d2f55aacfeefb6186f65a06f3ffae383fe042dbeef9d85d33487576" + sha256: "0cfbe0047e2591eba348938e9b4e620d5b1a8df6c374757e8eb8645252b8387f" url: "https://pub.dev" source: hosted - version: "2.7.3" + version: "2.11.0" record_use: dependency: transitive description: @@ -1268,34 +1252,34 @@ packages: dependency: transitive description: name: riverpod - sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" + sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.2" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66 + sha256: "3e275138862ccc22ed61444a1f9a840f753094c367f28f4123f50289cd204d68" url: "https://pub.dev" source: hosted - version: "1.0.0-dev.9" + version: "1.0.0-dev.10" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: "16471a1260b94e939394d78f1c63a9350936ac4a68c9fbdab40be47268c0b04f" + sha256: "674dbb26e2db3d9253166faf4758c796af14146b8fbcf5e7102bc8a04cd359b8" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "6f9220534d7a353b53c875ea191a84d28cb4e52ac420a66a1bd7318329d977b0" + sha256: "54d790c3fee1ae281c448801bfdbfaa9fd961a9d3998494e0fcd9ee32184d7eb" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" rxdart: dependency: transitive description: @@ -1308,34 +1292,34 @@ packages: dependency: transitive description: name: sentry - sha256: "1f78300740739ff4b4920802687879231554350eab73eb229778f463aabda440" + sha256: "330a341076e16b87aa8a4f97b0bd48c085737d087e81ef337aee2b4c3e8896f9" url: "https://pub.dev" source: hosted - version: "9.19.0" + version: "9.26.0" sentry_dart_plugin: dependency: "direct dev" description: name: sentry_dart_plugin - sha256: "2c5f263f8d5aea3cf387a7e03f77e355d35c71bd9a429474d633fbc085b86ceb" + sha256: da9c1d0b3c87a251bfc36301f16af090a88c2d59128fe9a6f908f5ac20340c97 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.4.0" sentry_dio: dependency: "direct main" description: name: sentry_dio - sha256: eb7260258f18c099f096923d86453d37b9677514953b0b75e163394dd072a4a3 + sha256: f958ba749e6c5ff5164b979c5c07d302fe98707e30ba7a694fd9f46ff3922d36 url: "https://pub.dev" source: hosted - version: "9.19.0" + version: "9.26.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "168bbb120f7684dee6c0e100817f56ee1925bc2eb7fa55a71253337640c7e241" + sha256: "163685070e173b9b28cd10e56eb76fdaca34067831bd7e3563f98a5bc5866165" url: "https://pub.dev" source: hosted - version: "9.19.0" + version: "9.26.0" shared_preferences: dependency: "direct main" description: @@ -1348,10 +1332,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" url: "https://pub.dev" source: hosted - version: "2.4.23" + version: "2.4.27" shared_preferences_foundation: dependency: transitive description: @@ -1441,10 +1425,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.4" source_map_stack_trace: dependency: transitive description: @@ -1489,10 +1473,10 @@ packages: dependency: transitive description: name: storage_client - sha256: "4801e8ca219a35e51cbb30589aba5306667ae8935b792504595a45273cef0b18" + sha256: "221263cfbe0c01575b7d7fe7543e44abff380eb4d38d936372844a1caa85ba12" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.6.0" stream_channel: dependency: transitive description: @@ -1521,18 +1505,18 @@ packages: dependency: transitive description: name: supabase - sha256: "40e5a8833c8834e140ef53b60a6181849667eba9ca125acb7f8e24c6a769d418" + sha256: "3879996256325fcc9abb03b62b12c07e4eaae2e008e1bf043cc1525184159a43" url: "https://pub.dev" source: hosted - version: "2.10.6" + version: "2.14.0" supabase_flutter: dependency: "direct main" description: name: supabase_flutter - sha256: c02ce58abcaf86cb8055ad40bfd98bbf5b93fed3b5b56b8220d88ed03842818b + sha256: c9916c1cd512ebf3107ec0f83c9dca13d2e1e789629c2a5df896586bff46ce5f url: "https://pub.dev" source: hosted - version: "2.12.4" + version: "2.16.0" system_info2: dependency: transitive description: @@ -1601,10 +1585,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.29" + version: "6.3.32" url_launcher_ios: dependency: transitive description: @@ -1641,10 +1625,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1657,10 +1641,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.6.0" vector_math: dependency: transitive description: @@ -1673,18 +1657,18 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: d1cb1d66a5aae2c702d68caca6c8347306d35e728fd94555fa21fa0448a972e0 + sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.0" vm_service: dependency: transitive description: name: vm_service - sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.1.0" + version: "15.2.0" watcher: dependency: transitive description: @@ -1729,26 +1713,18 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.3.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904" url: "https://pub.dev" source: hosted - version: "2.1.0" - x509: - dependency: transitive - description: - name: x509 - sha256: cbd1a63846884afd273cda247b0365284c8d85a365ca98e110413f93d105b935 - url: "https://pub.dev" - source: hosted - version: "0.2.4+3" + version: "3.0.3" xdg_directories: dependency: transitive description: @@ -1761,10 +1737,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" yaml: dependency: transitive description: @@ -1777,10 +1753,10 @@ packages: dependency: transitive description: name: yet_another_json_isolate - sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e + sha256: eaa26beb5990b25a49d942374fd5a0c5aa67a837e03b14b4c26134aaa1ed01a9 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" sdks: - dart: ">=3.11.4 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 57e183a0..dad78bfa 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 4.4.9+1 +version: 4.5.0+1 environment: sdk: ^3.11.4 @@ -29,7 +29,7 @@ environment: dependencies: crypto: ^3.0.2 cupertino_icons: ^1.0.8 - device_info_plus: ^12.4.0 + device_info_plus: ^13.2.0 dio: ^5.9.2 encrypt: ^5.0.3 firebase_analytics: ^12.3.0 @@ -47,12 +47,11 @@ dependencies: google_fonts: ^8.0.2 image_picker: ^1.1.2 intl: ^0.20.2 - jose: ^0.3.5+1 lucide_icons_flutter: ^3.1.14+1 markdown: any - package_info_plus: ^9.0.1 + package_info_plus: ^10.2.1 path: ^1.9.1 - permission_handler: ^11.3.1 + permission_handler: ^13.0.0 pointycastle: ^3.9.1 riverpod_annotation: ^4.0.2 sentry_dio: ^9.18.0 @@ -118,7 +117,7 @@ flutter_launcher_icons: android: "ic_launcher" ios: true image_path: "assets/images/icon.png" - min_sdk_android: 29 # android min sdk min:16, default:21 + min_sdk_android: 29 sentry: project: ghostclass-f diff --git a/mobile/test/add_attendance_dialog_test.dart b/mobile/test/add_attendance_dialog_test.dart index 77317aaa..6d50598b 100644 --- a/mobile/test/add_attendance_dialog_test.dart +++ b/mobile/test/add_attendance_dialog_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:ghostclass/models/attendance.dart'; import 'package:ghostclass/providers/dashboard_provider.dart'; import 'package:ghostclass/theme/app_theme.dart'; import 'package:ghostclass/widgets/add_attendance_dialog.dart'; +import 'package:intl/intl.dart'; import 'coverage_helper.dart'; @@ -39,4 +41,61 @@ void main() { expect(find.text('Add Extra Class'), findsOneWidget); expect(find.text('Add Record'), findsOneWidget); }); + + testWidgets( + 'AddAttendanceDialog shows blocked state when session is occupied', + (tester) async { + final mockData = createMockDashboardData(); + final todayStr = DateFormat('yyyy-MM-dd').format(DateTime.now()); + final occupiedDashboard = DashboardData( + courses: mockData.courses, + attendance: mockData.attendance, + tracking: [ + TrackingRecord( + id: 1, + date: todayStr, + session: 'I', + status: 'extra', + attendance: 'P', + course: 'TEST101', + ), + ], + stats: mockData.stats, + selectedSemester: mockData.selectedSemester, + selectedYear: mockData.selectedYear, + ); + + final overrides = [ + dashboardProvider.overrideWith( + () => MockDashboardNotifier(occupiedDashboard), + ), + ]; + + await tester.pumpWidget( + ProviderScope( + overrides: overrides, + child: MaterialApp( + theme: AppTheme.darkTheme, + home: Scaffold( + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: const AddAttendanceDialog(), + ), + ), + ), + ), + ), + ); + + await tester.pump(const Duration(milliseconds: 300)); + + // Session 1 ('1st Hour') is occupied. Tap on '1st Hour' to select it. + await tester.tap(find.text('1st Hour')); + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.text('Session occupied'), findsNWidgets(2)); + expect(find.text('Please select another period/hour'), findsOneWidget); + }, + ); } diff --git a/mobile/test/app_router_provider_test.dart b/mobile/test/app_router_provider_test.dart index 8969035f..4929e73b 100644 --- a/mobile/test/app_router_provider_test.dart +++ b/mobile/test/app_router_provider_test.dart @@ -45,7 +45,7 @@ void main() { await Supabase.initialize( url: 'https://example.com', - anonKey: 'anon', + publishableKey: 'anon', ); final container = ProviderContainer( diff --git a/mobile/test/auto_coverage_booster_test.dart b/mobile/test/auto_coverage_booster_test.dart index 7533491b..598fe296 100644 --- a/mobile/test/auto_coverage_booster_test.dart +++ b/mobile/test/auto_coverage_booster_test.dart @@ -1,3 +1,4 @@ +import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -40,6 +41,7 @@ import 'package:ghostclass/screens/scores_screen.dart'; import 'package:ghostclass/screens/splash_screen.dart'; import 'package:ghostclass/screens/static_screen.dart'; import 'package:ghostclass/screens/tracking_screen.dart'; +import 'package:ghostclass/services/dio_service.dart'; import 'package:ghostclass/services/logger.dart'; import 'package:ghostclass/theme/app_theme.dart'; import 'package:ghostclass/widgets/about/about_widgets.dart'; @@ -79,18 +81,21 @@ import 'package:ghostclass/widgets/tracking/tracking_header_widgets.dart'; import 'package:ghostclass/widgets/tracking/tracking_record_card.dart'; import 'package:ghostclass/widgets/tracking/tracking_subject_picker.dart'; import 'package:ghostclass/widgets/transparency_badge.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'coverage_helper.dart'; +class MockFirebaseAppCheck extends Mock implements FirebaseAppCheck {} + void main() { setUpAll(() async { SharedPreferences.setMockInitialValues({}); try { await Supabase.initialize( url: 'https://example.com', - anonKey: 'anon', + publishableKey: 'anon', ); } on Object catch (_) { // already initialized @@ -115,6 +120,7 @@ void main() { final mockLeave = createMockLeaveState(); final overrides = [ + appCheckProvider.overrideWithValue(MockFirebaseAppCheck()), dashboardProvider.overrideWith(() => MockDashboardNotifier(mockDashboard)), authProvider.overrideWith(() => MockAuthNotifier(mockUser)), trackingProvider.overrideWith(() => MockTrackingNotifier(mockTracking)), diff --git a/mobile/test/config/app_config_test.dart b/mobile/test/config/app_config_test.dart index 1678e803..c7bd814d 100644 --- a/mobile/test/config/app_config_test.dart +++ b/mobile/test/config/app_config_test.dart @@ -37,7 +37,7 @@ void main() { test('Sentry config returns valid DSN and Project Number', () { expect(AppConfig.sentryDsn, isNotEmpty); - expect(AppConfig.firebaseCloudProjectNumber, isNotEmpty); + expect(AppConfig.firebaseCloudProjectNumber, isA()); }); test('Metadata properties map standard strings safely', () { diff --git a/mobile/test/coverage_booster_test.dart b/mobile/test/coverage_booster_test.dart index 6f962c56..b8be5463 100644 --- a/mobile/test/coverage_booster_test.dart +++ b/mobile/test/coverage_booster_test.dart @@ -206,10 +206,7 @@ void main() { }); testWidgets('Coverage Booster: AppRouter & SplashScreen', (tester) async { - SharedPreferences.setMockInitialValues({ - 'ghostclass_jwks_cache': '{"keys": []}', - 'ghostclass_jwks_time': DateTime.now().toIso8601String(), - }); + SharedPreferences.setMockInitialValues({}); final mockSupabase = MockSupabaseClient(); final mockAuth = MockGoTrueClient(); diff --git a/mobile/test/coverage_shallow_test.dart b/mobile/test/coverage_shallow_test.dart index e2da4719..aebb2d6f 100644 --- a/mobile/test/coverage_shallow_test.dart +++ b/mobile/test/coverage_shallow_test.dart @@ -42,10 +42,7 @@ void main() { setUp(() { TestWidgetsFlutterBinding.ensureInitialized(); - SharedPreferences.setMockInitialValues({ - 'ghostclass_jwks_cache': '{"keys": []}', - 'ghostclass_jwks_time': DateTime.now().toIso8601String(), - }); + SharedPreferences.setMockInitialValues({}); }); testWidgets('Shallow render important widgets', (tester) async { diff --git a/mobile/test/logic/attendance_utils_test.dart b/mobile/test/logic/attendance_utils_test.dart index 85459f23..c80af96b 100644 --- a/mobile/test/logic/attendance_utils_test.dart +++ b/mobile/test/logic/attendance_utils_test.dart @@ -80,12 +80,18 @@ void main() { expect(normalizeDate('15/01/24'), '20240115'); }); - test('returns empty string for invalid dates', () { - expect(normalizeDate('invalid'), ''); - expect(normalizeDate(null), ''); - expect(normalizeDate('99-99-9999'), ''); - expect(normalizeDate('31-02-2024'), ''); // Invalid day for Feb - }); + test( + 'returns original raw string or empty for invalid dates to avoid key collisions', + () { + expect(normalizeDate('invalid'), 'invalid'); + expect(normalizeDate(null), ''); + expect(normalizeDate('99-99-9999'), '99-99-9999'); + expect( + normalizeDate('31-02-2024'), + '31-02-2024', + ); // Invalid day for Feb + }, + ); }); group('Attendance Utils - normalizeSession', () { diff --git a/mobile/test/logic/bunk_test.dart b/mobile/test/logic/bunk_test.dart index 6f27d6cd..9095c5d6 100644 --- a/mobile/test/logic/bunk_test.dart +++ b/mobile/test/logic/bunk_test.dart @@ -56,7 +56,7 @@ void main() { test('target 100% case', () { final res = calculateAttendance(9, 10, targetPercentage: 100); - expect(res.requiredToAttend, 999); + expect(res.requiredToAttend, 0x7FFFFFFF); }); test('clamped target', () { diff --git a/mobile/test/logic/encrypted_value_test.dart b/mobile/test/logic/encrypted_value_test.dart index e672a1c8..6dd0bf72 100644 --- a/mobile/test/logic/encrypted_value_test.dart +++ b/mobile/test/logic/encrypted_value_test.dart @@ -37,5 +37,22 @@ void main() { ); expect(corrupted.value, ''); }); + + test( + 'clearEntropy invalidates prior instances but supports new instances', + () { + final oldVal = EncryptedValue.fromPlaintext('session1'); + expect(oldVal.value, 'session1'); + + EncryptedValue.clearEntropy(); + + // Old instance is invalidated + expect(oldVal.value, ''); + + // New instance in new generation decrypts successfully + final newVal = EncryptedValue.fromPlaintext('session2'); + expect(newVal.value, 'session2'); + }, + ); }); } diff --git a/mobile/test/models/user_model_test.dart b/mobile/test/models/user_model_test.dart index 9fc97ce5..7bb0f870 100644 --- a/mobile/test/models/user_model_test.dart +++ b/mobile/test/models/user_model_test.dart @@ -203,6 +203,23 @@ void main() { expect(updated.phone, '+1234567890'); expect(updated.email, 'john@example.com'); }); + + test('allows explicitly clearing nullable fields to null', () { + const profile = UserProfile( + firstName: 'John', + avatarUrl: 'https://example.com/avatar.png', + phone: '+1234567890', + ); + + final updated = profile.copyWith( + avatarUrl: () => null, + phone: () => null, + ); + + expect(updated.avatarUrl, isNull); + expect(updated.phone, isNull); + expect(updated.firstName, 'John'); + }); }); group('equality', () { diff --git a/mobile/test/models/user_test.dart b/mobile/test/models/user_test.dart index 22f141fb..2d3b5f79 100644 --- a/mobile/test/models/user_test.dart +++ b/mobile/test/models/user_test.dart @@ -107,6 +107,7 @@ void main() { 'disabled_courses': { 'sem1': {'C1': 'Course1'}, }, + 'course_targets': {}, }; final s = UserSettings.fromJson(json); diff --git a/mobile/test/providers/notification_provider_test.dart b/mobile/test/providers/notification_provider_test.dart index ae7d263a..5d5a56d3 100644 --- a/mobile/test/providers/notification_provider_test.dart +++ b/mobile/test/providers/notification_provider_test.dart @@ -25,7 +25,7 @@ void main() { try { await Supabase.initialize( url: 'https://placeholder-domain.supabase.co', - anonKey: 'placeholder-anon-key', + publishableKey: 'placeholder-anon-key', ); } on Object catch (_) { // already initialized diff --git a/mobile/test/services/auth_profile_settings_coverage_test.dart b/mobile/test/services/auth_profile_settings_coverage_test.dart index c232adf5..a47e263a 100644 --- a/mobile/test/services/auth_profile_settings_coverage_test.dart +++ b/mobile/test/services/auth_profile_settings_coverage_test.dart @@ -37,7 +37,7 @@ void main() { try { await Supabase.initialize( url: 'https://example.com', - anonKey: 'anon', + publishableKey: 'anon', ); } on Object catch (_) { // already initialized diff --git a/mobile/test/services/dio_service_coverage_test.dart b/mobile/test/services/dio_service_coverage_test.dart index 5d14f272..ef32064b 100644 --- a/mobile/test/services/dio_service_coverage_test.dart +++ b/mobile/test/services/dio_service_coverage_test.dart @@ -5,19 +5,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ghostclass/config/app_config.dart'; import 'package:ghostclass/services/dio_service.dart'; -import 'package:ghostclass/services/jwe_interceptor.dart'; -import 'package:ghostclass/services/jwe_service.dart'; import 'package:mocktail/mocktail.dart'; class MockFirebaseAppCheck extends Mock implements FirebaseAppCheck {} -class MockJweService extends Mock implements JweService {} - class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} void main() { late MockFirebaseAppCheck mockAppCheck; - late MockJweService mockJweService; late MockHttpClientAdapter mockAdapter; late ProviderContainer container; late DioService dioService; @@ -28,19 +23,11 @@ void main() { setUp(() { mockAppCheck = MockFirebaseAppCheck(); - mockJweService = MockJweService(); mockAdapter = MockHttpClientAdapter(); - when( - () => mockJweService.encryptHeaderKey(), - ).thenAnswer((_) async => (jwe: 'mock-key', rcek: 'mock-rcek')); - container = ProviderContainer( overrides: [ appCheckProvider.overrideWithValue(mockAppCheck), - jweInterceptorProvider.overrideWithValue( - JweInterceptor(mockJweService), - ), ], ); dioService = container.read(dioServiceProvider); diff --git a/mobile/test/widgets/attendance/edit_instructor_dialog_test.dart b/mobile/test/widgets/attendance/edit_instructor_dialog_test.dart index 10e4eb85..e2b6699a 100644 --- a/mobile/test/widgets/attendance/edit_instructor_dialog_test.dart +++ b/mobile/test/widgets/attendance/edit_instructor_dialog_test.dart @@ -79,6 +79,7 @@ void main() { () => mockApi.upsertInstructor( courseCode: any(named: 'courseCode'), instructorName: any(named: 'instructorName'), + courseName: any(named: 'courseName'), supabaseToken: any(named: 'supabaseToken'), ), ).thenAnswer( @@ -157,6 +158,7 @@ void main() { () => mockApi.upsertInstructor( courseCode: 'CS101', instructorName: 'Dr. New', + courseName: 'Intro', supabaseToken: 'test-supabase-token', ), ).called(1); diff --git a/mobile/test/widgets/header_section_test.dart b/mobile/test/widgets/header_section_test.dart index 93f057b7..eebc8aa1 100644 --- a/mobile/test/widgets/header_section_test.dart +++ b/mobile/test/widgets/header_section_test.dart @@ -84,12 +84,12 @@ void main() { ), trackingRecords: const [], selectedSemester: 'odd', - selectedYear: '2026-27', + selectedYear: '2027-28', ), selectedSemester: 'odd', - selectedYear: '2026-27', + selectedYear: '2027-28', ); - const mockAcademic = AcademicState(semester: 'odd', year: '2026-27'); + const mockAcademic = AcademicState(semester: 'odd', year: '2027-28'); await tester.pumpWidget( ProviderScope( diff --git a/next.config.ts b/next.config.ts index 60be9605..844f65f0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,6 @@ import { withSentryConfig } from "@sentry/nextjs"; -import type { NextConfig } from "next"; import withSerwistInit from "@serwist/next"; +import type { NextConfig } from "next"; if (process.env.NODE_ENV === "production") { process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = "1"; @@ -28,7 +28,8 @@ const withSerwist = withSerwistInit({ // AND // 2. The dev SW flag is NOT set to true // This ensures production builds ALWAYS generate the service worker - disable: process.env.NODE_ENV !== "production" && process.env.NEXT_PUBLIC_ENABLE_SW_IN_DEV !== "true", + disable: process.env.NODE_ENV !== "production" && + process.env.NEXT_PUBLIC_ENABLE_SW_IN_DEV !== "true", // Must be false: caching navigation (document) responses breaks Next.js streaming SSR. // All protected pages use `export const dynamic = 'force-dynamic'`. When this is true, // Serwist wraps the response in a NetworkFirst/StaleWhileRevalidate strategy that buffers @@ -42,9 +43,9 @@ const withSerwist = withSerwistInit({ // Allowing both production and development hostnames ensures images work across environments. const allowedImageHostnames = (() => { const hosts = [ - "lh3.googleusercontent.com", // Google + "lh3.googleusercontent.com", // Google "avatars.githubusercontent.com", // GitHub - "secure.gravatar.com", // Gravatar + "secure.gravatar.com", // Gravatar ]; const envUrls = [ @@ -67,8 +68,8 @@ const allowedImageHostnames = (() => { if (!envUrls.some(Boolean)) { throw new Error( - '[next.config.ts] At least one Supabase URL or Supabase Proxy URL ' + - 'is required at build time for images.remotePatterns.' + "[next.config.ts] At least one Supabase URL or Supabase Proxy URL " + + "is required at build time for images.remotePatterns.", ); } @@ -88,7 +89,7 @@ const nextConfig = { // regardless of this setting. productionBrowserSourceMaps: true, - async headers() { + headers() { // 1. Define headers common to all environments const headersList = [ { @@ -126,7 +127,7 @@ const nextConfig = { ]; // 2. Only add HSTS in Production to prevent local SSL errors - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV === "production") { // 2-year max-age (63072000 seconds = 2 years) satisfies Lighthouse "max-age too low" audit (minimum recommended: 63072000). // preload qualifies the domain for HSTS preload lists (https://hstspreload.org). headersList.push({ @@ -191,36 +192,38 @@ const nextConfig = { experimental: { optimizePackageImports: [ - 'lucide-react', - 'date-fns', - 'framer-motion', - 'recharts', - '@radix-ui/react-alert-dialog', - '@radix-ui/react-avatar', - '@radix-ui/react-checkbox', - '@radix-ui/react-dialog', - '@radix-ui/react-dropdown-menu', - '@radix-ui/react-label', - '@radix-ui/react-popover', - '@radix-ui/react-progress', - '@radix-ui/react-radio-group', - '@radix-ui/react-scroll-area', - '@radix-ui/react-select', - '@radix-ui/react-separator', - '@radix-ui/react-slot', - '@radix-ui/react-switch', - '@radix-ui/react-tabs', + "lucide-react", + "date-fns", + "framer-motion", + "recharts", + "@radix-ui/react-alert-dialog", + "@radix-ui/react-avatar", + "@radix-ui/react-checkbox", + "@radix-ui/react-dialog", + "@radix-ui/react-dropdown-menu", + "@radix-ui/react-label", + "@radix-ui/react-popover", + "@radix-ui/react-progress", + "@radix-ui/react-radio-group", + "@radix-ui/react-scroll-area", + "@radix-ui/react-select", + "@radix-ui/react-separator", + "@radix-ui/react-slot", + "@radix-ui/react-switch", + "@radix-ui/react-tabs", ], }, // Performance: Minimize JavaScript bundle compiler: { - removeConsole: process.env.NODE_ENV === 'production' ? { - exclude: ['error', 'warn'], // Preserve console.error and console.warn; strip log/info from production - } : false, + removeConsole: process.env.NODE_ENV === "production" + ? { + exclude: ["error", "warn"], // Preserve console.error and console.warn; strip log/info from production + } + : false, }, - - generateBuildId: async () => { + + generateBuildId: () => { // Prefer the commit SHA injected by CI/CD for stable, traceable build IDs. // Fall back to a random UUID so that two builds without APP_COMMIT_SHA still get // different IDs โ€” preventing Next.js static-asset cache collisions across deployments. @@ -232,26 +235,28 @@ const nextConfig = { }, images: { - formats: ['image/avif', 'image/webp'], + formats: ["image/avif", "image/webp"], remotePatterns: [ ...allowedImageHostnames.map((hostname: string) => { - const isSupabase = hostname.includes('supabase'); + const isSupabase = hostname.includes("supabase"); return { - protocol: 'https' as const, + protocol: "https" as const, hostname, - port: '', + port: "", // Supabase storage is strictly nested under /storage/v1/object/public/ // while OAuth providers (Google, GitHub) serve images from various root paths. - pathname: isSupabase ? '/storage/v1/object/public/**' : '/**', + pathname: isSupabase ? "/storage/v1/object/public/**" : "/**", }; }), ], }, // eslint-disable-next-line sonarjs/no-hardcoded-ip - allowedDevOrigins: ['192.168.0.103'] + allowedDevOrigins: ["192.168.0.103"], } satisfies NextConfig; -const sentryCompatibleConfig = withSerwist(nextConfig) as Parameters[0]; +const sentryCompatibleConfig = withSerwist(nextConfig) as Parameters< + typeof withSentryConfig +>[0]; const sentryPluginOptions = { org: process.env.SENTRY_ORG, @@ -289,7 +294,7 @@ const sentryPluginOptions = { }, widenClientFileUpload: true, - tunnelRoute: "/monitoring" + tunnelRoute: "/monitoring", } satisfies Parameters[1]; -export default withSentryConfig(sentryCompatibleConfig, sentryPluginOptions); \ No newline at end of file +export default withSentryConfig(sentryCompatibleConfig, sentryPluginOptions); diff --git a/package-lock.json b/package-lock.json index 22fb013a..db91151e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,114 +1,113 @@ { "name": "ghostclass", - "version": "4.4.9", + "version": "4.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostclass", - "version": "4.4.9", - "dependencies": { - "@hookform/resolvers": "^5.2.2", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-avatar": "^1.1.11", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@scalar/nextjs-api-reference": "^0.10.0", - "@sentry/nextjs": "^10.41.0", - "@serwist/next": "^9.5.6", - "@supabase/ssr": "^0.10.2", - "@supabase/supabase-js": "^2.97.0", - "@tanstack/react-query": "^5.100.13", - "@tanstack/react-virtual": "^3.13.21", + "version": "4.5.0", + "dependencies": { + "@hookform/resolvers": "^5.5.7", + "@radix-ui/react-alert-dialog": "^1.1.23", + "@radix-ui/react-avatar": "^1.2.6", + "@radix-ui/react-checkbox": "^1.3.11", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-label": "^2.1.15", + "@radix-ui/react-popover": "^1.1.23", + "@radix-ui/react-progress": "^1.1.16", + "@radix-ui/react-radio-group": "^1.4.7", + "@radix-ui/react-scroll-area": "^1.2.18", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-switch": "^1.3.7", + "@radix-ui/react-tabs": "^1.1.21", + "@scalar/nextjs-api-reference": "^0.11.12", + "@sentry/nextjs": "^10.69.0", + "@serwist/next": "^9.5.12", + "@supabase/ssr": "^0.12.4", + "@supabase/supabase-js": "^2.111.0", + "@tanstack/react-query": "^5.101.4", + "@tanstack/react-virtual": "^3.14.9", "@upstash/ratelimit": "^2.0.8", - "@upstash/redis": "^1.36.2", - "axios": "^1.13.5", + "@upstash/redis": "^1.38.1", + "axios": "^1.19.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "firebase-admin": "^13.8.0", - "framer-motion": "^12.34.3", + "date-fns": "^4.4.0", + "firebase-admin": "^14.2.0", + "framer-motion": "^12.43.0", "googleapis": "^173.0.0", - "jose": "^6.2.2", "ldrs": "^1.1.9", - "lodash-es": "^4.17.23", - "lru-cache": "^11.2.6", - "lucide-react": "^1.16.0", - "next": "^16.1.6", + "lodash-es": "^4.18.1", + "lru-cache": "^11.5.2", + "lucide-react": "^1.28.0", + "next": "^16.2.12", "nextjs-toploader": "^3.9.17", "node-domexception": "^2.0.2", - "react": "^19.2.4", - "react-day-picker": "^10.0.0", - "react-dom": "^19.2.4", - "react-email": "^6.0.0", - "react-hook-form": "^7.71.2", - "react-is": "^19.2.5", + "react": "^19.2.8", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.8", + "react-email": "^6.9.1", + "react-hook-form": "^7.83.0", + "react-is": "^19.2.8", "react-markdown": "^10.1.0", "react-turnstile": "^1.1.5", - "recharts": "^3.7.0", - "resend": "^6.9.2", - "sanitize-html": "^2.17.0", + "recharts": "^3.10.1", + "resend": "^6.18.1", + "sanitize-html": "^2.17.6", "server-only": "^0.0.1", - "serwist": "^9.5.6", + "serwist": "^9.5.12", "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", - "uuid": "^14.0.0", - "zod": "^4.3.6" + "uuid": "^14.0.1", + "zod": "^4.4.3" }, "devDependencies": { - "@eslint/js": "^9.39.4", - "@opentelemetry/context-async-hooks": "^2.6.0", - "@playwright/test": "^1.59.1", + "@eslint/js": "^9.39.5", + "@opentelemetry/context-async-hooks": "^2.10.0", + "@playwright/test": "^1.62.1", "@tailwindcss/postcss": "^4", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.3.5", + "@types/node": "^26.1.2", "@types/nprogress": "^0.2.3", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@types/sanitize-html": "^2.16.0", - "@typescript-eslint/parser": "^8.56.1", - "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.0.18", - "@vitest/ui": "^4.1.5", - "eslint": "^9.39.4", - "eslint-config-next": "^16.1.6", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@types/sanitize-html": "^2.16.1", + "@typescript-eslint/parser": "^8.65.0", + "@vitejs/plugin-react": "^6.0.5", + "@vitest/coverage-v8": "^4.1.10", + "@vitest/ui": "^4.1.10", + "eslint": "^9.39.5", + "eslint-config-next": "^16.2.12", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-security": "^4.0.0", - "eslint-plugin-sonarjs": "^4.0.3", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-security": "^4.0.1", + "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-unused-imports": "^4.4.1", "glob": "^13.0.6", - "globals": "^17.3.0", - "happy-dom": "^20.7.0", + "globals": "^17.8.0", + "happy-dom": "^20.11.1", "husky": "^9.1.7", - "jsdom": "^29.0.0", - "lint-staged": "^17.0.4", + "jsdom": "^30.0.1", + "lint-staged": "^17.3.0", "rimraf": "^6.1.3", - "source-map": "^0.7.6", - "supabase": "^2.76.14", + "source-map": "^0.8.0", + "supabase": "^2.111.0", "tailwindcss": "^4", "typescript": "^6.0.3", - "typescript-eslint": "^8.56.1", - "vitest": "^4.1.5" + "typescript-eslint": "^8.65.0", + "vitest": "^4.1.10" }, "engines": { "node": ">=22.12.0", - "npm": ">=11" + "npm": ">=12" } }, "node_modules/@adobe/css-tools": { @@ -131,57 +130,82 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz", + "integrity": "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/estree": "^1.0.8", + "astring": "^1.9.0", + "esquery": "^1.7.0", + "meriyah": "^6.1.4", + "semifies": "^1.0.0", + "source-map": "^0.6.0" + }, + "bin": { + "code-transformer": "cli.js" + } + }, + "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.3.tgz", + "integrity": "sha512-qNbPwuMZ8f5ZuGj/ttPeB7a6C/S1bB6tNYaEL5vNiRKydSAxa4AU0gxCWgaP4fVju+AuwhcumSFjrEcGF9Dv7Q==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.18.0", + "es-module-lexer": "^2.1.0", + "magic-string": "^0.30.21", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.0.tgz", + "integrity": "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.18.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz", + "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-color-parser": "^4.1.9", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.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.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -236,13 +260,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -356,12 +380,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -395,17 +419,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -413,9 +437,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -449,9 +473,9 @@ } }, "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==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", "dev": true, "funding": [ { @@ -469,9 +493,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", "dev": true, "funding": [ { @@ -493,9 +517,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", - "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", "dev": true, "funding": [ { @@ -509,8 +533,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" }, "engines": { "node": ">=20.19.0" @@ -544,9 +568,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", - "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", "dev": true, "funding": [ { @@ -594,6 +618,21 @@ "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==", "license": "MIT" }, + "node_modules/@ecies/ciphers": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", + "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2.7.10", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -607,9 +646,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, "dependencies": { @@ -628,9 +667,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -644,9 +683,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -660,9 +699,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -676,9 +715,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -692,9 +731,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -708,9 +747,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -724,9 +763,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -740,9 +779,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -756,9 +795,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -772,9 +811,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -788,9 +827,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -804,9 +843,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -820,9 +859,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -836,9 +875,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -852,9 +891,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -868,9 +907,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -884,9 +923,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -900,9 +939,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -916,9 +955,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -932,9 +971,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -948,9 +987,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -964,9 +1003,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -980,9 +1019,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -996,9 +1035,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1012,9 +1051,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1028,9 +1067,9 @@ } }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -1044,9 +1083,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1127,9 +1166,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1139,7 +1178,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -1150,6 +1189,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -1163,10 +1219,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -1246,12 +1309,12 @@ "license": "Apache-2.0" }, "node_modules/@firebase/component": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz", - "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.4.tgz", + "integrity": "sha512-tLpOaaCol9ugUIYp2R3CbWPPA8Ajg/papX/XHEy8U52b/QXH3BbX8tTJX9aShDCjp+9sMAxMLD94i7lresdugQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.15.1", + "@firebase/util": "1.15.2", "tslib": "^2.1.0" }, "engines": { @@ -1259,16 +1322,16 @@ } }, "node_modules/@firebase/database": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz", - "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.4.tgz", + "integrity": "sha512-D+j4+8uhGtNd1tVD+X+c8JrC4ppStGJKyujSQt2NPwdN26QcCk0BeIxue+UqspHkHiFHyQOimwlzjLewGq6S+A==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.4", "@firebase/auth-interop-types": "0.2.5", - "@firebase/component": "0.7.3", + "@firebase/component": "0.7.4", "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", + "@firebase/util": "1.15.2", "faye-websocket": "0.11.4", "tslib": "^2.1.0" }, @@ -1277,30 +1340,42 @@ } }, "node_modules/@firebase/database-compat": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz", - "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.5.tgz", + "integrity": "sha512-m2KZDNXrg8DBzXWQNbbrjOhsJnM+ctsSFaDYKrqj1gEetQ8BSAwRuMUdeWLM9a6qPBgOvOA+o09j1BSEzdFqOg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/database": "1.1.3", - "@firebase/database-types": "1.0.20", + "@firebase/component": "0.7.4", + "@firebase/database": "1.1.4", + "@firebase/database-types": "1.0.21", "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", + "@firebase/util": "1.15.2", "tslib": "^2.1.0" }, "engines": { "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + }, + "peerDependenciesMeta": { + "@firebase/app": { + "optional": true + }, + "@firebase/app-compat": { + "optional": true + } } }, "node_modules/@firebase/database-types": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz", - "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==", + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.21.tgz", + "integrity": "sha512-SX1jUqhttKgg/m9dYRTvqU9QvucBooziWfA986r4cpsbi4zlsvewe424j3Vpduwd6DG1MSAMfBVT2VqA61FnkA==", "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.5", - "@firebase/util": "1.15.1" + "@firebase/util": "1.15.2" } }, "node_modules/@firebase/logger": { @@ -1316,9 +1391,9 @@ } }, "node_modules/@firebase/util": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz", - "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.2.tgz", + "integrity": "sha512-974pWIZVLDMc5GW5YAsj8y0XxULxIy/sPUy7tsxmWbF93KRIyh9xpuHlh0zDL+shUcf5nHDjFOg9YLiQ763eiA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1329,31 +1404,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -1361,26 +1436,26 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@google-cloud/firestore": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz", - "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-8.7.0.tgz", + "integrity": "sha512-EvMpZQUXkTRdweSvOu6VL6EEQwHjHAgWz2UYZR+Mj6Ao52S+TWieHbSn15jiNnEw8F8RhbZj7IGXZ1PFB1eA+A==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@opentelemetry/api": "^1.3.0", - "fast-deep-equal": "^3.1.1", + "@opentelemetry/api": "^1.9.0", + "fast-deep-equal": "^3.1.3", "functional-red-black-tree": "^1.0.1", - "google-gax": "^4.3.3", - "protobufjs": "^7.2.6" + "google-gax": "^5.0.1", + "protobufjs": "^7.5.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@google-cloud/paginator": { @@ -1418,9 +1493,9 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", - "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.21.0.tgz", + "integrity": "sha512-l+IFTkd+6Y5LoAuXyYCKNAKtw/Ci+rAMqgdTB1jv4iZiLhw0rtq+0qjIRbBizXkNzEFmXiXUW0H7sZQQvk1ffA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1437,8 +1512,7 @@ "mime": "^3.0.0", "p-limit": "^3.0.1", "retry-request": "^7.0.0", - "teeny-request": "^9.0.0", - "uuid": "^8.0.0" + "teeny-request": "^9.0.0" }, "engines": { "node": ">=14" @@ -1501,7 +1575,7 @@ "node": ">=12.10.0" } }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "node_modules/@grpc/proto-loader": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", @@ -1520,35 +1594,114 @@ "node": ">=6" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@hookform/resolvers": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz", - "integrity": "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.7.1.tgz", + "integrity": "sha512-8wS/P4UDr5sQDe4nFaV51TVyfDPrWgNIXweqG0Bs9Z5LSuzKLb+RQNPvkN2oHM5SRrJyWrVH/F+LOUcFjUyvwQ==", "license": "MIT", "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { - "react-hook-form": "^7.55.0" + "@sinclair/typebox": ">=0.25.24", + "@standard-schema/spec": "^1.0.0", + "@typeschema/main": ">=0.13.7", + "@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0", + "ajv": "^8.12.0", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "arktype": "^2.0.0", + "ata-validator": "^1.2.0", + "class-transformer": ">=0.4.0", + "class-validator": ">=0.12.0", + "computed-types": "^1.0.0", + "effect": "^3.10.3", + "fluentvalidation-ts": "^3.0.0", + "fp-ts": "^2.7.0", + "io-ts": "^2.0.0", + "joi": "^17.0.0", + "nope-validator": ">=0.12.0", + "react-hook-form": "^7.55.0", + "superstruct": ">=0.12.0", + "typanion": "^3.3.2", + "valibot": ">=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc", + "vest": ">=3.0.0", + "yup": "^1.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@sinclair/typebox": { + "optional": true + }, + "@standard-schema/spec": { + "optional": true + }, + "@typeschema/main": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "ajv": { + "optional": true + }, + "ajv-errors": { + "optional": true + }, + "ajv-formats": { + "optional": true + }, + "arktype": { + "optional": true + }, + "ata-validator": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "computed-types": { + "optional": true + }, + "effect": { + "optional": true + }, + "fluentvalidation-ts": { + "optional": true + }, + "fp-ts": { + "optional": true + }, + "io-ts": { + "optional": true + }, + "joi": { + "optional": true + }, + "nope-validator": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "typanion": { + "optional": true + }, + "valibot": { + "optional": true + }, + "vest": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } } }, "node_modules/@humanfs/core": { @@ -1628,9 +1781,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", "cpu": [ "arm64" ], @@ -1640,19 +1793,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.2" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", "cpu": [ "x64" ], @@ -1662,19 +1815,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", "cpu": [ "arm64" ], @@ -1688,9 +1860,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", "cpu": [ "x64" ], @@ -1704,9 +1876,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", "cpu": [ "arm" ], @@ -1723,9 +1895,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", "cpu": [ "arm64" ], @@ -1742,9 +1914,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", "cpu": [ "ppc64" ], @@ -1761,9 +1933,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", "cpu": [ "riscv64" ], @@ -1780,9 +1952,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", "cpu": [ "s390x" ], @@ -1799,9 +1971,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", "cpu": [ "x64" ], @@ -1818,9 +1990,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", "cpu": [ "arm64" ], @@ -1837,9 +2009,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", "cpu": [ "x64" ], @@ -1856,9 +2028,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", "cpu": [ "arm" ], @@ -1871,19 +2043,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.2" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", "cpu": [ "arm64" ], @@ -1896,19 +2068,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.2" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", "cpu": [ "ppc64" ], @@ -1921,19 +2093,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.2" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", "cpu": [ "riscv64" ], @@ -1946,19 +2118,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.2" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", "cpu": [ "s390x" ], @@ -1971,19 +2143,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.2" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", "cpu": [ "x64" ], @@ -1996,19 +2168,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.2" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", "cpu": [ "arm64" ], @@ -2021,19 +2193,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", "cpu": [ "x64" ], @@ -2046,38 +2218,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.3" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", "cpu": [ "arm64" ], @@ -2087,16 +2275,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", "cpu": [ "ia32" ], @@ -2106,16 +2294,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", "cpu": [ "x64" ], @@ -2125,7 +2313,7 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -2160,6 +2348,17 @@ "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==", + "license": "MIT", + "peer": true, + "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", @@ -2187,35 +2386,57 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@next/env": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", - "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz", + "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.6.tgz", - "integrity": "sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.12.tgz", + "integrity": "sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==", "dev": true, "license": "MIT", "dependencies": { @@ -2223,9 +2444,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", - "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz", + "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==", "cpu": [ "arm64" ], @@ -2239,9 +2460,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", - "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz", + "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==", "cpu": [ "x64" ], @@ -2255,9 +2476,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", - "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz", + "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==", "cpu": [ "arm64" ], @@ -2274,9 +2495,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", - "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz", + "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==", "cpu": [ "arm64" ], @@ -2293,9 +2514,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", - "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz", + "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==", "cpu": [ "x64" ], @@ -2312,9 +2533,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", - "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz", + "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==", "cpu": [ "x64" ], @@ -2331,9 +2552,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", - "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz", + "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==", "cpu": [ "arm64" ], @@ -2347,9 +2568,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", - "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz", + "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==", "cpu": [ "x64" ], @@ -2362,10 +2583,52 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodable/entities": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", - "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", "funding": [ { "type": "github", @@ -2433,9 +2696,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.214.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", - "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -2445,9 +2708,9 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", - "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.10.0.tgz", + "integrity": "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2458,9 +2721,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", - "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2473,12 +2736,12 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.214.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", - "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/api-logs": "0.220.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, @@ -2490,12 +2753,29 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2506,13 +2786,14 @@ } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2523,18 +2804,18 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", "funding": { @@ -2542,19 +2823,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", - "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", - "dev": true, + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.60.0" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@polka/url": { @@ -2609,13 +2890,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause", - "optional": true - }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -2631,36 +2905,35 @@ "optional": true }, "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause", "optional": true }, "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "license": "MIT" }, "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", - "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -2677,31 +2950,13 @@ } } }, - "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -2719,54 +2974,17 @@ } }, "node_modules/@radix-ui/react-avatar": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", - "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.3", - "@radix-ui/react-primitive": "2.1.4", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2784,19 +3002,18 @@ } }, "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2814,15 +3031,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -2839,28 +3056,10 @@ } } }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2873,9 +3072,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2888,25 +3087,26 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -2923,28 +3123,10 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2957,16 +3139,16 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -2984,18 +3166,18 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3013,9 +3195,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -3028,14 +3210,14 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3053,12 +3235,12 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3071,35 +3253,12 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", - "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -3117,29 +3276,29 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -3156,45 +3315,27 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -3211,40 +3352,22 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -3262,13 +3385,13 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3286,13 +3409,12 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3310,12 +3432,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -3332,32 +3454,14 @@ } } }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", - "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.3", - "@radix-ui/react-primitive": "2.1.4" + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -3374,28 +3478,21 @@ } } }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3412,22 +3509,23 @@ } } }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3444,21 +3542,21 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3475,21 +3573,34 @@ } } }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1" + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -3506,33 +3617,13 @@ } } }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -3549,13 +3640,13 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -3567,13 +3658,18 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.4" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3590,13 +3686,20 @@ } } }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3613,87 +3716,10 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -3706,13 +3732,14 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3725,30 +3752,12 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3761,13 +3770,10 @@ } }, "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", - "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.5.0" - }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -3779,9 +3785,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -3794,9 +3800,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -3809,12 +3815,12 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.1" + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -3827,12 +3833,12 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3845,12 +3851,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -3868,18 +3874,20 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "license": "MIT" }, "node_modules/@react-email/render": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.8.tgz", - "integrity": "sha512-5udvVr3U/WuGJZfLdLBOhkzrqRWd2Q5ZYmF7ppcy7FzWcwgshdqLMNqJOXcVzAXJXg/2bm7D+WGJzTtZOZMQnQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.1.0.tgz", + "integrity": "sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==", "license": "MIT", "dependencies": { + "entities": "^4.5.0", "html-to-text": "^9.0.5", + "html5parser": "^3.0.0", "prettier": "^3.5.3" }, "engines": { @@ -3890,6 +3898,18 @@ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, + "node_modules/@react-email/render/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", @@ -3916,20 +3936,10 @@ } } }, - "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", - "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -3944,9 +3954,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -3961,9 +3971,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -3978,9 +3988,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -3995,9 +4005,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -4012,9 +4022,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], @@ -4032,9 +4042,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], @@ -4052,9 +4062,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], @@ -4072,9 +4082,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], @@ -4092,9 +4102,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], @@ -4112,9 +4122,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], @@ -4132,9 +4142,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -4149,28 +4159,59 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -4185,9 +4226,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -4257,9 +4298,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", - "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", "cpu": [ "arm" ], @@ -4270,9 +4311,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", - "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", "cpu": [ "arm64" ], @@ -4283,9 +4324,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", - "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", "cpu": [ "arm64" ], @@ -4296,9 +4337,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", - "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", "cpu": [ "x64" ], @@ -4309,9 +4350,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", - "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", "cpu": [ "arm64" ], @@ -4322,9 +4363,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", - "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", "cpu": [ "x64" ], @@ -4335,9 +4376,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", - "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", "cpu": [ "arm" ], @@ -4351,9 +4392,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", - "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", "cpu": [ "arm" ], @@ -4367,9 +4408,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", - "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", "cpu": [ "arm64" ], @@ -4383,9 +4424,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", - "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", "cpu": [ "arm64" ], @@ -4399,9 +4440,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", - "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", "cpu": [ "loong64" ], @@ -4415,9 +4456,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", - "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", "cpu": [ "loong64" ], @@ -4431,9 +4472,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", - "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", "cpu": [ "ppc64" ], @@ -4447,9 +4488,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", - "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", "cpu": [ "ppc64" ], @@ -4463,9 +4504,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", - "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", "cpu": [ "riscv64" ], @@ -4479,9 +4520,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", - "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", "cpu": [ "riscv64" ], @@ -4495,9 +4536,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", - "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", "cpu": [ "s390x" ], @@ -4511,9 +4552,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", - "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", "cpu": [ "x64" ], @@ -4527,9 +4568,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", - "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", "cpu": [ "x64" ], @@ -4543,9 +4584,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", - "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", "cpu": [ "x64" ], @@ -4556,9 +4597,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", - "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", "cpu": [ "arm64" ], @@ -4569,9 +4610,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", - "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", "cpu": [ "arm64" ], @@ -4582,9 +4623,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", - "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", "cpu": [ "ia32" ], @@ -4595,9 +4636,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", - "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", "cpu": [ "x64" ], @@ -4608,9 +4649,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", - "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", "cpu": [ "x64" ], @@ -4628,35 +4669,35 @@ "license": "MIT" }, "node_modules/@scalar/client-side-rendering": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@scalar/client-side-rendering/-/client-side-rendering-0.1.12.tgz", - "integrity": "sha512-prwHK4ozTU268BHZ/5OstoKB23JSidDuvddAOp0bVz9c29ZxsyzzxPtPcVgF7X16LiZnS1OzY030FoDCM+iC9Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@scalar/client-side-rendering/-/client-side-rendering-0.3.5.tgz", + "integrity": "sha512-0MX6HN4hNhMlBTqm+PJ1Rgi5yQy7NkMEPMa6QXdyKAO59pQVzWlBGT+MlAnyXCeVrqFULzZh90v81t5UQ/uYEw==", "license": "MIT", "dependencies": { - "@scalar/schemas": "0.3.2", - "@scalar/types": "0.12.2", - "@scalar/validation": "0.6.0" + "@scalar/schemas": "0.8.0", + "@scalar/types": "0.17.0", + "@scalar/validation": "0.6.2" }, "engines": { "node": ">=22" } }, "node_modules/@scalar/helpers": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.8.0.tgz", - "integrity": "sha512-gmOC6VravNB9VDl6wnt/GOj4K/hn48tj5bpW4AM4MhH8Ubil6uu7g1DSoKHwltu8Ks79KEtR6JmOrROi9R7jaQ==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.9.2.tgz", + "integrity": "sha512-hjyMpMZjTBZQhyByZmz5oUgRKUQJO5V5AOiJxsVEGbUmgA7sJRQeTrXLB+BEwzaKS5nm2opJeNyMBYLFNK4hiQ==", "license": "MIT", "engines": { "node": ">=22" } }, "node_modules/@scalar/nextjs-api-reference": { - "version": "0.10.19", - "resolved": "https://registry.npmjs.org/@scalar/nextjs-api-reference/-/nextjs-api-reference-0.10.19.tgz", - "integrity": "sha512-+8e20fM+vPCg0sQfpJJVfB2QT2rSli1XPPpwVqyP2NyOqKskZx33ixdh5/I1VhlRPECgy8M+qSwVu0E6v5iV1w==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/@scalar/nextjs-api-reference/-/nextjs-api-reference-0.11.12.tgz", + "integrity": "sha512-JTzoTSMCvdQGae/ECL1sOCBe9/lo50Fk87Pb93JKubFXo5SHSsPcpWX0dFWwyoamlH0UpgHqEUw3xljFpmAbNw==", "license": "MIT", "dependencies": { - "@scalar/client-side-rendering": "0.1.12" + "@scalar/client-side-rendering": "0.3.5" }, "engines": { "node": ">=22" @@ -4667,25 +4708,25 @@ } }, "node_modules/@scalar/schemas": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.3.2.tgz", - "integrity": "sha512-iadXBgJ02XUU5C5s6/xh/PmGLzUPd7X8upXIvPWBXDcQ4FHACNgkG8PPZ/beYM8UPDDkTUPM3ygEs0G6jKwGjQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.8.0.tgz", + "integrity": "sha512-bwu/NCOghZI/cL6Ayti/Oeb5XEMRTncBsyQeHhkFwk4PZsR910me+kZPd5TAp6TqifMfDbAe3xyLSdlzU/KFLA==", "license": "MIT", "dependencies": { - "@scalar/helpers": "0.8.0", - "@scalar/validation": "0.6.0" + "@scalar/helpers": "0.9.2", + "@scalar/validation": "0.6.2" }, "engines": { "node": ">=22" } }, "node_modules/@scalar/types": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.12.2.tgz", - "integrity": "sha512-EzLkubCb7xioiTm9eYnmn/032akaq4kkrrdclgV2uezwtniR8ErQICjhMl2AjBWL6nstHiFZ9RnPZm2Z2/KM0Q==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.17.0.tgz", + "integrity": "sha512-mj033MX0EFOEwfpO3ch7FGGnMbye1aLlnP6LmTC9Il2AlBCDL41rYPk67jF5ICAMsAES1RQ+8N2bcC0MJnupLQ==", "license": "MIT", "dependencies": { - "@scalar/helpers": "0.8.0", + "@scalar/helpers": "0.9.2", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" @@ -4695,9 +4736,9 @@ } }, "node_modules/@scalar/validation": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.0.tgz", - "integrity": "sha512-tpmmG+/xRE2Kn9RpflU3AIyZv08v10+E1ZrJCx7z6+/91zHVxy0M73kC1LT4/8PbYNt85ywyC8+n+D99JdMcGA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.2.tgz", + "integrity": "sha512-Sc1TkcwGV6aVCO51AyKeaGiP8gpwAHxEtO5d3tZzPV+KsnlC/YokQxFxwBrbIXw73k9hmcExnJyGu3k5i6n6VA==", "license": "MIT", "engines": { "node": ">=20" @@ -4716,76 +4757,40 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/@sentry-internal/browser-utils": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.55.0.tgz", - "integrity": "sha512-zUvyBr13EK0evKsSTzwSimRzZ3P9kugS32dLCj3ea5gNN+/DFtU/GsMTdcIQDhusEDraIlH17AGgqJH5gUAv5w==", - "license": "MIT", - "dependencies": { - "@sentry/core": "10.55.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry-internal/feedback": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.55.0.tgz", - "integrity": "sha512-32X9WW1xs5DjCRlp89QJ/PLw4kbTIX6MsBDXN2RBN1nWBjm/2WcwXqO/v/WoIS4W2kTWXcZnQwalLSI22Fp33A==", - "license": "MIT", - "dependencies": { - "@sentry/core": "10.55.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry-internal/replay": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.55.0.tgz", - "integrity": "sha512-OkQpANGwYU5UKfwLk6Y+NpESRC8nrLBjawRDLwF6cJ8HpNScOuNNJDEJEGwXHVkJPH0pcIixsH8y0Qfcltq6Xw==", + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz", + "integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==", "license": "MIT", - "dependencies": { - "@sentry-internal/browser-utils": "10.55.0", - "@sentry/core": "10.55.0" - }, "engines": { - "node": ">=18" + "node": ">= 18" } }, - "node_modules/@sentry-internal/replay-canvas": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.55.0.tgz", - "integrity": "sha512-lu/y7k9cK7FZ/qJpL0fBX4WqK6IFa/+bTPhedEaC5UpzjUNP7BfXt0H+R7q9CHWmp20Ffh/wGfO3j7O+Tv2MAA==", + "node_modules/@sentry/browser": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.69.0.tgz", + "integrity": "sha512-8391tnm96YbR7b8SYfEA/NEIZuyb2r3SZrtAT0bhZtjlujcYWjo7gugQvk8sWLU9cAa/euD00eJoIoJvNfpd7Q==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "10.55.0", - "@sentry/core": "10.55.0" + "@sentry/browser-utils": "10.69.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0", + "@sentry/feedback": "10.69.0", + "@sentry/replay": "10.69.0", + "@sentry/replay-canvas": "10.69.0" }, "engines": { "node": ">=18" } }, - "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz", - "integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@sentry/browser": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.55.0.tgz", - "integrity": "sha512-5n1kxmW1m4j16ZDV9kt+Zo5uafFnKTy7s5YyEcGnC45KnOiO1Gy+QFd3woXns1K5GNxpjF7oOOc6tXgZLuXnQQ==", + "node_modules/@sentry/browser-utils": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.69.0.tgz", + "integrity": "sha512-e/u1Abj0zRPwR/deGZAP3GOULrsx67/XXnM5Skniqs4uxTsdNtPek1Nef0tpxwaQJYxwh6pWdhswLPPbbPOgBQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "10.55.0", - "@sentry-internal/feedback": "10.55.0", - "@sentry-internal/replay": "10.55.0", - "@sentry-internal/replay-canvas": "10.55.0", - "@sentry/core": "10.55.0" + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0" }, "engines": { "node": ">=18" @@ -4809,6 +4814,48 @@ "node": ">= 18" } }, + "node_modules/@sentry/bundler-plugins": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugins/-/bundler-plugins-10.69.0.tgz", + "integrity": "sha512-I1otnSJIH4IOugLp+kcBbT0Kcex+J8xnHuUyzMAwCYSpZ0FMVvA723uNmkMdW0PxRRw0oEhe0qDB+t+ZjHxUiA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/cli": "^2.58.6", + "@sentry/core": "10.69.0", + "dotenv": "^17.4.2", + "find-up": "^5.0.0", + "glob": "^13.0.6", + "magic-string": "~0.30.8" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "rollup": ">=3.2.0", + "webpack": ">=5.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@sentry/bundler-plugins/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@sentry/cli": { "version": "2.58.6", "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz", @@ -4974,31 +5021,56 @@ "node": ">=10" } }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@sentry/core": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.55.0.tgz", - "integrity": "sha512-XUyoNtDSYCvgJnoNzlh+YeAXfIPhCRIXbhWqqM3GQ3AFtZICi85lkyfsrwXEl9wzlPGYnU+Eg8F4tOfScx+FcQ==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.69.0.tgz", + "integrity": "sha512-+uuqVEeiDzYuAKjZLqsROKXvRTbl/QeH0gfGRtpYib1cud4rAFWRIkFmcR7Jb7JGFYwmReyQotiTj/hcDszTZg==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/feedback": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.69.0.tgz", + "integrity": "sha512-qrGz5Qaw93/IhMjlFN6uIaXeHwgHDaKGa6FkTAP6PonpkvSbGGqan6xfsENxzj9HUVoli1lZ6tMRDnt2qtSPhg==", "license": "MIT", + "dependencies": { + "@sentry/core": "10.69.0" + }, "engines": { "node": ">=18" } }, "node_modules/@sentry/nextjs": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.55.0.tgz", - "integrity": "sha512-ODiv6hy7+gmINFvbWAVaCqtxT2ceFW7AW65amy34z3fCgld9+LHTP2++p4g2LmQb/mYQPIbJrVEfJoqGASK4EQ==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.69.0.tgz", + "integrity": "sha512-48eYXezKAmSlSWtBsvwXvdHHHavXRDVIVZ3mI5vidhJwhqSs0ekwG0ZsPG3xNdaJLmnNEmavHEVupSbuuOTZZA==", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", - "@opentelemetry/semantic-conventions": "^1.40.0", "@rollup/plugin-commonjs": "28.0.1", - "@sentry-internal/browser-utils": "10.55.0", + "@sentry/browser-utils": "10.69.0", "@sentry/bundler-plugin-core": "^5.3.0", - "@sentry/core": "10.55.0", - "@sentry/node": "10.55.0", - "@sentry/opentelemetry": "10.55.0", - "@sentry/react": "10.55.0", - "@sentry/vercel-edge": "10.55.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0", + "@sentry/node": "10.69.0", + "@sentry/opentelemetry": "10.69.0", + "@sentry/react": "10.69.0", + "@sentry/server-utils": "10.69.0", + "@sentry/vercel-edge": "10.69.0", "@sentry/webpack-plugin": "^5.3.0", "rollup": "^4.60.3", "stacktrace-parser": "^0.1.11" @@ -5011,19 +5083,19 @@ } }, "node_modules/@sentry/node": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.55.0.tgz", - "integrity": "sha512-+fB/ByoHVWPLGgoafYciiMatTNyX1FHj1bsqZBN+Pw3McbuEU1nwCPLt9zuyZZiWlQtXKsyuACS4ZhXnID5l8A==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.69.0.tgz", + "integrity": "sha512-xEXA1YGIiTZbrW6MWV34uS6JGQuQg2ijTI0zed+FsJb9JZKPYel/GZK8Km26vfTVb+yCXFmWZNBesKegNcVdzg==", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", - "@opentelemetry/core": "^2.6.1", - "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/sdk-trace-base": "^2.6.1", - "@opentelemetry/semantic-conventions": "^1.40.0", - "@sentry/core": "10.55.0", - "@sentry/node-core": "10.55.0", - "@sentry/opentelemetry": "10.55.0", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0", + "@sentry/node-core": "10.69.0", + "@sentry/opentelemetry": "10.69.0", + "@sentry/server-utils": "10.69.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -5031,13 +5103,14 @@ } }, "node_modules/@sentry/node-core": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.55.0.tgz", - "integrity": "sha512-M8XMMIk9Y0PGZoEt37Oe5dQCdqDdJlBcwLXidpz/s5k4QtJvCO/BbtcivcuKI2htw5FwxJkSrHUzRvT36tlDpg==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.69.0.tgz", + "integrity": "sha512-IgArHczrZJxkgxoffHscj0NxQrG6kCazgmGQnlf3j58J1ec21YaUu8Tu+7G4Lo5tCiW3teQnwlKW1ttMXSqWRw==", "license": "MIT", "dependencies": { - "@sentry/core": "10.55.0", - "@sentry/opentelemetry": "10.55.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0", + "@sentry/opentelemetry": "10.69.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -5048,8 +5121,7 @@ "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.39.0" + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { @@ -5066,19 +5138,17 @@ }, "@opentelemetry/sdk-trace-base": { "optional": true - }, - "@opentelemetry/semantic-conventions": { - "optional": true } } }, "node_modules/@sentry/opentelemetry": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.55.0.tgz", - "integrity": "sha512-0+YrNmVNrttki4rWP4DW+UTt5MziepwDLNBde39tgc3cGCcy5fLSdDfhb4JfTaE5TXt4kd5XrkgvS/sDgm3RZg==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.69.0.tgz", + "integrity": "sha512-3FyWV6YcEJuvLrlaKGE1dHXCI+1YO0a62w7PkwlRg8yp6K6YXkmdwu9GjqaYD+Ju4tm7uC7mHIsGFQMm0M7pqQ==", "license": "MIT", "dependencies": { - "@sentry/core": "10.55.0" + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0" }, "engines": { "node": ">=18" @@ -5086,18 +5156,18 @@ "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.39.0" + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" } }, "node_modules/@sentry/react": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.55.0.tgz", - "integrity": "sha512-cf6wI0W1FdrL/7d5FTHXUdTN5k5uRZ9AQu5QZxUujnxWN+DBxUWtow1HDD1zTEonQsCFyYuhXVu3w+qmBBW11A==", + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.69.0.tgz", + "integrity": "sha512-f0Il/JMteHjdWPNZQB3rtp1Pcj2Leb3p0KSZuv3rh0EUril9CbWtQVy5zJhoAppi+MWWmgRWa+6BpHbQf+ABQA==", "license": "MIT", "dependencies": { - "@sentry/browser": "10.55.0", - "@sentry/core": "10.55.0" + "@sentry/browser": "10.69.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0" }, "engines": { "node": ">=18" @@ -5106,48 +5176,89 @@ "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, - "node_modules/@sentry/vercel-edge": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.55.0.tgz", - "integrity": "sha512-IB8MBTdCHnuUtxHWh39Ecr9/TvMqSYW9BOO7ExnByDjHhfXOzQnEs6VJvEEIwSyUEl1yIz1Y3WdOlY9I6lqeMA==", + "node_modules/@sentry/replay": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.69.0.tgz", + "integrity": "sha512-uRhmNhtFGPOlM0iniVmWKAX3KVXI0le41yYK/iKdPjinT9jA3ZrmykO/Fv1v/KI5znOtwa9D6eHRnDTTMRxFrg==", "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.9.1", - "@opentelemetry/resources": "^2.6.1", - "@sentry/core": "10.55.0" + "@sentry/browser-utils": "10.69.0", + "@sentry/core": "10.69.0" }, "engines": { "node": ">=18" } }, - "node_modules/@sentry/webpack-plugin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-5.3.0.tgz", - "integrity": "sha512-i3OQUrS0FZlXLgq57RIKDp+vHHzuvYKPCKewAPXULWKMsBXFGhP6veGRQ+6To/pmZkkXjEX5ofVNDy9C3jEPKQ==", + "node_modules/@sentry/replay-canvas": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.69.0.tgz", + "integrity": "sha512-VF6nXvSninHcc7dC1Zme0RjkC7VgRMCixs6jKaQX5zTNeqTW3dZGSefSOVv+ZteRi3hJvVORq985VjUC9Z/0+A==", "license": "MIT", "dependencies": { - "@sentry/bundler-plugin-core": "5.3.0" + "@sentry/core": "10.69.0", + "@sentry/replay": "10.69.0" }, "engines": { - "node": ">= 18" - }, - "peerDependencies": { + "node": ">=18" + } + }, + "node_modules/@sentry/server-utils": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.69.0.tgz", + "integrity": "sha512-0MwHrA8+nNvMIsqf8m3cXwCBlUjr6AS7N6CZvHJtY1DkqEvQqEbD5VIrhzEyHN/KMZIgQ8XeDCQRhjnXFQGRhg==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3", + "@apm-js-collab/tracing-hooks": "^0.13.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.69.0", + "meriyah": "^6.1.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/vercel-edge": { + "version": "10.69.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.69.0.tgz", + "integrity": "sha512-P3IsZyM3j8s4sGuxnGn1+EScVZHABsuzqv0NzIUen61ml0x+c6F6XXobvMhMcRp98fIQe1hZBpzH0VNxc7eCIA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/core": "10.69.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-5.4.0.tgz", + "integrity": "sha512-J3a0BvUZ75Qxy+v/Ap3Hx4ZEcSjlPHZ/jDtxdRhXQCyNeEb8xq0uUBTI9VLtGk2eNeNucOxOEJ5ngqdNjnEH/A==", + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugins": "^10.64.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { "webpack": ">=5.0.0" } }, "node_modules/@serwist/build": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.5.11.tgz", - "integrity": "sha512-PQfW+LhADYFOOp0PhEnjlgJCyKor6cYa06d3rID1OpiKzkmCApJV1WYfdTBB96jXaWv6OWcWSbSV4tqDLxvaVA==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.5.12.tgz", + "integrity": "sha512-U2UkA9BjdpniZkXDIG6NQRBEbXccRvqewzL3hfNEQERvM2bL6ezKvsVF9359akUWP8BxV950Kkq9LKGFmOR8Uw==", "license": "MIT", "dependencies": { - "@serwist/utils": "9.5.11", + "@serwist/utils": "9.5.12", "common-tags": "1.8.2", "glob": "13.0.6", "pretty-bytes": "6.1.1", - "source-map": "0.8.0-beta.0", - "type-fest": "5.6.0", - "zod": "4.4.1" + "source-map": "0.8.0", + "type-fest": "5.8.0", + "zod": "4.4.3" }, "engines": { "node": ">=18.0.0" @@ -5161,52 +5272,28 @@ } } }, - "node_modules/@serwist/build/node_modules/type-fest": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", - "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@serwist/build/node_modules/zod": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", - "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@serwist/next": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.5.11.tgz", - "integrity": "sha512-omT32H7U21ihCymSvOG9QeRJBuOEomJx4JdzKhUoqOW3DR10tH3m84VOHj3BvK0OcA7av3qj5FsyNFBB+f0n8A==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.5.12.tgz", + "integrity": "sha512-aKSDZmxC2w6mvLC1aOcO4OnZqdKIl4zrW2VndlZT12+m+nIyOWih1xqqw4LsvUGfQCx0AswtKUUQ7woRYZ2thg==", "license": "MIT", "dependencies": { - "@serwist/build": "9.5.11", - "@serwist/utils": "9.5.11", - "@serwist/webpack-plugin": "9.5.11", - "@serwist/window": "9.5.11", - "browserslist": "4.28.2", + "@serwist/build": "9.5.12", + "@serwist/utils": "9.5.12", + "@serwist/webpack-plugin": "9.5.12", + "@serwist/window": "9.5.12", + "browserslist": "4.28.6", "glob": "13.0.6", "kolorist": "1.8.0", - "semver": "7.7.4", - "serwist": "9.5.11", - "zod": "4.4.1" + "semver": "7.8.5", + "serwist": "9.5.12", + "zod": "4.4.3" }, "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "@serwist/cli": "^9.5.11", + "@serwist/cli": "^9.5.12", "next": ">=14.0.0", "react": ">=18.0.0", "typescript": ">=5.0.0" @@ -5220,10 +5307,43 @@ } } }, + "node_modules/@serwist/next/node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "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": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/@serwist/next/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5232,19 +5352,10 @@ "node": ">=10" } }, - "node_modules/@serwist/next/node_modules/zod": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", - "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@serwist/utils": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/@serwist/utils/-/utils-9.5.11.tgz", - "integrity": "sha512-zqxmwuHqWA3OwN82Wo8gFZ9QBemygJP3cap5JWAOG4UyJZgUZfmBXAXj+IMaD4eKZ/6pqrxHHDZ9uSWZmJ1mXA==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@serwist/utils/-/utils-9.5.12.tgz", + "integrity": "sha512-BHDwiGL7H7JS7wFvVlAWTFHGt0ffHuPgKtdkaYP0lEWJFl6fKJRCnTDZhwhusFvBhicf37XP8Y6PouTZbcLmgg==", "license": "MIT", "peerDependencies": { "browserslist": ">=4" @@ -5256,15 +5367,15 @@ } }, "node_modules/@serwist/webpack-plugin": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.5.11.tgz", - "integrity": "sha512-SlvO3A1UMcc1htCzMtLCtPQK6yISCO7B859ixLv7EiY/yayXjVxGm9vHqkJYpQ768PWyjEZXRY/X6EGRMA6wJQ==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.5.12.tgz", + "integrity": "sha512-+a5Fap7wVR4oN5jg1GxVNbWW+xUv3LO7UWYfg2D8PkJZiTCcX8PwkcmdzMGVr39dyOtIMTSAANw0af1zMjVbOA==", "license": "MIT", "dependencies": { - "@serwist/build": "9.5.11", - "@serwist/utils": "9.5.11", + "@serwist/build": "9.5.12", + "@serwist/utils": "9.5.12", "pretty-bytes": "6.1.1", - "zod": "4.4.1" + "zod": "4.4.3" }, "engines": { "node": ">=18.0.0" @@ -5282,23 +5393,14 @@ } } }, - "node_modules/@serwist/webpack-plugin/node_modules/zod": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", - "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@serwist/window": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.5.11.tgz", - "integrity": "sha512-OrH9srhmifUvY36NuukHSZby24XTEk4pHh3pfY0GBQzA9ouU1fYh+ORWhKxH7/wkVHRr3sc4YAhjtpfL14PjjQ==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.5.12.tgz", + "integrity": "sha512-+fjApJme34qfwdGE2kLT8sFx+xWO+a15477MN/B/b1v4HOWBmARuA9C2xGFBCT77XzCrzQ/2DxcDePZsqEYOlw==", "license": "MIT", "dependencies": { "@types/trusted-types": "2.0.7", - "serwist": "9.5.11" + "serwist": "9.5.12" }, "peerDependencies": { "typescript": ">=5.0.0" @@ -5334,21 +5436,21 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.2.tgz", - "integrity": "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.111.0.tgz", + "integrity": "sha512-hbRLgyQZEX0SDyF4LYXpv94qOIQyFATfpT5SIs2V0SisHnisVRkYgiPQWzv80l4S2mO3qof78rmoH9j5zoFsYQ==", "license": "MIT", "dependencies": { "tslib": "2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@supabase/cli-darwin-arm64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-arm64/-/cli-darwin-arm64-2.103.0.tgz", - "integrity": "sha512-BoOaHyoLuyOtmhxtRti7za3YYH9T05JenEla/zDXikqTiY5yDd63/1RxSF9mXt+ICFMkqHq3oFkFcDP5snumBQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-arm64/-/cli-darwin-arm64-2.111.0.tgz", + "integrity": "sha512-H1ucZ+9Z37Ha7uqYrKHfAy1vXWMVsN4gNlKaOpjKUYoHwDEbueEHVIDA1/PBIUd4HX+usJfpq+R+gWqzM/FKqQ==", "cpu": [ "arm64" ], @@ -5360,9 +5462,9 @@ ] }, "node_modules/@supabase/cli-darwin-x64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-x64/-/cli-darwin-x64-2.103.0.tgz", - "integrity": "sha512-SLBb5lfmIF9G+FanWZQvKG6HQeUKFmMv8ls8ZVm6OnO87pLm4aRKLfFYjeXXtiBiRYMZRwxE/RwrRy0uXc0wqw==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-x64/-/cli-darwin-x64-2.111.0.tgz", + "integrity": "sha512-4iMYm/XaAZJ8YzdJ2HBRVc7i9SRIwE6VQrnSt968WTt83M1y6knC7UENCKjMtO+As4QeZkICpUk50CeathqxbA==", "cpu": [ "x64" ], @@ -5374,9 +5476,9 @@ ] }, "node_modules/@supabase/cli-linux-arm64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64/-/cli-linux-arm64-2.103.0.tgz", - "integrity": "sha512-nk0fxTbQho+9OoUmIEBlgvEG69GmooMO1D8Ypt7SzgczNcLfYoCoKV9NMkQRUGi5jFXS5xFrRMPeSa/i3tNgwQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64/-/cli-linux-arm64-2.111.0.tgz", + "integrity": "sha512-2KSHITFMXe2u5yALupBWHGeQ1IY4C8GkcIWPWgNFdtAPo03pgs1hLWgL1eeqSh/a0wB/6rgUlM1fQR/hAgCyCA==", "cpu": [ "arm64" ], @@ -5391,9 +5493,9 @@ ] }, "node_modules/@supabase/cli-linux-arm64-musl": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.103.0.tgz", - "integrity": "sha512-zn2N7t/Mx1LUE/knZvJfJwNHKKvEl5KCqob1fetfPXoPN/GcI0UnfJ+V/bkPjhpPA1b8htVhq4brHMcqpV80mA==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.111.0.tgz", + "integrity": "sha512-RnvbVlJ4TX/UIwLiglBVQ2eL4GAl9SfWZmM5LENXyIaohBcRZHDva5t2OyK+BPGBtngjVGpb3QyfLdvkt9yJcw==", "cpu": [ "arm64" ], @@ -5408,9 +5510,9 @@ ] }, "node_modules/@supabase/cli-linux-x64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64/-/cli-linux-x64-2.103.0.tgz", - "integrity": "sha512-xk9ADh4D+luctRgscBzNvYLZa1qAw067IwateSE1AxhO3GOhdN6fiLZszl5LodO7CHmp54204dj+UcMct3KWFQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64/-/cli-linux-x64-2.111.0.tgz", + "integrity": "sha512-NwwNhiZZT4WYEPDXAbgZL+l77z/hoJ+w5t+52WBN1VlmoxwDmf2NP+UERM8wuCGFe/tmBlUuFE1eJYZfab0/qA==", "cpu": [ "x64" ], @@ -5425,9 +5527,9 @@ ] }, "node_modules/@supabase/cli-linux-x64-musl": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64-musl/-/cli-linux-x64-musl-2.103.0.tgz", - "integrity": "sha512-/fpuslo5ERFWJxbkRcpUFLV8T7087vOVU+clN2OeYTL8lgxVl04FPL3nW0qDgYeehb9GUeje5adgGvHYS006KA==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64-musl/-/cli-linux-x64-musl-2.111.0.tgz", + "integrity": "sha512-2QVpsy/3v+TzqE5GgTCPruoxTlF3d8QPGirjcnah8hP66nxXStB9yTvxmJq+LtO3gwMt1Kpm7is0roZVkV55Pw==", "cpu": [ "x64" ], @@ -5442,9 +5544,9 @@ ] }, "node_modules/@supabase/cli-windows-arm64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-windows-arm64/-/cli-windows-arm64-2.103.0.tgz", - "integrity": "sha512-oz1xMJ2bu/lfVV1WaNegGmzoxoEEaiBMg6g2Jm4aB6qCUgoIIkiXClghEnPotd0DWklpqFCOIp1lUb15mnjK6Q==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-windows-arm64/-/cli-windows-arm64-2.111.0.tgz", + "integrity": "sha512-twawKY5xfU2dNOnZrKDmrudxRG/XIKcBxOb6X2lMGJU3N1Hd+8oWpI9TwM5Qv+eXehtlsKDmUvbitStXvJr/xQ==", "cpu": [ "arm64" ], @@ -5456,9 +5558,9 @@ ] }, "node_modules/@supabase/cli-windows-x64": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/@supabase/cli-windows-x64/-/cli-windows-x64-2.103.0.tgz", - "integrity": "sha512-jueFJW/QmzWuVis2vGzeSDsrZ3gy5KIsq5vlpmhQF0bWpemgqP7buojPUiHCGSW6+9tTh8za8MraEy7HJGX/wQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-windows-x64/-/cli-windows-x64-2.111.0.tgz", + "integrity": "sha512-1F+X5tAYxAGx93ZZoIQBnIQ2Q2NnplKqjpigAS/zpTsNaWAjNi7EnxmuQKaEoAdqOHKbLhegVVDiw1+us3ZVpA==", "cpu": [ "x64" ], @@ -5470,87 +5572,87 @@ ] }, "node_modules/@supabase/functions-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.2.tgz", - "integrity": "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.111.0.tgz", + "integrity": "sha512-RW/OCsd6MO592zU8ifzP8/f8XzxxIdpb+Up5XaOtE26Fw+3zTp475WX7+GuuktiD1WF8pFUDe6khUPbMp77RCw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@supabase/phoenix": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", - "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.5.tgz", + "integrity": "sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==", "license": "MIT" }, "node_modules/@supabase/postgrest-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.2.tgz", - "integrity": "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.111.0.tgz", + "integrity": "sha512-pcqeDsnWP0lx9GawduYxNZJHeuTm53O7L0SC8RF8tniV3GWIPY6me6OTdnwzdwNUmNy1dzUVtSyIfE6+OflzPQ==", "license": "MIT", "dependencies": { "tslib": "2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@supabase/realtime-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.2.tgz", - "integrity": "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.111.0.tgz", + "integrity": "sha512-6oRf/vZyRwg8f8GbFSJkrD2w4HAu/yTvyMViHXHS+H5hNJzdXCrUR7cP5oW7daT3YlRnzRPY9LcGSJKZAmfMSg==", "license": "MIT", "dependencies": { - "@supabase/phoenix": "^0.4.2", + "@supabase/phoenix": "0.4.5", "tslib": "2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@supabase/ssr": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.10.3.tgz", - "integrity": "sha512-ux2CJgX89h0Fz2lY7ZNafNG2SkXpyRc5dz77K9eKeBLPdtywQixKwIuetDeIViAJBp/buOUVmgj8PVesOklNpw==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.12.4.tgz", + "integrity": "sha512-xHzcgI8cC1TpBKSwJcR5Yd8CCwfIq0SBc5yb4yz/YFw5tbCrEQ0QT3a+2jymCxHgQWLfzwN93HZ6eRbcoMkOlA==", "license": "MIT", "dependencies": { "cookie": "^1.0.2" }, "peerDependencies": { - "@supabase/supabase-js": "^2.105.3" + "@supabase/supabase-js": "^2.111.0" } }, "node_modules/@supabase/storage-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.2.tgz", - "integrity": "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.111.0.tgz", + "integrity": "sha512-UEViNmTzVOxE8dqUA81wls+n9xgmlvSFfhfwo6QxrO4kQOytCYyw3ciYFoi4XoD4Jl95NJ3jnndHN5iIudWzqw==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@supabase/supabase-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.2.tgz", - "integrity": "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.111.0.tgz", + "integrity": "sha512-9q0/AULthQnWeiDh1vGyjoJZbSY04bu6qHcWit70pqEYn5Kv/dkCPY62Ja1123jEnJbB9Vd2pjY7Kvk/lK3peA==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.106.2", - "@supabase/functions-js": "2.106.2", - "@supabase/postgrest-js": "2.106.2", - "@supabase/realtime-js": "2.106.2", - "@supabase/storage-js": "2.106.2" + "@supabase/auth-js": "2.111.0", + "@supabase/functions-js": "2.111.0", + "@supabase/postgrest-js": "2.111.0", + "@supabase/realtime-js": "2.111.0", + "@supabase/storage-js": "2.111.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@swc/helpers": { @@ -5563,49 +5665,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -5620,9 +5722,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -5637,9 +5739,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -5654,9 +5756,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -5671,9 +5773,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -5688,9 +5790,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], @@ -5708,9 +5810,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], @@ -5728,9 +5830,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], @@ -5748,9 +5850,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], @@ -5768,9 +5870,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -5786,11 +5888,11 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.10.0", - "@emnapi/runtime": "^1.10.0", - "@emnapi/wasi-threads": "^1.2.1", + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", + "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "engines": { @@ -5798,9 +5900,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -5815,9 +5917,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -5832,23 +5934,23 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", - "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "postcss": "^8.5.10", - "tailwindcss": "4.3.0" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, "node_modules/@tanstack/query-core": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", - "integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", "license": "MIT", "funding": { "type": "github", @@ -5856,12 +5958,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", - "integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.100.14" + "@tanstack/query-core": "5.101.4" }, "funding": { "type": "github", @@ -5872,12 +5974,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.26", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.26.tgz", - "integrity": "sha512-DosdgjOxCLahkn0o+ilmZYwEjo1glfMGuRT/j3PQ18yr5XqA8N/BCaL9IJ3B5TRl+nnzyK2IOFgAILwzN3a9xQ==", + "version": "3.14.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz", + "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.16.0" + "@tanstack/virtual-core": "3.17.7" }, "funding": { "type": "github", @@ -5889,9 +5991,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.16.0.tgz", - "integrity": "sha512-Er2N7q3WOiH6y2JLxsxNX+u2/sLqSsL0bxFgDjuiPiA7vKhZRm+IzcS17vRee3GNXr64UsesA5CAp9yTiIYw9A==", + "version": "3.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz", + "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==", "license": "MIT", "funding": { "type": "github", @@ -5919,9 +6021,9 @@ } }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz", + "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==", "dev": true, "license": "MIT", "dependencies": { @@ -5933,9 +6035,12 @@ "redent": "^3.0.0" }, "engines": { - "node": ">=14", + "node": ">=22", "npm": ">=6", "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11" } }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { @@ -5998,9 +6103,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -6137,9 +6242,9 @@ } }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -6149,7 +6254,6 @@ "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/json5": { @@ -6169,13 +6273,6 @@ "@types/node": "*" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT", - "optional": true - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -6192,12 +6289,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/nprogress": { @@ -6208,20 +6305,19 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", - "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", - "dev": true, + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -6241,16 +6337,16 @@ } }, "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.6.tgz", + "integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==", "license": "MIT", "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", + "hasown": "^2.0.4", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, @@ -6310,17 +6406,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", - "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/type-utils": "8.60.0", - "@typescript-eslint/utils": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -6333,15 +6429,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -6349,16 +6445,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", - "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -6374,14 +6470,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", - "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.0", - "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -6396,14 +6492,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", - "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6414,9 +6510,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", - "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -6431,15 +6527,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", - "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -6456,9 +6552,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", - "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -6470,16 +6566,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", - "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.0", - "@typescript-eslint/tsconfig-utils": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -6498,9 +6594,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -6511,16 +6607,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", - "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6535,13 +6631,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", - "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -6553,9 +6649,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { @@ -6859,6 +6955,17 @@ "node": ">=14.0.0" } }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", @@ -6926,22 +7033,22 @@ } }, "node_modules/@upstash/redis": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.0.tgz", - "integrity": "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.1.tgz", + "integrity": "sha512-hVqkWmhqobH7hpdzSCSrOwK7gWNASOAdf85l6/yxdB+giCYNYfl8FkSKxnqW2sqCdLDP7HzRTvy/ILC1AjBMUA==", "license": "MIT", "dependencies": { "uncrypto": "^0.1.3" } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -6961,14 +7068,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", - "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.10", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -6982,8 +7089,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.8", - "vitest": "4.1.8" + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6992,16 +7099,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -7010,13 +7117,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -7047,9 +7154,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7060,13 +7167,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -7074,14 +7181,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -7090,9 +7197,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -7100,13 +7207,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.8.tgz", - "integrity": "sha512-RUS2ZU2TsduVrI+9c12uTNaKrNUTsm6yFt3fueEUB9iKvyC2UP83F+sqIz00HQIah4UOL1TMoDAki8K0NjGvsA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.10.tgz", + "integrity": "sha512-EOUqfXHTXtpSHsyLHH40ts3Ue+hRhSGwzwzMlK0dTEOLSDYyOXLyr5JDGmHQWhN2DYI30gw6dVx3cdgM9FZl+Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.10", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -7118,17 +7225,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.8" + "vitest": "4.1.10" } }, "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -7136,6 +7243,181 @@ "url": "https://opencollective.com/vitest" } }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -7163,9 +7445,9 @@ } }, "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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7174,15 +7456,6 @@ "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==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -7206,16 +7479,15 @@ } }, "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -7223,10 +7495,11 @@ } }, "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -7239,42 +7512,17 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/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==", - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": 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==", "license": "MIT", + "peer": true, "dependencies": { - "environment": "^1.0.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "ajv": "^8.8.2" } }, "node_modules/ansi-regex": { @@ -7303,6 +7551,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7520,9 +7781,9 @@ "license": "MIT" }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", - "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", "dev": true, "license": "MIT", "dependencies": { @@ -7548,8 +7809,17 @@ "dev": true, "license": "MIT" }, - "node_modules/async-function": { - "version": "1.0.0", + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/async-function": { + "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, @@ -7601,9 +7871,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", - "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", "dev": true, "license": "MPL-2.0", "engines": { @@ -7611,13 +7881,13 @@ } }, "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } @@ -7690,9 +7960,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.33", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", - "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "version": "2.11.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.11.tgz", + "integrity": "sha512-/yImnXwyTvgMkhgekLHok/Rx5vO6E0BmStWlSqKWMVm2a2ITuZ1Tn+9bgLS+gZRdZmWtd8nxuhHpdmCUOWsTQQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -7721,15 +7991,15 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -7746,9 +8016,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "funding": [ { "type": "opencollective", @@ -7765,10 +8035,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -7784,6 +8054,26 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "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==", + "license": "MIT", + "peer": true + }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -7866,9 +8156,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001793", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", - "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "funding": [ { "type": "opencollective", @@ -7977,6 +8267,16 @@ "url": "https://paulmillr.com/funding/" } }, + "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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/citty": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", @@ -8001,39 +8301,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -8055,69 +8322,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "optional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -8216,32 +8420,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/conf/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/conf/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "ajv": "^8.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/conf/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==", - "license": "MIT" - }, "node_modules/conf/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8325,7 +8524,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/d3-array": { @@ -8489,6 +8687,21 @@ "node": ">=20" } }, + "node_modules/data-urls/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/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -8816,9 +9029,9 @@ } }, "node_modules/dot-prop": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.1.0.tgz", - "integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.2.0.tgz", + "integrity": "sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==", "license": "MIT", "dependencies": { "type-fest": "^5.0.0" @@ -8878,10 +9091,28 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/eciesjs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.5.0.tgz", + "integrity": "sha512-s0J9SEVYAEPg7J63GFMApLYzPH9VNIQIyC6s15JpnqVc0TqcKWdbgFlnAweEBRyMmko2dcs2sfC83Hj4J43tuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.6", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2.7.10", + "node": ">=16" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.364", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", - "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -8902,9 +9133,9 @@ } }, "node_modules/engine.io": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", - "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", @@ -8916,7 +9147,7 @@ "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1" + "ws": "~8.21.0" }, "engines": { "node": ">=10.2.0" @@ -8940,32 +9171,10 @@ "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", - "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/enhanced-resolve": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz", - "integrity": "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==", - "dev": true, + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -8979,6 +9188,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -8999,19 +9209,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -9081,6 +9278,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -9100,9 +9316,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", - "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9128,10 +9344,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -9175,15 +9390,18 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -9193,19 +9411,20 @@ } }, "node_modules/es-toolkit": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", - "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", "license": "MIT", "workspaces": [ "docs", - "benchmarks" + "benchmarks", + "tests/types" ] }, "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==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -9215,32 +9434,32 @@ "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" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -9265,9 +9484,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { @@ -9276,8 +9495,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -9325,13 +9544,13 @@ } }, "node_modules/eslint-config-next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.6.tgz", - "integrity": "sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.12.tgz", + "integrity": "sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.2.6", + "@next/eslint-plugin-next": "16.2.12", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", @@ -9422,9 +9641,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", - "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -9587,9 +9806,9 @@ } }, "node_modules/eslint-plugin-security": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz", - "integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.1.tgz", + "integrity": "sha512-/lZCkOxPOWaf1jXAqgICrS8St3BMBccIPvhOSUYuV6VCr1o5nFVG998FnTLt6w2Nxb8Uo0nM8fzmnhp+GY/aEg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -9603,9 +9822,9 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-4.0.3.tgz", - "integrity": "sha512-5drkJKLC9qQddIiaATV0e8+ygbUc7b0Ti6VB7M2d3jmKNh3X0RaiIJYTs3dr9xnlhlrxo+/s1FoO3Jgv6O/c7g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-4.2.0.tgz", + "integrity": "sha512-bqADfuNtTL7VK6RU29eoiFTtaaBKIpVPuX3bOl+rBpWSBa0zIBVZlqZNZQjfP6s4iXkAJokv5IsD8OsACkwApg==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { @@ -9613,23 +9832,24 @@ "builtin-modules": "^3.3.0", "bytes": "^3.1.2", "functional-red-black-tree": "^1.0.1", - "globals": "^17.4.0", + "globals": "^17.7.0", "jsx-ast-utils-x": "^0.1.0", "lodash.merge": "^4.6.2", "minimatch": "^10.2.5", "scslre": "^0.3.0", - "semver": "^7.7.4", + "semver": "^7.8.5", "ts-api-utils": "^2.5.0", - "typescript": ">=5" + "typescript": ">=5 <6.1.0", + "yaml": "^2.9.0" }, "peerDependencies": { "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-sonarjs/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -9685,6 +9905,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -9698,6 +9935,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -9733,7 +9977,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -9746,7 +9989,6 @@ "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" @@ -9759,7 +10001,6 @@ "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" @@ -9807,10 +10048,20 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "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==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9823,15 +10074,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/farmhash-modern": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", - "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9889,9 +10131,9 @@ "license": "Unlicense" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -9905,9 +10147,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", "funding": [ { "type": "github", @@ -9917,14 +10159,14 @@ "license": "MIT", "optional": true, "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" } }, "node_modules/fast-xml-parser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", - "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", + "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", "funding": [ { "type": "github", @@ -9934,11 +10176,12 @@ "license": "MIT", "optional": true, "dependencies": { - "@nodable/entities": "^2.1.0", + "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.3.0", - "xml-naming": "^0.1.0" + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -10076,25 +10319,24 @@ } }, "node_modules/firebase-admin": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.10.0.tgz", - "integrity": "sha512-rbuCrJvYRwqBqvbccMS8fj/x2zsaMisdf5RQbRzQzr14Rbq9r2UlpuBHqWAwrO6c9dIRF56xF/xoepXsD5yDuQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-14.2.0.tgz", + "integrity": "sha512-zfs5PdccEgjX479bbMmz95Rqc7ttQ4LV3qkGFlPiqOaOu/suBZc3fG52Qzn2hQjiSHkBM4NP2AZV5r2NvAt0TQ==", "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", - "@firebase/database-compat": "^2.0.0", - "@firebase/database-types": "^1.0.6", - "farmhash-modern": "^1.1.0", + "@firebase/database-compat": "^2.1.4", + "@firebase/database-types": "^1.0.20", "fast-deep-equal": "^3.1.1", - "google-auth-library": "^10.6.1", + "google-auth-library": "^10.6.2", "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.1.0" + "jwks-rsa": "^4.0.1" }, "engines": { - "node": ">=18" + "node": ">=22" }, "optionalDependencies": { - "@google-cloud/firestore": "^7.11.0", + "@google-cloud/firestore": "^8.6.0", "@google-cloud/storage": "^7.19.0" } }, @@ -10113,9 +10355,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -10156,16 +10398,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -10184,12 +10426,12 @@ } }, "node_modules/framer-motion": { - "version": "12.40.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", - "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz", + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==", "license": "MIT", "dependencies": { - "motion-dom": "^12.40.0", + "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, @@ -10234,18 +10476,21 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -10336,9 +10581,9 @@ } }, "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -10409,19 +10654,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -10487,9 +10719,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.1.tgz", + "integrity": "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==", "dev": true, "license": "MIT", "dependencies": { @@ -10530,9 +10762,9 @@ } }, "node_modules/globals": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", - "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "version": "17.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", + "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", "dev": true, "license": "MIT", "engines": { @@ -10560,9 +10792,9 @@ } }, "node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -10586,9 +10818,9 @@ } }, "node_modules/google-auth-library/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -10631,70 +10863,177 @@ } }, "node_modules/google-gax": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", - "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.8.tgz", + "integrity": "sha512-M4vpZcXQIC1gqIVGQ7eaU3jXQA6zecStyTXu514TYfThlgSurYJOxHZo9fzU6hAgwPWvuEynAVHWyaIk80VeEA==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", + "node-fetch": "^3.3.2", "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" + "proto3-json-serializer": "3.0.4", + "protobufjs": "^7.5.4", + "retry-request": "^8.0.2", + "rimraf": "^5.0.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "node_modules/google-gax/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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-gax/node_modules/gaxios": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", "license": "Apache-2.0", "optional": true, "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/google-gax/node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", "license": "Apache-2.0", "optional": true, "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/google-gax/node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "node_modules/google-gax/node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-gax/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-gax/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-gax/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "optional": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-gax/node_modules/retry-request": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.4.tgz", + "integrity": "sha512-pI6/7eabUYkZxamkOq0g0uMxKLLGnjzhefY+vL8bVXag5rto4OU2YBTPytWLuHH7aEKD6fn7kJycQfid0Mwnkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "teeny-request": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-gax/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/teeny-request": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.4.tgz", + "integrity": "sha512-R1Cg4Vu0UULeDfHL/kjABLaTW++9yD/B6n2g48y5dJ04hsEaxcfmAqbNDzNsbqAYJyIpZafjklLG9YxRu9uzOg==", "license": "Apache-2.0", "optional": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" + }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/google-logging-utils": { @@ -10720,19 +11059,20 @@ } }, "node_modules/googleapis-common": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz", - "integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.3.tgz", + "integrity": "sha512-7g1yzQKx0mmNTjiK0H9dJ8eqKqDBveES9vLHeg5neb3BMQy/d1oQefIMhIpOVT8a+f+LOcixMEdRbFIW/cQUJw==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", - "gaxios": "^7.0.0-rc.4", - "google-auth-library": "^10.1.0", + "gaxios": "7.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", "qs": "^6.7.0", "url-template": "^2.0.8" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, "node_modules/googleapis-common/node_modules/agent-base": { @@ -10745,14 +11085,46 @@ } }, "node_modules/googleapis-common/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" }, "engines": { "node": ">=18" @@ -10789,6 +11161,21 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/googleapis-common/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -10805,7 +11192,6 @@ "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/gtoken": { @@ -10823,18 +11209,19 @@ } }, "node_modules/happy-dom": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.9.0.tgz", - "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", + "version": "20.11.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.1.tgz", + "integrity": "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", - "ws": "^8.18.3" + "ws": "^8.21.0" }, "engines": { "node": ">=20.0.0" @@ -10857,7 +11244,6 @@ "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" @@ -11082,10 +11468,17 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html5parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html5parser/-/html5parser-3.0.0.tgz", + "integrity": "sha512-iNpSopa+4YHX50UOk825tBy7MghmXHo/ZpLskBYN0kAr1xhH8GlIMk5bLRXcZlfP3AnLUcSuFMu8C4MdOUxA8A==", + "license": "MIT" + }, "node_modules/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -11177,9 +11570,9 @@ } }, "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -11204,14 +11597,13 @@ } }, "node_modules/import-in-the-middle": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", - "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.3.tgz", + "integrity": "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==", "license": "Apache-2.0", "dependencies": { - "acorn": "^8.15.0", - "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" }, "engines": { @@ -11381,9 +11773,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -11467,6 +11859,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -11494,19 +11902,13 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, + "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==", "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, + "optional": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/is-generator-function": { @@ -11766,6 +12168,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -11882,25 +12297,56 @@ "node": ">= 0.4" } }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "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==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.7.tgz", + "integrity": "sha512-hq1OB1bALKfydZNoViyg6hPVGV4i93ny9Op+n4zP5RSf7SCZEXa/TsG2O3IEr7+WlHRTPnpqDmHfMH6qXAD60w==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11908,9 +12354,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -11931,39 +12377,39 @@ } }, "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", "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.3.5", + "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", + "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "canvas": "^3.0.0" + "canvas": "^3.2.3" }, "peerDependenciesMeta": { "canvas": { @@ -12010,10 +12456,9 @@ "license": "MIT" }, "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, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json-schema-typed": { @@ -12064,9 +12509,9 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12113,28 +12558,20 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", - "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-4.1.0.tgz", + "integrity": "sha512-sbkByqyATKYJP5F4RXj03N5TUNC0QLTjCAZvwTzC4BwJZ8e0/cWxN8YROnyUth2g1/ONWi4eSFHeu6oYalrc3Q==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", - "jose": "^4.15.4", + "jose": "^6.1.3", "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" + "lru-cache": "^11.0.0", + "lru-memoizer": "^3.0.0" }, "engines": { - "node": ">=14" - } - }, - "node_modules/jwks-rsa/node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "node": "^20.19.0 || ^22.12.0 || >= 23.0.0" } }, "node_modules/jws": { @@ -12509,14 +12946,13 @@ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, "node_modules/lint-staged": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.7.tgz", - "integrity": "sha512-JrSobt+tW3rH8IOMi8tDZd3foorM5yPEkLD/V2NxobgHrFfHWGee4MOLVuZeScgxftEwbHrPHIFA/ZL+nUJeuA==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz", + "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", "dev": true, "license": "MIT", "dependencies": { - "listr2": "^10.2.1", - "picomatch": "^4.0.4", + "picomatch": "^4.0.5", "string-argv": "^0.3.2", "tinyexec": "^1.2.4" }, @@ -12533,23 +12969,6 @@ "yaml": "^2.9.0" } }, - "node_modules/listr2": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", - "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.2.0", - "eventemitter3": "^5.0.4", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^10.0.0" - }, - "engines": { - "node": ">=22.13.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -12649,99 +13068,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-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/log-update/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -12772,46 +13098,28 @@ } }, "node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/lru-memoizer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", - "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-3.0.0.tgz", + "integrity": "sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==", "license": "MIT", "dependencies": { "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" - } - }, - "node_modules/lru-memoizer/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==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "lru-cache": "^11.0.1" } }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/lucide-react": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", - "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz", + "integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -12837,14 +13145,14 @@ } }, "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, @@ -12865,9 +13173,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -13057,6 +13365,13 @@ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "license": "CC0-1.0" }, + "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==", + "license": "MIT", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -13067,6 +13382,15 @@ "node": ">= 8" } }, + "node_modules/meriyah": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz", + "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==", + "license": "ISC", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -13593,12 +13917,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -13616,6 +13940,67 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "license": "MIT", + "peer": true, + "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": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -13632,9 +14017,9 @@ "license": "MIT" }, "node_modules/motion-dom": { - "version": "12.40.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", - "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz", + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==", "license": "MIT", "dependencies": { "motion-utils": "^12.39.0" @@ -13663,9 +14048,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", - "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", "funding": [ { "type": "github", @@ -13712,13 +14097,20 @@ "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==", + "license": "MIT", + "peer": true + }, "node_modules/next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", - "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz", + "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==", "license": "MIT", "dependencies": { - "@next/env": "16.2.6", + "@next/env": "16.2.12", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -13732,14 +14124,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.6", - "@next/swc-darwin-x64": "16.2.6", - "@next/swc-linux-arm64-gnu": "16.2.6", - "@next/swc-linux-arm64-musl": "16.2.6", - "@next/swc-linux-x64-gnu": "16.2.6", - "@next/swc-linux-x64-musl": "16.2.6", - "@next/swc-win32-arm64-msvc": "16.2.6", - "@next/swc-win32-x64-msvc": "16.2.6", + "@next/swc-darwin-arm64": "16.2.12", + "@next/swc-darwin-x64": "16.2.12", + "@next/swc-linux-arm64-gnu": "16.2.12", + "@next/swc-linux-arm64-musl": "16.2.12", + "@next/swc-linux-x64-gnu": "16.2.12", + "@next/swc-linux-x64-musl": "16.2.12", + "@next/swc-win32-arm64-msvc": "16.2.12", + "@next/swc-win32-x64-msvc": "16.2.12", "sharp": "^0.34.5" }, "peerDependencies": { @@ -13804,9 +14196,9 @@ } }, "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", "dev": true, "license": "MIT", "dependencies": { @@ -13865,9 +14257,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", - "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "license": "MIT", "engines": { "node": ">=18" @@ -14037,15 +14429,18 @@ } }, "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==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } }, "node_modules/once": { "version": "1.4.0", @@ -14057,22 +14452,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -14092,13 +14471,14 @@ } }, "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" }, @@ -14239,9 +14619,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", "funding": [ { "type": "github", @@ -14309,9 +14689,9 @@ "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==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -14321,44 +14701,44 @@ } }, "node_modules/picospinner": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/picospinner/-/picospinner-3.0.0.tgz", - "integrity": "sha512-lGA1TNsmy2bxvRsTI2cV01kfTwKzZjnZSDmF9llYNyMHMrU4sP87lQ5taiIKm88L3cbswjl008nwyGc3WpNvzg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/picospinner/-/picospinner-3.1.2.tgz", + "integrity": "sha512-0Z++uU8mvB0EvCPAEUq9BGiPcSravzJ+RPdXfjYlzXffqsogisiKRQqI6ctrSSJ6n985vxrgKtQ5n4sRINcJZg==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/playwright": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", - "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", - "dev": true, + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.60.0" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", - "dev": true, + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/possible-typed-array-names": { @@ -14372,15 +14752,15 @@ } }, "node_modules/postal-mime": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.4.tgz", - "integrity": "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==", + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.5.tgz", + "integrity": "sha512-GNEXKvWFQnbgO5NlrGzVa0FmWzBZ24PersAWErttSg1Hjpf0ATxTwS5DOMGaOpTG6bUh5cTr7xi0jAD942wCJA==", "license": "MIT-0" }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "funding": [ { "type": "opencollective", @@ -14397,7 +14777,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -14406,9 +14786,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -14434,9 +14814,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -14544,9 +14924,9 @@ "license": "MIT" }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -14554,22 +14934,22 @@ } }, "node_modules/proto3-json-serializer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", - "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", "license": "Apache-2.0", "optional": true, "dependencies": { - "protobufjs": "^7.2.5" + "protobufjs": "^7.4.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/protobufjs": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.2.tgz", - "integrity": "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "optional": true, @@ -14580,7 +14960,6 @@ "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", @@ -14608,12 +14987,13 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -14644,9 +15024,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14679,26 +15059,26 @@ } }, "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.6" + "react": "^19.2.8" } }, "node_modules/react-email": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-6.5.0.tgz", - "integrity": "sha512-WrJ+XPW87O1dabF4RJNGnTr7VTGsNa+BlMiinAZdH5fg8Kepwk++ZzX+LEieTlk+a3r13TaTJ4DfI9gv++y02g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-6.9.1.tgz", + "integrity": "sha512-uUDRgFukMUXRlrsCNGlA0PZuUlQ44faI9hT/D7uMjozdumLBdHjfQttQswRYLjyLW8fFtg2HBrxAESbFU4ZKKA==", "license": "MIT", "dependencies": { - "@babel/parser": "7.27.0", - "@babel/traverse": "7.27.0", - "@react-email/render": ">=2.0.8", + "@babel/parser": "7.29.2", + "@babel/traverse": "7.29.0", + "@react-email/render": ">=2.1.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "conf": "^15.0.2", @@ -14706,7 +15086,7 @@ "debounce": "^2.0.0", "esbuild": "^0.28.0", "glob": "^13.0.6", - "jiti": "2.4.2", + "jiti": "2.6.1", "log-symbols": "^7.0.0", "marked": "^15.0.12", "mime-types": "^3.0.0", @@ -14731,12 +15111,12 @@ } }, "node_modules/react-email/node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -14746,36 +15126,27 @@ } }, "node_modules/react-email/node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@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/react-email/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/react-email/node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -14821,9 +15192,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.77.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.77.0.tgz", - "integrity": "sha512-Sslh9YDYc0GDlWT/lxasnIduNo4v3yyvqRGvmGKUre5AFjDs/HV9/OafHGD8d+sB2yoL4UIL9L8X9i0WlZZebg==", + "version": "7.84.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.84.0.tgz", + "integrity": "sha512-+hWvQP6GLco56mDwrbU4XnHix8t1z90ltZsDIrREl+jnQFQxYLX8oAzqe/Xn8nHpmoXTY5M6oEXrAhbP1qevNQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -14837,9 +15208,9 @@ } }, "node_modules/react-is": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", - "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT" }, "node_modules/react-markdown": { @@ -15000,9 +15371,9 @@ } }, "node_modules/recharts": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", - "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", "license": "MIT", "workspaces": [ "www" @@ -15013,9 +15384,9 @@ "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", - "immer": "^10.1.1", + "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", - "reselect": "5.1.1", + "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" @@ -15205,18 +15576,18 @@ } }, "node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, "node_modules/resend": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/resend/-/resend-6.12.4.tgz", - "integrity": "sha512-lRpJ2Hxd+ht+JPDm97juRcUp9HOMuZyxaRFRFmc9Tx8iNWiei94Dx9v6SWufgKk2667C/uCeKKspMotOHSpCSg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.18.1.tgz", + "integrity": "sha512-XN8XIaDdKF+ziSQ3K23ndUcyhP7U3ze2gky6SPgYkuAOq54mH4Wdhwm7QylEQ3zlz0NzdX7/l1AgmJUZbdPI/Q==", "license": "MIT", "dependencies": { - "postal-mime": "2.7.4", + "postal-mime": "2.7.5", "standardwebhooks": "1.0.0" }, "engines": { @@ -15275,23 +15646,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15328,13 +15682,6 @@ "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": "6.1.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", @@ -15356,13 +15703,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -15372,27 +15719,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" } }, "node_modules/rollup": { - "version": "4.61.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", - "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "license": "MIT", "dependencies": { "@types/estree": "1.0.9" @@ -15405,31 +15752,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.61.0", - "@rollup/rollup-android-arm64": "4.61.0", - "@rollup/rollup-darwin-arm64": "4.61.0", - "@rollup/rollup-darwin-x64": "4.61.0", - "@rollup/rollup-freebsd-arm64": "4.61.0", - "@rollup/rollup-freebsd-x64": "4.61.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", - "@rollup/rollup-linux-arm-musleabihf": "4.61.0", - "@rollup/rollup-linux-arm64-gnu": "4.61.0", - "@rollup/rollup-linux-arm64-musl": "4.61.0", - "@rollup/rollup-linux-loong64-gnu": "4.61.0", - "@rollup/rollup-linux-loong64-musl": "4.61.0", - "@rollup/rollup-linux-ppc64-gnu": "4.61.0", - "@rollup/rollup-linux-ppc64-musl": "4.61.0", - "@rollup/rollup-linux-riscv64-gnu": "4.61.0", - "@rollup/rollup-linux-riscv64-musl": "4.61.0", - "@rollup/rollup-linux-s390x-gnu": "4.61.0", - "@rollup/rollup-linux-x64-gnu": "4.61.0", - "@rollup/rollup-linux-x64-musl": "4.61.0", - "@rollup/rollup-openbsd-x64": "4.61.0", - "@rollup/rollup-openharmony-arm64": "4.61.0", - "@rollup/rollup-win32-arm64-msvc": "4.61.0", - "@rollup/rollup-win32-ia32-msvc": "4.61.0", - "@rollup/rollup-win32-x64-gnu": "4.61.0", - "@rollup/rollup-win32-x64-msvc": "4.61.0", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" } }, @@ -15543,22 +15891,126 @@ } }, "node_modules/sanitize-html": { - "version": "2.17.4", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.4.tgz", - "integrity": "sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==", + "version": "2.17.6", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.6.tgz", + "integrity": "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^10.1.0", + "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/saxes": { - "version": "6.0.0", + "node_modules/sanitize-html/node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/sanitize-html/node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "domutils": "^4.0.2", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, @@ -15576,6 +16028,26 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/scslre": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", @@ -15603,6 +16075,12 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/semifies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz", + "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==", + "license": "Apache-2.0" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -15619,12 +16097,12 @@ "license": "MIT" }, "node_modules/serwist": { - "version": "9.5.11", - "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.5.11.tgz", - "integrity": "sha512-Bq6uwJFd4ET60BWI77v3VbazKHv6k7lECOiiCFwKyBu/slaCn0GHJ5L5RfsuJUKrnbD9lYUCDo6sqaKRM5M2vA==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.5.12.tgz", + "integrity": "sha512-PwREKJrSb3ja40XJyBntPOm7okcYZJCJPd++jUVz8tzxY1VYO9dyKSm0MimZ8LtUk0pIUTN0q2cfGe7R0wQsaQ==", "license": "MIT", "dependencies": { - "@serwist/utils": "9.5.11", + "@serwist/utils": "9.5.12", "idb": "8.0.3" }, "peerDependencies": { @@ -15686,54 +16164,59 @@ } }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/sharp/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "optional": true, "bin": { @@ -15767,14 +16250,14 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -15845,19 +16328,6 @@ "dev": true, "license": "ISC" }, - "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/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -15879,36 +16349,6 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/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/socket.io": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", @@ -15928,40 +16368,19 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", - "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", "license": "MIT", "dependencies": { "debug": "~4.4.1", - "ws": "~8.20.1" - } - }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", - "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 - } + "ws": "~8.21.0" } }, "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==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -15982,9 +16401,9 @@ } }, "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz", + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 12" @@ -15999,6 +16418,17 @@ "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -16055,9 +16485,9 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, @@ -16113,22 +16543,27 @@ } }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", + "optional": true, "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, + "node_modules/string-width/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==", + "license": "MIT", + "optional": true + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -16184,19 +16619,20 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -16206,16 +16642,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -16257,32 +16693,16 @@ } }, "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, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", + "optional": true, "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi/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": ">=8" } }, "node_modules/strip-bom": { @@ -16321,9 +16741,9 @@ } }, "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", "funding": [ { "type": "github", @@ -16331,7 +16751,10 @@ } ], "license": "MIT", - "optional": true + "optional": true, + "dependencies": { + "anynum": "^1.0.1" + } }, "node_modules/stubborn-fs": { "version": "2.0.0", @@ -16397,23 +16820,27 @@ } }, "node_modules/supabase": { - "version": "2.103.0", - "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.103.0.tgz", - "integrity": "sha512-LAdBSEzbmJIAf3UojILFL1DF2npwUpqGZ5FNtuqu9pzUdPMHvICyFeQeQCpUpLNrqmee78LqYwiFBpHtBP/TQQ==", + "version": "2.111.0", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.111.0.tgz", + "integrity": "sha512-0cjCRdYNV1h2XXa0wm04mdct7QuDU7sMul/NwETRJmN3+HCsdEu6u5n0oygL97fdf6sDrGmnAdBh8E4wsv1ayg==", "dev": true, "license": "MIT", + "dependencies": { + "eciesjs": "^0.5.0", + "jose": "^6.2.3" + }, "bin": { "supabase": "dist/supabase.js" }, "optionalDependencies": { - "@supabase/cli-darwin-arm64": "2.103.0", - "@supabase/cli-darwin-x64": "2.103.0", - "@supabase/cli-linux-arm64": "2.103.0", - "@supabase/cli-linux-arm64-musl": "2.103.0", - "@supabase/cli-linux-x64": "2.103.0", - "@supabase/cli-linux-x64-musl": "2.103.0", - "@supabase/cli-windows-arm64": "2.103.0", - "@supabase/cli-windows-x64": "2.103.0" + "@supabase/cli-darwin-arm64": "2.111.0", + "@supabase/cli-darwin-x64": "2.111.0", + "@supabase/cli-linux-arm64": "2.111.0", + "@supabase/cli-linux-arm64-musl": "2.111.0", + "@supabase/cli-linux-x64": "2.111.0", + "@supabase/cli-linux-x64-musl": "2.111.0", + "@supabase/cli-windows-arm64": "2.111.0", + "@supabase/cli-windows-x64": "2.111.0" } }, "node_modules/supports-color": { @@ -16472,16 +16899,15 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -16508,6 +16934,32 @@ "node": ">=14" } }, + "node_modules/terser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "license": "BSD-2-Clause", + "peer": true, + "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/terser/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==", + "license": "MIT", + "peer": true + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -16522,9 +16974,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "license": "MIT", "engines": { "node": ">=18" @@ -16548,9 +17000,9 @@ } }, "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==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -16558,22 +17010,22 @@ } }, "node_modules/tldts": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz", - "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.2" + "tldts-core": "^7.4.10" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz", - "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", "dev": true, "license": "MIT" }, @@ -16601,9 +17053,9 @@ } }, "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==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -16714,9 +17166,9 @@ } }, "node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -16810,7 +17262,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -16821,16 +17273,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", - "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.60.0", - "@typescript-eslint/parser": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -16882,19 +17334,19 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", - "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, "node_modules/unified": { @@ -17128,9 +17580,9 @@ "optional": true }, "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -17200,16 +17652,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -17226,7 +17678,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -17292,32 +17744,305 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "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", + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "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": { @@ -17333,12 +18058,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -17395,6 +18120,19 @@ "node": ">=18" } }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -17414,10 +18152,98 @@ "node": ">=20" } }, + "node_modules/webpack": { + "version": "5.109.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.2.tgz", + "integrity": "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "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-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "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==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -17448,18 +18274,18 @@ } }, "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==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.11.0", + "@exodus/bytes": "^1.15.1", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.14.0 || >=24.0.0" } }, "node_modules/when-exit": { @@ -17551,9 +18377,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", - "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { @@ -17600,36 +18426,23 @@ } }, "node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", + "optional": true, "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=20" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -17638,10 +18451,9 @@ "optional": true }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -17670,9 +18482,9 @@ } }, "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", "funding": [ { "type": "github", @@ -17714,7 +18526,6 @@ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", - "optional": true, "bin": { "yaml": "bin.mjs" }, @@ -17726,9 +18537,9 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "optional": true, "dependencies": { @@ -17754,51 +18565,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "optional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -17812,9 +18578,9 @@ } }, "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index 06b643ee..8274cc1f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ghostclass", - "version": "4.4.9", + "version": "4.5.0", "private": true, "engines": { "node": ">=22.12.0", - "npm": ">=11" + "npm": ">=12" }, "scripts": { "dev": "next dev -H 0.0.0.0 --webpack", @@ -17,7 +17,6 @@ "build:webpack": "next build --webpack", "start": "next start", "lint": "eslint src", - "infisical:dev": "infisical run -- next dev -H 0.0.0.0 --webpack", "test": "vitest", "test:watch": "vitest --watch", "test:ui": "vitest --ui", @@ -26,6 +25,7 @@ "test:e2e:ui": "playwright test --ui", "test:e2e:headed": "playwright test --headed", "test:all": "npm run test:coverage && npm run test:e2e", + "generate:firebase-json": "node scripts/generate-firebase-json.js", "prepare": "husky" }, "browserslist": [ @@ -41,110 +41,117 @@ "js-yaml": "^4.1.1", "rollup": "^4.60.3", "glob": "^13.0.6", - "source-map": "^0.7.6", + "source-map": "^0.8.0", "minimatch": "^10.2.5", "flatted": "^3.4.2", "@tootallnate/once": "^3.0.1", "postcss": "^8.5.14", - "uuid": "^14.0.0" + "sharp": "^0.35.0", + "uuid": "^14.0.1" }, "dependencies": { - "@hookform/resolvers": "^5.2.2", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-avatar": "^1.1.11", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@scalar/nextjs-api-reference": "^0.10.0", - "@sentry/nextjs": "^10.41.0", - "@serwist/next": "^9.5.6", - "@supabase/ssr": "^0.10.2", - "@supabase/supabase-js": "^2.97.0", - "@tanstack/react-query": "^5.100.13", - "@tanstack/react-virtual": "^3.13.21", + "@hookform/resolvers": "^5.5.7", + "@radix-ui/react-alert-dialog": "^1.1.23", + "@radix-ui/react-avatar": "^1.2.6", + "@radix-ui/react-checkbox": "^1.3.11", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-label": "^2.1.15", + "@radix-ui/react-popover": "^1.1.23", + "@radix-ui/react-progress": "^1.1.16", + "@radix-ui/react-radio-group": "^1.4.7", + "@radix-ui/react-scroll-area": "^1.2.18", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-switch": "^1.3.7", + "@radix-ui/react-tabs": "^1.1.21", + "@scalar/nextjs-api-reference": "^0.11.12", + "@sentry/nextjs": "^10.69.0", + "@serwist/next": "^9.5.12", + "@supabase/ssr": "^0.12.4", + "@supabase/supabase-js": "^2.111.0", + "@tanstack/react-query": "^5.101.4", + "@tanstack/react-virtual": "^3.14.9", "@upstash/ratelimit": "^2.0.8", - "@upstash/redis": "^1.36.2", - "axios": "^1.13.5", + "@upstash/redis": "^1.38.1", + "axios": "^1.19.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "firebase-admin": "^13.8.0", - "framer-motion": "^12.34.3", + "date-fns": "^4.4.0", + "firebase-admin": "^14.2.0", + "framer-motion": "^12.43.0", "googleapis": "^173.0.0", - "jose": "^6.2.2", "ldrs": "^1.1.9", - "lodash-es": "^4.17.23", - "lru-cache": "^11.2.6", - "lucide-react": "^1.16.0", - "next": "^16.1.6", + "lodash-es": "^4.18.1", + "lru-cache": "^11.5.2", + "lucide-react": "^1.28.0", + "next": "^16.2.12", "nextjs-toploader": "^3.9.17", "node-domexception": "^2.0.2", - "react": "^19.2.4", - "react-day-picker": "^10.0.0", - "react-dom": "^19.2.4", - "react-email": "^6.0.0", - "react-hook-form": "^7.71.2", - "react-is": "^19.2.5", + "react": "^19.2.8", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.8", + "react-email": "^6.9.1", + "react-hook-form": "^7.83.0", + "react-is": "^19.2.8", "react-markdown": "^10.1.0", "react-turnstile": "^1.1.5", - "recharts": "^3.7.0", - "resend": "^6.9.2", - "sanitize-html": "^2.17.0", + "recharts": "^3.10.1", + "resend": "^6.18.1", + "sanitize-html": "^2.17.6", "server-only": "^0.0.1", - "serwist": "^9.5.6", + "serwist": "^9.5.12", "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", - "uuid": "^14.0.0", - "zod": "^4.3.6" + "uuid": "^14.0.1", + "zod": "^4.4.3" }, "devDependencies": { - "@eslint/js": "^9.39.4", - "@opentelemetry/context-async-hooks": "^2.6.0", - "@playwright/test": "^1.59.1", + "@eslint/js": "^9.39.5", + "@opentelemetry/context-async-hooks": "^2.10.0", + "@playwright/test": "^1.62.1", "@tailwindcss/postcss": "^4", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.3.5", + "@types/node": "^26.1.2", "@types/nprogress": "^0.2.3", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@types/sanitize-html": "^2.16.0", - "@typescript-eslint/parser": "^8.56.1", - "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.0.18", - "@vitest/ui": "^4.1.5", - "eslint": "^9.39.4", - "eslint-config-next": "^16.1.6", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@types/sanitize-html": "^2.16.1", + "@typescript-eslint/parser": "^8.65.0", + "@vitejs/plugin-react": "^6.0.5", + "@vitest/coverage-v8": "^4.1.10", + "@vitest/ui": "^4.1.10", + "eslint": "^9.39.5", + "eslint-config-next": "^16.2.12", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-security": "^4.0.0", - "eslint-plugin-sonarjs": "^4.0.3", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-security": "^4.0.1", + "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-unused-imports": "^4.4.1", "glob": "^13.0.6", - "globals": "^17.3.0", - "happy-dom": "^20.7.0", + "globals": "^17.8.0", + "happy-dom": "^20.11.1", "husky": "^9.1.7", - "jsdom": "^29.0.0", - "lint-staged": "^17.0.4", + "jsdom": "^30.0.1", + "lint-staged": "^17.3.0", "rimraf": "^6.1.3", - "source-map": "^0.7.6", - "supabase": "^2.76.14", + "source-map": "^0.8.0", + "supabase": "^2.111.0", "tailwindcss": "^4", "typescript": "^6.0.3", - "typescript-eslint": "^8.56.1", - "vitest": "^4.1.5" + "typescript-eslint": "^8.65.0", + "vitest": "^4.1.10" + }, + "allowScripts": { + "@firebase/util@1.15.2": true, + "@sentry/cli@2.58.6": true, + "esbuild@0.28.1": true, + "protobufjs@7.6.5": true, + "unrs-resolver@1.12.2": true } } diff --git a/playwright.config.ts b/playwright.config.ts index 3a7977e8..fc46b0f5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,40 +1,40 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ - testDir: './e2e', + testDir: "./e2e", fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: 'html', + reporter: "html", use: { - baseURL: 'http://localhost:3000', - trace: 'on-first-retry', - screenshot: 'only-on-failure', + baseURL: "http://localhost:3000", + trace: "on-first-retry", + screenshot: "only-on-failure", }, projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, + name: "firefox", + use: { ...devices["Desktop Firefox"] }, }, { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, + name: "webkit", + use: { ...devices["Desktop Safari"] }, }, { - name: 'Mobile Chrome', - use: { ...devices['Pixel 5'] }, + name: "Mobile Chrome", + use: { ...devices["Pixel 5"] }, }, ], webServer: { - command: 'npm run dev', - url: 'http://localhost:3000', + command: "npm run dev", + url: "http://localhost:3000", reuseExistingServer: !process.env.CI, }, -}) +}); diff --git a/postcss.config.mjs b/postcss.config.mjs index 297374d8..61e36849 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,6 +1,6 @@ const config = { plugins: { - '@tailwindcss/postcss': {}, + "@tailwindcss/postcss": {}, }, }; diff --git a/public/favicon.svg b/public/favicon.svg index d81ba18f..94593499 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,230 +1,911 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/openapi/openapi.yaml b/public/openapi/openapi.yaml index 737107f2..d67743b8 100644 --- a/public/openapi/openapi.yaml +++ b/public/openapi/openapi.yaml @@ -1,1915 +1,1892 @@ -# NOTE: This file contains template variables (e.g., ${NEXT_PUBLIC_APP_EMAIL}, ${NEXT_PUBLIC_APP_URL}) -# that are substituted at request time by src/app/api/openapi/route.ts. -# Do NOT serve this file directly (e.g., via the static /api-docs/openapi.yaml path) as the -# placeholders will be returned literally. Always use the /api/openapi endpoint instead. -openapi: 3.1.0 - -info: - title: GhostClass API - version: 4.4.9 - description: | - **GhostClass API** provides endpoints for authentication, profile synchronization, - attendance integrations with EzyGo, telemetry, and build provenance. - - ## Authentication - - The API supports two authentication methods: - - 1. **Bearer Token (CRON_SECRET)**: For automated cron jobs - - Add `Authorization: Bearer ` header - - Used by automated sync processes - - 2. **Session Cookie (SupabaseAuth)**: For authenticated users - - Automatically included when logged in through the web interface - - Managed by Supabase Auth - - ## Rate Limiting - - All endpoints are rate-limited to prevent abuse: - - - `/api/auth/save-token` & `/api/cron/sync`: Configurable via environment variables - - `/api/backend/*`: Configurable via `PROXY_RATE_LIMIT_REQUESTS` and `PROXY_RATE_LIMIT_WINDOW` - - Contact Form (Server Action): Configurable via environment variables - - Rate limit information is returned in response headers: - - `X-RateLimit-Limit`: Maximum requests allowed - - `X-RateLimit-Remaining`: Remaining requests in current window - - `X-RateLimit-Reset`: Unix timestamp when limit resets - - ## Base URLs - - - Production: Configured via `NEXT_PUBLIC_APP_URL` - - Development: `http://localhost:3000` - - ## Error Handling - - All error responses follow a consistent format with appropriate HTTP status codes and descriptive messages. - - contact: - name: GhostClass Support - email: "contact${NEXT_PUBLIC_APP_EMAIL}" - url: ${NEXT_PUBLIC_GITHUB_URL} - - license: - name: GPL-3.0 - url: ${NEXT_PUBLIC_GITHUB_URL}/blob/main/LICENSE - -servers: - - url: ${NEXT_PUBLIC_APP_URL} - description: Production - - url: https://localhost:3000 - description: Development (HTTPS) - - url: http://localhost:3000 - description: Development (HTTP) - -tags: - - name: Authentication - description: Endpoints for managing EzyGo authentication tokens - - name: Mobile - description: Mobile-first authenticated endpoints and JWE-secured bridge APIs - - name: Security - description: Security endpoints for CSRF protection - - name: Sync - description: Endpoints for synchronizing attendance data with EzyGo - - name: Proxy - description: Secure backend proxy and pass-through endpoints for EzyGo APIs - - name: Health - description: System health and status endpoints - - name: Analytics - description: Server-side analytics event forwarding - - name: Reporting - description: Browser-generated violation and telemetry reports - - name: Profile - description: User profile management - - name: Keys - description: Public key discovery endpoints for JWE/JWKS - - name: Documentation - description: OpenAPI and API reference endpoints - - name: Provenance - description: Build provenance and version metadata - -paths: - /api/auth/save-token: - post: - tags: - - Authentication - summary: Save EzyGo authentication token - security: [] # No authentication required for this endpoint - description: | - Saves the EzyGo JWT authentication token and establishes a secure session for the user. - - **What it does:** - - Validates the EzyGo token by verifying with EzyGo API - - Creates or retrieves a Supabase authentication account (ghost login) - - Encrypts and stores the token securely in the database - - Establishes a session cookie for subsequent authenticated requests - - **Multi-Device Support:** - - Uses canonical password pattern for persistent authentication - - On first login: Generates and encrypts a canonical password for the user - - On subsequent logins: Retrieves and decrypts the stored password - - Enables concurrent sessions from multiple devices without invalidation - - Logout on one device does not affect sessions on other devices - - **Authentication:** No authentication required (this endpoint creates authentication) - - **Rate Limiting:** Configured via AUTH_RATE_LIMIT_REQUESTS and AUTH_RATE_LIMIT_WINDOW environment variables - - **Side Effects:** - - Creates a new user account in Supabase Auth if not exists - - Stores encrypted token in the database - - Stores encrypted canonical password on first login (auth_password, auth_password_iv) - - Sets session cookies in the response - - **Use Cases:** - - Initial user authentication from EzyGo - - Token refresh when existing token expires - - Multi-device login (desktop, mobile, tablet simultaneously) - - operationId: saveAuthToken - - requestBody: - required: true - description: EzyGo authentication token - content: - application/json: - schema: - type: object - required: - - token - properties: - token: - type: string - minLength: 20 - maxLength: 2000 - description: Valid EzyGo JWT token obtained from EzyGo authentication - example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - examples: - validToken: - summary: Valid EzyGo token - description: Example of a valid JWT token from EzyGo - value: - # trivy:ignore:jwt-token - token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcm5hbWUiOiJzdHVkZW50MTIzIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" - - responses: - "200": - description: Token successfully saved and session established - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - example: true - examples: - success: - summary: Successful authentication - value: - success: true - - "400": - description: Bad request - Missing or invalid token - content: - application/json: - schema: - $ref: "#/components/schemas/ValidationError" - examples: - missingToken: - summary: Token not provided - value: - message: "Missing credentials" - - "401": - description: Unauthorized - Token verification failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - examples: - invalidToken: - summary: Invalid or expired token - value: - message: "Invalid or expired token" - verificationFailed: - summary: Could not verify user identity - value: - message: "Could not verify user identity" - - "429": - description: Rate limit exceeded - headers: - X-RateLimit-Limit: - schema: - type: integer - description: Maximum number of requests allowed in the time window (from AUTH_RATE_LIMIT_REQUESTS) - example: 5 - X-RateLimit-Remaining: - schema: - type: integer - description: Number of requests remaining in current window - example: 0 - X-RateLimit-Reset: - schema: - type: integer - description: Unix timestamp when the rate limit resets - example: 1706108400 - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - examples: - tooManyRequests: - summary: Rate limit exceeded - value: - error: "Too many requests. Slow down!" - retryAfter: 900 - - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - examples: - serverError: - summary: Failed to establish session - value: - message: "Failed to establish secure session" - - /api/csrf: - get: - tags: - - Security - summary: Get CSRF token - security: [] # No authentication required - description: | - Retrieves a CSRF token for use in subsequent authenticated requests. The token is automatically set as a cookie and returned in the response. - - **What it does:** - - Generates a new CSRF token if one doesn't exist - - Sets the token as a cookie in the response - - Returns the token value for client-side use - - **Authentication:** None required - - **Rate Limiting:** Standard rate limiting applies - - **Use Cases:** - - Initialize CSRF protection before making authenticated requests - - Retrieve current CSRF token for client-side operations - - operationId: getCsrfToken - - responses: - "200": - description: CSRF token retrieved successfully - headers: - Set-Cookie: - schema: - type: string - description: CSRF token cookie - example: csrf_token=abc123...; Path=/; SameSite=Strict - content: - application/json: - schema: - type: object - properties: - token: - type: string - description: The CSRF token value - example: "abc123def456..." - message: - type: string - description: Success message - example: "CSRF token initialized successfully" - examples: - success: - summary: Token retrieved - value: - token: "abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" - message: "CSRF token initialized successfully" - - post: - tags: - - Security - summary: Regenerate CSRF token - security: [] # No authentication required - description: | - Explicitly regenerates the CSRF token, replacing any existing one. - Useful after privilege escalation or when extra rotation is desired. - - **Authentication:** None required - - **Rate Limiting:** Configured via AUTH_RATE_LIMIT_REQUESTS / AUTH_RATE_LIMIT_WINDOW - - operationId: regenerateCsrfToken - - responses: - "200": - description: New CSRF token generated - headers: - Set-Cookie: - schema: - type: string - description: Updated CSRF token cookie - content: - application/json: - schema: - type: object - properties: - token: - type: string - description: The new CSRF token value - message: - type: string - description: Success message - examples: - success: - summary: Token regenerated - value: - token: "new123abc456def789..." - message: "CSRF token refreshed successfully" - "429": - description: Rate limit exceeded - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - "500": - description: Unable to determine client IP or internal error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/security/attestation: - get: - tags: - - Security - - Mobile - summary: Get decoded attestation details - security: [] - description: | - Returns the decoded security attestation details for the current request's Firebase App Check token. - Used by the mobile app for troubleshooting, transparency, and verifying app integrity. - operationId: getAttestationDetails - responses: - "200": - description: Attestation details retrieved successfully - content: - application/json: - schema: - type: object - properties: - verified: - type: boolean - description: Whether the App Check token is valid - criticalRisk: - type: boolean - description: Whether a critical integrity risk was detected - appCheck: - type: boolean - description: Alias for verified - appCheckCriticalRisk: - type: boolean - description: Alias for criticalRisk - appCheckError: - type: string - nullable: true - description: Error message if verification failed - appId: - type: string - nullable: true - description: The Firebase App ID associated with the token - details: - type: object - description: Non-sensitive claims and metadata extracted from the token - enforced: - type: boolean - description: Whether App Check verification is currently enforced on the server - reason: - type: string - description: User-friendly explanation of the status - action: - type: string - description: Recommended action if verification failed - latestVersion: - type: string - description: Latest available version of the app - minVersion: - type: string - description: Minimum supported version of the app - type: - type: string - example: "security" - timestamp: - type: string - format: date-time - - /api/logout: - post: - tags: - - Authentication - summary: Logout and clear session - security: - - SupabaseAuth: [] - description: | - Clears authentication cookies and ends the user session. - - **What it does:** - - Removes authentication token cookie - - Removes CSRF token cookie - - Invalidates the session - - **Authentication:** Session cookie (automatically cleared) - - **Rate Limiting:** None - - **Use Cases:** - - User logout - - Session termination - - operationId: logout - - responses: - "200": - description: Successfully logged out - content: - application/json: - schema: - type: object - properties: - ok: - type: boolean - example: true - examples: - success: - summary: Logout successful - value: - ok: true - - /api/cron/sync: - get: - tags: - - Sync - summary: Sync attendance data with EzyGo - description: | - Synchronizes attendance records from EzyGo with the local database. Supports both automated cron jobs and authenticated user-initiated syncs. - - **What it does:** - - Fetches latest attendance data from EzyGo API - - Compares with local tracking records - - Detects and resolves conflicts (course mismatches, attendance discrepancies) - - Sends email notifications for conflicts - - Updates sync timestamps - - **Authentication:** - - **Option 1 (Cron)**: Bearer token with CRON_SECRET in Authorization header - - **Option 2 (Users)**: Valid session cookie (authenticated users) - - **Rate Limiting:** Configured via RATE_LIMIT_REQUESTS and RATE_LIMIT_WINDOW environment variables - - **Side Effects:** - - Deletes verified/matched attendance records - - Updates conflicted records with 'correction' status - - Creates notifications in the database - - Sends email alerts for attendance conflicts - - Updates `last_synced_at` timestamp for users - - **Use Cases:** - - Automated daily/hourly sync via cron job - - Manual sync triggered by users from dashboard - - Batch sync for multiple users (cron only) - - Single user sync with optional username filter - - operationId: syncAttendance - - security: - - BearerAuth: [] - - SupabaseAuth: [] - - parameters: - - name: username - in: query - required: false - description: | - Filter sync to a specific user by username. Only applicable when using Bearer token authentication (cron mode). - - - If provided with cron auth: syncs only the specified user - - If omitted with cron auth: syncs up to 10 least-recently-synced users - - Ignored for authenticated user requests (always syncs the current user) - schema: - type: string - example: "student123" - - responses: - "200": - description: Sync completed successfully - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - description: Whether sync operation completed successfully - example: true - processed: - type: integer - description: Number of users processed - example: 5 - deletions: - type: integer - description: Number of tracking records deleted (verified/matched) - example: 23 - conflicts: - type: integer - description: Number of attendance conflicts detected - example: 2 - updates: - type: integer - description: Number of records updated to 'correction' status - example: 2 - errors: - type: integer - description: Number of users that failed to sync - example: 0 - examples: - successfulSync: - summary: Successful sync with multiple users - value: - success: true - processed: 5 - deletions: 23 - conflicts: 2 - updates: 2 - errors: 0 - noUsers: - summary: No eligible users to sync - value: - success: true - processed: 0 - deletions: 0 - conflicts: 0 - updates: 0 - errors: 0 - - "401": - description: Unauthorized - Invalid or missing authentication - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - examples: - unauthorized: - summary: Not authenticated - value: - message: "Unauthorized" - - "429": - description: Rate limit exceeded - headers: - X-RateLimit-Reset: - schema: - type: integer - description: Unix timestamp when the rate limit resets - example: 1706108460 - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - examples: - tooManyRequests: - summary: Rate limit exceeded - value: - error: "Too many requests" - retryAfter: 60 - - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - examples: - serverError: - summary: Configuration error - value: - error: "Server configuration error: NEXT_PUBLIC_APP_URL is not set" - - /api/analytics/track: - post: - tags: - - Analytics - summary: Track a server-side analytics event - security: [] - description: | - Proxies GA4 Measurement Protocol events through the server so ad blockers - do not suppress them. Validates event shapes, sanitises values to GA4 limits, - and enforces origin and rate-limit guards before forwarding to GA4. - - **Authentication:** None required - - **Rate Limiting:** Configured via RATE_LIMIT_REQUESTS / RATE_LIMIT_WINDOW - - operationId: trackAnalyticsEvent - - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - clientId - - events - properties: - clientId: - type: string - maxLength: 100 - description: GA4 client ID in `timestamp.randomhex` format - example: "1706108400.a1b2c3d4" - events: - type: array - maxItems: 25 - description: Up to 25 GA4 events per request - items: - type: object - required: - - name - properties: - name: - type: string - maxLength: 40 - description: Event name (lowercase, underscores, digits only) - params: - type: object - description: Arbitrary GA4 event parameters - additionalProperties: - oneOf: - - type: string - - type: number - - type: boolean - userProperties: - type: object - description: Optional GA4 user properties (key โ†’ string value) - additionalProperties: - type: string - - responses: - "200": - description: Events forwarded to GA4 - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - example: true - "400": - description: Validation error (bad clientId, malformed events, etc.) - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "403": - description: Forbidden โ€“ request origin not allowed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "429": - description: Rate limit exceeded - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/attendance/summary-batch: - post: - summary: Batch fetch attendance summaries - description: | - Retrieves attendance summaries for multiple courses in a single request. - Optimized for dashboard card views to avoid N+1 network requests. - - Requires Firebase App Check and JWE encryption. - tags: - - Attendance - security: - - SupabaseAuth: [] - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - courses - properties: - courses: - type: array - items: - type: object - required: - - id - - code - properties: - id: - type: integer - code: - type: string - name: - type: string - responses: - "200": - description: Batch summary data - content: - application/json: - schema: - type: object - additionalProperties: - $ref: "#/components/schemas/CourseDetail" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "429": - description: Too many requests - content: - application/json: - $ref: "#/components/schemas/RateLimitError" - - /api/courses/add: - post: - tags: - - Mobile - summary: Add a new course to class lineup - description: | - Adds a new course to the user's class lineup. - Primarily used by the mobile app to sync or manually add a course. - operationId: addCourse - security: - - SupabaseAuth: [] - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - courseCode - - courseName - properties: - courseCode: - type: string - description: The code of the course (e.g. CS101) - example: "CS101" - courseName: - type: string - description: The name of the course - example: "Introduction to Computer Science" - responses: - "201": - description: Course added successfully - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Course added successfully" - "400": - description: Bad request - Missing required fields or class association - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "409": - description: Conflict - Course already exists in the class lineup - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "422": - description: Validation failed - Invalid field formats - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/instructors/upsert: - post: - tags: - - Mobile - summary: Save or update course instructor - description: | - Saves or updates instructor mapping for a specific course, semester, and academic year in the user's class. - operationId: upsertInstructor - security: - - SupabaseAuth: [] - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - courseCode - - instructorName - properties: - courseCode: - type: string - example: "CS101" - instructorName: - type: string - example: "Dr. John Doe" - responses: - "200": - description: Instructor saved successfully - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Instructor saved successfully" - "400": - description: Bad request - Missing required fields or class association - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "422": - description: Validation failed - Invalid field formats - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/scores/batch: - post: - tags: - - Proxy - - Mobile - summary: Batch fetch exam questions and answers - description: | - Fetches exam questions and graded answers for multiple exams in parallel. - Optimized for mobile dashboard to replace multiple individual API calls. - operationId: batchFetchScores - security: - - SupabaseAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - examIds - properties: - examIds: - type: array - maxItems: 25 - description: Array of exam IDs to fetch details for - items: - type: integer - example: [123, 456] - responses: - "200": - description: Batch exam details retrieved successfully - content: - application/json: - schema: - type: object - additionalProperties: - type: object - properties: - questions: - type: array - items: - $ref: "#/components/schemas/ExamQuestion" - answers: - type: array - items: - $ref: "#/components/schemas/ExamAnswer" - error: - type: string - nullable: true - "400": - description: Bad request - Missing IP, invalid format, or invalid JSON - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Unauthorized - Invalid session or missing EzyGo token - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "429": - description: Rate limit exceeded - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/csp-report: - post: - tags: - - Reporting - summary: Receive CSP violation reports - security: [] - description: | - Browser endpoint for Content-Security-Policy violation reports. - Accepts both legacy `application/csp-report` (report-uri directive) and - modern `application/reports+json` (Reporting API v1) payloads. - Always returns **204 No Content** so the browser does not retry. - Body is capped at **8 KB** to prevent abuse on this unauthenticated endpoint. - - **Authentication:** None required (reports are sent automatically by browsers) - - operationId: receiveCspReport - - requestBody: - required: true - content: - application/csp-report: - schema: - type: object - description: "Legacy CSP report wrapped in a csp-report envelope object" - application/reports+json: - schema: - type: array - description: Reporting API v1 array of report objects - items: - type: object - - responses: - "204": - description: Report received and logged (browser should not retry) - "400": - description: Invalid Content-Length header value - "413": - description: Payload too large (exceeds 8 KB limit) - "415": - description: Unsupported content type - - /api/health: - get: - tags: - - Health - summary: Health check endpoint - security: [] # No authentication required for health checks - description: | - Returns the health status of the API service. Used for monitoring and uptime checks. - - **What it does:** - - Confirms the API is responsive - - Returns current version information - - Provides timestamp for monitoring - - **Authentication:** None required - - **Rate Limiting:** None (health checks should always be available) - - **Use Cases:** - - Monitoring tools and uptime checkers - - Load balancer health checks - - Deployment verification - - operationId: healthCheck - - responses: - "200": - description: Service is healthy and operational - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: [ok] - description: Health status of the service - example: "ok" - version: - type: string - description: Current version of the application - example: "1.0.0" - timestamp: - type: string - format: date-time - description: Current server timestamp in ISO 8601 format - example: "2026-01-24T20:52:27.674Z" - examples: - healthy: - summary: Service is healthy - value: - status: "ok" - version: "1.0.0" - timestamp: "2026-01-24T20:52:27.674Z" - - /api/health/ezygo: - get: - tags: - - Health - summary: EzyGo integration health check - security: [] - description: | - Returns health status of the EzyGo API integration including circuit breaker - and rate-limiter state. In **development/test** environments the response - includes detailed metrics (active requests, queue length, CB state). - In **production** only `status` and `timestamp` are returned. - - Returns HTTP **503** when the circuit breaker is open. - - operationId: healthCheckEzygo - - responses: - "200": - description: EzyGo integration healthy or degraded - headers: - Cache-Control: - schema: - type: string - example: "no-store, max-age=0" - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: [healthy, degraded, unhealthy] - timestamp: - type: string - format: date-time - examples: - healthy: - summary: Circuit closed, no queue backlog - value: - status: healthy - timestamp: "2026-02-01T12:00:00.000Z" - degraded: - summary: Requests queuing up - value: - status: degraded - timestamp: "2026-02-01T12:00:00.000Z" - "503": - description: Circuit breaker open โ€“ upstream EzyGo unreachable - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: [unhealthy] - timestamp: - type: string - format: date-time - - /api/profile: - get: - tags: - - Profile - summary: Fetch user profile - security: - - SupabaseAuth: [] - description: | - Returns the current user's profile data from the local database, soft-synced - with EzyGo. PII fields (`phone`, `gender`, `birth_date`) are AES-256-GCM - encrypted at rest; the server decrypts them before responding. - - **Full Bundle:** This endpoint returns an "uber-bundle" containing: - - Basic profile info - - Academic context (current_semester, current_year) - - User settings (bunk calculator, target percentage, disabled courses) - - Terms compliance status - - Auth bridge token (decrypted ezygo_token) - - **Origin validation:** In production, requests must include a matching `Origin` - header (or `Sec-Fetch-Site: same-origin`). Cross-site requests are rejected - with 400/403 as defence-in-depth against CSRF on this read path. - - operationId: getProfile - - responses: - "200": - description: Profile returned successfully - headers: - Cache-Control: - schema: - type: string - example: "no-store, max-age=0" - content: - application/json: - schema: - $ref: "#/components/schemas/UserProfile" - "400": - description: Origin header missing (and Sec-Fetch-Site is not same-origin) - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "403": - description: Origin not allowed (cross-site request rejected in production) - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Not authenticated - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - patch: - tags: - - Profile - summary: Update user profile - security: - - SupabaseAuth: [] - description: | - Updates user-editable profile fields. PII values (`gender`, `birth_date`) are - AES-256-GCM encrypted before being written to the database. - - **CSRF:** Requires the `x-csrf-token` request header. - - **Validation:** `first_name` is required (min 2 chars). All other fields - are optional; omitting an optional field does **not** clear it. - - operationId: updateProfile - - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - first_name - properties: - first_name: - type: string - minLength: 2 - example: "Aditya" - last_name: - type: string - nullable: true - example: "Kumar" - gender: - type: string - enum: [male, female, other] - nullable: true - birth_date: - type: string - format: date - nullable: true - example: "2003-08-15" - - responses: - "200": - description: Profile updated; returns saved plaintext values - content: - application/json: - schema: - type: object - properties: - first_name: - type: string - last_name: - type: string - nullable: true - gender: - type: string - nullable: true - birth_date: - type: string - nullable: true - "401": - description: Not authenticated - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "403": - description: Invalid CSRF token - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "422": - description: Validation failed - content: - application/json: - schema: - type: object - properties: - error: - type: string - example: "Validation failed" - details: - type: object - "500": - description: Database update failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/provenance: - get: - tags: - - Provenance - summary: Build provenance and version metadata - security: [] - description: | - Returns build provenance metadata: commit SHA, app version, image digest, - audit and signature status, and build timestamp. - - Extended CI metadata (`github_run_id`, `github_run_number`, `github_repo`) - is included only when the request presents - `Accept: application/vnd.ghostclass.provenance+json`. - - Responses are **never cached** (`Cache-Control: no-store`). - - operationId: getProvenance - - parameters: - - name: Accept - in: header - required: false - description: | - Pass `application/vnd.ghostclass.provenance+json` to receive extended - CI/build metadata in addition to the base payload. - schema: - type: string - - responses: - "200": - description: Provenance metadata - headers: - Cache-Control: - schema: - type: string - example: "no-store, max-age=0" - content: - application/json: - schema: - type: object - properties: - commit: - type: string - description: Commit SHA (legacy alias for commit_sha) - commit_sha: - type: string - build_id: - type: string - app_version: - type: string - image_digest: - type: string - container: - type: boolean - timestamp: - type: string - format: date-time - audit_status: - type: string - signature_status: - type: string - examples: - base: - summary: Standard production payload - value: - commit: "a1b2c3d" - commit_sha: "a1b2c3d" - build_id: "123456789" - app_version: "1.9.5" - image_digest: "sha256:abc123..." - container: true - timestamp: "2026-02-01T10:00:00.000Z" - audit_status: "PASSED" - signature_status: "SIGNED" - - /api/openapi: - get: - tags: - - Documentation - summary: Get resolved OpenAPI YAML - security: [] - description: | - Returns the OpenAPI YAML document with `${NEXT_PUBLIC_*}` template tokens - resolved at request time. - operationId: getOpenApiSpec - responses: - "200": - description: OpenAPI YAML document - content: - application/yaml: - schema: - type: string - - /api/.well-known/jwks.json: - get: - tags: - - Keys - summary: Get JWKS public keys - security: [] - description: | - Returns the JSON Web Key Set (JWKS) used by clients to discover - active public keys for cryptographic operations. - operationId: getJwks - responses: - "200": - description: JSON Web Key Set - content: - application/jwk-set+json: - schema: - type: object - properties: - keys: - type: array - items: - type: object - "500": - description: Failed to fetch keys - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/auth/sync: - post: - tags: - - Authentication - - Mobile - summary: Self-heal authentication state - description: | - Attempts to refresh Supabase session cookies and restore EzyGo auth token - from server-side storage after client-side auth/session drift. - operationId: syncAuth - responses: - "200": - description: Sync attempt completed - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - message: - type: string - terminal: - type: boolean - "401": - description: Session expired and cannot be recovered - headers: - x-terminal-auth: - schema: - type: string - description: Present and set to `true` when re-authentication is required - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/auth/register-fcm: - post: - tags: - - Authentication - - Mobile - summary: Register mobile FCM push token - description: | - Registers or updates the Firebase Cloud Messaging (FCM) push token for the authenticated user. - Enables sending push notifications to the user's mobile device. - operationId: registerFcmToken - security: - - SupabaseAuth: [] - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - fcm_token - properties: - fcm_token: - type: string - description: The Firebase Cloud Messaging registration token - example: "bk3RNwXXeDY:AP91bH..." - responses: - "200": - description: FCM token registered successfully - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - example: true - "400": - description: Bad request - Missing IP or invalid JSON - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Unauthorized - Invalid session or bearer token - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "422": - description: Validation failed - Empty token - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "429": - description: Rate limit exceeded - content: - application/json: - schema: - $ref: "#/components/schemas/RateLimitError" - "500": - description: Internal server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/contact: - post: - tags: - - Mobile - summary: Submit contact/support message - description: | - Submits a contact message through the shared contact service. - Supports both authenticated and guest submissions. - operationId: submitContact - requestBody: - required: true - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: Message accepted - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - id: - type: string - "400": - description: Validation or payload error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Contact processing failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/user/accept-terms: - post: - tags: - - Mobile - summary: Accept terms for mobile user - description: | - Updates `terms_version` and `terms_accepted_at` for authenticated mobile users. - operationId: acceptTermsMobile - security: - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - version - properties: - version: - type: string - responses: - "200": - description: Terms acceptance stored - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - version: - type: string - "400": - description: Missing version - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Failed to update terms acceptance - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - /api/backend/{path}: - description: | - Proxies requests to EzyGo backend endpoints. - - **Rate Limiting:** Configured via `PROXY_RATE_LIMIT_REQUESTS` and `PROXY_RATE_LIMIT_WINDOW`. - parameters: - - name: path - in: path - required: true - description: Forwarded EzyGo backend path segment(s) - schema: - type: string - get: - tags: - - Proxy - summary: Proxy GET request to EzyGo backend - operationId: proxyGet - responses: - "200": - description: Proxied response - content: - application/json: - schema: - type: object - additionalProperties: true - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - post: - tags: - - Proxy - summary: Proxy POST request to EzyGo backend - operationId: proxyPost - requestBody: - required: false - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: Proxied response - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - put: - tags: - - Proxy - summary: Proxy PUT request to EzyGo backend - operationId: proxyPut - requestBody: - required: false - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: Proxied response - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - patch: - tags: - - Proxy - summary: Proxy PATCH request to EzyGo backend - operationId: proxyPatch - requestBody: - required: false - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: Proxied response - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - delete: - tags: - - Proxy - summary: Proxy DELETE request to EzyGo backend - operationId: proxyDelete - responses: - "200": - description: Proxied response - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - head: - tags: - - Proxy - summary: Proxy HEAD request to EzyGo backend - operationId: proxyHead - responses: - "200": - description: Proxied response - "4XX": - description: Client error from proxy or upstream - "5XX": - description: Upstream/proxy failure - -components: - schemas: - CourseDetail: - type: object - properties: - present: - type: integer - absent: - type: integer - total: - type: integer - percentage: - type: number - course: - type: object - properties: - id: - oneOf: - - type: integer - - type: string - name: - type: string - code: - type: string - - UserProfile: - type: object - description: Decrypted user profile data (PII fields returned as plaintext) - properties: - id: - oneOf: - - type: string - - type: integer - description: EzyGo user ID - username: - type: string - example: "student123" - email: - type: string - format: email - first_name: - type: string - nullable: true - last_name: - type: string - nullable: true - phone: - type: string - nullable: true - description: Decrypted phone number (AES-256-GCM at rest) - gender: - type: string - nullable: true - description: Decrypted gender (AES-256-GCM at rest) - birth_date: - type: string - nullable: true - description: Decrypted birth date in YYYY-MM-DD format (AES-256-GCM at rest) - avatar_url: - type: string - nullable: true - terms_version: - type: string - nullable: true - terms_accepted_at: - type: string - format: date-time - nullable: true - current_semester: - type: string - nullable: true - current_year: - type: string - nullable: true - ezygo_token: - type: string - nullable: true - settings: - type: object - properties: - bunk_calculator_enabled: - type: boolean - target_percentage: - type: integer - disabled_courses: - type: object - additionalProperties: - type: object - additionalProperties: - type: string - - Error: - type: object - properties: - error: - type: string - description: Error message describing what went wrong - message: - type: string - description: Detailed error message - example: - error: "Internal Server Error" - - ValidationError: - type: object - properties: - message: - type: string - description: Validation error message - errors: - type: array - description: List of validation errors (if multiple fields failed) - items: - type: object - properties: - field: - type: string - description: Field that failed validation - message: - type: string - description: Validation error message for this field - example: - message: "Missing credentials" - - RateLimitError: - type: object - properties: - error: - type: string - description: Rate limit error message - example: "Too many requests. Slow down!" - retryAfter: - type: integer - description: Unix timestamp (in seconds) when the rate limit resets. Matches the `X-RateLimit-Reset` header. - example: 1735689600 - required: - - error - - retryAfter - - ExamQuestion: - type: object - properties: - id: - type: integer - question_no: - type: string - name: - type: string - maximum_mark: - type: string - module_id: - type: integer - exam_id: - type: integer - institution_id: - type: integer - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - - ExamAnswer: - type: object - properties: - id: - type: integer - answer: - type: string - nullable: true - score: - type: string - nullable: true - choice_id: - type: integer - nullable: true - examquestion_id: - type: integer - student_id: - type: integer - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - - securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: | - Bearer token authentication using CRON_SECRET environment variable. - Used for automated cron jobs. - - Example: `Authorization: Bearer ` - - SupabaseAuth: - type: apiKey - in: cookie - name: sb-* - description: | - Session cookie authentication managed by Supabase Auth. - Automatically included when users are logged in through the web interface. - Cookie names typically start with 'sb-' prefix. - -externalDocs: - description: GitHub Repository - url: ${NEXT_PUBLIC_GITHUB_URL} +# NOTE: This file contains template variables (e.g., ${NEXT_PUBLIC_APP_EMAIL}, ${NEXT_PUBLIC_APP_URL}) +# that are substituted at request time by src/app/api/openapi/route.ts. +# Do NOT serve this file directly (e.g., via the static /api-docs/openapi.yaml path) as the +# placeholders will be returned literally. Always use the /api/openapi endpoint instead. +openapi: 3.1.0 + +info: + title: GhostClass API + version: 4.5.0 + description: | + **GhostClass API** provides endpoints for authentication, profile synchronization, + attendance integrations with EzyGo, telemetry, and build provenance. + + ## Authentication + + The API supports two authentication methods: + + 1. **Bearer Token (CRON_SECRET)**: For automated cron jobs + - Add `Authorization: Bearer ` header + - Used by automated sync processes + + 2. **Session Cookie (SupabaseAuth)**: For authenticated users + - Automatically included when logged in through the web interface + - Managed by Supabase Auth + + ## Rate Limiting + + All endpoints are rate-limited to prevent abuse: + + - `/api/auth/save-token` & `/api/cron/sync`: Configurable via environment variables + - `/api/backend/*`: Configurable via `PROXY_RATE_LIMIT_REQUESTS` and `PROXY_RATE_LIMIT_WINDOW` + - Contact Form (Server Action): Configurable via environment variables + + Rate limit information is returned in response headers: + - `X-RateLimit-Limit`: Maximum requests allowed + - `X-RateLimit-Remaining`: Remaining requests in current window + - `X-RateLimit-Reset`: Unix timestamp when limit resets + + ## Base URLs + + - Production: Configured via `NEXT_PUBLIC_APP_URL` + - Development: `http://localhost:3000` + + ## Error Handling + + All error responses follow a consistent format with appropriate HTTP status codes and descriptive messages. + + contact: + name: GhostClass Support + email: "contact${NEXT_PUBLIC_APP_EMAIL}" + url: ${NEXT_PUBLIC_GITHUB_URL} + + license: + name: GPL-3.0 + url: ${NEXT_PUBLIC_GITHUB_URL}/blob/main/LICENSE + +servers: + - url: ${NEXT_PUBLIC_APP_URL} + description: Production + - url: https://localhost:3000 + description: Development (HTTPS) + - url: http://localhost:3000 + description: Development (HTTP) + +tags: + - name: Authentication + description: Endpoints for managing EzyGo authentication tokens + - name: Mobile + description: Mobile-first authenticated endpoints and bridge APIs + - name: Security + description: Security endpoints for CSRF protection + - name: Sync + description: Synchronization endpoints for mobile and web + - name: Attendance + description: Attendance tracking and calculation endpoints + - name: Courses + description: Course management and search endpoints + - name: Scores + description: Student scores and academic performance endpoints + - name: Classes + description: Class list and instructor discovery endpoints + - name: Analytics + description: Application analytics and telemetry tracking + - name: Contact + description: Contact form submission and support endpoints + - name: Health + description: Health check and diagnostic endpoints + - name: Reporting + description: Browser-generated violation and telemetry reports + - name: Profile + description: User profile management + - name: Documentation + description: OpenAPI and API reference endpoints + - name: Provenance + description: Build provenance and version metadata + +paths: + /api/auth/save-token: + post: + tags: + - Authentication + summary: Save EzyGo authentication token + security: [] # No authentication required for this endpoint + description: | + Saves the EzyGo JWT authentication token and establishes a secure session for the user. + + **What it does:** + - Validates the EzyGo token by verifying with EzyGo API + - Creates or retrieves a Supabase authentication account (ghost login) + - Encrypts and stores the token securely in the database + - Establishes a session cookie for subsequent authenticated requests + + **Multi-Device Support:** + - Uses canonical password pattern for persistent authentication + - On first login: Generates and encrypts a canonical password for the user + - On subsequent logins: Retrieves and decrypts the stored password + - Enables concurrent sessions from multiple devices without invalidation + - Logout on one device does not affect sessions on other devices + + **Authentication:** No authentication required (this endpoint creates authentication) + + **Rate Limiting:** Configured via AUTH_RATE_LIMIT_REQUESTS and AUTH_RATE_LIMIT_WINDOW environment variables + + **Side Effects:** + - Creates a new user account in Supabase Auth if not exists + - Stores encrypted token in the database + - Stores encrypted canonical password on first login (auth_password, auth_password_iv) + - Sets session cookies in the response + + **Use Cases:** + - Initial user authentication from EzyGo + - Token refresh when existing token expires + - Multi-device login (desktop, mobile, tablet simultaneously) + + operationId: saveAuthToken + + requestBody: + required: true + description: EzyGo authentication token + content: + application/json: + schema: + type: object + required: + - token + properties: + token: + type: string + minLength: 20 + maxLength: 2000 + description: Valid EzyGo JWT token obtained from EzyGo authentication + example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + examples: + validToken: + summary: Valid EzyGo token + description: Example of a valid JWT token from EzyGo + value: + # trivy:ignore:jwt-token + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcm5hbWUiOiJzdHVkZW50MTIzIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + + responses: + "200": + description: Token successfully saved and session established + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + examples: + success: + summary: Successful authentication + value: + success: true + + "400": + description: Bad request - Missing or invalid token + content: + application/json: + schema: + $ref: "#/components/schemas/ValidationError" + examples: + missingToken: + summary: Token not provided + value: + message: "Missing credentials" + + "401": + description: Unauthorized - Token verification failed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + invalidToken: + summary: Invalid or expired token + value: + message: "Invalid or expired token" + verificationFailed: + summary: Could not verify user identity + value: + message: "Could not verify user identity" + + "429": + description: Rate limit exceeded + headers: + X-RateLimit-Limit: + schema: + type: integer + description: Maximum number of requests allowed in the time window (from AUTH_RATE_LIMIT_REQUESTS) + example: 5 + X-RateLimit-Remaining: + schema: + type: integer + description: Number of requests remaining in current window + example: 0 + X-RateLimit-Reset: + schema: + type: integer + description: Unix timestamp when the rate limit resets + example: 1706108400 + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + examples: + tooManyRequests: + summary: Rate limit exceeded + value: + error: "Too many requests. Slow down!" + retryAfter: 900 + + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + serverError: + summary: Failed to establish session + value: + message: "Failed to establish secure session" + + /api/csrf: + get: + tags: + - Security + summary: Get CSRF token + security: [] # No authentication required + description: | + Retrieves a CSRF token for use in subsequent authenticated requests. The token is automatically set as a cookie and returned in the response. + + **What it does:** + - Generates a new CSRF token if one doesn't exist + - Sets the token as a cookie in the response + - Returns the token value for client-side use + + **Authentication:** None required + + **Rate Limiting:** Standard rate limiting applies + + **Use Cases:** + - Initialize CSRF protection before making authenticated requests + - Retrieve current CSRF token for client-side operations + + operationId: getCsrfToken + + responses: + "200": + description: CSRF token retrieved successfully + headers: + Set-Cookie: + schema: + type: string + description: CSRF token cookie + example: csrf_token=abc123...; Path=/; SameSite=Strict + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The CSRF token value + example: "abc123def456..." + message: + type: string + description: Success message + example: "CSRF token initialized successfully" + examples: + success: + summary: Token retrieved + value: + token: "abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" + message: "CSRF token initialized successfully" + + post: + tags: + - Security + summary: Regenerate CSRF token + security: [] # No authentication required + description: | + Explicitly regenerates the CSRF token, replacing any existing one. + Useful after privilege escalation or when extra rotation is desired. + + **Authentication:** None required + + **Rate Limiting:** Configured via AUTH_RATE_LIMIT_REQUESTS / AUTH_RATE_LIMIT_WINDOW + + operationId: regenerateCsrfToken + + responses: + "200": + description: New CSRF token generated + headers: + Set-Cookie: + schema: + type: string + description: Updated CSRF token cookie + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The new CSRF token value + message: + type: string + description: Success message + examples: + success: + summary: Token regenerated + value: + token: "new123abc456def789..." + message: "CSRF token refreshed successfully" + "429": + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + "500": + description: Unable to determine client IP or internal error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/security/attestation: + get: + tags: + - Security + - Mobile + summary: Get decoded attestation details + security: [] + description: | + Returns the decoded security attestation details for the current request's Firebase App Check token. + Used by the mobile app for troubleshooting, transparency, and verifying app integrity. + operationId: getAttestationDetails + responses: + "200": + description: Attestation details retrieved successfully + content: + application/json: + schema: + type: object + properties: + verified: + type: boolean + description: Whether the App Check token is valid + criticalRisk: + type: boolean + description: Whether a critical integrity risk was detected + appCheck: + type: boolean + description: Alias for verified + appCheckCriticalRisk: + type: boolean + description: Alias for criticalRisk + appCheckError: + type: string + nullable: true + description: Error message if verification failed + appId: + type: string + nullable: true + description: The Firebase App ID associated with the token + details: + type: object + description: Non-sensitive claims and metadata extracted from the token + enforced: + type: boolean + description: Whether App Check verification is currently enforced on the server + reason: + type: string + description: User-friendly explanation of the status + action: + type: string + description: Recommended action if verification failed + latestVersion: + type: string + description: Latest available version of the app + minVersion: + type: string + description: Minimum supported version of the app + type: + type: string + example: "security" + timestamp: + type: string + format: date-time + + /api/logout: + post: + tags: + - Authentication + summary: Logout and clear session + security: + - SupabaseAuth: [] + description: | + Clears authentication cookies and ends the user session. + + **What it does:** + - Removes authentication token cookie + - Removes CSRF token cookie + - Invalidates the session + + **Authentication:** Session cookie (automatically cleared) + + **Rate Limiting:** None + + **Use Cases:** + - User logout + - Session termination + + operationId: logout + + responses: + "200": + description: Successfully logged out + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: true + examples: + success: + summary: Logout successful + value: + ok: true + + /api/cron/sync: + get: + tags: + - Sync + summary: Sync attendance data with EzyGo + description: | + Synchronizes attendance records from EzyGo with the local database. Supports both automated cron jobs and authenticated user-initiated syncs. + + **What it does:** + - Fetches latest attendance data from EzyGo API + - Compares with local tracking records + - Detects and resolves conflicts (course mismatches, attendance discrepancies) + - Sends email notifications for conflicts + - Updates sync timestamps + + **Authentication:** + - **Option 1 (Cron)**: Bearer token with CRON_SECRET in Authorization header + - **Option 2 (Users)**: Valid session cookie (authenticated users) + + **Rate Limiting:** Configured via RATE_LIMIT_REQUESTS and RATE_LIMIT_WINDOW environment variables + + **Side Effects:** + - Deletes verified/matched attendance records + - Updates conflicted records with 'correction' status + - Creates notifications in the database + - Sends email alerts for attendance conflicts + - Updates `last_synced_at` timestamp for users + + **Use Cases:** + - Automated daily/hourly sync via cron job + - Manual sync triggered by users from dashboard + - Batch sync for multiple users (cron only) + - Single user sync with optional username filter + + operationId: syncAttendance + + security: + - BearerAuth: [] + - SupabaseAuth: [] + + parameters: + - name: username + in: query + required: false + description: | + Filter sync to a specific user by username. Only applicable when using Bearer token authentication (cron mode). + + - If provided with cron auth: syncs only the specified user + - If omitted with cron auth: syncs up to 10 least-recently-synced users + - Ignored for authenticated user requests (always syncs the current user) + schema: + type: string + example: "student123" + + responses: + "200": + description: Sync completed successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: Whether sync operation completed successfully + example: true + processed: + type: integer + description: Number of users processed + example: 5 + deletions: + type: integer + description: Number of tracking records deleted (verified/matched) + example: 23 + conflicts: + type: integer + description: Number of attendance conflicts detected + example: 2 + updates: + type: integer + description: Number of records updated to 'correction' status + example: 2 + errors: + type: integer + description: Number of users that failed to sync + example: 0 + examples: + successfulSync: + summary: Successful sync with multiple users + value: + success: true + processed: 5 + deletions: 23 + conflicts: 2 + updates: 2 + errors: 0 + noUsers: + summary: No eligible users to sync + value: + success: true + processed: 0 + deletions: 0 + conflicts: 0 + updates: 0 + errors: 0 + + "401": + description: Unauthorized - Invalid or missing authentication + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unauthorized: + summary: Not authenticated + value: + message: "Unauthorized" + + "429": + description: Rate limit exceeded + headers: + X-RateLimit-Reset: + schema: + type: integer + description: Unix timestamp when the rate limit resets + example: 1706108460 + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + examples: + tooManyRequests: + summary: Rate limit exceeded + value: + error: "Too many requests" + retryAfter: 60 + + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + serverError: + summary: Configuration error + value: + error: "Server configuration error: NEXT_PUBLIC_APP_URL is not set" + + /api/analytics/track: + post: + tags: + - Analytics + summary: Track a server-side analytics event + security: [] + description: | + Proxies GA4 Measurement Protocol events through the server so ad blockers + do not suppress them. Validates event shapes, sanitises values to GA4 limits, + and enforces origin and rate-limit guards before forwarding to GA4. + + **Authentication:** None required + + **Rate Limiting:** Configured via RATE_LIMIT_REQUESTS / RATE_LIMIT_WINDOW + + operationId: trackAnalyticsEvent + + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - clientId + - events + properties: + clientId: + type: string + maxLength: 100 + description: GA4 client ID in `timestamp.randomhex` format + example: "1706108400.a1b2c3d4" + events: + type: array + maxItems: 25 + description: Up to 25 GA4 events per request + items: + type: object + required: + - name + properties: + name: + type: string + maxLength: 40 + description: Event name (lowercase, underscores, digits only) + params: + type: object + description: Arbitrary GA4 event parameters + additionalProperties: + oneOf: + - type: string + - type: number + - type: boolean + userProperties: + type: object + description: Optional GA4 user properties (key โ†’ string value) + additionalProperties: + type: string + + responses: + "200": + description: Events forwarded to GA4 + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + "400": + description: Validation error (bad clientId, malformed events, etc.) + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden โ€“ request origin not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "429": + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/attendance/summary-batch: + post: + summary: Batch fetch attendance summaries + description: | + Retrieves attendance summaries for multiple courses in a single request. + Optimized for dashboard card views to avoid N+1 network requests. + + Requires Firebase App Check. + tags: + - Attendance + security: + - SupabaseAuth: [] + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - courses + properties: + courses: + type: array + items: + type: object + required: + - id + - code + properties: + id: + type: integer + code: + type: string + name: + type: string + responses: + "200": + description: Batch summary data + content: + application/json: + schema: + type: object + additionalProperties: + $ref: "#/components/schemas/CourseDetail" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "429": + description: Too many requests + content: + application/json: + $ref: "#/components/schemas/RateLimitError" + + /api/courses/add: + post: + tags: + - Mobile + summary: Add a new course to class lineup + description: | + Adds a new course to the user's class lineup. + Primarily used by the mobile app to sync or manually add a course. + operationId: addCourse + security: + - SupabaseAuth: [] + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - courseCode + - courseName + properties: + courseCode: + type: string + description: The code of the course (e.g. CS101) + example: "CS101" + courseName: + type: string + description: The name of the course + example: "Introduction to Computer Science" + responses: + "201": + description: Course added successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Course added successfully" + "400": + description: Bad request - Missing required fields or class association + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "409": + description: Conflict - Course already exists in the class lineup + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + description: Validation failed - Invalid field formats + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/instructors/upsert: + post: + tags: + - Mobile + summary: Save or update course instructor + description: | + Saves or updates instructor mapping for a specific course, semester, and academic year in the user's class. + operationId: upsertInstructor + security: + - SupabaseAuth: [] + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - courseCode + - instructorName + properties: + courseCode: + type: string + example: "CS101" + instructorName: + type: string + example: "Dr. John Doe" + responses: + "200": + description: Instructor saved successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Instructor saved successfully" + "400": + description: Bad request - Missing required fields or class association + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + description: Validation failed - Invalid field formats + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/scores/batch: + post: + tags: + - Proxy + - Mobile + summary: Batch fetch exam questions and answers + description: | + Fetches exam questions and graded answers for multiple exams in parallel. + Optimized for mobile dashboard to replace multiple individual API calls. + operationId: batchFetchScores + security: + - SupabaseAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - examIds + properties: + examIds: + type: array + maxItems: 25 + description: Array of exam IDs to fetch details for + items: + type: integer + example: [123, 456] + responses: + "200": + description: Batch exam details retrieved successfully + content: + application/json: + schema: + type: object + additionalProperties: + type: object + properties: + questions: + type: array + items: + $ref: "#/components/schemas/ExamQuestion" + answers: + type: array + items: + $ref: "#/components/schemas/ExamAnswer" + error: + type: string + nullable: true + "400": + description: Bad request - Missing IP, invalid format, or invalid JSON + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized - Invalid session or missing EzyGo token + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "429": + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/csp-report: + post: + tags: + - Reporting + summary: Receive CSP violation reports + security: [] + description: | + Browser endpoint for Content-Security-Policy violation reports. + Accepts both legacy `application/csp-report` (report-uri directive) and + modern `application/reports+json` (Reporting API v1) payloads. + Always returns **204 No Content** so the browser does not retry. + Body is capped at **8 KB** to prevent abuse on this unauthenticated endpoint. + + **Authentication:** None required (reports are sent automatically by browsers) + + operationId: receiveCspReport + + requestBody: + required: true + content: + application/csp-report: + schema: + type: object + description: "Legacy CSP report wrapped in a csp-report envelope object" + application/reports+json: + schema: + type: array + description: Reporting API v1 array of report objects + items: + type: object + + responses: + "204": + description: Report received and logged (browser should not retry) + "400": + description: Invalid Content-Length header value + "413": + description: Payload too large (exceeds 8 KB limit) + "415": + description: Unsupported content type + + /api/health: + get: + tags: + - Health + summary: Health check endpoint + security: [] # No authentication required for health checks + description: | + Returns the health status of the API service. Used for monitoring and uptime checks. + + **What it does:** + - Confirms the API is responsive + - Returns current version information + - Provides timestamp for monitoring + + **Authentication:** None required + + **Rate Limiting:** None (health checks should always be available) + + **Use Cases:** + - Monitoring tools and uptime checkers + - Load balancer health checks + - Deployment verification + + operationId: healthCheck + + responses: + "200": + description: Service is healthy and operational + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [ok] + description: Health status of the service + example: "ok" + version: + type: string + description: Current version of the application + example: "1.0.0" + timestamp: + type: string + format: date-time + description: Current server timestamp in ISO 8601 format + example: "2026-01-24T20:52:27.674Z" + examples: + healthy: + summary: Service is healthy + value: + status: "ok" + version: "1.0.0" + timestamp: "2026-01-24T20:52:27.674Z" + + /api/health/ezygo: + get: + tags: + - Health + summary: EzyGo integration health check + security: [] + description: | + Returns health status of the EzyGo API integration including circuit breaker + and rate-limiter state. In **development/test** environments the response + includes detailed metrics (active requests, queue length, CB state). + In **production** only `status` and `timestamp` are returned. + + Returns HTTP **503** when the circuit breaker is open. + + operationId: healthCheckEzygo + + responses: + "200": + description: EzyGo integration healthy or degraded + headers: + Cache-Control: + schema: + type: string + example: "no-store, max-age=0" + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [healthy, degraded, unhealthy] + timestamp: + type: string + format: date-time + examples: + healthy: + summary: Circuit closed, no queue backlog + value: + status: healthy + timestamp: "2026-02-01T12:00:00.000Z" + degraded: + summary: Requests queuing up + value: + status: degraded + timestamp: "2026-02-01T12:00:00.000Z" + "503": + description: Circuit breaker open โ€“ upstream EzyGo unreachable + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [unhealthy] + timestamp: + type: string + format: date-time + + /api/profile: + get: + tags: + - Profile + summary: Fetch user profile + security: + - SupabaseAuth: [] + description: | + Returns the current user's profile data from the local database, soft-synced + with EzyGo. PII fields (`phone`, `gender`, `birth_date`) are AES-256-GCM + encrypted at rest; the server decrypts them before responding. + + **Full Bundle:** This endpoint returns an "uber-bundle" containing: + - Basic profile info + - Academic context (current_semester, current_year) + - User settings (bunk calculator, target percentage, disabled courses) + - Terms compliance status + - Auth bridge token (decrypted ezygo_token) + + **Origin validation:** In production, requests must include a matching `Origin` + header (or `Sec-Fetch-Site: same-origin`). Cross-site requests are rejected + with 400/403 as defence-in-depth against CSRF on this read path. + + operationId: getProfile + + responses: + "200": + description: Profile returned successfully + headers: + Cache-Control: + schema: + type: string + example: "no-store, max-age=0" + content: + application/json: + schema: + $ref: "#/components/schemas/UserProfile" + "400": + description: Origin header missing (and Sec-Fetch-Site is not same-origin) + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Origin not allowed (cross-site request rejected in production) + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + patch: + tags: + - Profile + summary: Update user profile + security: + - SupabaseAuth: [] + description: | + Updates user-editable profile fields. PII values (`gender`, `birth_date`) are + AES-256-GCM encrypted before being written to the database. + + **CSRF:** Requires the `x-csrf-token` request header. + + **Validation:** `first_name` is required (min 2 chars). All other fields + are optional; omitting an optional field does **not** clear it. + + operationId: updateProfile + + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - first_name + properties: + first_name: + type: string + minLength: 2 + example: "Aditya" + last_name: + type: string + nullable: true + example: "Kumar" + gender: + type: string + enum: [male, female, other] + nullable: true + birth_date: + type: string + format: date + nullable: true + example: "2003-08-15" + + responses: + "200": + description: Profile updated; returns saved plaintext values + content: + application/json: + schema: + type: object + properties: + first_name: + type: string + last_name: + type: string + nullable: true + gender: + type: string + nullable: true + birth_date: + type: string + nullable: true + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Invalid CSRF token + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + description: Validation failed + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Validation failed" + details: + type: object + "500": + description: Database update failed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/provenance: + get: + tags: + - Provenance + summary: Build provenance and version metadata + security: [] + description: | + Returns build provenance metadata: commit SHA, app version, image digest, + audit and signature status, and build timestamp. + + Extended CI metadata (`github_run_id`, `github_run_number`, `github_repo`) + is included only when the request presents + `Accept: application/vnd.ghostclass.provenance+json`. + + Responses are **never cached** (`Cache-Control: no-store`). + + operationId: getProvenance + + parameters: + - name: Accept + in: header + required: false + description: | + Pass `application/vnd.ghostclass.provenance+json` to receive extended + CI/build metadata in addition to the base payload. + schema: + type: string + + responses: + "200": + description: Provenance metadata + headers: + Cache-Control: + schema: + type: string + example: "no-store, max-age=0" + content: + application/json: + schema: + type: object + properties: + commit: + type: string + description: Commit SHA (legacy alias for commit_sha) + commit_sha: + type: string + build_id: + type: string + app_version: + type: string + image_digest: + type: string + container: + type: boolean + timestamp: + type: string + format: date-time + audit_status: + type: string + signature_status: + type: string + examples: + base: + summary: Standard production payload + value: + commit: "a1b2c3d" + commit_sha: "a1b2c3d" + build_id: "123456789" + app_version: "1.9.5" + image_digest: "sha256:abc123..." + container: true + timestamp: "2026-02-01T10:00:00.000Z" + audit_status: "PASSED" + signature_status: "SIGNED" + + /api/openapi: + get: + tags: + - Documentation + summary: Get resolved OpenAPI YAML + security: [] + description: | + Returns the OpenAPI YAML document with `${NEXT_PUBLIC_*}` template tokens + resolved at request time. + operationId: getOpenApiSpec + responses: + "200": + description: OpenAPI YAML document + content: + application/yaml: + schema: + type: string + + /api/auth/sync: + post: + tags: + - Authentication + - Mobile + summary: Self-heal authentication state + description: | + Attempts to refresh Supabase session cookies and restore EzyGo auth token + from server-side storage after client-side auth/session drift. + operationId: syncAuth + responses: + "200": + description: Sync attempt completed + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + message: + type: string + terminal: + type: boolean + "401": + description: Session expired and cannot be recovered + headers: + x-terminal-auth: + schema: + type: string + description: Present and set to `true` when re-authentication is required + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/auth/register-fcm: + post: + tags: + - Authentication + - Mobile + summary: Register mobile FCM push token + description: | + Registers or updates the Firebase Cloud Messaging (FCM) push token for the authenticated user. + Enables sending push notifications to the user's mobile device. + operationId: registerFcmToken + security: + - SupabaseAuth: [] + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - fcm_token + properties: + fcm_token: + type: string + description: The Firebase Cloud Messaging registration token + example: "bk3RNwXXeDY:AP91bH..." + responses: + "200": + description: FCM token registered successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + "400": + description: Bad request - Missing IP or invalid JSON + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized - Invalid session or bearer token + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + description: Validation failed - Empty token + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "429": + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitError" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/contact: + post: + tags: + - Mobile + summary: Submit contact/support message + description: | + Submits a contact message through the shared contact service. + Supports both authenticated and guest submissions. + operationId: submitContact + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: Message accepted + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + id: + type: string + "400": + description: Validation or payload error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Contact processing failed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/user/accept-terms: + post: + tags: + - Mobile + summary: Accept terms for mobile user + description: | + Updates `terms_version` and `terms_accepted_at` for authenticated mobile users. + operationId: acceptTermsMobile + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - version + properties: + version: + type: string + responses: + "200": + description: Terms acceptance stored + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + version: + type: string + "400": + description: Missing version + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Failed to update terms acceptance + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /api/backend/{path}: + description: | + Proxies requests to EzyGo backend endpoints. + + **Rate Limiting:** Configured via `PROXY_RATE_LIMIT_REQUESTS` and `PROXY_RATE_LIMIT_WINDOW`. + parameters: + - name: path + in: path + required: true + description: Forwarded EzyGo backend path segment(s) + schema: + type: string + get: + tags: + - Proxy + summary: Proxy GET request to EzyGo backend + operationId: proxyGet + responses: + "200": + description: Proxied response + content: + application/json: + schema: + type: object + additionalProperties: true + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + post: + tags: + - Proxy + summary: Proxy POST request to EzyGo backend + operationId: proxyPost + requestBody: + required: false + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: Proxied response + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + put: + tags: + - Proxy + summary: Proxy PUT request to EzyGo backend + operationId: proxyPut + requestBody: + required: false + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: Proxied response + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + patch: + tags: + - Proxy + summary: Proxy PATCH request to EzyGo backend + operationId: proxyPatch + requestBody: + required: false + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: Proxied response + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + delete: + tags: + - Proxy + summary: Proxy DELETE request to EzyGo backend + operationId: proxyDelete + responses: + "200": + description: Proxied response + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + head: + tags: + - Proxy + summary: Proxy HEAD request to EzyGo backend + operationId: proxyHead + responses: + "200": + description: Proxied response + "4XX": + description: Client error from proxy or upstream + "5XX": + description: Upstream/proxy failure + +components: + schemas: + CourseDetail: + type: object + properties: + present: + type: integer + absent: + type: integer + total: + type: integer + percentage: + type: number + course: + type: object + properties: + id: + oneOf: + - type: integer + - type: string + name: + type: string + code: + type: string + + UserProfile: + type: object + description: Decrypted user profile data (PII fields returned as plaintext) + properties: + id: + oneOf: + - type: string + - type: integer + description: EzyGo user ID + username: + type: string + example: "student123" + email: + type: string + format: email + first_name: + type: string + nullable: true + last_name: + type: string + nullable: true + phone: + type: string + nullable: true + description: Decrypted phone number (AES-256-GCM at rest) + gender: + type: string + nullable: true + description: Decrypted gender (AES-256-GCM at rest) + birth_date: + type: string + nullable: true + description: Decrypted birth date in YYYY-MM-DD format (AES-256-GCM at rest) + avatar_url: + type: string + nullable: true + terms_version: + type: string + nullable: true + terms_accepted_at: + type: string + format: date-time + nullable: true + current_semester: + type: string + nullable: true + current_year: + type: string + nullable: true + ezygo_token: + type: string + nullable: true + settings: + type: object + properties: + bunk_calculator_enabled: + type: boolean + target_percentage: + type: integer + disabled_courses: + type: object + additionalProperties: + type: object + additionalProperties: + type: string + + Error: + type: object + properties: + error: + type: string + description: Error message describing what went wrong + message: + type: string + description: Detailed error message + example: + error: "Internal Server Error" + + ValidationError: + type: object + properties: + message: + type: string + description: Validation error message + errors: + type: array + description: List of validation errors (if multiple fields failed) + items: + type: object + properties: + field: + type: string + description: Field that failed validation + message: + type: string + description: Validation error message for this field + example: + message: "Missing credentials" + + RateLimitError: + type: object + properties: + error: + type: string + description: Rate limit error message + example: "Too many requests. Slow down!" + retryAfter: + type: integer + description: Unix timestamp (in seconds) when the rate limit resets. Matches the `X-RateLimit-Reset` header. + example: 1735689600 + required: + - error + - retryAfter + + ExamQuestion: + type: object + properties: + id: + type: integer + question_no: + type: string + name: + type: string + maximum_mark: + type: string + module_id: + type: integer + exam_id: + type: integer + institution_id: + type: integer + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + ExamAnswer: + type: object + properties: + id: + type: integer + answer: + type: string + nullable: true + score: + type: string + nullable: true + choice_id: + type: integer + nullable: true + examquestion_id: + type: integer + student_id: + type: integer + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: | + Bearer token authentication using CRON_SECRET environment variable. + Used for automated cron jobs. + + Example: `Authorization: Bearer ` + + SupabaseAuth: + type: apiKey + in: cookie + name: sb-* + description: | + Session cookie authentication managed by Supabase Auth. + Automatically included when users are logged in through the web interface. + Cookie names typically start with 'sb-' prefix. + +externalDocs: + description: GitHub Repository + url: ${NEXT_PUBLIC_GITHUB_URL} diff --git a/scripts/build-sw.js b/scripts/build-sw.js index ac3b8e9d..62037d29 100644 --- a/scripts/build-sw.js +++ b/scripts/build-sw.js @@ -1,52 +1,53 @@ -#!/usr/bin/env node -/** - * Build service worker for production - * This script ensures the service worker is generated even in standalone mode - */ - -const { build } = require('esbuild'); -const fs = require('fs'); -const path = require('path'); - -async function buildServiceWorker() { - console.log('๐Ÿ”จ Building service worker...'); - - const swSrc = path.join(__dirname, '../src/sw.ts'); - const swDest = path.join(__dirname, '../public/sw.js'); - - // Check if source exists - if (!fs.existsSync(swSrc)) { - console.error(`โŒ Service worker source not found at ${swSrc}`); - process.exit(1); - } - - try { - // Build the TypeScript service worker to JavaScript - await build({ - entryPoints: [swSrc], - bundle: true, - outfile: swDest, - format: 'iife', - target: 'es2020', - minify: process.env.NODE_ENV === 'production', - define: { - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), - }, - banner: { - js: '// Service Worker - Generated by build-sw.js', - }, - }); - - console.log(`โœ… Service worker built successfully at ${swDest}`); - - // Verify the file exists and has content - const stats = fs.statSync(swDest); - console.log(` Size: ${(stats.size / 1024).toFixed(2)} KB`); - - } catch (error) { - console.error('โŒ Failed to build service worker:', error); - process.exit(1); - } -} - -buildServiceWorker(); +#!/usr/bin/env node +/** + * Build service worker for production + * This script ensures the service worker is generated even in standalone mode + */ + +const { build } = require("esbuild"); +const fs = require("node:fs"); +const path = require("node:path"); + +async function buildServiceWorker() { + console.log("๐Ÿ”จ Building service worker..."); + + const swSrc = path.join(__dirname, "../src/sw.ts"); + const swDest = path.join(__dirname, "../public/sw.js"); + + // Check if source exists + if (!fs.existsSync(swSrc)) { + console.error(`โŒ Service worker source not found at ${swSrc}`); + process.exit(1); + } + + try { + // Build the TypeScript service worker to JavaScript + await build({ + entryPoints: [swSrc], + bundle: true, + outfile: swDest, + format: "iife", + target: "es2020", + minify: process.env.NODE_ENV === "production", + define: { + "process.env.NODE_ENV": JSON.stringify( + process.env.NODE_ENV || "production", + ), + }, + banner: { + js: "// Service Worker - Generated by build-sw.js", + }, + }); + + console.log(`โœ… Service worker built successfully at ${swDest}`); + + // Verify the file exists and has content + const stats = fs.statSync(swDest); + console.log(` Size: ${(stats.size / 1024).toFixed(2)} KB`); + } catch (error) { + console.error("โŒ Failed to build service worker:", error); + process.exit(1); + } +} + +buildServiceWorker(); diff --git a/scripts/check-coverage.js b/scripts/check-coverage.js index add96d3e..c86e74ec 100644 --- a/scripts/check-coverage.js +++ b/scripts/check-coverage.js @@ -1,38 +1,45 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require("node:fs"); +const path = require("node:path"); -const coverageFile = path.join(process.cwd(), 'coverage', 'coverage-final.json'); +const coverageFile = path.join( + process.cwd(), + "coverage", + "coverage-final.json", +); if (!fs.existsSync(coverageFile)) { - console.error('Coverage file not found'); + console.error("Coverage file not found"); process.exit(1); } -const coverage = JSON.parse(fs.readFileSync(coverageFile, 'utf8')); +const coverage = JSON.parse(fs.readFileSync(coverageFile, "utf8")); const lowCoverageFiles = []; const allFiles = []; for (const [file, data] of Object.entries(coverage)) { const s = data.s; const sTotal = Object.keys(s).length; - const sCovered = Object.values(s).filter(v => v > 0).length; + const sCovered = Object.values(s).filter((v) => v > 0).length; const sPct = sTotal === 0 ? 100 : (sCovered / sTotal) * 100; const f = data.f; const fTotal = Object.keys(f).length; - const fCovered = Object.values(f).filter(v => v > 0).length; + const fCovered = Object.values(f).filter((v) => v > 0).length; const fPct = fTotal === 0 ? 100 : (fCovered / fTotal) * 100; const b = data.b; const bTotal = Object.values(b).reduce((acc, curr) => acc + curr.length, 0); - const bCovered = Object.values(b).reduce((acc, curr) => acc + curr.filter(v => v > 0).length, 0); + const bCovered = Object.values(b).reduce( + (acc, curr) => acc + curr.filter((v) => v > 0).length, + 0, + ); const bPct = bTotal === 0 ? 100 : (bCovered / bTotal) * 100; - const percentage = sPct; + const percentage = sPct; if (percentage < 50) { lowCoverageFiles.push({ file, percentage: percentage.toFixed(2) }); } - + allFiles.push({ file, sPct, fPct, bPct }); } @@ -41,13 +48,17 @@ allFiles.sort((a, b) => a.sPct - b.sPct); console.log(`Total files checked: ${allFiles.length}`); if (lowCoverageFiles.length > 0) { - console.log('Files with < 50% statement coverage:'); - lowCoverageFiles.forEach(f => console.log(`${f.file}: ${f.percentage}%`)); + console.log("Files with < 50% statement coverage:"); + lowCoverageFiles.forEach((f) => console.log(`${f.file}: ${f.percentage}%`)); } else { - console.log('All files have >= 50% statement coverage!'); + console.log("All files have >= 50% statement coverage!"); } -console.log('\nBottom 10 files by statement coverage:'); -allFiles.slice(0, 10).forEach(f => { - console.log(`${f.file.replace(process.cwd(), '')}: S:${f.sPct.toFixed(2)}% F:${f.fPct.toFixed(2)}% B:${f.bPct.toFixed(2)}%`); +console.log("\nBottom 10 files by statement coverage:"); +allFiles.slice(0, 10).forEach((f) => { + console.log( + `${f.file.replace(process.cwd(), "")}: S:${f.sPct.toFixed(2)}% F:${ + f.fPct.toFixed(2) + }% B:${f.bPct.toFixed(2)}%`, + ); }); diff --git a/scripts/fetch-build-time-vars.js b/scripts/fetch-build-time-vars.js index 9163409c..c8a4b2f0 100644 --- a/scripts/fetch-build-time-vars.js +++ b/scripts/fetch-build-time-vars.js @@ -1,21 +1,27 @@ #!/usr/bin/env node -const fs = require('fs'); +const fs = require("node:fs"); +const { generateFirebaseJson } = require("./generate-firebase-json.js"); /** * Authenticates with Infisical using Universal Auth. */ async function authenticate(apiBaseUrl, clientId, clientSecret) { console.log(`๐Ÿ”‘ Authenticating with Infisical (${apiBaseUrl})...`); - const loginRes = await fetch(`${apiBaseUrl}/api/v1/auth/universal-auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ clientId, clientSecret }) - }); + const loginRes = await fetch( + `${apiBaseUrl}/api/v1/auth/universal-auth/login`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ clientId, clientSecret }), + }, + ); if (!loginRes.ok) { const errText = await loginRes.text(); - console.error(`โŒ Authentication failed: ${loginRes.status} ${loginRes.statusText}\n${errText}`); + console.error( + `โŒ Authentication failed: ${loginRes.status} ${loginRes.statusText}\n${errText}`, + ); process.exit(1); } @@ -28,23 +34,32 @@ async function authenticate(apiBaseUrl, clientId, clientSecret) { * Resolves project slug or ID to a verified UUID. */ async function resolveProjectId(apiBaseUrl, accessToken, projectSlugOrId) { - const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(projectSlugOrId); + const isUuid = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( + projectSlugOrId, + ); if (!isUuid) { console.log(`๐Ÿ” Resolving project slug "${projectSlugOrId}" to ID...`); - const projectRes = await fetch(`${apiBaseUrl}/api/v1/projects/slug/${projectSlugOrId}`, { - headers: { 'Authorization': `Bearer ${accessToken}` } - }); + const projectRes = await fetch( + `${apiBaseUrl}/api/v1/projects/slug/${projectSlugOrId}`, + { + headers: { "Authorization": `Bearer ${accessToken}` }, + }, + ); if (projectRes.ok) { const projectData = await projectRes.json(); - const projectObj = projectData.project || projectData.workspace || projectData; + const projectObj = projectData.project || projectData.workspace || + projectData; const resolvedId = projectObj.id || projectObj._id || projectSlugOrId; console.log(`โœ“ Resolved project slug to ID: ${resolvedId}`); return resolvedId; } else { const errText = await projectRes.text(); - console.log(`โš  Failed to resolve slug via API, falling back to slug value: ${errText}`); + console.log( + `โš  Failed to resolve slug via API, falling back to slug value: ${errText}`, + ); return projectSlugOrId; } } @@ -56,23 +71,37 @@ async function resolveProjectId(apiBaseUrl, accessToken, projectSlugOrId) { /** * Fetches the secret list from the targeted Infisical environment and path. */ -async function fetchSecrets(apiBaseUrl, accessToken, projectId, envSlug, secretPath) { - console.log(`๐Ÿ“ฅ Fetching variables from path "${secretPath}" [env: ${envSlug}]...`); - const secretsUrl = `${apiBaseUrl}/api/v4/secrets?projectId=${encodeURIComponent(projectId)}&environment=${encodeURIComponent(envSlug)}&secretPath=${encodeURIComponent(secretPath)}&viewSecretValue=true`; - +async function fetchSecrets( + apiBaseUrl, + accessToken, + projectId, + envSlug, + secretPath, +) { + console.log( + `๐Ÿ“ฅ Fetching variables from path "${secretPath}" [env: ${envSlug}]...`, + ); + const secretsUrl = `${apiBaseUrl}/api/v4/secrets?projectId=${ + encodeURIComponent(projectId) + }&environment=${encodeURIComponent(envSlug)}&secretPath=${ + encodeURIComponent(secretPath) + }&viewSecretValue=true`; + const secretsRes = await fetch(secretsUrl, { - headers: { 'Authorization': `Bearer ${accessToken}` } + headers: { "Authorization": `Bearer ${accessToken}` }, }); if (!secretsRes.ok) { const errText = await secretsRes.text(); - console.error(`โŒ Failed to fetch secrets: ${secretsRes.status} ${secretsRes.statusText}\n${errText}`); + console.error( + `โŒ Failed to fetch secrets: ${secretsRes.status} ${secretsRes.statusText}\n${errText}`, + ); process.exit(1); } const { secrets } = await secretsRes.json(); if (!secrets || !Array.isArray(secrets)) { - console.error('โŒ Invalid secrets response format.'); + console.error("โŒ Invalid secrets response format."); process.exit(1); } @@ -85,36 +114,39 @@ async function fetchSecrets(apiBaseUrl, accessToken, projectId, envSlug, secretP */ function exportSecrets(secrets) { const githubEnvFile = process.env.GITHUB_ENV; - + // Define exactly which keys should be masked in GitHub logs const keysToMask = [ - 'NEXT_PUBLIC_BACKEND_URL', - 'NEXT_PUBLIC_SUPABASE_URL', - 'NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY', - 'NEXT_PUBLIC_SUPABASE_CF_PROXY_URL', - 'NEXT_PUBLIC_SUPABASE_AWS_PROXY_URL', - 'NEXT_PUBLIC_SENTRY_DSN', - 'NEXT_PUBLIC_TURNSTILE_SITE_KEY', - 'NEXT_PUBLIC_GA_ID' + "NEXT_PUBLIC_BACKEND_URL", + "NEXT_PUBLIC_SUPABASE_URL", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY", + "NEXT_PUBLIC_SUPABASE_CF_PROXY_URL", + "NEXT_PUBLIC_SUPABASE_AWS_PROXY_URL", + "NEXT_PUBLIC_SENTRY_DSN", + "NEXT_PUBLIC_TURNSTILE_SITE_KEY", + "NEXT_PUBLIC_GA_ID", ]; // Explicitly omit dev URLs/keys from masking const keysToOmitFromMasking = [ - 'NEXT_PUBLIC_SUPABASE_DEV_URL', - 'NEXT_PUBLIC_SUPABASE_DEV_PUBLISHABLE_KEY' + "NEXT_PUBLIC_SUPABASE_DEV_URL", + "NEXT_PUBLIC_SUPABASE_DEV_PUBLISHABLE_KEY", ]; if (githubEnvFile) { console.log(`๐Ÿ“ Exporting variables to GITHUB_ENV...`); for (const secret of secrets) { if ( - keysToMask.includes(secret.secretKey) && - !keysToOmitFromMasking.includes(secret.secretKey) && + keysToMask.includes(secret.secretKey) && + !keysToOmitFromMasking.includes(secret.secretKey) && secret.secretValue ) { console.log(`::add-mask::${secret.secretValue}`); } - fs.appendFileSync(githubEnvFile, `${secret.secretKey}=${secret.secretValue}\n`); + fs.appendFileSync( + githubEnvFile, + `${secret.secretKey}=${secret.secretValue}\n`, + ); console.log(` + ${secret.secretKey}`); } console.log(`๐ŸŽ‰ Variables exported successfully!`); @@ -122,7 +154,7 @@ function exportSecrets(secrets) { console.log(`โ„น๏ธ GITHUB_ENV is not set. Values loaded:`); for (const secret of secrets) { if ( - keysToMask.includes(secret.secretKey) && + keysToMask.includes(secret.secretKey) && !keysToOmitFromMasking.includes(secret.secretKey) ) { console.log(` ${secret.secretKey}=[MASKED]`); @@ -139,23 +171,38 @@ function exportSecrets(secrets) { async function main() { const clientId = process.env.INFISICAL_CLIENT_ID; const clientSecret = process.env.INFISICAL_CLIENT_SECRET; - const projectSlugOrId = process.env.INFISICAL_PROJECT_SLUG || process.env.INFISICAL_PROJECT_ID; - const envSlug = process.env.INFISICAL_ENV_SLUG || 'prod'; - const secretPath = process.env.INFISICAL_SECRET_PATH || '/build-time'; - const apiBaseUrl = process.env.INFISICAL_API_URL || 'https://app.infisical.com'; + const projectSlugOrId = process.env.INFISICAL_PROJECT_SLUG || + process.env.INFISICAL_PROJECT_ID; + const envSlug = process.env.INFISICAL_ENV_SLUG || "prod"; + const secretPath = process.env.INFISICAL_SECRET_PATH || "/build-time"; + const apiBaseUrl = process.env.INFISICAL_API_URL || + "https://app.infisical.com"; if (!clientId || !clientSecret || !projectSlugOrId) { - console.error('โŒ Missing required Infisical credentials (INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET) or project identifier.'); + console.error( + "โŒ Missing required Infisical credentials (INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET) or project identifier.", + ); process.exit(1); } const accessToken = await authenticate(apiBaseUrl, clientId, clientSecret); - const projectId = await resolveProjectId(apiBaseUrl, accessToken, projectSlugOrId); - const secrets = await fetchSecrets(apiBaseUrl, accessToken, projectId, envSlug, secretPath); + const projectId = await resolveProjectId( + apiBaseUrl, + accessToken, + projectSlugOrId, + ); + const secrets = await fetchSecrets( + apiBaseUrl, + accessToken, + projectId, + envSlug, + secretPath, + ); exportSecrets(secrets); + generateFirebaseJson(secrets); } -main().catch(err => { - console.error('โŒ Script failed:', err); +main().catch((err) => { + console.error("โŒ Script failed:", err); process.exit(1); }); diff --git a/scripts/generate-blur.js b/scripts/generate-blur.js index b92e0f23..8d5c7a80 100644 --- a/scripts/generate-blur.js +++ b/scripts/generate-blur.js @@ -1,20 +1,23 @@ -const sharp = require('sharp'); -const path = require('path'); +const sharp = require("sharp"); +const path = require("node:path"); async function generateBlurPlaceholder() { - const inputPath = path.join(__dirname, '../public/logo.png'); + const inputPath = path.join(__dirname, "../public/logo.png"); // Resize on a transparent canvas, keep alpha, and blur lightly const buffer = await sharp(inputPath) - .resize(24, 24, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .resize(24, 24, { + fit: "contain", + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) .ensureAlpha() .blur(1) .png() .toBuffer(); - const base64 = buffer.toString('base64'); - console.log('\nโœ… Add this to your Image component:'); + const base64 = buffer.toString("base64"); + console.log("\nโœ… Add this to your Image component:"); console.log(`\nblurDataURL="data:image/png;base64,${base64}"\n`); } -generateBlurPlaceholder().catch(console.error); \ No newline at end of file +generateBlurPlaceholder().catch(console.error); diff --git a/scripts/generate-firebase-json.js b/scripts/generate-firebase-json.js new file mode 100644 index 00000000..01aba576 --- /dev/null +++ b/scripts/generate-firebase-json.js @@ -0,0 +1,255 @@ +#!/usr/bin/env node + +const fs = require("node:fs"); +const path = require("node:path"); + +/** +/** + * Helper to retrieve variable from secrets array or process.env. + * @param {string} key + * @param {Record} envMap + * @returns {string} + */ +function getVar(key, envMap) { + if (envMap && typeof envMap === "object") { + const val = Object.getOwnPropertyDescriptor(envMap, key); + if (val && val.value) return val.value; + } + const envVal = Object.getOwnPropertyDescriptor(process.env, key); + if (envVal && envVal.value) return envVal.value; + return ""; +} + +/** + * Dynamically generates mobile/lib/firebase_options.dart from Infisical secrets or process.env. + * @param {string} [targetFile] + * @returns {boolean} + */ +function generateFirebaseOptionsDart(secrets, targetFile) { + let targetPath = targetFile; + if (typeof secrets === "string" && !targetFile) { + targetPath = secrets; + } else if (!targetPath) { + targetPath = path.join( + process.cwd(), + "mobile", + "lib", + "firebase_options.dart", + ); + } + const dartContent = `// File generated dynamically during build workflow. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: String.fromEnvironment('FIREBASE_API_KEY_ANDROID'), + appId: String.fromEnvironment('FIREBASE_ANDROID_APP_ID'), + messagingSenderId: String.fromEnvironment('FIREBASE_MESSAGING_SENDER_ID'), + projectId: String.fromEnvironment('FIREBASE_PROJECT_ID'), + storageBucket: String.fromEnvironment('FIREBASE_STORAGE_BUCKET'), + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: String.fromEnvironment('FIREBASE_API_KEY_IOS'), + appId: String.fromEnvironment('FIREBASE_IOS_APP_ID'), + messagingSenderId: String.fromEnvironment('FIREBASE_MESSAGING_SENDER_ID'), + projectId: String.fromEnvironment('FIREBASE_PROJECT_ID'), + storageBucket: String.fromEnvironment('FIREBASE_STORAGE_BUCKET'), + iosBundleId: String.fromEnvironment('FIREBASE_IOS_BUNDLE_ID'), + ); +} +`; + + try { + const dir = path.dirname(targetPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(targetPath, dartContent); + console.log(`โœ“ Dynamically generated ${targetPath}`); + return true; + } catch (err) { + console.warn(`โš ๏ธ Could not write ${targetPath}:`, err.message); + return false; + } +} + +/** + * Helper to build Dart configurations object for flutter firebase.json. + */ +function buildDartConfigurations(projectId, appIds) { + const { + androidAppIdDefault, + iosAppIdDefault, + androidAppIdNexus, + iosAppIdNexus, + androidAppIdNexusMec, + iosAppIdNexusMec, + } = appIds; + const dartConfigurations = {}; + + // Standard lib/firebase_options.dart target + if (androidAppIdDefault || (!androidAppIdNexus && !androidAppIdNexusMec)) { + dartConfigurations["lib/firebase_options.dart"] = { + projectId: projectId, + configurations: { + android: androidAppIdDefault, + ios: iosAppIdDefault, + }, + }; + } + + // Flavor target: Nexus + if (androidAppIdNexus) { + dartConfigurations["lib/firebase_options_nexus.dart"] = { + projectId: projectId, + configurations: { + android: androidAppIdNexus, + ios: iosAppIdNexus || androidAppIdNexus, + }, + }; + } + + // Flavor target: Nexus MEC + if (androidAppIdNexusMec || (androidAppIdNexus && iosAppIdNexusMec)) { + dartConfigurations["lib/firebase_options_nexus_mec.dart"] = { + projectId: projectId, + configurations: { + android: androidAppIdNexusMec || androidAppIdNexus, + ios: iosAppIdNexusMec || iosAppIdNexus || androidAppIdNexus, + }, + }; + } + + return dartConfigurations; +} + +/** + * Dynamically generates mobile/firebase.json from Infisical secrets or process.env. + * @param {Array<{secretKey: string, secretValue: string}>} [secrets=[]] + * @param {string} [targetFile] + * @returns {boolean} + */ +function generateFirebaseJson(secrets = [], targetFile) { + const envMap = {}; + if (Array.isArray(secrets)) { + for (const s of secrets) { + if (s && s.secretKey) { + envMap[s.secretKey] = s.secretValue; + } + } + } + + const projectId = getVar("FIREBASE_PROJECT_ID", envMap) || + getVar("FIREBASE_PROJECT_ID_NEXUS", envMap) || + getVar("NEXT_PUBLIC_FIREBASE_PROJECT_ID", envMap); + + const androidAppIdNexus = getVar("FIREBASE_ANDROID_APP_ID_NEXUS", envMap); + const iosAppIdNexus = getVar("FIREBASE_IOS_APP_ID_NEXUS", envMap); + const androidAppIdNexusMec = getVar( + "FIREBASE_ANDROID_APP_ID_NEXUS_MEC", + envMap, + ); + const iosAppIdNexusMec = getVar("FIREBASE_IOS_APP_ID_NEXUS_MEC", envMap); + + const androidAppIdDefault = getVar("FIREBASE_ANDROID_APP_ID", envMap) || + getVar("FIREBASE_APP_ID_ANDROID", envMap) || + androidAppIdNexus; + const iosAppIdDefault = getVar("FIREBASE_IOS_APP_ID", envMap) || + getVar("FIREBASE_APP_ID_IOS", envMap) || + iosAppIdNexus || + androidAppIdDefault; + + // Check if minimum requirements exist + if (!projectId || (!androidAppIdDefault && !androidAppIdNexus)) { + console.log( + "โ„น๏ธ Skipping mobile/firebase.json generation (missing Firebase project/app IDs).", + ); + return false; + } + + const defaultAndroidAppId = androidAppIdNexus || androidAppIdDefault; + const dartConfigurations = buildDartConfigurations(projectId, { + androidAppIdDefault, + iosAppIdDefault, + androidAppIdNexus, + iosAppIdNexus, + androidAppIdNexusMec, + iosAppIdNexusMec, + }); + + const firebaseJson = { + flutter: { + platforms: { + android: { + default: { + projectId: projectId, + appId: defaultAndroidAppId, + fileOutput: "android/app/google-services.json", + }, + }, + dart: dartConfigurations, + }, + }, + }; + + const targetPath = targetFile || + path.join(process.cwd(), "mobile", "firebase.json"); + try { + const dir = path.dirname(targetPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(targetPath, JSON.stringify(firebaseJson, null, 2) + "\n"); + console.log(`โœ“ Dynamically generated ${targetPath}`); + + // Also generate firebase_options.dart with dynamic secret values + generateFirebaseOptionsDart(); + + return true; + } catch (err) { + console.warn(`โš ๏ธ Could not write ${targetPath}:`, err.message); + return false; + } +} + +if (require.main === module) { + generateFirebaseJson(); +} + +module.exports = { generateFirebaseJson, generateFirebaseOptionsDart }; diff --git a/scripts/rotate-database-encryption.js b/scripts/rotate-database-encryption.js index 0ca9ca1b..ae810f28 100644 --- a/scripts/rotate-database-encryption.js +++ b/scripts/rotate-database-encryption.js @@ -2,14 +2,14 @@ /** * rotate-database-encryption.js - * + * * Standalone administrative utility to securely rotate the symmetric AES-256-GCM * database encryption key across all persistent storage columns in the `users` table. - * + * * Execution Model: * Runs entirely offline/concurrently as an Admin-Initiated operation using standard * Supabase client API connections. Zero modifications to live application source code required. - * + * * Usage: * SUPABASE_URL=https://... \ * SUPABASE_SECRET_KEY=sb_secret_... \ @@ -18,36 +18,42 @@ * node scripts/rotate-database-encryption.js */ -const crypto = require('crypto'); -const { createClient } = require('@supabase/supabase-js'); +const crypto = require("node:crypto"); +const { createClient } = require("@supabase/supabase-js"); const colors = { - green: '\x1b[32m', - yellow: '\x1b[33m', - cyan: '\x1b[36m', - red: '\x1b[31m', - reset: '\x1b[0m', - bold: '\x1b[1m', + green: "\x1b[32m", + yellow: "\x1b[33m", + cyan: "\x1b[36m", + red: "\x1b[31m", + reset: "\x1b[0m", + bold: "\x1b[1m", }; // Validates input pattern matching exactly 64 hexadecimal characters const KEY_PATTERN = /^[a-f0-9]{64}$/i; const IV_PATTERN = /^[a-f0-9]{24}$/i; -const ALGORITHM = 'aes-256-gcm'; +const ALGORITHM = "aes-256-gcm"; // Symmetrically encrypted schema companion definitions const SENSITIVE_COLUMNS = [ - { contentCol: 'ezygo_token', ivCol: 'ezygo_iv' }, - { contentCol: 'auth_password', ivCol: 'auth_password_iv' }, - { contentCol: 'phone', ivCol: 'phone_iv' }, - { contentCol: 'gender', ivCol: 'gender_iv' }, - { contentCol: 'birth_date', ivCol: 'birth_date_iv' }, + { contentCol: "ezygo_token", ivCol: "ezygo_iv" }, + { contentCol: "auth_password", ivCol: "auth_password_iv" }, + { contentCol: "phone", ivCol: "phone_iv" }, + { contentCol: "gender", ivCol: "gender_iv" }, + { contentCol: "birth_date", ivCol: "birth_date_iv" }, ]; function printHeader() { - console.log(`\n${colors.bold}${colors.cyan}=====================================================================${colors.reset}`); - console.log(`${colors.bold} GhostClass Standalone Database Encryption Key Rotator ${colors.reset}`); - console.log(`${colors.bold}${colors.cyan}=====================================================================${colors.reset}\n`); + console.log( + `\n${colors.bold}${colors.cyan}=====================================================================${colors.reset}`, + ); + console.log( + `${colors.bold} GhostClass Standalone Database Encryption Key Rotator ${colors.reset}`, + ); + console.log( + `${colors.bold}${colors.cyan}=====================================================================${colors.reset}\n`, + ); } function printUsageAndExit(errorMessage) { @@ -56,10 +62,18 @@ function printUsageAndExit(errorMessage) { console.error(`${colors.red}โŒ Error: ${errorMessage}${colors.reset}\n`); } console.log(`${colors.yellow}Required Environment Variables:${colors.reset}`); - console.log(` ${colors.bold}SUPABASE_URL${colors.reset} โ†’ Real production Supabase URL (https://*.supabase.co)`); - console.log(` ${colors.bold}SUPABASE_SECRET_KEY${colors.reset} โ†’ Supabase Service Role Secret Key (sb_secret_*)`); - console.log(` ${colors.bold}OLD_ENCRYPTION_KEY${colors.reset} โ†’ Outgoing 64-hex symmetric encryption key`); - console.log(` ${colors.bold}NEW_ENCRYPTION_KEY${colors.reset} โ†’ Newly generated 64-hex symmetric encryption key\n`); + console.log( + ` ${colors.bold}SUPABASE_URL${colors.reset} โ†’ Real production Supabase URL (https://*.supabase.co)`, + ); + console.log( + ` ${colors.bold}SUPABASE_SECRET_KEY${colors.reset} โ†’ Supabase Service Role Secret Key (sb_secret_*)`, + ); + console.log( + ` ${colors.bold}OLD_ENCRYPTION_KEY${colors.reset} โ†’ Outgoing 64-hex symmetric encryption key`, + ); + console.log( + ` ${colors.bold}NEW_ENCRYPTION_KEY${colors.reset} โ†’ Newly generated 64-hex symmetric encryption key\n`, + ); console.log(`${colors.cyan}Example Usage:${colors.reset}`); console.log(` SUPABASE_URL=https://xyz.supabase.co \\`); console.log(` SUPABASE_SECRET_KEY=sb_secret_123... \\`); @@ -70,29 +84,38 @@ function printUsageAndExit(errorMessage) { } function validateEnvironment() { - const { SUPABASE_URL, SUPABASE_SECRET_KEY, OLD_ENCRYPTION_KEY, NEW_ENCRYPTION_KEY } = process.env; - - if (!SUPABASE_URL || !SUPABASE_URL.startsWith('https://')) { - printUsageAndExit('Missing or invalid SUPABASE_URL'); + const { + SUPABASE_URL, + SUPABASE_SECRET_KEY, + OLD_ENCRYPTION_KEY, + NEW_ENCRYPTION_KEY, + } = process.env; + + if (!SUPABASE_URL || !SUPABASE_URL.startsWith("https://")) { + printUsageAndExit("Missing or invalid SUPABASE_URL"); } if (!SUPABASE_SECRET_KEY) { - printUsageAndExit('Missing SUPABASE_SECRET_KEY'); + printUsageAndExit("Missing SUPABASE_SECRET_KEY"); } if (!OLD_ENCRYPTION_KEY || !KEY_PATTERN.test(OLD_ENCRYPTION_KEY)) { - printUsageAndExit('OLD_ENCRYPTION_KEY must be exactly 64 hexadecimal characters'); + printUsageAndExit( + "OLD_ENCRYPTION_KEY must be exactly 64 hexadecimal characters", + ); } if (!NEW_ENCRYPTION_KEY || !KEY_PATTERN.test(NEW_ENCRYPTION_KEY)) { - printUsageAndExit('NEW_ENCRYPTION_KEY must be exactly 64 hexadecimal characters'); + printUsageAndExit( + "NEW_ENCRYPTION_KEY must be exactly 64 hexadecimal characters", + ); } if (OLD_ENCRYPTION_KEY.toLowerCase() === NEW_ENCRYPTION_KEY.toLowerCase()) { - printUsageAndExit('NEW_ENCRYPTION_KEY must differ from OLD_ENCRYPTION_KEY'); + printUsageAndExit("NEW_ENCRYPTION_KEY must differ from OLD_ENCRYPTION_KEY"); } return { supabaseUrl: SUPABASE_URL, supabaseKey: SUPABASE_SECRET_KEY, - oldKeyBuffer: Buffer.from(OLD_ENCRYPTION_KEY, 'hex'), - newKeyBuffer: Buffer.from(NEW_ENCRYPTION_KEY, 'hex'), + oldKeyBuffer: Buffer.from(OLD_ENCRYPTION_KEY, "hex"), + newKeyBuffer: Buffer.from(NEW_ENCRYPTION_KEY, "hex"), }; } @@ -101,24 +124,28 @@ function validateEnvironment() { */ function decryptPayload(ivHex, contentString, keyBuffer) { if (!ivHex || !contentString) { - throw new Error('Missing companion parameters'); + throw new Error("Missing companion parameters"); } if (!IV_PATTERN.test(ivHex)) { - throw new Error('Malformed Initialisation Vector pattern'); + throw new Error("Malformed Initialisation Vector pattern"); } - const parts = contentString.split(':'); + const parts = contentString.split(":"); if (parts.length !== 2) { - throw new Error('Malformed content delimiter payload'); + throw new Error("Malformed content delimiter payload"); } const [authTagHex, encryptedHex] = parts; if (authTagHex.length !== 32) { - throw new Error('Malformed authentication tag buffer length'); + throw new Error("Malformed authentication tag buffer length"); } - const decipher = crypto.createDecipheriv(ALGORITHM, keyBuffer, Buffer.from(ivHex, 'hex')); - decipher.setAuthTag(Buffer.from(authTagHex, 'hex')); - let plaintext = decipher.update(encryptedHex, 'hex', 'utf8'); - plaintext += decipher.final('utf8'); + const decipher = crypto.createDecipheriv( + ALGORITHM, + keyBuffer, + Buffer.from(ivHex, "hex"), + ); + decipher.setAuthTag(Buffer.from(authTagHex, "hex")); + let plaintext = decipher.update(encryptedHex, "hex", "utf8"); + plaintext += decipher.final("utf8"); return plaintext; } @@ -128,12 +155,12 @@ function decryptPayload(ivHex, contentString, keyBuffer) { function encryptPayload(plaintextString, keyBuffer) { const ivBuffer = crypto.randomBytes(12); const cipher = crypto.createCipheriv(ALGORITHM, keyBuffer, ivBuffer); - let ciphertextHex = cipher.update(plaintextString, 'utf8', 'hex'); - ciphertextHex += cipher.final('hex'); - const authTagHex = cipher.getAuthTag().toString('hex'); + let ciphertextHex = cipher.update(plaintextString, "utf8", "hex"); + ciphertextHex += cipher.final("hex"); + const authTagHex = cipher.getAuthTag().toString("hex"); return { - iv: ivBuffer.toString('hex'), + iv: ivBuffer.toString("hex"), content: `${authTagHex}:${ciphertextHex}`, }; } @@ -172,7 +199,9 @@ function processUserRecord(userRow, oldKeyBuffer, newKeyBuffer, stats) { continue; } catch (fallbackErr) { stats.fieldsFailedDecryption += 1; - console.warn(`${colors.yellow}โš ๏ธ Warning: Record ID ${userRow.id} column '${contentCol}' failed decoding under both keys: ${fallbackErr.message}. Skipping.${colors.reset}`); + console.warn( + `${colors.yellow}โš ๏ธ Warning: Record ID ${userRow.id} column '${contentCol}' failed decoding under both keys: ${fallbackErr.message}. Skipping.${colors.reset}`, + ); continue; } } @@ -197,17 +226,24 @@ function processUserRecord(userRow, oldKeyBuffer, newKeyBuffer, stats) { async function processUsersBatch(usersBatch, supabase, env, stats) { for (const userRow of usersBatch) { stats.totalRowsTraversed += 1; - const { modified, updates } = processUserRecord(userRow, env.oldKeyBuffer, env.newKeyBuffer, stats); + const { modified, updates } = processUserRecord( + userRow, + env.oldKeyBuffer, + env.newKeyBuffer, + stats, + ); if (modified) { // Execute an atomic transaction update pushing fresh Initialisation Vectors and ciphertext const { error: updateError } = await supabase - .from('users') + .from("users") .update(updates) - .eq('id', userRow.id); + .eq("id", userRow.id); if (updateError) { - console.error(`${colors.red}โŒ Failed persisting updates for Record ID ${userRow.id}: ${updateError.message}${colors.reset}`); + console.error( + `${colors.red}โŒ Failed persisting updates for Record ID ${userRow.id}: ${updateError.message}${colors.reset}`, + ); } else { stats.rowsModified += 1; } @@ -222,8 +258,12 @@ async function executeRotation() { printHeader(); const env = validateEnvironment(); - console.log(`${colors.green}โœ“ Credentials validated successfully.${colors.reset}`); - console.log(`Connecting to Supabase instance at: ${colors.cyan}${env.supabaseUrl}${colors.reset} ...\n`); + console.log( + `${colors.green}โœ“ Credentials validated successfully.${colors.reset}`, + ); + console.log( + `Connecting to Supabase instance at: ${colors.cyan}${env.supabaseUrl}${colors.reset} ...\n`, + ); const supabase = createClient(env.supabaseUrl, env.supabaseKey, { auth: { persistSession: false }, @@ -243,17 +283,21 @@ async function executeRotation() { let offset = 0; let hasMoreRecords = true; - console.log(`${colors.bold}Commencing systematic database record inspection...${colors.reset}\n`); + console.log( + `${colors.bold}Commencing systematic database record inspection...${colors.reset}\n`, + ); while (hasMoreRecords) { const { data: usersBatch, error: fetchError } = await supabase - .from('users') - .select('*') - .order('id', { ascending: true }) + .from("users") + .select("*") + .order("id", { ascending: true }) .range(offset, offset + limit - 1); if (fetchError) { - console.error(`${colors.red}โŒ Critical database traversal abort: ${fetchError.message}${colors.reset}`); + console.error( + `${colors.red}โŒ Critical database traversal abort: ${fetchError.message}${colors.reset}`, + ); process.exit(1); } @@ -264,7 +308,9 @@ async function executeRotation() { await processUsersBatch(usersBatch, supabase, env, stats); offset += usersBatch.length; - process.stdout.write(` Processed rows: ${colors.bold}${stats.totalRowsTraversed}${colors.reset} | Upgraded rows: ${colors.bold}${colors.green}${stats.rowsModified}${colors.reset}\r`); + process.stdout.write( + ` Processed rows: ${colors.bold}${stats.totalRowsTraversed}${colors.reset} | Upgraded rows: ${colors.bold}${colors.green}${stats.rowsModified}${colors.reset}\r`, + ); // Terminate range scan if incoming batch size falls below request threshold if (usersBatch.length < limit) { @@ -272,25 +318,49 @@ async function executeRotation() { } } - console.log(`\n\n${colors.bold}${colors.green}โœ” Database Bulk Encryption Key Rotation finalized successfully.${colors.reset}\n`); + console.log( + `\n\n${colors.bold}${colors.green}โœ” Database Bulk Encryption Key Rotation finalized successfully.${colors.reset}\n`, + ); console.log(`${colors.cyan}Execution Summary Statistics:${colors.reset}`); - console.log(` Total User Rows Scanned : ${colors.bold}${stats.totalRowsTraversed}${colors.reset}`); - console.log(` User Rows Successfully Upgraded: ${colors.bold}${colors.green}${stats.rowsModified}${colors.reset}`); - console.log(` Total Symmetrical Fields Found: ${colors.bold}${stats.totalFieldsEncountered}${colors.reset}`); - console.log(` Fields Decoded & Re-encrypted : ${colors.bold}${colors.green}${stats.fieldsReEncrypted}${colors.reset}`); - console.log(` Fields Pre-migrated / Skipped : ${colors.bold}${colors.yellow}${stats.fieldsAlreadyUpgraded}${colors.reset}`); + console.log( + ` Total User Rows Scanned : ${colors.bold}${stats.totalRowsTraversed}${colors.reset}`, + ); + console.log( + ` User Rows Successfully Upgraded: ${colors.bold}${colors.green}${stats.rowsModified}${colors.reset}`, + ); + console.log( + ` Total Symmetrical Fields Found: ${colors.bold}${stats.totalFieldsEncountered}${colors.reset}`, + ); + console.log( + ` Fields Decoded & Re-encrypted : ${colors.bold}${colors.green}${stats.fieldsReEncrypted}${colors.reset}`, + ); + console.log( + ` Fields Pre-migrated / Skipped : ${colors.bold}${colors.yellow}${stats.fieldsAlreadyUpgraded}${colors.reset}`, + ); if (stats.fieldsFailedDecryption > 0) { - console.log(` Fields Errored / Unresolved : ${colors.bold}${colors.red}${stats.fieldsFailedDecryption}${colors.reset}`); + console.log( + ` Fields Errored / Unresolved : ${colors.bold}${colors.red}${stats.fieldsFailedDecryption}${colors.reset}`, + ); } - console.log(`\n${colors.bold}${colors.yellow}Next Action Required:${colors.reset}`); - console.log(`Update ${colors.bold}ENCRYPTION_KEY${colors.reset} inside your Infisical Dashboard \`/runtime\` folder to the new key string`); - console.log(`and restart your production container so the Infisical CLI wrapper injects the fresh key at boot.\n`); + console.log( + `\n${colors.bold}${colors.yellow}Next Action Required:${colors.reset}`, + ); + console.log( + `Update ${colors.bold}ENCRYPTION_KEY${colors.reset} inside your Infisical Dashboard \`/runtime\` folder to the new key string`, + ); + console.log( + `and restart your production container so the Infisical CLI wrapper injects the fresh key at boot.\n`, + ); } // Ensure execution safety by scoping promises securely if (require.main === module) { executeRotation().catch((err) => { - console.error(`\n${colors.red}โŒ Fatal runtime script error: ${err.stack || err.message}${colors.reset}`); + console.error( + `\n${colors.red}โŒ Fatal runtime script error: ${ + err.stack || err.message + }${colors.reset}`, + ); process.exit(1); }); } diff --git a/scripts/sync-secrets.js b/scripts/sync-secrets.js index 910fd4dd..3f53391b 100644 --- a/scripts/sync-secrets.js +++ b/scripts/sync-secrets.js @@ -2,13 +2,13 @@ /** * sync-secrets.js - DEPRECATED - * + * * GhostClass has migrated to centralized secret management using Infisical. * Manual synchronization of `.env` values to GitHub Actions and Coolify is no longer necessary. - * + * * Infisical's native Integrations automatically keep GitHub Actions (Secrets & Variables) * and Coolify project environments fully synchronized in the background whenever values are updated. - * + * * Local Development Setup: * 1. Install the Infisical CLI: https://infisical.com/docs/cli/overview * 2. Authenticate: infisical login @@ -16,16 +16,26 @@ */ const colors = { - yellow: '\x1b[33m', - cyan: '\x1b[36m', - reset: '\x1b[0m', + yellow: "\x1b[33m", + cyan: "\x1b[36m", + reset: "\x1b[0m", }; -console.log(`${colors.yellow}โš ๏ธ NOTICE: sync-secrets.js has been deprecated.${colors.reset}\n`); -console.log(`GhostClass now uses ${colors.cyan}Infisical${colors.reset} for centralized environment variable management.`); -console.log(`GitHub Actions secrets/variables and Coolify runtime environments are automatically`); +console.log( + `${colors.yellow}โš ๏ธ NOTICE: sync-secrets.js has been deprecated.${colors.reset}\n`, +); +console.log( + `GhostClass now uses ${colors.cyan}Infisical${colors.reset} for centralized environment variable management.`, +); +console.log( + `GitHub Actions secrets/variables and Coolify runtime environments are automatically`, +); console.log(`synchronized via native Infisical integrations.\n`); -console.log(`To run locally with injected secrets, use:\n ${colors.cyan}infisical run -- npm run dev${colors.reset}\n`); -console.log(`For full environment configuration documentation, refer to SECURITY.md and .example.env.`); +console.log( + `To run locally with injected secrets, use:\n ${colors.cyan}infisical run -- npm run dev${colors.reset}\n`, +); +console.log( + `For full environment configuration documentation, refer to SECURITY.md and .example.env.`, +); -process.exit(0); \ No newline at end of file +process.exit(0); diff --git a/scripts/sync-version.js b/scripts/sync-version.js index 8034bf04..4cf7c0a6 100644 --- a/scripts/sync-version.js +++ b/scripts/sync-version.js @@ -1,37 +1,42 @@ -const fs = require('fs'); -const path = require('path'); -const { execFileSync } = require('child_process'); +const fs = require("node:fs"); +const path = require("node:path"); +const { execFileSync } = require("node:child_process"); // 1. Determine running mode and target version -let isPreCommit = process.argv.includes('--pre-commit'); +const isPreCommit = process.argv.includes("--pre-commit"); let targetVersion = process.env.NEXT_PUBLIC_APP_VERSION; if (!targetVersion && !isPreCommit && process.argv[2]) { targetVersion = process.argv[2]; } - // Pre-commit hook mode: automatically derive target version from package.json -const packageJsonPath = path.join(__dirname, '..', 'package.json'); +const packageJsonPath = path.join(__dirname, "..", "package.json"); if (isPreCommit) { if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); targetVersion = packageJson.version; } else { - console.log(' Husky Pre-commit: package.json not found. Skipping version sync.'); + console.log( + " Husky Pre-commit: package.json not found. Skipping version sync.", + ); process.exit(0); } } // Fail-fast validation: ensure targetVersion is present and is a valid semver string if (!targetVersion) { - console.error('โŒ Error: Target version is missing. Please set NEXT_PUBLIC_APP_VERSION, provide a target version CLI argument, or run in --pre-commit mode.'); + console.error( + "โŒ Error: Target version is missing. Please set NEXT_PUBLIC_APP_VERSION, provide a target version CLI argument, or run in --pre-commit mode.", + ); process.exit(1); } const semverRegex = /^\d+\.\d+\.\d+$/; if (!semverRegex.test(targetVersion)) { - console.error(`โŒ Error: Invalid version format "${targetVersion}". Version must be a valid major.minor.patch semver string (e.g., "1.2.3").`); + console.error( + `โŒ Error: Invalid version format "${targetVersion}". Version must be a valid major.minor.patch semver string (e.g., "1.2.3").`, + ); process.exit(1); } @@ -42,7 +47,7 @@ const updatedFiles = []; // Helper to write file and track changes function updateFile(filePath, modifier) { if (fs.existsSync(filePath)) { - const original = fs.readFileSync(filePath, 'utf8'); + const original = fs.readFileSync(filePath, "utf8"); const modified = modifier(original); if (original !== modified) { fs.writeFileSync(filePath, modified); @@ -56,64 +61,84 @@ function updateFile(filePath, modifier) { updateFile(packageJsonPath, (content) => { const json = JSON.parse(content); json.version = targetVersion; - return JSON.stringify(json, null, 2) + '\n'; + return JSON.stringify(json, null, 2) + "\n"; }); // 2. Update package-lock.json -const packageLockPath = path.join(__dirname, '..', 'package-lock.json'); +const packageLockPath = path.join(__dirname, "..", "package-lock.json"); updateFile(packageLockPath, (content) => { const json = JSON.parse(content); json.version = targetVersion; - if (json.packages && json.packages['']) { - json.packages[''].version = targetVersion; + if (json.packages && json.packages[""]) { + json.packages[""].version = targetVersion; } - return JSON.stringify(json, null, 2) + '\n'; + return JSON.stringify(json, null, 2) + "\n"; }); // 3. Update public/openapi/openapi.yaml -const openApiPath = path.join(__dirname, '..', 'public', 'openapi', 'openapi.yaml'); +const openApiPath = path.join( + __dirname, + "..", + "public", + "openapi", + "openapi.yaml", +); updateFile(openApiPath, (content) => { - return content.replace(/^ {2}version:\s*\d+\.\d+\.\d+/m, ` version: ${targetVersion}`); + return content.replace( + /^ {2}version:\s*\d+\.\d+\.\d+/m, + ` version: ${targetVersion}`, + ); }); // 4. Update mobile/pubspec.yaml -const pubspecPath = path.join(__dirname, '..', 'mobile', 'pubspec.yaml'); +const pubspecPath = path.join(__dirname, "..", "mobile", "pubspec.yaml"); updateFile(pubspecPath, (content) => { - return content.replace(/^version:\s*\d+\.\d+\.\d+\+\d+/m, `version: ${targetVersion}+1`); + return content.replace( + /^version:\s*\d+\.\d+\.\d+\+\d+/m, + `version: ${targetVersion}+1`, + ); }); // 6. Update environment files -['.example.env', '.env', '.env.local'].forEach(file => { - const filePath = path.join(__dirname, '..', file); +[".example.env", ".env", ".env.local"].forEach((file) => { + const filePath = path.join(__dirname, "..", file); updateFile(filePath, (content) => { - let res = content.replace( + const res = content.replace( /^(NEXT_PUBLIC_APP_VERSION=)\d+\.\d+\.\d+/gm, - `$1${targetVersion}` + `$1${targetVersion}`, ); return res.replace( /^(MIN_APP_VERSION=)\d+\.\d+\.\d+/gm, - `$1${targetVersion}` + `$1${targetVersion}`, ); }); }); - // 7. Update mobile/lib/config/app_config.dart -const appConfigPath = path.join(__dirname, '..', 'mobile', 'lib', 'config', 'app_config.dart'); +const appConfigPath = path.join( + __dirname, + "..", + "mobile", + "lib", + "config", + "app_config.dart", +); updateFile(appConfigPath, (content) => { return content.replace( /('APP_VERSION',\s*defaultValue:\s*')\d+\.\d+\.\d+(')/g, - `$1${targetVersion}$2` + `$1${targetVersion}$2`, ); }); // In pre-commit mode, stage all updated files automatically if (isPreCommit && updatedFiles.length > 0) { - console.log('๐Ÿš€ Husky Pre-commit: Staging auto-synchronized version files...'); - updatedFiles.forEach(file => { + console.log( + "๐Ÿš€ Husky Pre-commit: Staging auto-synchronized version files...", + ); + updatedFiles.forEach((file) => { try { /* eslint-disable-next-line */ - execFileSync('git', ['add', file]); + execFileSync("git", ["add", file]); console.log(`โœ“ Staged ${path.basename(file)}`); } catch { console.error(`โŒ Failed to stage ${path.basename(file)}`); diff --git a/scripts/update-pinned-artifacts.sh b/scripts/update-pinned-artifacts.sh index 384cefd8..8eb082b1 100644 --- a/scripts/update-pinned-artifacts.sh +++ b/scripts/update-pinned-artifacts.sh @@ -14,15 +14,44 @@ echo "Updating pinned artifacts in $PIN_FILE" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT +# Check JSON tool availability (prefer jq, fallback to node) +HAS_JQ=false +HAS_NODE=false + +if command -v jq >/dev/null 2>&1; then + HAS_JQ=true +elif command -v node >/dev/null 2>&1; then + HAS_NODE=true +else + echo "โŒ Error: Neither 'jq' nor 'node' is installed. Please install jq or node to update pinned artifacts." >&2 + exit 1 +fi + # Update npm packages: fetch registry metadata and write dist.tarball and dist.shasum update_npm() { - name=$1 - echo "Fetching npm metadata for $name (latest)..." - meta="$tmp/${name}.json" - curl -fsSL "https://registry.npmjs.org/$name/latest" -o "$meta" - url=$(jq -r '.dist.tarball' "$meta") - shasum=$(jq -r '.dist.shasum' "$meta") - jq --arg u "$url" --arg s "$shasum" '.[$name] |= . + {url: $u, shasum: $s}' --arg name "$name" "$PIN_FILE" > "$tmp/pinned.json" && mv "$tmp/pinned.json" "$PIN_FILE" + pkg_name=$1 + key_name=${2:-$1} + echo "Fetching npm metadata for $pkg_name (latest)..." + meta="$tmp/${key_name}.json" + curl -fsSL "https://registry.npmjs.org/$pkg_name/latest" -o "$meta" + + if [ "$HAS_JQ" = true ]; then + url=$(jq -r '.dist.tarball' "$meta") + shasum=$(jq -r '.dist.shasum' "$meta") + jq --arg u "$url" --arg s "$shasum" --arg key "$key_name" '.[$key] |= . + {url: $u, shasum: $s}' "$PIN_FILE" > "$tmp/pinned.json" && mv "$tmp/pinned.json" "$PIN_FILE" + else + node -e ' + const fs = require("fs"); + const meta = JSON.parse(fs.readFileSync(process.argv[1])); + const pin = JSON.parse(fs.readFileSync(process.argv[2])); + const key = process.argv[3]; + pin[key] = Object.assign({}, pin[key], { + url: meta.dist.tarball, + shasum: meta.dist.shasum + }); + fs.writeFileSync(process.argv[2], JSON.stringify(pin, null, 2) + "\n"); + ' "$meta" "$PIN_FILE" "$key_name" + fi } # Update supabase: attempt to get latest release asset URL and compute sha256 @@ -31,25 +60,51 @@ update_supabase() { api="https://api.github.com/repos/supabase/cli/releases/latest" release="$tmp/supabase_release.json" curl -fsSL "$api" -o "$release" - # Try to find linux amd64 asset - asset_url=$(jq -r '.assets[] | select(.name | test("linux.*amd64.*tar.gz")) | .browser_download_url' "$release" | head -n1) - if [ -z "$asset_url" ]; then - asset_url=$(jq -r '.assets[0].browser_download_url' "$release") + + if [ "$HAS_JQ" = true ]; then + asset_url=$(jq -r '.assets[] | select(.name | test("linux.*amd64.*tar.gz")) | .browser_download_url' "$release" | head -n1) + if [ -z "$asset_url" ]; then + asset_url=$(jq -r '.assets[0].browser_download_url' "$release") + fi + else + asset_url=$(node -e ' + const fs = require("fs"); + const rel = JSON.parse(fs.readFileSync(process.argv[1])); + const match = (rel.assets || []).find(a => /linux.*amd64.*tar\.gz/.test(a.name)); + console.log(match ? match.browser_download_url : (rel.assets[0] ? rel.assets[0].browser_download_url : "")); + ' "$release") fi + if [ -z "$asset_url" ]; then echo "Could not find supabase release asset URL" >&2 return 1 fi + echo "Downloading supabase asset to compute sha256..." curl -fsSL "$asset_url" -o "$tmp/supabase.tar.gz" sha256=$(sha256sum "$tmp/supabase.tar.gz" | cut -d' ' -f1) - jq --arg u "$asset_url" --arg s "$sha256" '.supabase |= . + {url: $u, sha256: $s}' "$PIN_FILE" > "$tmp/pinned.json" && mv "$tmp/pinned.json" "$PIN_FILE" + + if [ "$HAS_JQ" = true ]; then + jq --arg u "$asset_url" --arg s "$sha256" '.supabase |= . + {url: $u, sha256: $s}' "$PIN_FILE" > "$tmp/pinned.json" && mv "$tmp/pinned.json" "$PIN_FILE" + else + node -e ' + const fs = require("fs"); + const pin = JSON.parse(fs.readFileSync(process.argv[1])); + pin.supabase = Object.assign({}, pin.supabase, { + url: process.argv[2], + sha256: process.argv[3] + }); + fs.writeFileSync(process.argv[1], JSON.stringify(pin, null, 2) + "\n"); + ' "$PIN_FILE" "$asset_url" "$sha256" + fi } # Update entries -update_npm "playwright" -update_npm "firebase-tools" +update_npm "npm" "npm" +update_npm "playwright" "playwright" +update_npm "firebase-tools" "firebase" +update_npm "@infisical/cli" "infisical" update_supabase -echo "Updated $PIN_FILE" -jq . "$PIN_FILE" +echo "โœ… Updated $PIN_FILE:" +cat "$PIN_FILE" diff --git a/src/__mocks__/axios.ts b/src/__mocks__/axios.ts index 6a671405..f6a70881 100644 --- a/src/__mocks__/axios.ts +++ b/src/__mocks__/axios.ts @@ -1,4 +1,4 @@ -import { vi } from 'vitest'; +import { vi } from "vitest"; const mockAxios = { create: vi.fn(() => ({ diff --git a/src/__tests__/axios-mock.test.ts b/src/__tests__/axios-mock.test.ts index 69979d1e..74ccdc1a 100644 --- a/src/__tests__/axios-mock.test.ts +++ b/src/__tests__/axios-mock.test.ts @@ -1,15 +1,15 @@ -import { describe, it, expect } from 'vitest'; -import mockAxios from '../__mocks__/axios'; +import { describe, expect, it } from "vitest"; +import mockAxios from "../__mocks__/axios"; -describe('axios mock', () => { - it('has create function that returns an instance with interceptors', () => { +describe("axios mock", () => { + it("has create function that returns an instance with interceptors", () => { const instance = mockAxios.create(); expect(instance.interceptors.request.use).toBeDefined(); expect(instance.interceptors.response.use).toBeDefined(); expect(instance.request).toBeDefined(); }); - it('has request and verb methods', () => { + it("has request and verb methods", () => { expect(mockAxios.request).toBeDefined(); expect(mockAxios.get).toBeDefined(); expect(mockAxios.post).toBeDefined(); diff --git a/src/__tests__/instrumentation-client.coverage.test.ts b/src/__tests__/instrumentation-client.coverage.test.ts index a2c7146d..f2f1222d 100644 --- a/src/__tests__/instrumentation-client.coverage.test.ts +++ b/src/__tests__/instrumentation-client.coverage.test.ts @@ -1,16 +1,17 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockInit = vi.fn(); -const mockReplayIntegration = vi.fn(() => ({ name: 'replay' })); +const mockReplayIntegration = vi.fn(() => ({ name: "replay" })); const mockCaptureRouterTransitionStart = vi.fn(); -vi.mock('@sentry/nextjs', () => ({ +vi.mock("@sentry/nextjs", () => ({ init: (...args: any[]) => mockInit(...args), replayIntegration: () => mockReplayIntegration(), - captureRouterTransitionStart: (...args: any[]) => mockCaptureRouterTransitionStart(...args), + captureRouterTransitionStart: (...args: any[]) => + mockCaptureRouterTransitionStart(...args), })); -describe('instrumentation-client coverage', () => { +describe("instrumentation-client coverage", () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); @@ -20,52 +21,57 @@ describe('instrumentation-client coverage', () => { vi.unstubAllEnvs(); }); - it('initializes Sentry in development with default replay rate', async () => { - vi.stubEnv('NODE_ENV', 'development'); - await import('../instrumentation-client'); - + it("initializes Sentry in development with default replay rate", async () => { + vi.stubEnv("NODE_ENV", "development"); + await import("../instrumentation-client"); + expect(mockInit).toHaveBeenCalledWith(expect.objectContaining({ tracesSampleRate: 1, replaysSessionSampleRate: 0.1, })); }); - it('initializes Sentry in production with env replay rate', async () => { - vi.stubEnv('NODE_ENV', 'production'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', '0.5'); - await import('../instrumentation-client'); - + it("initializes Sentry in production with env replay rate", async () => { + vi.stubEnv("NODE_ENV", "production"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", "0.5"); + await import("../instrumentation-client"); + expect(mockInit).toHaveBeenCalledWith(expect.objectContaining({ tracesSampleRate: 0.1, replaysSessionSampleRate: 0.5, - integrations: expect.arrayContaining([{ name: 'replay' }]), + integrations: expect.arrayContaining([{ name: "replay" }]), })); }); - it('initializes Sentry in production with zero replay rate', async () => { - vi.stubEnv('NODE_ENV', 'production'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', '0'); - await import('../instrumentation-client'); - + it("initializes Sentry in production with zero replay rate", async () => { + vi.stubEnv("NODE_ENV", "production"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", "0"); + await import("../instrumentation-client"); + expect(mockInit).toHaveBeenCalledWith(expect.objectContaining({ replaysSessionSampleRate: 0, integrations: [], })); }); - it('initializes Sentry in production with missing replay rate (defaults to 0)', async () => { - vi.stubEnv('NODE_ENV', 'production'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', undefined as any); - await import('../instrumentation-client'); - + it("initializes Sentry in production with missing replay rate (defaults to 0)", async () => { + vi.stubEnv("NODE_ENV", "production"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", undefined as any); + await import("../instrumentation-client"); + expect(mockInit).toHaveBeenCalledWith(expect.objectContaining({ replaysSessionSampleRate: 0, })); }); - it('covers onRouterTransitionStart', async () => { - const { onRouterTransitionStart } = await import('../instrumentation-client'); - onRouterTransitionStart('/test', 'push'); - expect(mockCaptureRouterTransitionStart).toHaveBeenCalledWith('/test', 'push'); + it("covers onRouterTransitionStart", async () => { + const { onRouterTransitionStart } = await import( + "../instrumentation-client" + ); + onRouterTransitionStart("/test", "push"); + expect(mockCaptureRouterTransitionStart).toHaveBeenCalledWith( + "/test", + "push", + ); }); }); diff --git a/src/__tests__/instrumentation-client.test.ts b/src/__tests__/instrumentation-client.test.ts index 71de452f..84d04fa2 100644 --- a/src/__tests__/instrumentation-client.test.ts +++ b/src/__tests__/instrumentation-client.test.ts @@ -1,13 +1,13 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import * as Sentry from '@sentry/nextjs'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import * as Sentry from "@sentry/nextjs"; -vi.mock('@sentry/nextjs', () => ({ +vi.mock("@sentry/nextjs", () => ({ init: vi.fn(), - replayIntegration: vi.fn(() => ({ name: 'Replay' })), + replayIntegration: vi.fn(() => ({ name: "Replay" })), captureRouterTransitionStart: vi.fn(), })); -describe('Instrumentation Client', () => { +describe("Instrumentation Client", () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); @@ -17,24 +17,24 @@ describe('Instrumentation Client', () => { vi.unstubAllEnvs(); }); - it('initializes Sentry correctly without replay in dev', async () => { - vi.stubEnv('NODE_ENV', 'development'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', '0'); - - await import('../instrumentation-client'); - + it("initializes Sentry correctly without replay in dev", async () => { + vi.stubEnv("NODE_ENV", "development"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", "0"); + + await import("../instrumentation-client"); + expect(Sentry.init).toHaveBeenCalled(); const options = vi.mocked(Sentry.init).mock.calls[0][0] as any; expect(options.integrations).toHaveLength(1); // Default replay rate is 0.1 in dev expect(options.tracesSampleRate).toBe(1); }); - it('initializes Sentry with replay in prod if rate > 0', async () => { - vi.stubEnv('NODE_ENV', 'production'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', '0.1'); - - await import('../instrumentation-client'); - + it("initializes Sentry with replay in prod if rate > 0", async () => { + vi.stubEnv("NODE_ENV", "production"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", "0.1"); + + await import("../instrumentation-client"); + const options = vi.mocked(Sentry.init).mock.calls[0][0] as any; expect(options.integrations).toHaveLength(1); expect(options.tracesSampleRate).toBe(0.1); @@ -42,19 +42,24 @@ describe('Instrumentation Client', () => { expect(options.replaysOnErrorSampleRate).toBe(0.5); // 0.1 * 5 }); - it('caps replaysOnErrorSampleRate at 1', async () => { - vi.stubEnv('NODE_ENV', 'production'); - vi.stubEnv('NEXT_PUBLIC_SENTRY_REPLAY_RATE', '0.5'); - - await import('../instrumentation-client'); - + it("caps replaysOnErrorSampleRate at 1", async () => { + vi.stubEnv("NODE_ENV", "production"); + vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAY_RATE", "0.5"); + + await import("../instrumentation-client"); + const options = vi.mocked(Sentry.init).mock.calls[0][0] as any; expect(options.replaysOnErrorSampleRate).toBe(1); }); - it('captures router transitions', async () => { - const { onRouterTransitionStart } = await import('../instrumentation-client'); - onRouterTransitionStart('/test', 'push'); - expect(Sentry.captureRouterTransitionStart).toHaveBeenCalledWith('/test', 'push'); + it("captures router transitions", async () => { + const { onRouterTransitionStart } = await import( + "../instrumentation-client" + ); + onRouterTransitionStart("/test", "push"); + expect(Sentry.captureRouterTransitionStart).toHaveBeenCalledWith( + "/test", + "push", + ); }); }); diff --git a/src/__tests__/instrumentation-edge.test.ts b/src/__tests__/instrumentation-edge.test.ts index cec6e0c4..442b6e7f 100644 --- a/src/__tests__/instrumentation-edge.test.ts +++ b/src/__tests__/instrumentation-edge.test.ts @@ -1,101 +1,110 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import * as Sentry from '@sentry/nextjs'; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import * as Sentry from "@sentry/nextjs"; -vi.mock('@sentry/nextjs', () => ({ +vi.mock("@sentry/nextjs", () => ({ init: vi.fn(), })); -describe('Instrumentation Edge', () => { +describe("Instrumentation Edge", () => { let initOptions: any; beforeEach(async () => { - vi.stubEnv('NEXT_PUBLIC_SENTRY_DSN', 'https://test@sentry.io/1'); + vi.stubEnv("NEXT_PUBLIC_SENTRY_DSN", "https://test@sentry.io/1"); vi.clearAllMocks(); vi.resetModules(); // Import the module once to trigger Sentry.init - await import('../instrumentation-edge'); + await import("../instrumentation-edge"); initOptions = vi.mocked(Sentry.init).mock.calls[0][0]; }); - it('initializes Sentry correctly', () => { + it("initializes Sentry correctly", () => { expect(Sentry.init).toHaveBeenCalled(); expect(initOptions.dsn).toBeDefined(); }); - it('scrubs GA4 secrets from URLs in beforeBreadcrumb', () => { + it("scrubs GA4 secrets from URLs in beforeBreadcrumb", () => { const beforeBreadcrumb = initOptions?.beforeBreadcrumb; expect(beforeBreadcrumb).toBeDefined(); const breadcrumb = { data: { - url: 'https://www.google-analytics.com/collect?v=2&api_secret=supersecret' - } + url: + "https://www.google-analytics.com/collect?v=2&api_secret=supersecret", + }, }; const result = beforeBreadcrumb!(breadcrumb); - expect(result.data.url).toContain('api_secret=%5BFiltered%5D'); + expect(result.data.url).toContain("api_secret=%5BFiltered%5D"); }); - it('ignores non-GA URLs in breadcrumbs', () => { + it("ignores non-GA URLs in breadcrumbs", () => { const beforeBreadcrumb = initOptions?.beforeBreadcrumb; - const url = 'https://example.com/api?api_secret=keep-me'; + const url = "https://example.com/api?api_secret=keep-me"; const breadcrumb = { data: { url } }; const result = beforeBreadcrumb!(breadcrumb); expect(result.data.url).toBe(url); }); - it('filters out network error types in beforeSend', () => { + it("filters out network error types in beforeSend", () => { const beforeSend = initOptions?.beforeSend; expect(beforeSend).toBeDefined(); - const abortError = new Error('The request was aborted'); + const abortError = new Error("The request was aborted"); const result = beforeSend!({}, { originalException: abortError }); expect(result).toBeNull(); - const socketError = new Error('socket hang up'); + const socketError = new Error("socket hang up"); const result2 = beforeSend!({}, { originalException: socketError }); expect(result2).toBeNull(); - const realError = new Error('Database crash'); + const realError = new Error("Database crash"); const result3 = beforeSend!({}, { originalException: realError }); expect(result3).not.toBeNull(); }); - it('scrubs auth and cookie headers in beforeSend', () => { + it("scrubs auth and cookie headers in beforeSend", () => { const beforeSend = initOptions?.beforeSend; const event: any = { request: { headers: { - 'authorization': 'Bearer secret', - 'cookie': 'session=abc', - 'user-agent': 'browser' - } - } + "authorization": "Bearer secret", + "cookie": "session=abc", + "user-agent": "browser", + }, + }, }; const result = beforeSend!(event, {}); expect(result.request.headers.authorization).toBeUndefined(); expect(result.request.headers.cookie).toBeUndefined(); - expect(result.request.headers['user-agent']).toBe('browser'); + expect(result.request.headers["user-agent"]).toBe("browser"); }); - it('scrubs GA4 secrets in beforeSendTransaction', () => { + it("scrubs GA4 secrets in beforeSendTransaction", () => { const beforeSendTransaction = initOptions?.beforeSendTransaction; expect(beforeSendTransaction).toBeDefined(); const event: any = { spans: [ - { data: { 'http.url': 'https://google-analytics.com/collect?api_secret=123' } }, - { data: { 'url': 'https://google-analytics.com/collect?api_secret=456' } } - ] + { + data: { + "http.url": "https://google-analytics.com/collect?api_secret=123", + }, + }, + { + data: { + "url": "https://google-analytics.com/collect?api_secret=456", + }, + }, + ], }; const result = beforeSendTransaction!(event); - expect(result.spans[0].data['http.url']).toContain('%5BFiltered%5D'); - expect(result.spans[1].data['url']).toContain('%5BFiltered%5D'); + expect(result.spans[0].data["http.url"]).toContain("%5BFiltered%5D"); + expect(result.spans[1].data["url"]).toContain("%5BFiltered%5D"); }); - it('handles invalid URLs gracefully', () => { + it("handles invalid URLs gracefully", () => { const beforeBreadcrumb = initOptions?.beforeBreadcrumb; - const breadcrumb = { data: { url: 'not-a-url' } }; + const breadcrumb = { data: { url: "not-a-url" } }; const result = beforeBreadcrumb!(breadcrumb); - expect(result.data.url).toBe('not-a-url'); + expect(result.data.url).toBe("not-a-url"); }); }); diff --git a/src/__tests__/instrumentation-server.test.ts b/src/__tests__/instrumentation-server.test.ts index 08d1a371..520392f2 100644 --- a/src/__tests__/instrumentation-server.test.ts +++ b/src/__tests__/instrumentation-server.test.ts @@ -1,100 +1,119 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import * as Sentry from '@sentry/nextjs'; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import * as Sentry from "@sentry/nextjs"; -vi.mock('@sentry/nextjs', () => ({ +vi.mock("@sentry/nextjs", () => ({ init: vi.fn(), })); -describe('instrumentation-server', () => { +describe("instrumentation-server", () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); }); - it('initializes Sentry with expected options', async () => { - process.env.NEXT_PUBLIC_GIT_COMMIT_SHA = 'abc 123 '; - await import('../instrumentation-server'); + it("initializes Sentry with expected options", async () => { + process.env.NEXT_PUBLIC_GIT_COMMIT_SHA = "abc 123 "; + await import("../instrumentation-server"); expect(Sentry.init).toHaveBeenCalled(); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - + // Test release sanitization - expect(options.release).toBe('abc-123--tag-'); + expect(options.release).toBe("abc-123--tag-"); }); - it('scrubs GA4 api_secret in breadcrumbs', async () => { - await import('../instrumentation-server'); + it("scrubs GA4 api_secret in breadcrumbs", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - - const breadcrumb: any = { data: { url: 'https://www.google-analytics.com/collect?api_secret=secret123&v=1' } }; + + const breadcrumb: any = { + data: { + url: + "https://www.google-analytics.com/collect?api_secret=secret123&v=1", + }, + }; const processed: any = options.beforeBreadcrumb!(breadcrumb); - expect(processed?.data?.url).toContain('api_secret=%5BFiltered%5D'); + expect(processed?.data?.url).toContain("api_secret=%5BFiltered%5D"); }); - it('ignores non-GA4 URLs in breadcrumbs', async () => { - await import('../instrumentation-server'); + it("ignores non-GA4 URLs in breadcrumbs", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - - const url = 'https://example.com/api?api_secret=keep-me'; + + const url = "https://example.com/api?api_secret=keep-me"; const breadcrumb: any = { data: { url } }; const processed: any = options.beforeBreadcrumb!(breadcrumb); expect(processed?.data?.url).toBe(url); }); - it('handles malformed URLs in scrubbing', async () => { - await import('../instrumentation-server'); + it("handles malformed URLs in scrubbing", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - - const url = 'not-a-url'; + + const url = "not-a-url"; const breadcrumb: any = { data: { url } }; const processed: any = options.beforeBreadcrumb!(breadcrumb); expect(processed?.data?.url).toBe(url); }); - it('filters out network error types in beforeSend', async () => { - await import('../instrumentation-server'); + it("filters out network error types in beforeSend", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - - const abortError = { message: 'The request was aborted' }; - const result = options.beforeSend!({} as any, { originalException: abortError } as any); + + const abortError = { message: "The request was aborted" }; + const result = options.beforeSend!( + {} as any, + { originalException: abortError } as any, + ); expect(result).toBeNull(); - const realError = { message: 'Database crash' }; - const result2 = options.beforeSend!({} as any, { originalException: realError } as any); + const realError = { message: "Database crash" }; + const result2 = options.beforeSend!( + {} as any, + { originalException: realError } as any, + ); expect(result2).not.toBeNull(); }); - it('scrubs auth headers in beforeSend', async () => { - await import('../instrumentation-server'); + it("scrubs auth headers in beforeSend", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - + const event: any = { request: { headers: { - 'authorization': 'Bearer secret', - 'cookie': 'session=abc', - 'user-agent': 'browser' - } - } + "authorization": "Bearer secret", + "cookie": "session=abc", + "user-agent": "browser", + }, + }, }; const result: any = options.beforeSend!(event, {} as any); expect(result?.request?.headers?.authorization).toBeUndefined(); expect(result?.request?.headers?.cookie).toBeUndefined(); - expect(result?.request?.headers?.['user-agent']).toBe('browser'); + expect(result?.request?.headers?.["user-agent"]).toBe("browser"); }); - it('scrubs transactions', async () => { - await import('../instrumentation-server'); + it("scrubs transactions", async () => { + await import("../instrumentation-server"); const options = vi.mocked(Sentry.init).mock.calls[0][0]; - + const event: any = { spans: [ - { data: { 'http.url': 'https://google-analytics.com/collect?api_secret=123' } }, - { data: { 'url': 'https://google-analytics.com/collect?api_secret=456' } }, - { data: { 'other': 'no-change' } } - ] + { + data: { + "http.url": "https://google-analytics.com/collect?api_secret=123", + }, + }, + { + data: { + "url": "https://google-analytics.com/collect?api_secret=456", + }, + }, + { data: { "other": "no-change" } }, + ], }; const result: any = options.beforeSendTransaction!(event as any, {} as any); - expect(result?.spans?.[0]?.data?.['http.url']).toContain('%5BFiltered%5D'); - expect(result?.spans?.[1]?.data?.['url']).toContain('%5BFiltered%5D'); + expect(result?.spans?.[0]?.data?.["http.url"]).toContain("%5BFiltered%5D"); + expect(result?.spans?.[1]?.data?.["url"]).toContain("%5BFiltered%5D"); }); }); diff --git a/src/__tests__/instrumentation.test.ts b/src/__tests__/instrumentation.test.ts index a9ae82a9..3870b1dc 100644 --- a/src/__tests__/instrumentation.test.ts +++ b/src/__tests__/instrumentation.test.ts @@ -1,16 +1,16 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { register } from '../instrumentation'; -import { validateEnvironment } from '@/lib/validate-env'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { register } from "../instrumentation"; +import { validateEnvironment } from "@/lib/validate-env"; -vi.mock('@/lib/validate-env', () => ({ +vi.mock("@/lib/validate-env", () => ({ validateEnvironment: vi.fn(), })); // Mock the dynamic imports -vi.mock('./instrumentation-server', () => ({})); -vi.mock('./instrumentation-edge', () => ({})); +vi.mock("./instrumentation-server", () => ({})); +vi.mock("./instrumentation-edge", () => ({})); -describe('instrumentation register', () => { +describe("instrumentation register", () => { const originalEnv = process.env; beforeEach(() => { @@ -22,22 +22,22 @@ describe('instrumentation register', () => { process.env = originalEnv; }); - it('calls validateEnvironment in nodejs runtime and non-build phase', async () => { - process.env.NEXT_RUNTIME = 'nodejs'; - process.env.NEXT_PHASE = 'phase-production-server'; + it("calls validateEnvironment in nodejs runtime and non-build phase", async () => { + process.env.NEXT_RUNTIME = "nodejs"; + process.env.NEXT_PHASE = "phase-production-server"; await register(); expect(validateEnvironment).toHaveBeenCalled(); }); - it('skips validateEnvironment in build phase', async () => { - process.env.NEXT_RUNTIME = 'nodejs'; - process.env.NEXT_PHASE = 'phase-production-build'; + it("skips validateEnvironment in build phase", async () => { + process.env.NEXT_RUNTIME = "nodejs"; + process.env.NEXT_PHASE = "phase-production-build"; await register(); expect(validateEnvironment).not.toHaveBeenCalled(); }); - it('handles edge runtime', async () => { - process.env.NEXT_RUNTIME = 'edge'; + it("handles edge runtime", async () => { + process.env.NEXT_RUNTIME = "edge"; await register(); // Verification is that it didn't crash and hit the branch expect(true).toBe(true); diff --git a/src/__tests__/proxy.coverage.test.ts b/src/__tests__/proxy.coverage.test.ts index 10898c39..fe1a0648 100644 --- a/src/__tests__/proxy.coverage.test.ts +++ b/src/__tests__/proxy.coverage.test.ts @@ -1,8 +1,14 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { NextRequest } from "next/server"; // --- Mocks --- -const { mockGetUser, mockFrom, mockMaybeSingle, mockSingle, mockCreateServerClient } = vi.hoisted(() => { +const { + mockGetUser, + mockFrom, + mockMaybeSingle, + mockSingle, + mockCreateServerClient, +} = vi.hoisted(() => { return { mockGetUser: vi.fn(), mockFrom: vi.fn(), @@ -19,11 +25,15 @@ vi.mock("@supabase/ssr", () => ({ const options = args[2]; if (options?.cookies) { // Call getAll to cover the branch in proxy.ts - if (typeof options.cookies.getAll === 'function') options.cookies.getAll(); + if (typeof options.cookies.getAll === "function") { + options.cookies.getAll(); + } // Call setAll to cover the branch in proxy.ts - if (typeof options.cookies.setAll === 'function') options.cookies.setAll([{ name: 'test', value: 'val', options: {} }]); + if (typeof options.cookies.setAll === "function") { + options.cookies.setAll([{ name: "test", value: "val", options: {} }]); + } } - + return { auth: { getUser: mockGetUser }, from: mockFrom, @@ -36,11 +46,11 @@ vi.mock("../lib/crypto", () => ({ })); vi.mock("../lib/logger", () => ({ - logger: { - warn: vi.fn(), - error: vi.fn(), - dev: vi.fn(), - info: vi.fn() + logger: { + warn: vi.fn(), + error: vi.fn(), + dev: vi.fn(), + info: vi.fn(), }, })); @@ -64,7 +74,7 @@ describe("proxy.ts coverage hardening", () => { beforeEach(() => { vi.clearAllMocks(); vi.stubEnv("NODE_ENV", "production"); - + mockFrom.mockReturnValue({ select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), @@ -80,19 +90,21 @@ describe("proxy.ts coverage hardening", () => { it("covers isApiDocs CSP branch", async () => { const request = new NextRequest("https://localhost/api-docs"); mockGetUser.mockResolvedValue({ data: { user: null }, error: null }); - + const response = await proxy(request); - expect(response.headers.get("Content-Security-Policy")).toContain("script-src 'self'"); + expect(response.headers.get("Content-Security-Policy")).toContain( + "script-src 'self'", + ); }); it("covers development Supabase URL/Key branches", async () => { vi.stubEnv("NODE_ENV", "development"); vi.stubEnv("NEXT_PUBLIC_SUPABASE_DEV_URL", "https://dev-url"); vi.stubEnv("NEXT_PUBLIC_SUPABASE_DEV_PUBLISHABLE_KEY", "dev-key"); - + const request = new NextRequest("https://localhost/"); mockGetUser.mockResolvedValue({ data: { user: null }, error: null }); - + await proxy(request); expect(mockGetUser).toHaveBeenCalled(); }); @@ -102,16 +114,16 @@ describe("proxy.ts coverage hardening", () => { mockGetUser .mockRejectedValueOnce(new Error("fetch failure")) .mockResolvedValueOnce({ data: { user: { id: "user-1" } }, error: null }); - + const request = new NextRequest("https://localhost/dashboard", { - headers: { cookie: "terms_version=2.5" } + headers: { cookie: "terms_version=2.5" }, }); const proxyPromise = proxy(request); - + // Wait for the retry timeout await vi.runAllTimersAsync(); const response = await proxyPromise; - + expect(mockGetUser).toHaveBeenCalledTimes(2); expect(response.status).toBe(200); vi.useRealTimers(); @@ -122,15 +134,15 @@ describe("proxy.ts coverage hardening", () => { mockGetUser .mockRejectedValueOnce(new Error("Network Error")) .mockRejectedValueOnce(new Error("Persistent failure")); - + const request = new NextRequest("https://localhost/dashboard", { - headers: { cookie: "terms_version=2.5" } + headers: { cookie: "terms_version=2.5" }, }); const proxyPromise = proxy(request); - + await vi.runAllTimersAsync(); const response = await proxyPromise; - + expect(mockGetUser).toHaveBeenCalledTimes(2); expect(response.status).toBe(307); // Redirect to / due to auth failure vi.useRealTimers(); @@ -138,17 +150,19 @@ describe("proxy.ts coverage hardening", () => { it("covers clearSessionCookies line 21 (sb- auth cookies)", async () => { mockGetUser.mockResolvedValue({ data: { user: null }, error: null }); - + const request = new NextRequest("https://localhost/dashboard", { headers: { - cookie: "sb-example-auth-token=value" - } + cookie: "sb-example-auth-token=value", + }, }); - + const response = await proxy(request); expect(response.status).toBe(307); const setCookies = response.headers.getSetCookie(); - expect(setCookies.some(c => c.includes("sb-example-auth-token=;"))).toBe(true); + expect(setCookies.some((c) => c.includes("sb-example-auth-token=;"))).toBe( + true, + ); }); it("covers proxy.ts line 180 unexpected throw", async () => { @@ -156,23 +170,25 @@ describe("proxy.ts coverage hardening", () => { // This is tricky because createServerClient is called inside proxy() // I'll mock createServerClient to return an object where auth is a getter that throws mockCreateServerClient.mockReturnValueOnce({ - get auth() { throw new Error("Unexpected auth failure"); }, - from: mockFrom + get auth() { + throw new Error("Unexpected auth failure"); + }, + from: mockFrom, }); const request = new NextRequest("https://localhost/dashboard"); const response = await proxy(request); - + expect(response.status).toBe(307); expect(response.headers.get("location")).toBe("https://localhost/"); }); it("covers getUserWithRetry non-transient error", async () => { mockGetUser.mockRejectedValueOnce(new Error("Fatal error")); - + const request = new NextRequest("https://localhost/dashboard"); const response = await proxy(request); - + expect(mockGetUser).toHaveBeenCalledTimes(1); expect(response.status).toBe(307); }); @@ -180,9 +196,9 @@ describe("proxy.ts coverage hardening", () => { it("covers clearSessionCookies catch block", async () => { // We need to trigger a redirect for an unauthenticated user to hit clearSessionCookies mockGetUser.mockResolvedValue({ data: { user: null }, error: null }); - + const request = new NextRequest("https://localhost/dashboard"); - + // Mock getAll to throw after the first call (which happens in createServerClient) let callCount = 0; request.cookies.getAll = vi.fn().mockImplementation(() => { @@ -197,29 +213,37 @@ describe("proxy.ts coverage hardening", () => { }); it("covers EzyGo session self-healing success", async () => { - mockGetUser.mockResolvedValue({ data: { user: { id: "user-healing" } }, error: null }); - mockMaybeSingle.mockResolvedValue({ - data: { ezygo_token: "token", ezygo_iv: "iv" }, - error: null + mockGetUser.mockResolvedValue({ + data: { user: { id: "user-healing" } }, + error: null, }); - + mockMaybeSingle.mockResolvedValue({ + data: { ezygo_token: "token", ezygo_iv: "iv" }, + error: null, + }); + const request = new NextRequest("https://localhost/dashboard", { - headers: { cookie: "terms_version=2.5" } + headers: { cookie: "terms_version=2.5" }, }); // No ezygo_access_token cookie in request - + const response = await proxy(request); expect(response.status).toBe(200); const setCookies = response.headers.getSetCookie(); - expect(setCookies.some(c => c.includes("ezygo_access_token=decrypted-token"))).toBe(true); + expect( + setCookies.some((c) => c.includes("ezygo_access_token=decrypted-token")), + ).toBe(true); }); it("covers EzyGo session self-healing failure (DB error)", async () => { - mockGetUser.mockResolvedValue({ data: { user: { id: "user-healing-fail" } }, error: null }); + mockGetUser.mockResolvedValue({ + data: { user: { id: "user-healing-fail" } }, + error: null, + }); mockMaybeSingle.mockRejectedValue(new Error("DB error")); - + const request = new NextRequest("https://localhost/dashboard", { - headers: { cookie: "terms_version=2.5" } + headers: { cookie: "terms_version=2.5" }, }); const response = await proxy(request); expect(response.status).toBe(200); @@ -227,15 +251,21 @@ describe("proxy.ts coverage hardening", () => { }); it("covers terms_redirect_count protection loop (Scenario B)", async () => { - mockGetUser.mockResolvedValue({ data: { user: { id: "user-loop" } }, error: null }); - mockSingle.mockResolvedValue({ data: { terms_version: "1.0" }, error: null }); - + mockGetUser.mockResolvedValue({ + data: { user: { id: "user-loop" } }, + error: null, + }); + mockSingle.mockResolvedValue({ + data: { terms_version: "1.0" }, + error: null, + }); + const request = new NextRequest("https://localhost/dashboard", { headers: { - cookie: "terms_redirect_count=3" - } + cookie: "terms_redirect_count=3", + }, }); - + const response = await proxy(request); expect(response.status).toBe(307); expect(response.headers.get("location")).toBe("https://localhost/"); @@ -244,9 +274,9 @@ describe("proxy.ts coverage hardening", () => { it("covers isRefreshTokenNotFoundError with status 400 and message branch", async () => { mockGetUser.mockRejectedValueOnce({ status: 400, - message: "Invalid Refresh Token" + message: "Invalid Refresh Token", }); - + const request = new NextRequest("https://localhost/dashboard"); const response = await proxy(request); expect(response.status).toBe(307); @@ -258,8 +288,11 @@ describe("proxy.ts coverage hardening", () => { mockGetUser.mockClear(); mockGetUser .mockRejectedValueOnce({ status, message: "Gateway error" }) - .mockResolvedValueOnce({ data: { user: { id: "user-retry" } }, error: null }); - + .mockResolvedValueOnce({ + data: { user: { id: "user-retry" } }, + error: null, + }); + const request = new NextRequest("https://localhost/dashboard"); const proxyPromise = proxy(request); await vi.runAllTimersAsync(); diff --git a/src/__tests__/proxy.test.ts b/src/__tests__/proxy.test.ts index fa40e6f5..6b0708bc 100644 --- a/src/__tests__/proxy.test.ts +++ b/src/__tests__/proxy.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { NextRequest } from "next/server"; // --- Mocks (must be hoisted before imports) --- @@ -155,7 +155,7 @@ describe("proxy โ€“ cross-device terms sync", () => { // Should set the terms_version cookie const setCookies = response.headers.getSetCookie(); const termsCookie = setCookies.find((h) => - h.toLowerCase().startsWith("terms_version="), + h.toLowerCase().startsWith("terms_version=") ); expect(termsCookie).toBeDefined(); expect(termsCookie).toContain("2.3"); @@ -181,7 +181,7 @@ describe("proxy โ€“ cross-device terms sync", () => { // Should set the updated terms_version cookie const setCookies = response.headers.getSetCookie(); const termsCookie = setCookies.find((h) => - h.toLowerCase().startsWith("terms_version="), + h.toLowerCase().startsWith("terms_version=") ); expect(termsCookie).toBeDefined(); expect(termsCookie).toContain("2.3"); diff --git a/src/__tests__/sw.test.ts b/src/__tests__/sw.test.ts index d94d0ea9..4862a175 100644 --- a/src/__tests__/sw.test.ts +++ b/src/__tests__/sw.test.ts @@ -1,9 +1,9 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Mock Serwist -vi.mock('serwist', () => { +vi.mock("serwist", () => { return { - Serwist: vi.fn().mockImplementation(function() { + Serwist: vi.fn().mockImplementation(function () { return { addEventListeners: vi.fn() }; }), CacheFirst: vi.fn(), @@ -29,7 +29,7 @@ type MockSelf = { __SW_MANIFEST: []; }; -describe('Service Worker', () => { +describe("Service Worker", () => { let fetchHandler: Listener | undefined; let activateHandler: Listener | undefined; let messageHandler: Listener | undefined; @@ -48,7 +48,7 @@ describe('Service Worker', () => { listeners.set(type, handlers); }), skipWaiting: vi.fn(), - location: { origin: 'https://example.com' }, + location: { origin: "https://example.com" }, clients: { get: vi.fn(), }, @@ -58,25 +58,26 @@ describe('Service Worker', () => { __SW_MANIFEST: [], }; - vi.stubGlobal('self', mockSelf); - vi.stubGlobal('caches', mockSelf.caches); + vi.stubGlobal("self", mockSelf); + vi.stubGlobal("caches", mockSelf.caches); // Import the SW file - await import('../sw'); + await import("../sw"); - fetchHandler = listeners.get('fetch')?.[0]; - activateHandler = listeners.get('activate')?.[1] || listeners.get('activate')?.[0]; // activate listener - messageHandler = listeners.get('message')?.[0]; + fetchHandler = listeners.get("fetch")?.[0]; + activateHandler = listeners.get("activate")?.[1] || + listeners.get("activate")?.[0]; // activate listener + messageHandler = listeners.get("message")?.[0]; }); afterEach(() => { vi.unstubAllGlobals(); }); - describe('Fetch Event', () => { - it('bypasses SW for navigation requests', () => { + describe("Fetch Event", () => { + it("bypasses SW for navigation requests", () => { const event = { - request: { mode: 'navigate', url: 'https://example.com/page' }, + request: { mode: "navigate", url: "https://example.com/page" }, stopImmediatePropagation: vi.fn(), respondWith: vi.fn(), }; @@ -85,9 +86,9 @@ describe('Service Worker', () => { expect(event.respondWith).not.toHaveBeenCalled(); }); - it('bypasses SW for /monitoring and /api/ routes', () => { + it("bypasses SW for /monitoring and /api/ routes", () => { const monitoringEvent = { - request: { url: 'https://example.com/monitoring/collect' }, + request: { url: "https://example.com/monitoring/collect" }, stopImmediatePropagation: vi.fn(), respondWith: vi.fn(), }; @@ -95,7 +96,7 @@ describe('Service Worker', () => { expect(monitoringEvent.stopImmediatePropagation).toHaveBeenCalled(); const apiEvent = { - request: { url: 'https://example.com/api/user' }, + request: { url: "https://example.com/api/user" }, stopImmediatePropagation: vi.fn(), respondWith: vi.fn(), }; @@ -103,9 +104,12 @@ describe('Service Worker', () => { expect(apiEvent.stopImmediatePropagation).toHaveBeenCalled(); }); - it('lets other requests pass through to Serwist', () => { + it("lets other requests pass through to Serwist", () => { const staticEvent = { - request: { mode: 'no-cors', url: 'https://example.com/static/style.css' }, + request: { + mode: "no-cors", + url: "https://example.com/static/style.css", + }, stopImmediatePropagation: vi.fn(), respondWith: vi.fn(), }; @@ -114,52 +118,52 @@ describe('Service Worker', () => { }); }); - describe('Activate Event', () => { - it('purges deprecated caches', async () => { + describe("Activate Event", () => { + it("purges deprecated caches", async () => { const event = { waitUntil: vi.fn((p) => p), }; await activateHandler!(event); - expect(mockSelf.caches.delete).toHaveBeenCalledWith('attendance-data'); - expect(mockSelf.caches.delete).toHaveBeenCalledWith('pages'); + expect(mockSelf.caches.delete).toHaveBeenCalledWith("attendance-data"); + expect(mockSelf.caches.delete).toHaveBeenCalledWith("pages"); }); }); - describe('Message Event', () => { - it('handles SKIP_WAITING message correctly', async () => { + describe("Message Event", () => { + it("handles SKIP_WAITING message correctly", async () => { const event = { - data: { type: 'SKIP_WAITING' }, - source: { id: 'client-1' }, + data: { type: "SKIP_WAITING" }, + source: { id: "client-1" }, }; mockSelf.clients.get.mockResolvedValue({ - url: 'https://example.com/dashboard', + url: "https://example.com/dashboard", }); await messageHandler!(event); - - expect(mockSelf.clients.get).toHaveBeenCalledWith('client-1'); + + expect(mockSelf.clients.get).toHaveBeenCalledWith("client-1"); expect(mockSelf.skipWaiting).toHaveBeenCalled(); }); - it('ignores SKIP_WAITING from cross-origin sources', async () => { + it("ignores SKIP_WAITING from cross-origin sources", async () => { const event = { - data: { type: 'SKIP_WAITING' }, - source: { id: 'client-1' }, + data: { type: "SKIP_WAITING" }, + source: { id: "client-1" }, }; mockSelf.clients.get.mockResolvedValue({ - url: 'https://malicious.com/attack', + url: "https://malicious.com/attack", }); await messageHandler!(event); - + expect(mockSelf.skipWaiting).not.toHaveBeenCalled(); }); - it('ignores other message types', async () => { + it("ignores other message types", async () => { const event = { - data: { type: 'OTHER' }, + data: { type: "OTHER" }, }; await messageHandler!(event); expect(mockSelf.skipWaiting).not.toHaveBeenCalled(); diff --git a/src/app/(auth)/__tests__/AuthError.test.tsx b/src/app/(auth)/__tests__/AuthError.test.tsx index b6e55094..6606f3f9 100644 --- a/src/app/(auth)/__tests__/AuthError.test.tsx +++ b/src/app/(auth)/__tests__/AuthError.test.tsx @@ -1,8 +1,8 @@ /** @vitest-environment jsdom */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import AuthError from '../error'; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import AuthError from "../error"; import * as Sentry from "@sentry/nextjs"; import { logger } from "@/lib/logger"; @@ -30,25 +30,29 @@ vi.mock("@/components/error-fallback", () => ({ ), })); -describe('AuthError', () => { +describe("AuthError", () => { beforeEach(() => { vi.clearAllMocks(); }); - it('logs error and captures exception on mount', () => { - const error = new Error('Auth error') as Error & { digest?: string }; - error.digest = 'auth-digest'; + it("logs error and captures exception on mount", () => { + const error = new Error("Auth error") as Error & { digest?: string }; + error.digest = "auth-digest"; const reset = vi.fn(); render(); - expect(logger.error).toHaveBeenCalledWith("[auth] Render error:", "Auth error", "auth-digest"); + expect(logger.error).toHaveBeenCalledWith( + "[auth] Render error:", + "Auth error", + "auth-digest", + ); expect(Sentry.captureException).toHaveBeenCalledWith(error, { tags: { location: "auth", digest: "auth-digest", }, }); - expect(screen.getByTestId('error-fallback')).toBeInTheDocument(); + expect(screen.getByTestId("error-fallback")).toBeInTheDocument(); }); }); diff --git a/src/app/(auth)/__tests__/error.test.tsx b/src/app/(auth)/__tests__/error.test.tsx index 9db99e94..cb771bff 100644 --- a/src/app/(auth)/__tests__/error.test.tsx +++ b/src/app/(auth)/__tests__/error.test.tsx @@ -1,9 +1,9 @@ /** @vitest-environment jsdom */ -import { describe, it, vi, expect, beforeEach } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import AuthError from '../error'; -import { logger } from '@/lib/logger'; -import * as Sentry from '@sentry/nextjs'; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import AuthError from "../error"; +import { logger } from "@/lib/logger"; +import * as Sentry from "@sentry/nextjs"; type ErrorFallbackProps = { error: Error; @@ -11,17 +11,17 @@ type ErrorFallbackProps = { homeUrl?: string; }; -vi.mock('@/lib/logger', () => ({ +vi.mock("@/lib/logger", () => ({ logger: { error: vi.fn(), }, })); -vi.mock('@sentry/nextjs', () => ({ +vi.mock("@sentry/nextjs", () => ({ captureException: vi.fn(), })); -vi.mock('@/components/error-fallback', () => ({ +vi.mock("@/components/error-fallback", () => ({ ErrorFallback: ({ error, reset, homeUrl }: ErrorFallbackProps) => (
{error.message} @@ -31,36 +31,36 @@ vi.mock('@/components/error-fallback', () => ({ ), })); -describe('AuthError', () => { - const mockError = new Error('Test auth error') as Error & { digest?: string }; - mockError.digest = 'test-digest'; +describe("AuthError", () => { + const mockError = new Error("Test auth error") as Error & { digest?: string }; + mockError.digest = "test-digest"; const mockReset = vi.fn(); beforeEach(() => { vi.clearAllMocks(); }); - it('renders correctly and logs errors', () => { + it("renders correctly and logs errors", () => { render(); - expect(screen.getByTestId('error-fallback')).toBeInTheDocument(); - expect(screen.getByText('Test auth error')).toBeInTheDocument(); - expect(screen.getByText('/')).toBeInTheDocument(); + expect(screen.getByTestId("error-fallback")).toBeInTheDocument(); + expect(screen.getByText("Test auth error")).toBeInTheDocument(); + expect(screen.getByText("/")).toBeInTheDocument(); expect(logger.error).toHaveBeenCalledWith( - '[auth] Render error:', - 'Test auth error', - 'test-digest' + "[auth] Render error:", + "Test auth error", + "test-digest", ); expect(Sentry.captureException).toHaveBeenCalledWith(mockError, { - tags: { location: 'auth', digest: 'test-digest' }, + tags: { location: "auth", digest: "test-digest" }, }); }); - it('handles reset call', () => { + it("handles reset call", () => { render(); - - fireEvent.click(screen.getByText('Reset')); + + fireEvent.click(screen.getByText("Reset")); expect(mockReset).toHaveBeenCalled(); }); }); diff --git a/src/app/(auth)/__tests__/loading.test.tsx b/src/app/(auth)/__tests__/loading.test.tsx index 9873a10e..ad0d9a04 100644 --- a/src/app/(auth)/__tests__/loading.test.tsx +++ b/src/app/(auth)/__tests__/loading.test.tsx @@ -3,13 +3,14 @@ * Next.js renders this component while the auth page is streaming. */ -import { describe, it, expect, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import React from "react"; // Mock the Loading spinner so it renders a predictable DOM node. vi.mock("@/components/loading", () => ({ - Loading: () => React.createElement("div", { "data-testid": "auth-loading-spinner" }), + Loading: () => + React.createElement("div", { "data-testid": "auth-loading-spinner" }), })); import AuthLoading from "@/app/(auth)/loading"; diff --git a/src/app/(auth)/__tests__/page.test.tsx b/src/app/(auth)/__tests__/page.test.tsx index 949a475d..093e0a8a 100644 --- a/src/app/(auth)/__tests__/page.test.tsx +++ b/src/app/(auth)/__tests__/page.test.tsx @@ -1,26 +1,28 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import LoginPage from '../page'; +import { describe, expect, it, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import LoginPage from "../page"; type FooterProps = { className?: string; }; -vi.mock('@/components/layout/footer', () => ({ - Footer: ({ className }: FooterProps) =>
Footer
, +vi.mock("@/components/layout/footer", () => ({ + Footer: ({ className }: FooterProps) => ( +
Footer
+ ), })); -vi.mock('@/components/user/login-form-client', () => ({ +vi.mock("@/components/user/login-form-client", () => ({ LoginFormClient: () =>
LoginFormClient
, })); -describe('LoginPage', () => { - it('renders login form and footer', async () => { +describe("LoginPage", () => { + it("renders login form and footer", async () => { // LoginPage is async const Page = await LoginPage(); render(Page); - expect(screen.getByTestId('login-form')).toBeDefined(); - expect(screen.getByText('Footer')).toBeDefined(); + expect(screen.getByTestId("login-form")).toBeDefined(); + expect(screen.getByText("Footer")).toBeDefined(); }); }); diff --git a/src/app/(auth)/page.tsx b/src/app/(auth)/page.tsx index 55ee9691..2f46b6ea 100644 --- a/src/app/(auth)/page.tsx +++ b/src/app/(auth)/page.tsx @@ -24,4 +24,4 @@ export default async function LoginPage() {