From 5e5c38fb21d9b949325bc664e5fcf9196e56db6b Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Fri, 15 May 2026 16:41:17 -0400 Subject: [PATCH] feat: automate deployment and update group workflows --- .gitignore | 4 + README.md | 15 +- ansible.cfg | 15 + ansible/backup-now.yml | 21 + ansible/group_vars/all.yml | 55 + ansible/group_vars/vault.yml.example | 15 + ansible/inventory/hosts.yml | 9 + ansible/requirements.yml | 7 + ansible/roles/app/handlers/main.yml | 10 + ansible/roles/app/tasks/main.yml | 196 + ansible/roles/app/templates/env.j2 | 6 + .../templates/groupe-meditation.service.j2 | 18 + ansible/roles/backup/tasks/main.yml | 18 + .../templates/groupe-meditation-backup.j2 | 18 + ansible/roles/certbot/tasks/main.yml | 17 + ansible/roles/common/tasks/main.yml | 50 + ansible/roles/nginx/handlers/main.yml | 6 + ansible/roles/nginx/tasks/main.yml | 39 + .../nginx/templates/groupe-meditation.conf.j2 | 37 + ansible/roles/postgresql/tasks/main.yml | 51 + ansible/site.yml | 38 + ansible/verify.yml | 42 + backend/app/core/security.py | 3 + backend/app/main.py | 6 +- backend/app/models/jumelage.py | 23 - backend/app/models/poste_module.py | 16 + backend/app/models/reserve.py | 19 + backend/app/models/tache.py | 30 - backend/app/routers/accueil.py | 49 +- backend/app/routers/collectes.py | 103 +- backend/app/routers/contributions.py | 109 +- backend/app/routers/depenses.py | 23 +- backend/app/routers/evenements.py | 34 +- backend/app/routers/invitations.py | 2 +- backend/app/routers/jumelages.py | 99 - backend/app/routers/membres.py | 27 +- backend/app/routers/operations_bancaires.py | 150 +- backend/app/routers/postes.py | 244 +- backend/app/routers/rapports.py | 61 +- backend/app/schemas/phase1.py | 23 + backend/app/schemas/phase7.py | 4 +- backend/app/schemas/phase8.py | 28 +- backend/app/schemas/schemas.py | 17 +- deploy/seed.py | 4 +- docs/ANSIBLE.md | 122 + docs/UTILISATEURS.md | 426 ++ frontend/package-lock.json | 6485 +++++++++++++++++ frontend/src/App.jsx | 17 +- frontend/src/pages/Accueil.jsx | 7 +- frontend/src/pages/AffecterPostes.jsx | 44 +- frontend/src/pages/Collecte.jsx | 100 +- frontend/src/pages/Depense.jsx | 2 +- frontend/src/pages/EnvoiContributions.jsx | 75 +- frontend/src/pages/EtatFinancier.jsx | 32 +- frontend/src/pages/Evenements.jsx | 26 +- frontend/src/pages/GererMembres.jsx | 61 +- frontend/src/pages/GererPostes.jsx | 224 +- frontend/src/pages/GestionMandats.jsx | 36 + frontend/src/pages/Impliquer.jsx | 2 +- frontend/src/pages/InviterMembre.jsx | 86 - frontend/src/pages/JumelerParrain.jsx | 173 - frontend/src/pages/PlanifierRotations.jsx | 6 +- frontend/src/pages/Rapports.jsx | 2 +- frontend/src/pages/TerminerMandat.jsx | 6 +- frontend/src/pages/Tresorerie.jsx | 298 +- frontend/src/stores/api.js | 25 +- frontend/src/stores/store.js | 6 +- 67 files changed, 8991 insertions(+), 1031 deletions(-) create mode 100644 .gitignore create mode 100644 ansible.cfg create mode 100644 ansible/backup-now.yml create mode 100644 ansible/group_vars/all.yml create mode 100644 ansible/group_vars/vault.yml.example create mode 100644 ansible/inventory/hosts.yml create mode 100644 ansible/requirements.yml create mode 100644 ansible/roles/app/handlers/main.yml create mode 100644 ansible/roles/app/tasks/main.yml create mode 100644 ansible/roles/app/templates/env.j2 create mode 100644 ansible/roles/app/templates/groupe-meditation.service.j2 create mode 100644 ansible/roles/backup/tasks/main.yml create mode 100644 ansible/roles/backup/templates/groupe-meditation-backup.j2 create mode 100644 ansible/roles/certbot/tasks/main.yml create mode 100644 ansible/roles/common/tasks/main.yml create mode 100644 ansible/roles/nginx/handlers/main.yml create mode 100644 ansible/roles/nginx/tasks/main.yml create mode 100644 ansible/roles/nginx/templates/groupe-meditation.conf.j2 create mode 100644 ansible/roles/postgresql/tasks/main.yml create mode 100644 ansible/site.yml create mode 100644 ansible/verify.yml delete mode 100644 backend/app/models/jumelage.py create mode 100644 backend/app/models/poste_module.py create mode 100644 backend/app/models/reserve.py delete mode 100644 backend/app/models/tache.py delete mode 100644 backend/app/routers/jumelages.py create mode 100644 docs/ANSIBLE.md create mode 100644 docs/UTILISATEURS.md create mode 100644 frontend/package-lock.json create mode 100644 frontend/src/pages/GestionMandats.jsx delete mode 100644 frontend/src/pages/InviterMembre.jsx delete mode 100644 frontend/src/pages/JumelerParrain.jsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..557d6ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.retry +ansible/group_vars/vault.yml diff --git a/README.md b/README.md index ae55f0a..e082767 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,14 @@ Application mobile-first pour la gestion des groupes AA. 1 module = 1 tâche. - **M5** Ajuster inventaire — +/−, ajout catalogue - **M6** Commander jetons — état stock 30 jours -### Gestion (G1-G9) — Exécutif +### Gestion — Exécutif - **G1** Inviter un membre — code 48h - **G2** Gérer les membres — fiche complète, droit à l'oubli -- **G3** Affecter les postes — n'affiche que les postes vacants -- **G4** Terminer un mandat — transition manuelle, note pour le successeur -- **G5** Planifier les rotations — calendrier serviteurs - **G6** Lancer un sondage — oui/non, choix multiple, unanimité -- **G7** Jumeler parrain/filleul — confidentiel - **G9** Paramètres du groupe — nom, horaire, adresse, format -- **G10** Gérer les postes — créer, abolir, candidatures +- **G11** Gestion des mandats — affecter, terminer, planifier les rotations +- **G12** Gestion des postes — créer, abolir, candidatures, accès aux modules ## Trésorerie @@ -103,6 +100,12 @@ deploy/ Service systemd, NGINX, seed, backup docs/ DEPLOIEMENT.md, RUNBOOK.md ``` +## Documentation + +- [Guide utilisateur](docs/UTILISATEURS.md) +- [Déploiement Ansible](docs/ANSIBLE.md) +- [Runbook d'exploitation](docs/RUNBOOK.md) + --- *«Nos leaders sont des serviteurs de confiance ; ils ne gouvernent pas.» — 2e Tradition* diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..f21e926 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,15 @@ +[defaults] +inventory = ansible/inventory/hosts.yml +roles_path = ansible/roles +stdout_callback = yaml +bin_ansible_callbacks = True +interpreter_python = auto_silent +host_key_checking = True +retry_files_enabled = False + +[ssh_connection] +control_path_dir = /tmp/ansible-cp + +[privilege_escalation] +become = True +become_method = sudo diff --git a/ansible/backup-now.yml b/ansible/backup-now.yml new file mode 100644 index 0000000..9662fb8 --- /dev/null +++ b/ansible/backup-now.yml @@ -0,0 +1,21 @@ +--- +- name: Executer une sauvegarde immediate + hosts: groupe_meditation + become: true + gather_facts: false + + tasks: + - name: Lancer le script de sauvegarde + ansible.builtin.command: /etc/cron.daily/groupe-meditation-backup + changed_when: true + + - name: Lister les dernieres sauvegardes + ansible.builtin.command: + cmd: ls -1t "{{ groupe_meditation_backup_dir }}" + register: backups + changed_when: false + + - name: Afficher les sauvegardes recentes + ansible.builtin.debug: + msg: "{{ backups.stdout_lines[:5] }}" + diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..e1345ac --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,55 @@ +--- +# Domaine public servi par NGINX. +groupe_meditation_domain: groupe-meditation.87-16.org + +# Chemins de déploiement. +groupe_meditation_app_dir: /opt/groupe-meditation +groupe_meditation_backend_dir: "{{ groupe_meditation_app_dir }}/backend" +groupe_meditation_deploy_dir: "{{ groupe_meditation_app_dir }}/deploy" +groupe_meditation_frontend_dir: "{{ groupe_meditation_app_dir }}/frontend" +groupe_meditation_web_root: /var/www/groupe-meditation +groupe_meditation_upload_dir: "{{ groupe_meditation_app_dir }}/uploads/preuves" + +# Service applicatif. +groupe_meditation_service_name: groupe-meditation +groupe_meditation_service_user: www-data +groupe_meditation_service_group: www-data +groupe_meditation_api_host: 127.0.0.1 +groupe_meditation_api_port: 8000 +groupe_meditation_uvicorn_workers: 2 + +# Base de donnees locale PostgreSQL. +groupe_meditation_db_name: groupe_meditation +groupe_meditation_db_user: grpmed +groupe_meditation_db_host: 127.0.0.1 +groupe_meditation_db_port: 5432 + +# A remplacer dans ansible/group_vars/vault.yml chiffre avec ansible-vault. +groupe_meditation_db_password: CHANGE_ME_DB_PASSWORD +groupe_meditation_jwt_secret: CHANGE_ME_JWT_SECRET + +groupe_meditation_database_url: >- + postgresql+asyncpg://{{ groupe_meditation_db_user }}:{{ groupe_meditation_db_password }}@{{ groupe_meditation_db_host }}:{{ groupe_meditation_db_port }}/{{ groupe_meditation_db_name }} +groupe_meditation_cors_origins: + - "https://{{ groupe_meditation_domain }}" +groupe_meditation_invitation_expire_hours: 48 + +# Initialisation applicative. Le seed existant est interactif; Ansible lui +# fournit ces valeurs seulement si la base ne contient encore aucun groupe. +groupe_meditation_run_seed: true +groupe_meditation_first_member: + prenom: CHANGE_ME + nom: "" + courriel: CHANGE_ME@example.com + telephone: CHANGE_ME + pin: "1234" + poste_numero: "1" + +# TLS Let's Encrypt. Activez apres que le DNS pointe vers la machine. +groupe_meditation_enable_tls: false +groupe_meditation_certbot_email: admin@example.com + +# Sauvegardes. +groupe_meditation_backup_dir: /var/backups/groupe-meditation +groupe_meditation_backup_retention_days: 30 + diff --git a/ansible/group_vars/vault.yml.example b/ansible/group_vars/vault.yml.example new file mode 100644 index 0000000..5ebdf17 --- /dev/null +++ b/ansible/group_vars/vault.yml.example @@ -0,0 +1,15 @@ +--- +# Copier vers ansible/group_vars/vault.yml puis chiffrer: +# ansible-vault encrypt ansible/group_vars/vault.yml + +groupe_meditation_db_password: "remplacer-par-un-mot-de-passe-solide" +groupe_meditation_jwt_secret: "sortie-de-openssl-rand-base64-48" + +groupe_meditation_first_member: + prenom: "VotrePrenom" + nom: "" + courriel: "vous@example.com" + telephone: "000-000-0000" + pin: "1234" + poste_numero: "1" + diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml new file mode 100644 index 0000000..2573e18 --- /dev/null +++ b/ansible/inventory/hosts.yml @@ -0,0 +1,9 @@ +--- +all: + children: + groupe_meditation: + hosts: + groupe-meditation-prod: + ansible_host: 192.168.12.46 + ansible_user: ansible + ansible_ssh_private_key_file: ~/.ssh/id_ed25519_ansible_chezlepro diff --git a/ansible/requirements.yml b/ansible/requirements.yml new file mode 100644 index 0000000..4a76da5 --- /dev/null +++ b/ansible/requirements.yml @@ -0,0 +1,7 @@ +--- +collections: + - name: ansible.posix + version: ">=1.5.0" + - name: community.postgresql + version: ">=3.4.0" + diff --git a/ansible/roles/app/handlers/main.yml b/ansible/roles/app/handlers/main.yml new file mode 100644 index 0000000..d745d70 --- /dev/null +++ b/ansible/roles/app/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Recharger systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Redemarrer groupe-meditation + ansible.builtin.service: + name: "{{ groupe_meditation_service_name }}" + state: restarted + diff --git a/ansible/roles/app/tasks/main.yml b/ansible/roles/app/tasks/main.yml new file mode 100644 index 0000000..2833f7b --- /dev/null +++ b/ansible/roles/app/tasks/main.yml @@ -0,0 +1,196 @@ +--- +- name: Installer les paquets requis par l'application + ansible.builtin.apt: + name: + - build-essential + - libcairo2 + - libffi-dev + - libgdk-pixbuf-2.0-0 + - libpango-1.0-0 + - libpangocairo-1.0-0 + - python3 + - python3-pip + - python3-venv + state: present + +- name: Creer les repertoires applicatifs + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: "{{ item.owner | default(groupe_meditation_service_user) }}" + group: "{{ item.group | default(groupe_meditation_service_group) }}" + mode: "{{ item.mode | default('0755') }}" + loop: + - path: "{{ groupe_meditation_app_dir }}" + owner: root + group: root + - path: "{{ groupe_meditation_upload_dir }}" + mode: "0755" + - path: "{{ groupe_meditation_web_root }}" + +- name: Synchroniser le backend + ansible.posix.synchronize: + src: "{{ playbook_dir }}/../backend/" + dest: "{{ groupe_meditation_backend_dir }}/" + delete: true + rsync_opts: + - "--exclude=venv/" + - "--exclude=.env" + - "--exclude=__pycache__/" + - "--exclude=*.pyc" + notify: Redemarrer groupe-meditation + +- name: Synchroniser les fichiers de deploiement + ansible.posix.synchronize: + src: "{{ playbook_dir }}/../deploy/" + dest: "{{ groupe_meditation_deploy_dir }}/" + delete: true + notify: Redemarrer groupe-meditation + +- name: Synchroniser le frontend + ansible.posix.synchronize: + src: "{{ playbook_dir }}/../frontend/" + dest: "{{ groupe_meditation_frontend_dir }}/" + delete: true + rsync_opts: + - "--exclude=node_modules/" + - "--exclude=dist/" + +- name: Creer l'environnement virtuel Python + ansible.builtin.command: + cmd: python3 -m venv "{{ groupe_meditation_backend_dir }}/venv" + creates: "{{ groupe_meditation_backend_dir }}/venv/bin/python" + +- name: Installer les dependances Python + ansible.builtin.pip: + requirements: "{{ groupe_meditation_backend_dir }}/requirements.txt" + virtualenv: "{{ groupe_meditation_backend_dir }}/venv" + notify: Redemarrer groupe-meditation + +- name: Deployer le fichier d'environnement backend + ansible.builtin.template: + src: env.j2 + dest: "{{ groupe_meditation_backend_dir }}/.env" + owner: "{{ groupe_meditation_service_user }}" + group: "{{ groupe_meditation_service_group }}" + mode: "0640" + no_log: true + notify: Redemarrer groupe-meditation + +- name: Installer les dependances frontend sur le poste de controle avec npm ci + ansible.builtin.command: + cmd: npm ci + chdir: "{{ playbook_dir }}/../frontend" + delegate_to: localhost + become: false + run_once: true + when: lookup('ansible.builtin.fileglob', playbook_dir + '/../frontend/package-lock.json') | length > 0 + changed_when: true + +- name: Installer les dependances frontend sur le poste de controle avec npm install + ansible.builtin.command: + cmd: npm install + chdir: "{{ playbook_dir }}/../frontend" + delegate_to: localhost + become: false + run_once: true + when: lookup('ansible.builtin.fileglob', playbook_dir + '/../frontend/package-lock.json') | length == 0 + changed_when: true + +- name: Construire le frontend sur le poste de controle + ansible.builtin.command: + cmd: npm run build + chdir: "{{ playbook_dir }}/../frontend" + delegate_to: localhost + become: false + run_once: true + changed_when: true + +- name: Deployer les fichiers statiques frontend + ansible.posix.synchronize: + src: "{{ playbook_dir }}/../frontend/dist/" + dest: "{{ groupe_meditation_web_root }}/" + delete: true + +- name: Ajuster les permissions applicatives + ansible.builtin.file: + path: "{{ item }}" + owner: "{{ groupe_meditation_service_user }}" + group: "{{ groupe_meditation_service_group }}" + recurse: true + loop: + - "{{ groupe_meditation_app_dir }}" + - "{{ groupe_meditation_web_root }}" + +- name: Installer l'unite systemd + ansible.builtin.template: + src: groupe-meditation.service.j2 + dest: "/etc/systemd/system/{{ groupe_meditation_service_name }}.service" + owner: root + group: root + mode: "0644" + notify: + - Recharger systemd + - Redemarrer groupe-meditation + +- name: Appliquer les handlers avant le seed + ansible.builtin.meta: flush_handlers + +- name: Compter les groupes existants + community.postgresql.postgresql_query: + db: "{{ groupe_meditation_db_name }}" + login_user: "{{ groupe_meditation_db_user }}" + login_password: "{{ groupe_meditation_db_password }}" + login_host: "{{ groupe_meditation_db_host }}" + port: "{{ groupe_meditation_db_port }}" + query: "SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'groupes';" + register: groupes_table + no_log: true + +- name: Compter les donnees applicatives existantes + community.postgresql.postgresql_query: + db: "{{ groupe_meditation_db_name }}" + login_user: "{{ groupe_meditation_db_user }}" + login_password: "{{ groupe_meditation_db_password }}" + login_host: "{{ groupe_meditation_db_host }}" + port: "{{ groupe_meditation_db_port }}" + query: "SELECT COUNT(*) AS count FROM groupes;" + register: groupes_count + when: groupes_table.query_result[0].count | int > 0 + no_log: true + +- name: Initialiser la base avec le seed existant + ansible.builtin.shell: + cmd: | + set -euo pipefail + printf '%s\n%s\n%s\n%s\n%s\n%s\n' \ + {{ groupe_meditation_first_member.prenom | quote }} \ + {{ groupe_meditation_first_member.nom | default("") | quote }} \ + {{ groupe_meditation_first_member.courriel | quote }} \ + {{ groupe_meditation_first_member.telephone | quote }} \ + {{ groupe_meditation_first_member.pin | quote }} \ + {{ groupe_meditation_first_member.poste_numero | default("1") | quote }} \ + | {{ groupe_meditation_backend_dir }}/venv/bin/python ../deploy/seed.py + executable: /bin/bash + chdir: "{{ groupe_meditation_backend_dir }}" + when: + - groupe_meditation_run_seed | bool + - groupes_table.query_result[0].count | int == 0 or (groupes_count.query_result[0].count | default(0) | int == 0) + no_log: true + notify: Redemarrer groupe-meditation + +- name: Activer et demarrer le service applicatif + ansible.builtin.service: + name: "{{ groupe_meditation_service_name }}" + state: started + enabled: true + +- name: Verifier la sante locale de l'API + ansible.builtin.uri: + url: "http://{{ groupe_meditation_api_host }}:{{ groupe_meditation_api_port }}/api/health" + return_content: true + status_code: 200 + register: api_health + retries: 6 + delay: 5 + until: api_health.status == 200 diff --git a/ansible/roles/app/templates/env.j2 b/ansible/roles/app/templates/env.j2 new file mode 100644 index 0000000..959a268 --- /dev/null +++ b/ansible/roles/app/templates/env.j2 @@ -0,0 +1,6 @@ +# Gere par Ansible. +DATABASE_URL={{ groupe_meditation_database_url }} +JWT_SECRET={{ groupe_meditation_jwt_secret }} +CORS_ORIGINS={{ groupe_meditation_cors_origins | to_json }} +INVITATION_EXPIRE_HOURS={{ groupe_meditation_invitation_expire_hours }} + diff --git a/ansible/roles/app/templates/groupe-meditation.service.j2 b/ansible/roles/app/templates/groupe-meditation.service.j2 new file mode 100644 index 0000000..31828f7 --- /dev/null +++ b/ansible/roles/app/templates/groupe-meditation.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Groupe Meditation API +After=network.target postgresql.service +Requires=postgresql.service + +[Service] +Type=simple +User={{ groupe_meditation_service_user }} +Group={{ groupe_meditation_service_group }} +WorkingDirectory={{ groupe_meditation_backend_dir }} +Environment=PATH={{ groupe_meditation_backend_dir }}/venv/bin +ExecStart={{ groupe_meditation_backend_dir }}/venv/bin/uvicorn app.main:app --host {{ groupe_meditation_api_host }} --port {{ groupe_meditation_api_port }} --workers {{ groupe_meditation_uvicorn_workers }} +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target + diff --git a/ansible/roles/backup/tasks/main.yml b/ansible/roles/backup/tasks/main.yml new file mode 100644 index 0000000..6c4c326 --- /dev/null +++ b/ansible/roles/backup/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Creer le repertoire de sauvegardes + ansible.builtin.file: + path: "{{ groupe_meditation_backup_dir }}" + state: directory + owner: root + group: root + mode: "0750" + +- name: Installer la sauvegarde quotidienne + ansible.builtin.template: + src: groupe-meditation-backup.j2 + dest: /etc/cron.daily/groupe-meditation-backup + owner: root + group: root + mode: "0750" + no_log: true + diff --git a/ansible/roles/backup/templates/groupe-meditation-backup.j2 b/ansible/roles/backup/templates/groupe-meditation-backup.j2 new file mode 100644 index 0000000..57bb711 --- /dev/null +++ b/ansible/roles/backup/templates/groupe-meditation-backup.j2 @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +BACKUP_DIR="{{ groupe_meditation_backup_dir }}" +RETENTION_DAYS="{{ groupe_meditation_backup_retention_days }}" +DB_NAME="{{ groupe_meditation_db_name }}" +DB_USER="{{ groupe_meditation_db_user }}" +DB_HOST="{{ groupe_meditation_db_host }}" +DATE="$(date +%Y-%m-%d_%H%M)" + +mkdir -p "$BACKUP_DIR" +export PGPASSWORD="{{ groupe_meditation_db_password }}" + +pg_dump -U "$DB_USER" -h "$DB_HOST" -Fc "$DB_NAME" > "$BACKUP_DIR/grpmed_${DATE}.dump" +find "$BACKUP_DIR" -name "grpmed_*.dump" -mtime +"$RETENTION_DAYS" -delete + +echo "[$(date)] Sauvegarde terminee: grpmed_${DATE}.dump" + diff --git a/ansible/roles/certbot/tasks/main.yml b/ansible/roles/certbot/tasks/main.yml new file mode 100644 index 0000000..f53fa8e --- /dev/null +++ b/ansible/roles/certbot/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Installer Certbot + ansible.builtin.apt: + name: + - certbot + - python3-certbot-nginx + state: present + +- name: Obtenir ou renouveler le certificat Let's Encrypt + ansible.builtin.command: + cmd: >- + certbot --nginx --non-interactive --agree-tos + --email {{ groupe_meditation_certbot_email }} + -d {{ groupe_meditation_domain }} + args: + creates: "/etc/letsencrypt/live/{{ groupe_meditation_domain }}/fullchain.pem" + diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..7641732 --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- name: Installer les sources APT Debian standard + ansible.builtin.copy: + dest: /etc/apt/sources.list.d/debian.sources + owner: root + group: root + mode: "0644" + content: | + Types: deb + URIs: https://deb.debian.org/debian + Suites: bookworm bookworm-updates + Components: main + Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + + Types: deb + URIs: https://deb.debian.org/debian-security + Suites: bookworm-security + Components: main + Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + register: debian_sources + +- name: Nettoyer les anciens index APT si les sources changent + ansible.builtin.file: + path: /var/lib/apt/lists + state: absent + when: debian_sources.changed + +- name: Recreer le repertoire des listes APT + ansible.builtin.file: + path: /var/lib/apt/lists/partial + state: directory + owner: _apt + group: root + mode: "0700" + +- name: Mettre a jour le cache APT + ansible.builtin.apt: + update_cache: true + cache_valid_time: 0 + +- name: Installer les paquets systeme communs + ansible.builtin.apt: + name: + - acl + - ca-certificates + - curl + - gnupg + - rsync + - unzip + state: present diff --git a/ansible/roles/nginx/handlers/main.yml b/ansible/roles/nginx/handlers/main.yml new file mode 100644 index 0000000..5396263 --- /dev/null +++ b/ansible/roles/nginx/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Recharger nginx + ansible.builtin.service: + name: nginx + state: reloaded + diff --git a/ansible/roles/nginx/tasks/main.yml b/ansible/roles/nginx/tasks/main.yml new file mode 100644 index 0000000..a761b29 --- /dev/null +++ b/ansible/roles/nginx/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Installer NGINX + ansible.builtin.apt: + name: nginx + state: present + +- name: Installer le site NGINX + ansible.builtin.template: + src: groupe-meditation.conf.j2 + dest: /etc/nginx/sites-available/groupe-meditation + owner: root + group: root + mode: "0644" + notify: Recharger nginx + +- name: Activer le site NGINX + ansible.builtin.file: + src: /etc/nginx/sites-available/groupe-meditation + dest: /etc/nginx/sites-enabled/groupe-meditation + state: link + force: true + notify: Recharger nginx + +- name: Desactiver le site NGINX par defaut + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: Recharger nginx + +- name: Verifier la configuration NGINX + ansible.builtin.command: nginx -t + changed_when: false + +- name: Activer et demarrer NGINX + ansible.builtin.service: + name: nginx + state: started + enabled: true + diff --git a/ansible/roles/nginx/templates/groupe-meditation.conf.j2 b/ansible/roles/nginx/templates/groupe-meditation.conf.j2 new file mode 100644 index 0000000..5e6a345 --- /dev/null +++ b/ansible/roles/nginx/templates/groupe-meditation.conf.j2 @@ -0,0 +1,37 @@ +server { + listen 80; + server_name {{ groupe_meditation_domain }}; + + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + + location /api/ { + proxy_pass http://{{ groupe_meditation_api_host }}:{{ groupe_meditation_api_port }}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + root {{ groupe_meditation_web_root }}; + index index.html; + + location ~* \.(js|css|png|jpg|svg|woff2|ico)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + client_max_body_size 5M; +} + diff --git a/ansible/roles/postgresql/tasks/main.yml b/ansible/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..c61a4bb --- /dev/null +++ b/ansible/roles/postgresql/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Installer PostgreSQL et les dependances Python + ansible.builtin.apt: + name: + - postgresql + - postgresql-client + - python3-psycopg2 + state: present + +- name: Activer PostgreSQL + ansible.builtin.service: + name: postgresql + state: started + enabled: true + +- name: Creer l'utilisateur PostgreSQL applicatif + community.postgresql.postgresql_user: + name: "{{ groupe_meditation_db_user }}" + password: "{{ groupe_meditation_db_password }}" + role_attr_flags: LOGIN + state: present + become_user: postgres + +- name: Creer la base PostgreSQL applicative + community.postgresql.postgresql_db: + name: "{{ groupe_meditation_db_name }}" + owner: "{{ groupe_meditation_db_user }}" + encoding: UTF8 + state: present + become_user: postgres + +- name: Donner les droits sur le schema public + community.postgresql.postgresql_privs: + db: "{{ groupe_meditation_db_name }}" + type: schema + objs: public + roles: "{{ groupe_meditation_db_user }}" + privs: ALL + state: present + become_user: postgres + +- name: Verifier la connexion PostgreSQL TCP locale + community.postgresql.postgresql_query: + db: "{{ groupe_meditation_db_name }}" + login_user: "{{ groupe_meditation_db_user }}" + login_password: "{{ groupe_meditation_db_password }}" + login_host: "{{ groupe_meditation_db_host }}" + port: "{{ groupe_meditation_db_port }}" + query: SELECT 1; + no_log: true + diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..5167571 --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,38 @@ +--- +- name: Installer Groupe Meditation + hosts: groupe_meditation + become: true + gather_facts: true + + pre_tasks: + - name: Charger les secrets locaux si presents + ansible.builtin.include_vars: + file: "{{ playbook_dir }}/group_vars/vault.yml" + when: lookup('ansible.builtin.fileglob', playbook_dir + '/group_vars/vault.yml') | length > 0 + no_log: true + + - name: Valider Debian + ansible.builtin.assert: + that: + - ansible_facts.os_family == "Debian" + fail_msg: "Ce playbook cible Debian." + + - name: Valider les secrets obligatoires + ansible.builtin.assert: + that: + - groupe_meditation_db_password != "CHANGE_ME_DB_PASSWORD" + - groupe_meditation_jwt_secret != "CHANGE_ME_JWT_SECRET" + - groupe_meditation_first_member.prenom != "CHANGE_ME" + - groupe_meditation_first_member.courriel != "CHANGE_ME@example.com" + - groupe_meditation_first_member.telephone != "CHANGE_ME" + - groupe_meditation_first_member.pin is match('^[0-9]{4}$') + fail_msg: "Renseignez les variables sensibles, idealement dans ansible/group_vars/vault.yml chiffre." + + roles: + - common + - postgresql + - app + - nginx + - backup + - role: certbot + when: groupe_meditation_enable_tls | bool diff --git a/ansible/verify.yml b/ansible/verify.yml new file mode 100644 index 0000000..bae1d37 --- /dev/null +++ b/ansible/verify.yml @@ -0,0 +1,42 @@ +--- +- name: Verifier Groupe Meditation + hosts: groupe_meditation + become: true + gather_facts: false + + tasks: + - name: Verifier le service applicatif + ansible.builtin.service_facts: + + - name: Valider que le service est actif + ansible.builtin.assert: + that: + - ansible_facts.services[groupe_meditation_service_name + '.service'].state == 'running' + fail_msg: "Le service {{ groupe_meditation_service_name }} n'est pas actif." + + - name: Verifier la sante API locale + ansible.builtin.uri: + url: "http://{{ groupe_meditation_api_host }}:{{ groupe_meditation_api_port }}/api/health" + status_code: 200 + return_content: true + register: api_health + + - name: Afficher la reponse API + ansible.builtin.debug: + var: api_health.json + + - name: Verifier la configuration NGINX + ansible.builtin.command: nginx -t + changed_when: false + + - name: Verifier la presence du frontend + ansible.builtin.stat: + path: "{{ groupe_meditation_web_root }}/index.html" + register: frontend_index + + - name: Valider le frontend + ansible.builtin.assert: + that: + - frontend_index.stat.exists + fail_msg: "Le fichier {{ groupe_meditation_web_root }}/index.html est absent." + diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 90642e6..b0c4fb6 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -42,12 +42,15 @@ async def get_current_membre( from app.models.membre import Membre payload = decode_token(credentials.credentials) membre_id = payload.get("sub") + groupe_id = payload.get("grp") if not membre_id: raise HTTPException(status_code=401, detail="Token invalide") result = await db.execute(select(Membre).where(Membre.id == membre_id, Membre.actif == True)) membre = result.scalar_one_or_none() if not membre: raise HTTPException(status_code=401, detail="Membre introuvable ou inactif") + if groupe_id and str(membre.groupe_id) != str(groupe_id): + raise HTTPException(status_code=401, detail="Session invalide pour ce groupe") return membre diff --git a/backend/app/main.py b/backend/app/main.py index 9d28751..b1aedf6 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -9,7 +9,7 @@ from fastapi.middleware.cors import CORSMiddleware from app.core.config import settings from app.core.database import engine, Base -from app.routers import auth, membres, postes, accueil, invitations, collectes, reunions, depenses, decisions, calendrier, rapports, rotations, parametres, presences, pv, rapports_rsg, jetons, anniversaires, commandes_jetons, litterature, ventes_litterature, contributions, config_repartition, sondages, jumelages, propositions, operations_bancaires, evenements +from app.routers import auth, membres, postes, accueil, invitations, collectes, reunions, depenses, decisions, calendrier, rapports, rotations, parametres, presences, pv, rapports_rsg, jetons, anniversaires, commandes_jetons, litterature, ventes_litterature, contributions, config_repartition, sondages, propositions, operations_bancaires, evenements # Import models pour que create_all crée toutes les tables from app.models.sync_log import SyncLog # noqa: F401 @@ -17,7 +17,8 @@ from app.models.proposition import Proposition # noqa: F401 from app.models.operation_bancaire import OperationBancaire # noqa: F401 from app.models.evenement import Evenement # noqa: F401 from app.models.candidature import Candidature # noqa: F401 -from app.models.tache import Tache, PosteTache # noqa: F401 +from app.models.poste_module import PosteModule # noqa: F401 +from app.models.reserve import Reserve # noqa: F401 @asynccontextmanager @@ -68,7 +69,6 @@ app.include_router(ventes_litterature.router, prefix="/api") app.include_router(contributions.router, prefix="/api") app.include_router(config_repartition.router, prefix="/api") app.include_router(sondages.router, prefix="/api") -app.include_router(jumelages.router, prefix="/api") app.include_router(propositions.router, prefix="/api") app.include_router(operations_bancaires.router, prefix="/api") app.include_router(evenements.router, prefix="/api") diff --git a/backend/app/models/jumelage.py b/backend/app/models/jumelage.py deleted file mode 100644 index 437f635..0000000 --- a/backend/app/models/jumelage.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Table jumelages — Parrainage discret.""" -import uuid -from datetime import date, datetime, timezone -from sqlalchemy import String, Date, DateTime, ForeignKey, Enum as SAEnum -from sqlalchemy.orm import Mapped, mapped_column -from app.core.database import Base - - -class Jumelage(Base): - __tablename__ = "jumelages" - - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) - groupe_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("groupes.id"), nullable=False) - parrain_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("membres.id"), nullable=False) - filleul_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("membres.id"), nullable=False) - date_jumelage: Mapped[date] = mapped_column(Date, nullable=False) - statut: Mapped[str] = mapped_column( - SAEnum("actif", "termine", name="statut_jumelage"), - default="actif", - ) - note: Mapped[str | None] = mapped_column(String(255)) - cree_par: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("membres.id")) - cree_le: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) diff --git a/backend/app/models/poste_module.py b/backend/app/models/poste_module.py new file mode 100644 index 0000000..6da8427 --- /dev/null +++ b/backend/app/models/poste_module.py @@ -0,0 +1,16 @@ +"""Acces aux modules par poste.""" +import uuid +from datetime import datetime, timezone +from sqlalchemy import DateTime, ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column +from app.core.database import Base + + +class PosteModule(Base): + __tablename__ = "postes_modules" + + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) + poste_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("postes.id"), nullable=False) + module_code: Mapped[str] = mapped_column(String(20), nullable=False) + cree_le: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + diff --git a/backend/app/models/reserve.py b/backend/app/models/reserve.py new file mode 100644 index 0000000..16f6667 --- /dev/null +++ b/backend/app/models/reserve.py @@ -0,0 +1,19 @@ +"""Table reserves — montants mis de cote par le groupe.""" +import uuid +from datetime import datetime, timezone +from sqlalchemy import Boolean, DateTime, ForeignKey, Numeric, String, Text +from sqlalchemy.orm import Mapped, mapped_column +from app.core.database import Base + + +class Reserve(Base): + __tablename__ = "reserves" + + id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) + groupe_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("groupes.id"), nullable=False) + nom: Mapped[str] = mapped_column(String(120), nullable=False) + montant: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False) + note: Mapped[str | None] = mapped_column(Text) + active: Mapped[bool] = mapped_column(Boolean, default=True) + cree_le: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + diff --git a/backend/app/models/tache.py b/backend/app/models/tache.py deleted file mode 100644 index 9707e1a..0000000 --- a/backend/app/models/tache.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Table taches — tâches assignables aux postes.""" -import uuid -from datetime import datetime, timezone -from sqlalchemy import String, Text, DateTime, ForeignKey, Integer -from sqlalchemy.orm import Mapped, mapped_column -from app.core.database import Base - - -class Tache(Base): - __tablename__ = "taches" - - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) - nom: Mapped[str] = mapped_column(String(200), nullable=False) - informations: Mapped[str | None] = mapped_column(Text) - ordre_affichage: Mapped[int] = mapped_column(Integer, default=99) - cree_le: Mapped[datetime] = mapped_column( - DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) - ) - - -class PosteTache(Base): - """Association poste ↔ tâche (N:N). Une tâche peut être affectée à plusieurs postes.""" - __tablename__ = "postes_taches" - - id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) - poste_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("postes.id"), nullable=False) - tache_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("taches.id"), nullable=False) - cree_le: Mapped[datetime] = mapped_column( - DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) - ) diff --git a/backend/app/routers/accueil.py b/backend/app/routers/accueil.py index 1e6769e..784f141 100644 --- a/backend/app/routers/accueil.py +++ b/backend/app/routers/accueil.py @@ -8,6 +8,7 @@ from app.core.security import get_current_membre from app.models.affectation import Affectation from app.models.poste import Poste from app.models.groupe import Groupe +from app.models.poste_module import PosteModule from app.schemas.schemas import AccueilResponse, ModuleTuile, MembrePublic router = APIRouter(prefix="/accueil", tags=["Accueil"]) @@ -15,16 +16,18 @@ router = APIRouter(prefix="/accueil", tags=["Accueil"]) # ── Définition des modules ── MODULES_CONSULTATION = [ ModuleTuile(code="C1", nom="Rapports PDF", description="Finances, PV, résolutions", icone="📄", couleur="blue"), - ModuleTuile(code="C2", nom="Événements à venir", description="Anniversaires et événements", icone="📅", couleur="green"), + ModuleTuile(code="C2", nom="Événements", description="Anniversaires et événements", icone="📅", couleur="green"), ModuleTuile(code="C4", nom="Littérature", description="Inventaire et prix", icone="📚", couleur="purple"), ModuleTuile(code="C5", nom="Décisions en cours", description="Suivi réunion d'affaires", icone="📌", couleur="red"), ModuleTuile(code="C6", nom="Votes ouverts", description="Conscience de groupe", icone="🗳️", couleur="indigo"), ModuleTuile(code="C7", nom="Mon profil", description="Modifier, PIN, notifications", icone="👤", couleur="gray"), ModuleTuile(code="C8", nom="Propositions", description="Soumettre, seconder, résolutions", icone="📋", couleur="orange"), - ModuleTuile(code="C9", nom="S'impliquer", description="Postes disponibles, candidature", icone="🙋", couleur="yellow"), + ModuleTuile(code="C9", nom="Postes à pourvoir", description="Postes disponibles, candidature", icone="🙋", couleur="yellow"), ModuleTuile(code="C10", nom="État financier", description="Résultats et transactions", icone="📊", couleur="blue"), ] +MODULES_PAR_CODE = {m.code: m for m in MODULES_CONSULTATION} + # Modules de saisie visibles par TOUS les membres MODULES_SAISIE_TOUS = [ ModuleTuile(code="S1", nom="Collecte 7e", description="Saisir le panier", icone="💰", couleur="green"), @@ -61,17 +64,17 @@ MODULES_SAISIE = { } MODULES_GESTION = [ - ModuleTuile(code="G1", nom="Inviter un membre", description="Code d'invitation 48h", icone="➕", couleur="green"), - ModuleTuile(code="G2", nom="Gérer les membres", description="Fiche complète, désactiver", icone="👥", couleur="blue"), - ModuleTuile(code="G3", nom="Affecter les postes", description="Assigner un serviteur", icone="🎯", couleur="purple"), - ModuleTuile(code="G4", nom="Terminer un mandat", description="Transition manuelle", icone="🔄", couleur="orange"), - ModuleTuile(code="G5", nom="Planifier rotations", description="Calendrier serviteurs", icone="📅", couleur="green"), + ModuleTuile(code="G2", nom="Gestion des membres", description="Membres, invitations, désactivation", icone="👥", couleur="blue"), ModuleTuile(code="G6", nom="Lancer un sondage", description="Conscience de groupe", icone="📊", couleur="indigo"), - ModuleTuile(code="G7", nom="Jumeler parrain", description="Parrain / filleul", icone="🤝", couleur="yellow"), ModuleTuile(code="G9", nom="Paramètres groupe", description="Nom, horaire, adresse", icone="🔧", couleur="gray"), - ModuleTuile(code="G10", nom="Gérer les postes", description="Créer, abolir, candidatures", icone="📋", couleur="purple"), + ModuleTuile(code="G11", nom="Gestion des mandats", description="Affecter, terminer, rotations", icone="🎯", couleur="purple"), + ModuleTuile(code="G12", nom="Gestion des postes", description="Postes, candidatures, accès", icone="📋", couleur="purple"), ] +for _modules in [MODULES_SAISIE_TOUS, MODULES_GESTION, *MODULES_SAISIE.values()]: + for _m in _modules: + MODULES_PAR_CODE[_m.code] = _m + @router.get("/", response_model=AccueilResponse) async def accueil( @@ -90,11 +93,13 @@ async def accueil( postes_noms = [] postes_categories = [] + poste_ids = [] for a in affectations: poste = await db.get(Poste, a.poste_id) if poste: postes_noms.append(poste.nom) postes_categories.append(poste.categorie) + poste_ids.append(poste.id) # Modules de consultation : toujours visibles modules_consultation = MODULES_CONSULTATION @@ -102,14 +107,32 @@ async def accueil( # Modules de saisie : collecte pour tous + modules par poste modules_saisie = list(MODULES_SAISIE_TOUS) codes_vus = {"S1", "S5"} - for nom_poste in postes_noms: - for m in MODULES_SAISIE.get(nom_poste, []): - if m.code not in codes_vus: + result = await db.execute(select(PosteModule).where(PosteModule.poste_id.in_(poste_ids))) if poste_ids else None + modules_configures = result.scalars().all() if result else [] + + if modules_configures: + codes = sorted({pm.module_code for pm in modules_configures}) + for code in codes: + m = MODULES_PAR_CODE.get(code) + if m and not code.startswith("G") and m.code not in codes_vus: modules_saisie.append(m) codes_vus.add(m.code) + else: + for nom_poste in postes_noms: + for m in MODULES_SAISIE.get(nom_poste, []): + if m.code not in codes_vus: + modules_saisie.append(m) + codes_vus.add(m.code) # Modules de gestion : seulement si exécutif - modules_gestion = MODULES_GESTION if "executif" in postes_categories else [] + if modules_configures: + modules_gestion = [ + MODULES_PAR_CODE[code] + for code in sorted({pm.module_code for pm in modules_configures}) + if code.startswith("G") and code in MODULES_PAR_CODE + ] + else: + modules_gestion = MODULES_GESTION if "executif" in postes_categories else [] # Info groupe pour le frontend groupe = await db.get(Groupe, membre.groupe_id) diff --git a/backend/app/routers/collectes.py b/backend/app/routers/collectes.py index e1cbde7..7a7be62 100644 --- a/backend/app/routers/collectes.py +++ b/backend/app/routers/collectes.py @@ -12,6 +12,7 @@ from app.models.membre import Membre from app.models.depense import Depense from app.models.envoi_contribution import EnvoiContribution from app.models.operation_bancaire import OperationBancaire +from app.models.reserve import Reserve from app.models.affectation import Affectation from app.models.poste import Poste from app.schemas.phase1 import CollecteCreate, CollecteOut, CollecteUpdate, SoldesOut @@ -291,7 +292,28 @@ async def soldes_tresorerie( result = await db.execute(q) retraits_depuis = float(result.scalar()) - solde_banque = solde_releve + depots_depuis - retraits_depuis + # Contributions débitées après relevé + q = select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where( + EnvoiContribution.groupe_id == gid, + EnvoiContribution.debitee == True, + ) + if filtre_depuis: + q = q.where(EnvoiContribution.date_envoi > filtre_depuis) + result = await db.execute(q) + contributions_debitees_depuis = float(result.scalar()) + + # Dépenses bancaires débitées après relevé + q = select(func.coalesce(func.sum(Depense.montant), 0)).where( + Depense.groupe_id == gid, + Depense.paye == True, + Depense.methode.in_(["VIREMENT", "CHEQUE"]), + ) + if filtre_depuis: + q = q.where(Depense.date_confirmation > filtre_depuis) + result = await db.execute(q) + depenses_bancaires_depuis = float(result.scalar()) + + solde_banque = solde_releve + depots_depuis - retraits_depuis - contributions_debitees_depuis - depenses_bancaires_depuis # ── Dépenses engagées non payées (chèque/virement votés mais pas encore débités) ── result = await db.execute( @@ -303,25 +325,35 @@ async def soldes_tresorerie( ) engagements_non_debites = float(result.scalar()) - # ── Contributions envoyées non débitées ── - # (on considère toutes les contributions comme engagées) + # ── Contributions engagées non débitées ── + result = await db.execute( + select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where( + EnvoiContribution.groupe_id == gid, + EnvoiContribution.engagee == True, + EnvoiContribution.debitee == False, + ) + ) + contributions_non_debitees = float(result.scalar()) + engagements_non_debites += contributions_non_debitees # ── Réserves ── - reserve_prudente = 500.0 - - # Réserve anniversaire : +20$/mois depuis la création du groupe - from app.models.groupe import Groupe as GroupeModel - groupe = await db.get(GroupeModel, gid) - mois_depuis_creation = 0 - if groupe and groupe.cree_le: - from datetime import date as date_type - aujourdhui = date_type.today() - cree = groupe.cree_le.date() if hasattr(groupe.cree_le, 'date') else groupe.cree_le - mois_depuis_creation = (aujourdhui.year - cree.year) * 12 + (aujourdhui.month - cree.month) - mois_depuis_creation = max(0, mois_depuis_creation) - reserve_anniversaire = mois_depuis_creation * 20.0 - - total_reserves = reserve_prudente + reserve_anniversaire + result = await db.execute( + select(Reserve).where( + Reserve.groupe_id == gid, + Reserve.active == True, + ).order_by(Reserve.nom) + ) + reserves = result.scalars().all() + reserves_details = [ + { + "id": str(r.id), + "nom": r.nom, + "montant": float(r.montant), + "note": r.note, + } + for r in reserves + ] + total_reserves = sum(r["montant"] for r in reserves_details) # ── Somme disponible ── disponible = solde_banque - engagements_non_debites - total_reserves @@ -337,6 +369,11 @@ async def soldes_tresorerie( OperationBancaire.groupe_id == gid, OperationBancaire.type == "depot")) total_depots = float(result.scalar()) + result = await db.execute( + select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == gid, OperationBancaire.type == "retrait")) + total_retraits = float(result.scalar()) + result = await db.execute( select(func.coalesce(func.sum(Depense.montant), 0)).where( Depense.groupe_id == gid, Depense.paye == True, Depense.methode == "COLLECTE")) @@ -348,7 +385,7 @@ async def soldes_tresorerie( VenteLitterature.groupe_id == gid, VenteLitterature.remis_tresorier == True)) ventes_recues = float(result.scalar()) - encaisse = total_collectes_recues + ventes_recues - total_depots - depenses_cash + encaisse = total_collectes_recues + ventes_recues + total_retraits - total_depots - depenses_cash # ── Argent en circulation (collectes non remises au trésorier) ── result = await db.execute( @@ -393,12 +430,13 @@ async def soldes_tresorerie( return { "solde_banque": round(solde_banque, 2), "date_releve": date_releve, - "encaisse": round(max(0, encaisse), 2), + "encaisse": round(encaisse, 2), "disponible": round(disponible, 2), "engagements_non_debites": round(engagements_non_debites, 2), - "reserve_prudente": reserve_prudente, - "reserve_anniversaire": round(reserve_anniversaire, 2), + "reserve_prudente": 0, + "reserve_anniversaire": 0, "total_reserves": round(total_reserves, 2), + "reserves_details": reserves_details, "en_possession": round(total_en_circulation, 2), "en_possession_details": details_circulation, "ventes_non_remises": round(total_ventes_non_remises, 2), @@ -410,6 +448,7 @@ async def soldes_tresorerie( async def registre_transactions( mois: int | None = None, annee: int | None = None, + type_transaction: str | None = None, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): @@ -428,6 +467,11 @@ async def registre_transactions( return d.year == annee return True + def ajouter_transaction(transaction): + if type_transaction and transaction["type"] != type_transaction: + return + transactions.append(transaction) + # 1. Collectes reçues result = await db.execute( select(Collecte).where(Collecte.groupe_id == gid, Collecte.statut == "recu") @@ -436,7 +480,7 @@ async def registre_transactions( for c in result.scalars().all(): if filtre_mois(c.date_collecte): saisi = await db.get(Membre, c.saisi_par) if c.saisi_par else None - transactions.append({ + ajouter_transaction({ "date": str(c.date_collecte), "type": "collecte", "description": f"Collecte 7e Tradition", @@ -455,7 +499,7 @@ async def registre_transactions( for v in result.scalars().all(): if filtre_mois(v.date_vente): item = await db.get(Litterature, v.litterature_id) - transactions.append({ + ajouter_transaction({ "date": str(v.date_vente), "type": "vente_litterature", "description": f"Vente : {item.titre if item else '?'} (x{v.quantite})", @@ -472,7 +516,7 @@ async def registre_transactions( for d in result.scalars().all(): if filtre_mois(d.date_depense): qui = await db.get(Membre, d.qui) - transactions.append({ + ajouter_transaction({ "date": str(d.date_depense), "type": "depense", "description": f"{d.quoi} ({d.methode})", @@ -483,12 +527,15 @@ async def registre_transactions( # 4. Envois de contributions result = await db.execute( - select(EnvoiContribution).where(EnvoiContribution.groupe_id == gid) + select(EnvoiContribution).where( + EnvoiContribution.groupe_id == gid, + EnvoiContribution.debitee == True, + ) .order_by(EnvoiContribution.date_envoi) ) for e in result.scalars().all(): if filtre_mois(e.date_envoi): - transactions.append({ + ajouter_transaction({ "date": str(e.date_envoi), "type": "contribution", "description": f"Contribution {e.destination} ({e.methode})", @@ -508,7 +555,7 @@ async def registre_transactions( if filtre_mois(op.date_operation): m = await db.get(Membre, op.effectue_par) is_depot = op.type == "depot" - transactions.append({ + ajouter_transaction({ "date": str(op.date_operation), "type": f"banque_{op.type}", "description": f"{'Dépôt' if is_depot else 'Retrait'} bancaire{(' — ' + op.note) if op.note else ''}", diff --git a/backend/app/routers/contributions.py b/backend/app/routers/contributions.py index 062f4b7..18507ff 100644 --- a/backend/app/routers/contributions.py +++ b/backend/app/routers/contributions.py @@ -2,7 +2,7 @@ from datetime import date from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, desc +from sqlalchemy import select, desc, func from app.core.database import get_db from app.core.security import get_current_membre @@ -28,6 +28,20 @@ DEFAUT_REPARTITION = { } +async def _prochain_numero_resolution(groupe_id, annee: int, db: AsyncSession) -> str: + from app.models.proposition import Proposition + + result = await db.execute( + select(func.count(Proposition.id)).where( + Proposition.groupe_id == groupe_id, + Proposition.numero_resolution.isnot(None), + Proposition.numero_resolution.like(f"RES-{annee}-%"), + ) + ) + count = result.scalar() or 0 + return f"RES-{annee}-{count + 1:03d}" + + async def require_tresorier(membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db)): result = await db.execute( select(Affectation).join(Poste).where( @@ -92,30 +106,40 @@ async def enregistrer_envoi( membre=Depends(require_tresorier), db: AsyncSession = Depends(get_db), ): - """M4 — Enregistrer un envoi de contribution. Exige une résolution adoptée.""" + """M4 — Enregistrer un envoi de contribution et créer sa résolution.""" from app.models.proposition import Proposition if req.destination not in DESTINATIONS: raise HTTPException(status_code=400, detail=f"Destination invalide. Choix: {DESTINATIONS}") if req.methode.upper() not in METHODES_CONTRIBUTION: raise HTTPException(status_code=400, detail=f"Méthode invalide. Choix: {METHODES_CONTRIBUTION}") + if req.proposeur_id == req.secondeur_id: + raise HTTPException(status_code=400, detail="Le proposeur et le secondeur doivent être différents") - # Valider la résolution - if not req.resolution_id: - raise HTTPException( - status_code=400, - detail="Une contribution doit faire l'objet d'une résolution adoptée. Créez d'abord une proposition, faites-la seconder et adopter." - ) - result = await db.execute( - select(Proposition).where( - Proposition.id == req.resolution_id, - Proposition.groupe_id == membre.groupe_id, - Proposition.statut == "adoptee", - ) + proposeur = await db.get(Membre, req.proposeur_id) + secondeur = await db.get(Membre, req.secondeur_id) + if not proposeur or proposeur.groupe_id != membre.groupe_id or not proposeur.actif: + raise HTTPException(status_code=404, detail="Proposeur introuvable ou inactif") + if not secondeur or secondeur.groupe_id != membre.groupe_id or not secondeur.actif: + raise HTTPException(status_code=404, detail="Secondeur introuvable ou inactif") + + numero_resolution = await _prochain_numero_resolution(membre.groupe_id, date.today().year, db) + resolution = Proposition( + groupe_id=membre.groupe_id, + type_proposition="contribution", + sujet=f"Envoi de {req.montant:.2f} $ à {DESTINATIONS_LABELS[req.destination]}", + description=( + f"Contribution enregistrée le {req.date_envoi} par le module Envoi contributions. " + f"Méthode prévue : {req.methode.upper()}." + ), + proposeur_id=req.proposeur_id, + secondeur_id=req.secondeur_id, + statut="adoptee", + numero_resolution=numero_resolution, + date_vote=date.today(), ) - resolution = result.scalar_one_or_none() - if not resolution: - raise HTTPException(status_code=400, detail="Résolution introuvable ou non adoptée") + db.add(resolution) + await db.flush() envoi = EnvoiContribution( groupe_id=membre.groupe_id, @@ -123,8 +147,9 @@ async def enregistrer_envoi( destination=req.destination, montant=req.montant, methode=req.methode.upper(), - resolution_id=req.resolution_id, + resolution_id=resolution.id, engagee=True, + debitee=False, saisi_par=membre.id, ) db.add(envoi) @@ -145,6 +170,54 @@ async def enregistrer_envoi( ) +@router.post("/{envoi_id}/confirmer-debit", response_model=EnvoiContributionOut) +async def confirmer_debit_contribution( + envoi_id: str, + membre=Depends(require_tresorier), + db: AsyncSession = Depends(get_db), +): + """Le trésorier confirme que la contribution a été débitée du compte.""" + result = await db.execute( + select(EnvoiContribution).where( + EnvoiContribution.id == envoi_id, + EnvoiContribution.groupe_id == membre.groupe_id, + ) + ) + envoi = result.scalar_one_or_none() + if not envoi: + raise HTTPException(status_code=404, detail="Envoi introuvable") + if envoi.debitee: + raise HTTPException(status_code=400, detail="Contribution déjà débitée") + + from app.routers.operations_bancaires import _solde_banque_brut, _total_reserves + + solde_banque = await _solde_banque_brut(membre.groupe_id, db) + if float(envoi.montant) > solde_banque: + raise HTTPException(status_code=400, detail="Le solde en banque est insuffisant pour confirmer ce débit") + total_reserves = await _total_reserves(membre.groupe_id, db) + if solde_banque - float(envoi.montant) < total_reserves: + raise HTTPException(status_code=400, detail="Ce débit ferait dépasser les réserves par rapport au solde en banque") + + envoi.debitee = True + envoi.engagee = True + await db.flush() + + saisi = await db.get(Membre, envoi.saisi_par) if envoi.saisi_par else None + return EnvoiContributionOut( + id=envoi.id, + date_envoi=envoi.date_envoi, + destination=envoi.destination, + destination_label=DESTINATIONS_LABELS.get(envoi.destination, envoi.destination), + montant=float(envoi.montant), + methode=envoi.methode, + resolution_id=envoi.resolution_id, + engagee=envoi.engagee, + debitee=envoi.debitee, + saisi_par_prenom=saisi.prenom if saisi else None, + cree_le=envoi.cree_le, + ) + + @router.get("/", response_model=list[EnvoiContributionOut]) async def liste_envois( limite: int = 30, diff --git a/backend/app/routers/depenses.py b/backend/app/routers/depenses.py index 0456f4f..9fd863e 100644 --- a/backend/app/routers/depenses.py +++ b/backend/app/routers/depenses.py @@ -114,7 +114,8 @@ async def confirmer_depense( db: AsyncSession = Depends(get_db), ): """Le trésorier confirme la dépense et renseigne le mode de paiement.""" - if methode.upper() not in METHODES_VALIDES: + methode_norm = methode.upper() + if methode_norm not in METHODES_VALIDES: raise HTTPException(status_code=400, detail=f"Méthode invalide. Choix : {', '.join(METHODES_VALIDES)}") result = await db.execute( @@ -124,12 +125,19 @@ async def confirmer_depense( if not depense: raise HTTPException(status_code=404, detail="Dépense introuvable") - depense.methode = methode.upper() + if methode_norm == "COLLECTE": + from app.routers.operations_bancaires import _encaisse + + encaisse = await _encaisse(membre.groupe_id, db) + if float(depense.montant) > encaisse: + raise HTTPException(status_code=400, detail="L'encaisse disponible est insuffisante pour payer cette dépense") + + depense.methode = methode_norm depense.confirme_par = membre.id depense.date_confirmation = datetime.now(timezone.utc) # Si cash (COLLECTE) → payé immédiatement - if methode.upper() == "COLLECTE": + if methode_norm == "COLLECTE": depense.paye = True # Si DU → le groupe doit rembourser, pas encore payé @@ -157,6 +165,15 @@ async def confirmer_debit( if depense.methode not in ("CHEQUE", "VIREMENT"): raise HTTPException(status_code=400, detail="Seuls les chèques/virements nécessitent confirmation de débit") + from app.routers.operations_bancaires import _solde_banque_brut, _total_reserves + + solde_banque = await _solde_banque_brut(membre.groupe_id, db) + if float(depense.montant) > solde_banque: + raise HTTPException(status_code=400, detail="Le solde en banque est insuffisant pour confirmer ce débit") + total_reserves = await _total_reserves(membre.groupe_id, db) + if solde_banque - float(depense.montant) < total_reserves: + raise HTTPException(status_code=400, detail="Ce débit ferait dépasser les réserves par rapport au solde en banque") + depense.paye = True await db.flush() return await _to_out(depense, db) diff --git a/backend/app/routers/evenements.py b/backend/app/routers/evenements.py index f42bafa..35f55c7 100644 --- a/backend/app/routers/evenements.py +++ b/backend/app/routers/evenements.py @@ -1,4 +1,4 @@ -"""Routes — Événements à venir (anniversaires + ad hoc + résolutions).""" +"""Routes — Événements (anniversaires + ad hoc + résolutions).""" from datetime import date, timedelta from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession @@ -13,33 +13,27 @@ from app.models.poste import Poste from app.models.evenement import Evenement from app.models.proposition import Proposition -router = APIRouter(prefix="/evenements", tags=["Événements à venir"]) +router = APIRouter(prefix="/evenements", tags=["Événements"]) -def _prochain_anniversaire(date_abstinence: date, aujourdhui: date) -> tuple[int, date, int]: - """Retourne (années, prochaine date, jours restants).""" +def _anniversaire_sobriete(date_abstinence: date, annee: int) -> tuple[int, date]: try: - anniv = date_abstinence.replace(year=aujourdhui.year) + anniv = date_abstinence.replace(year=annee) except ValueError: - anniv = date(aujourdhui.year, 3, 1) - if anniv < aujourdhui: - try: - anniv = date_abstinence.replace(year=aujourdhui.year + 1) - except ValueError: - anniv = date(aujourdhui.year + 1, 3, 1) - annees = anniv.year - date_abstinence.year - jours = (anniv - aujourdhui).days - return max(0, annees), anniv, jours + anniv = date(annee, 3, 1) + return max(0, anniv.year - date_abstinence.year), anniv @router.get("/") async def evenements_a_venir( jours: int = 90, + jours_avant: int = 365, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): - """Tous les événements à venir : réunions, anniversaires, ad hoc.""" + """Tous les événements : anniversaires, ad hoc et résolutions.""" aujourdhui = date.today() + debut = aujourdhui - timedelta(days=jours_avant) limite = aujourdhui + timedelta(days=jours) groupe = await db.get(Groupe, membre.groupe_id) district = groupe.district if groupe else "87-16" @@ -57,11 +51,13 @@ async def evenements_a_venir( ) ) for m, groupe_nom in result.all(): - annees, prochain, jours_restants = _prochain_anniversaire(m.date_abstinence, aujourdhui) - if 0 <= jours_restants <= jours: + for annee in range(debut.year, limite.year + 1): + annees, anniv = _anniversaire_sobriete(m.date_abstinence, annee) + if anniv < debut or anniv > limite: + continue evenements.append({ "type": "anniversaire", - "date": str(prochain), + "date": str(anniv), "titre": f"🎂 {m.prenom} — {annees} an{'s' if annees > 1 else ''}", "detail": f"Anniversaire de sobriété · {groupe_nom}", "lieu": None, @@ -74,7 +70,7 @@ async def evenements_a_venir( .join(Groupe, Evenement.groupe_id == Groupe.id) .where( Groupe.district == district, - Evenement.date_evenement >= aujourdhui, + Evenement.date_evenement >= debut, Evenement.date_evenement <= limite, ) ) diff --git a/backend/app/routers/invitations.py b/backend/app/routers/invitations.py index a48d8d9..a65509c 100644 --- a/backend/app/routers/invitations.py +++ b/backend/app/routers/invitations.py @@ -18,7 +18,7 @@ async def creer_invitation( executif=Depends(require_executif), db: AsyncSession = Depends(get_db), ): - """G1 — Générer un code d'invitation (expire après 48h).""" + """Générer un code d'invitation (expire après 48h).""" code = secrets.token_hex(6).upper() # 12 caractères hex invitation = Invitation( groupe_id=executif.groupe_id, diff --git a/backend/app/routers/jumelages.py b/backend/app/routers/jumelages.py deleted file mode 100644 index ec18d0d..0000000 --- a/backend/app/routers/jumelages.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Routes G7 — Jumelage parrain/filleul (confidentiel).""" -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, desc - -from app.core.database import get_db -from app.core.security import require_executif -from app.models.jumelage import Jumelage -from app.models.membre import Membre -from app.schemas.phase8 import JumelageCreate, JumelageOut, JumelageUpdate - -router = APIRouter(prefix="/jumelages", tags=["G7 — Parrainage"]) - - -async def _to_out(j: Jumelage, db: AsyncSession) -> JumelageOut: - parrain = await db.get(Membre, j.parrain_id) - filleul = await db.get(Membre, j.filleul_id) - cree = await db.get(Membre, j.cree_par) if j.cree_par else None - return JumelageOut( - id=j.id, - parrain_prenom=parrain.prenom if parrain else "?", - filleul_prenom=filleul.prenom if filleul else "?", - date_jumelage=j.date_jumelage, - statut=j.statut, - note=j.note, - cree_par_prenom=cree.prenom if cree else None, - ) - - -@router.post("/", response_model=JumelageOut) -async def creer_jumelage( - req: JumelageCreate, - membre=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """G7 — Jumeler un parrain et un filleul.""" - if req.parrain_id == req.filleul_id: - raise HTTPException(status_code=400, detail="Le parrain et le filleul doivent être différents") - - parrain = await db.get(Membre, req.parrain_id) - filleul = await db.get(Membre, req.filleul_id) - if not parrain or not parrain.actif: - raise HTTPException(status_code=404, detail="Parrain introuvable ou inactif") - if not filleul or not filleul.actif: - raise HTTPException(status_code=404, detail="Filleul introuvable ou inactif") - - jumelage = Jumelage( - groupe_id=membre.groupe_id, - parrain_id=req.parrain_id, - filleul_id=req.filleul_id, - date_jumelage=req.date_jumelage, - note=req.note, - cree_par=membre.id, - ) - db.add(jumelage) - await db.flush() - - return await _to_out(jumelage, db) - - -@router.get("/", response_model=list[JumelageOut]) -async def liste_jumelages( - statut: str | None = None, - membre=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """Liste des jumelages (confidentiel — exécutif seulement).""" - query = select(Jumelage).where(Jumelage.groupe_id == membre.groupe_id) - if statut: - query = query.where(Jumelage.statut == statut) - query = query.order_by(desc(Jumelage.date_jumelage)) - - result = await db.execute(query) - jumelages = result.scalars().all() - return [await _to_out(j, db) for j in jumelages] - - -@router.patch("/{jumelage_id}", response_model=JumelageOut) -async def modifier_jumelage( - jumelage_id: str, - req: JumelageUpdate, - membre=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """Modifier ou terminer un jumelage.""" - result = await db.execute( - select(Jumelage).where(Jumelage.id == jumelage_id, Jumelage.groupe_id == membre.groupe_id) - ) - jumelage = result.scalar_one_or_none() - if not jumelage: - raise HTTPException(status_code=404, detail="Jumelage introuvable") - - if req.statut and req.statut in ("actif", "termine"): - jumelage.statut = req.statut - if req.note is not None: - jumelage.note = req.note - await db.flush() - - return await _to_out(jumelage, db) diff --git a/backend/app/routers/membres.py b/backend/app/routers/membres.py index af138c2..23d1d36 100644 --- a/backend/app/routers/membres.py +++ b/backend/app/routers/membres.py @@ -1,7 +1,7 @@ """Routes membres — profil et gestion.""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select +from sqlalchemy import select, func from app.core.database import get_db from app.core.security import get_current_membre, require_executif, hash_pin, verify_pin @@ -25,13 +25,32 @@ async def modifier_profil( ): """C7 — Modifier mon profil.""" if updates.prenom is not None: - membre.prenom = updates.prenom.strip().title() + prenom = updates.prenom.strip() + if not prenom: + raise HTTPException(status_code=400, detail="Le prénom est obligatoire") + membre.prenom = prenom.title() if updates.nom is not None: membre.nom = updates.nom.strip().title() if updates.nom.strip() else None if updates.telephone is not None: - membre.telephone = updates.telephone.strip() + telephone = updates.telephone.strip() + if not telephone: + raise HTTPException(status_code=400, detail="Le téléphone est obligatoire") + membre.telephone = telephone if updates.courriel is not None: - membre.courriel = updates.courriel.strip().lower() + courriel = updates.courriel.strip().lower() + if not courriel or "@" not in courriel: + raise HTTPException(status_code=400, detail="Courriel invalide") + result = await db.execute( + select(Membre).where( + func.lower(Membre.courriel) == courriel, + Membre.groupe_id == membre.groupe_id, + Membre.id != membre.id, + Membre.actif == True, + ) + ) + if result.scalar_one_or_none(): + raise HTTPException(status_code=400, detail="Ce courriel est déjà utilisé dans ce groupe") + membre.courriel = courriel if updates.date_abstinence is not None: membre.date_abstinence = updates.date_abstinence await db.flush() diff --git a/backend/app/routers/operations_bancaires.py b/backend/app/routers/operations_bancaires.py index ea254ac..c662fc8 100644 --- a/backend/app/routers/operations_bancaires.py +++ b/backend/app/routers/operations_bancaires.py @@ -1,15 +1,19 @@ """Routes — Opérations bancaires (dépôts, retraits).""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, desc +from sqlalchemy import select, desc, func from app.core.database import get_db from app.core.security import get_current_membre from app.models.operation_bancaire import OperationBancaire +from app.models.collecte import Collecte +from app.models.depense import Depense +from app.models.reserve import Reserve +from app.models.vente_litterature import VenteLitterature from app.models.membre import Membre from app.models.affectation import Affectation from app.models.poste import Poste -from app.schemas.phase1 import OperationBancaireCreate, OperationBancaireOut +from app.schemas.phase1 import OperationBancaireCreate, OperationBancaireOut, ReserveCreate, ReserveOut, ReserveUpdate router = APIRouter(prefix="/banque", tags=["Opérations bancaires"]) @@ -28,6 +32,77 @@ async def _require_tresorier(membre=Depends(get_current_membre), db: AsyncSessio return membre +async def _encaisse(groupe_id, db: AsyncSession) -> float: + total_collectes = float((await db.execute( + select(func.coalesce(func.sum(Collecte.montant), 0)).where( + Collecte.groupe_id == groupe_id, Collecte.statut == "recu") + )).scalar()) + ventes_recues = float((await db.execute( + select(func.coalesce(func.sum(VenteLitterature.montant), 0)).where( + VenteLitterature.groupe_id == groupe_id, VenteLitterature.remis_tresorier == True) + )).scalar()) + retraits = float((await db.execute( + select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == groupe_id, OperationBancaire.type == "retrait") + )).scalar()) + depots = float((await db.execute( + select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == groupe_id, OperationBancaire.type == "depot") + )).scalar()) + depenses_cash = float((await db.execute( + select(func.coalesce(func.sum(Depense.montant), 0)).where( + Depense.groupe_id == groupe_id, Depense.paye == True, Depense.methode == "COLLECTE") + )).scalar()) + return total_collectes + ventes_recues + retraits - depots - depenses_cash + + +async def _solde_banque_brut(groupe_id, db: AsyncSession) -> float: + from app.models.envoi_contribution import EnvoiContribution + + result = await db.execute( + select(OperationBancaire).where( + OperationBancaire.groupe_id == groupe_id, + OperationBancaire.type == "releve", + ).order_by(desc(OperationBancaire.date_operation)).limit(1) + ) + dernier_releve = result.scalar_one_or_none() + solde_releve = float(dernier_releve.montant) if dernier_releve else 0.0 + filtre_depuis = dernier_releve.date_operation if dernier_releve else None + + q_depots = select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == groupe_id, OperationBancaire.type == "depot") + q_retraits = select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == groupe_id, OperationBancaire.type == "retrait") + q_contribs = select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where( + EnvoiContribution.groupe_id == groupe_id, EnvoiContribution.debitee == True) + q_depenses = select(func.coalesce(func.sum(Depense.montant), 0)).where( + Depense.groupe_id == groupe_id, + Depense.paye == True, + Depense.methode.in_(["VIREMENT", "CHEQUE"]), + ) + if filtre_depuis: + q_depots = q_depots.where(OperationBancaire.date_operation > filtre_depuis) + q_retraits = q_retraits.where(OperationBancaire.date_operation > filtre_depuis) + q_contribs = q_contribs.where(EnvoiContribution.date_envoi > filtre_depuis) + q_depenses = q_depenses.where(Depense.date_confirmation > filtre_depuis) + + depots = float((await db.execute(q_depots)).scalar()) + retraits = float((await db.execute(q_retraits)).scalar()) + contribs = float((await db.execute(q_contribs)).scalar()) + depenses = float((await db.execute(q_depenses)).scalar()) + return solde_releve + depots - retraits - contribs - depenses + + +async def _total_reserves(groupe_id, db: AsyncSession, exclure_id: str | None = None) -> float: + query = select(func.coalesce(func.sum(Reserve.montant), 0)).where( + Reserve.groupe_id == groupe_id, + Reserve.active == True, + ) + if exclure_id: + query = query.where(Reserve.id != exclure_id) + return float((await db.execute(query)).scalar()) + + @router.post("/", response_model=OperationBancaireOut) async def enregistrer_operation( req: OperationBancaireCreate, @@ -39,6 +114,16 @@ async def enregistrer_operation( raise HTTPException(status_code=400, detail="Type doit être 'depot', 'retrait' ou 'releve'") if req.montant <= 0: raise HTTPException(status_code=400, detail="Le montant doit être positif") + solde_banque = await _solde_banque_brut(membre.groupe_id, db) + total_reserves = await _total_reserves(membre.groupe_id, db) + if req.type == "depot" and req.montant > await _encaisse(membre.groupe_id, db): + raise HTTPException(status_code=400, detail="Le dépôt ne peut pas dépasser l'encaisse disponible") + if req.type == "retrait" and req.montant > solde_banque: + raise HTTPException(status_code=400, detail="Le retrait ne peut pas dépasser le solde en banque") + if req.type == "retrait" and solde_banque - req.montant < total_reserves: + raise HTTPException(status_code=400, detail="Le retrait ferait dépasser les réserves par rapport au solde en banque") + if req.type == "releve" and req.montant < total_reserves: + raise HTTPException(status_code=400, detail="Le relevé ne peut pas être inférieur au total des réserves") op = OperationBancaire( groupe_id=membre.groupe_id, @@ -61,6 +146,67 @@ async def enregistrer_operation( ) +@router.get("/reserves", response_model=list[ReserveOut]) +async def liste_reserves(membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db)): + result = await db.execute( + select(Reserve).where(Reserve.groupe_id == membre.groupe_id, Reserve.active == True) + .order_by(Reserve.nom) + ) + return [ + ReserveOut(id=r.id, nom=r.nom, montant=float(r.montant), note=r.note, active=r.active, cree_le=r.cree_le) + for r in result.scalars().all() + ] + + +@router.post("/reserves", response_model=ReserveOut) +async def creer_reserve(req: ReserveCreate, membre=Depends(_require_tresorier), db: AsyncSession = Depends(get_db)): + if not req.nom.strip(): + raise HTTPException(status_code=400, detail="Nom requis") + if req.montant < 0: + raise HTTPException(status_code=400, detail="Le montant doit être positif ou nul") + total = await _total_reserves(membre.groupe_id, db) + req.montant + solde_banque = await _solde_banque_brut(membre.groupe_id, db) + if total > solde_banque: + raise HTTPException(status_code=400, detail="Le total des réserves ne peut pas dépasser le solde en banque") + reserve = Reserve(groupe_id=membre.groupe_id, nom=req.nom.strip(), montant=req.montant, note=req.note) + db.add(reserve) + await db.flush() + return ReserveOut(id=reserve.id, nom=reserve.nom, montant=float(reserve.montant), note=reserve.note, active=reserve.active, cree_le=reserve.cree_le) + + +@router.patch("/reserves/{reserve_id}", response_model=ReserveOut) +async def modifier_reserve(reserve_id: str, req: ReserveUpdate, membre=Depends(_require_tresorier), db: AsyncSession = Depends(get_db)): + reserve = await db.get(Reserve, reserve_id) + if not reserve or reserve.groupe_id != membre.groupe_id or not reserve.active: + raise HTTPException(status_code=404, detail="Réserve introuvable") + montant = float(reserve.montant) if req.montant is None else req.montant + if montant < 0: + raise HTTPException(status_code=400, detail="Le montant doit être positif ou nul") + total = await _total_reserves(membre.groupe_id, db, reserve_id) + montant + solde_banque = await _solde_banque_brut(membre.groupe_id, db) + if total > solde_banque: + raise HTTPException(status_code=400, detail="Le total des réserves ne peut pas dépasser le solde en banque") + if req.nom is not None: + if not req.nom.strip(): + raise HTTPException(status_code=400, detail="Nom requis") + reserve.nom = req.nom.strip() + reserve.montant = montant + if req.note is not None: + reserve.note = req.note.strip() or None + await db.flush() + return ReserveOut(id=reserve.id, nom=reserve.nom, montant=float(reserve.montant), note=reserve.note, active=reserve.active, cree_le=reserve.cree_le) + + +@router.delete("/reserves/{reserve_id}") +async def supprimer_reserve(reserve_id: str, membre=Depends(_require_tresorier), db: AsyncSession = Depends(get_db)): + reserve = await db.get(Reserve, reserve_id) + if not reserve or reserve.groupe_id != membre.groupe_id or not reserve.active: + raise HTTPException(status_code=404, detail="Réserve introuvable") + reserve.active = False + await db.flush() + return {"message": "Réserve supprimée"} + + @router.get("/", response_model=list[OperationBancaireOut]) async def liste_operations( limite: int = 20, diff --git a/backend/app/routers/postes.py b/backend/app/routers/postes.py index 1eaea02..7e4f714 100644 --- a/backend/app/routers/postes.py +++ b/backend/app/routers/postes.py @@ -1,8 +1,8 @@ -"""Routes postes, affectations, candidatures, tâches.""" +"""Routes postes, affectations, candidatures et accès aux modules.""" from datetime import date from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select +from sqlalchemy import select, func from app.core.database import get_db from app.core.security import get_current_membre, require_executif @@ -10,12 +10,26 @@ from app.models.poste import Poste from app.models.affectation import Affectation from app.models.membre import Membre from app.models.candidature import Candidature -from app.models.tache import Tache, PosteTache -from app.schemas.schemas import PosteOut, PosteCreate, AffectationCreate, TerminerMandatRequest, TacheCreate, TacheOut +from app.models.poste_module import PosteModule +from app.schemas.schemas import PosteOut, PosteCreate, AffectationCreate, TerminerMandatRequest router = APIRouter(prefix="/postes", tags=["Postes et affectations"]) +async def _prochain_numero_resolution(groupe_id, annee: int, db: AsyncSession) -> str: + from app.models.proposition import Proposition + + result = await db.execute( + select(func.count(Proposition.id)).where( + Proposition.groupe_id == groupe_id, + Proposition.numero_resolution.isnot(None), + Proposition.numero_resolution.like(f"RES-{annee}-%"), + ) + ) + count = result.scalar() or 0 + return f"RES-{annee}-{count + 1:03d}" + + # ── Postes ── @router.get("/", response_model=list[PosteOut]) @@ -30,32 +44,7 @@ async def liste_postes( result = await db.execute(query.order_by(Poste.ordre_affichage)) postes = result.scalars().all() - out = [] - for p in postes: - # Charger les tâches associées - result_t = await db.execute( - select(Tache).join(PosteTache).where(PosteTache.poste_id == p.id) - .order_by(Tache.ordre_affichage) - ) - taches_list = result_t.scalars().all() - taches = [{"id": str(t.id), "nom": t.nom, "informations": t.informations} - for t in taches_list] - - # Description composite : description du poste + tâches associées - desc_parts = [] - if p.description: - desc_parts.append(p.description) - if taches_list: - desc_parts.append("Tâches : " + ", ".join( - f"{t.nom}" + (f" ({t.informations})" if t.informations else "") - for t in taches_list - )) - - po = PosteOut.model_validate(p) - po.description = " — ".join(desc_parts) if desc_parts else p.description - po.taches = taches - out.append(po) - return out + return [PosteOut.model_validate(p) for p in postes] @router.post("/", response_model=PosteOut) @@ -157,26 +146,11 @@ async def postes_disponibles( ) deja_postule = result_cand.scalars().first() is not None - # Description composite avec tâches - result_t = await db.execute( - select(Tache).join(PosteTache).where(PosteTache.poste_id == p.id) - .order_by(Tache.ordre_affichage) - ) - taches_list = result_t.scalars().all() - desc_parts = [] - if p.description: - desc_parts.append(p.description) - if taches_list: - desc_parts.append("Tâches : " + ", ".join( - f"{t.nom}" + (f" ({t.informations})" if t.informations else "") - for t in taches_list - )) - disponibles.append({ "id": str(p.id), "nom": p.nom, "categorie": p.categorie, - "description": " — ".join(desc_parts) if desc_parts else p.description, + "description": p.description, "prerequis": p.prerequis, "deja_postule": deja_postule, }) @@ -499,6 +473,49 @@ async def affectations_groupe(executif=Depends(require_executif), db: AsyncSessi @router.post("/affectations") async def creer_affectation(req: AffectationCreate, executif=Depends(require_executif), db: AsyncSession = Depends(get_db)): + from app.models.proposition import Proposition + + if req.proposeur_id == req.secondeur_id: + raise HTTPException(status_code=400, detail="Le proposeur et le secondeur doivent être différents") + + cible = await db.get(Membre, req.membre_id) + proposeur = await db.get(Membre, req.proposeur_id) + secondeur = await db.get(Membre, req.secondeur_id) + poste = await db.get(Poste, req.poste_id) + if not cible or cible.groupe_id != executif.groupe_id or not cible.actif: + raise HTTPException(status_code=404, detail="Membre à affecter introuvable ou inactif") + if not proposeur or proposeur.groupe_id != executif.groupe_id or not proposeur.actif: + raise HTTPException(status_code=404, detail="Proposeur introuvable ou inactif") + if not secondeur or secondeur.groupe_id != executif.groupe_id or not secondeur.actif: + raise HTTPException(status_code=404, detail="Secondeur introuvable ou inactif") + if not poste or not poste.actif: + raise HTTPException(status_code=404, detail="Poste introuvable ou aboli") + + result = await db.execute( + select(Affectation).where( + Affectation.groupe_id == executif.groupe_id, + Affectation.poste_id == req.poste_id, + Affectation.statut == "actif", + ) + ) + if result.scalar_one_or_none(): + raise HTTPException(status_code=400, detail="Ce poste est déjà affecté") + + numero_resolution = await _prochain_numero_resolution(executif.groupe_id, req.date_debut.year, db) + resolution = Proposition( + groupe_id=executif.groupe_id, + type_proposition="nomination", + sujet=f"Nomination de {cible.prenom} au poste de {poste.nom}", + description=f"Affectation directe au poste de {poste.nom} à partir du {req.date_debut}.", + proposeur_id=req.proposeur_id, + secondeur_id=req.secondeur_id, + statut="adoptee", + numero_resolution=numero_resolution, + date_vote=req.date_debut, + ) + db.add(resolution) + await db.flush() + affectation = Affectation( groupe_id=executif.groupe_id, membre_id=req.membre_id, @@ -508,7 +525,12 @@ async def creer_affectation(req: AffectationCreate, executif=Depends(require_exe ) db.add(affectation) await db.flush() - return {"message": "Affectation créée", "id": str(affectation.id)} + return { + "message": "Affectation créée", + "id": str(affectation.id), + "resolution_id": str(resolution.id), + "numero_resolution": resolution.numero_resolution, + } @router.post("/affectations/{affectation_id}/terminer") @@ -566,133 +588,41 @@ async def supprimer_poste_permanent( if poste.actif: raise HTTPException(status_code=400, detail="Abolissez d'abord le poste avant de le supprimer") - # Supprimer les liens tâches - result = await db.execute(select(PosteTache).where(PosteTache.poste_id == poste.id)) - for pt in result.scalars().all(): - await db.delete(pt) - await db.delete(poste) await db.flush() return {"message": f"Poste «{poste.nom}» supprimé définitivement"} -# ── Tâches ── +# ── Acces aux modules ── -@router.get("/taches", response_model=list[TacheOut]) -async def liste_taches( - _=Depends(get_current_membre), - db: AsyncSession = Depends(get_db), -): - """Lister toutes les tâches avec les postes associés.""" - result = await db.execute(select(Tache).order_by(Tache.ordre_affichage)) - taches = result.scalars().all() - out = [] - for t in taches: - result_p = await db.execute( - select(Poste.nom).join(PosteTache).where(PosteTache.tache_id == t.id) - ) - postes_noms = [row[0] for row in result_p.all()] - out.append(TacheOut( - id=t.id, nom=t.nom, informations=t.informations, postes=postes_noms - )) - return out - - -@router.post("/taches", response_model=TacheOut) -async def creer_tache( - req: TacheCreate, +@router.get("/modules") +async def modules_par_poste( executif=Depends(require_executif), db: AsyncSession = Depends(get_db), ): - """Créer une tâche (exécutif).""" - tache = Tache(nom=req.nom.strip(), informations=req.informations) - db.add(tache) - await db.flush() - return TacheOut(id=tache.id, nom=tache.nom, informations=tache.informations) + result = await db.execute(select(PosteModule)) + modules = result.scalars().all() + par_poste: dict[str, list[str]] = {} + for m in modules: + par_poste.setdefault(str(m.poste_id), []).append(m.module_code) + return par_poste -@router.patch("/taches/{tache_id}") -async def modifier_tache( - tache_id: str, - nom: str | None = None, - informations: str | None = None, - executif=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """Modifier une tâche.""" - tache = await db.get(Tache, tache_id) - if not tache: - raise HTTPException(status_code=404, detail="Tâche introuvable") - if nom is not None: - tache.nom = nom.strip() - if informations is not None: - tache.informations = informations.strip() or None - await db.flush() - return {"message": "Tâche modifiée"} - - -@router.delete("/taches/{tache_id}") -async def supprimer_tache( - tache_id: str, - executif=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """Supprimer une tâche et ses associations.""" - tache = await db.get(Tache, tache_id) - if not tache: - raise HTTPException(status_code=404, detail="Tâche introuvable") - result = await db.execute(select(PosteTache).where(PosteTache.tache_id == tache.id)) - for pt in result.scalars().all(): - await db.delete(pt) - await db.delete(tache) - await db.flush() - return {"message": f"Tâche «{tache.nom}» supprimée"} - - -# ── Affectation tâche ↔ poste ── - -@router.post("/{poste_id}/taches/{tache_id}") -async def affecter_tache( +@router.put("/{poste_id}/modules") +async def definir_modules_poste( poste_id: str, - tache_id: str, + codes: list[str], executif=Depends(require_executif), db: AsyncSession = Depends(get_db), ): - """Affecter une tâche à un poste. Une tâche peut être affectée à plusieurs postes.""" poste = await db.get(Poste, poste_id) - tache = await db.get(Tache, tache_id) if not poste or not poste.actif: raise HTTPException(status_code=404, detail="Poste introuvable ou aboli") - if not tache: - raise HTTPException(status_code=404, detail="Tâche introuvable") - # Vérifier doublon - result = await db.execute( - select(PosteTache).where(PosteTache.poste_id == poste_id, PosteTache.tache_id == tache_id) - ) - if result.scalars().first(): - raise HTTPException(status_code=400, detail="Cette tâche est déjà affectée à ce poste") - - pt = PosteTache(poste_id=poste_id, tache_id=tache_id) - db.add(pt) + result = await db.execute(select(PosteModule).where(PosteModule.poste_id == poste.id)) + for item in result.scalars().all(): + await db.delete(item) + for code in sorted(set(codes)): + db.add(PosteModule(poste_id=poste.id, module_code=code)) await db.flush() - return {"message": f"Tâche «{tache.nom}» affectée au poste «{poste.nom}»"} - - -@router.delete("/{poste_id}/taches/{tache_id}") -async def desaffecter_tache( - poste_id: str, - tache_id: str, - executif=Depends(require_executif), - db: AsyncSession = Depends(get_db), -): - """Retirer une tâche d'un poste.""" - result = await db.execute( - select(PosteTache).where(PosteTache.poste_id == poste_id, PosteTache.tache_id == tache_id) - ) - pt = result.scalars().first() - if not pt: - raise HTTPException(status_code=404, detail="Association introuvable") - await db.delete(pt) - await db.flush() - return {"message": "Tâche retirée du poste"} + return {"message": "Accès aux modules mis à jour"} diff --git a/backend/app/routers/rapports.py b/backend/app/routers/rapports.py index 7b6edc5..46c53b5 100644 --- a/backend/app/routers/rapports.py +++ b/backend/app/routers/rapports.py @@ -220,7 +220,10 @@ async def rapports_disponibles( select( extract("year", EnvoiContribution.date_envoi).label("annee"), extract("month", EnvoiContribution.date_envoi).label("mois"), - ).where(EnvoiContribution.groupe_id == membre.groupe_id) + ).where( + EnvoiContribution.groupe_id == membre.groupe_id, + EnvoiContribution.debitee == True, + ) .group_by("annee", "mois") ) mois_contributions = [{"annee": int(r.annee), "mois": int(r.mois)} for r in result.all()] @@ -301,6 +304,7 @@ async def rapport_financier_complet( result = await db.execute( select(EnvoiContribution).where( EnvoiContribution.groupe_id == gid, + EnvoiContribution.debitee == True, extract("year", EnvoiContribution.date_envoi) == annee, extract("month", EnvoiContribution.date_envoi) == mois, ) @@ -401,6 +405,7 @@ async def etat_financier_temps_reel( """État des résultats et transactions en temps réel (JSON).""" from sqlalchemy import func, desc from app.models.operation_bancaire import OperationBancaire + from app.models.reserve import Reserve from app.models.vente_litterature import VenteLitterature from app.models.litterature import Litterature @@ -444,7 +449,9 @@ async def etat_financier_temps_reel( # ── Contributions ── result = await db.execute( select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where( - EnvoiContribution.groupe_id == gid)) + EnvoiContribution.groupe_id == gid, + EnvoiContribution.debitee == True, + )) total_contributions = float(result.scalar()) # ── Encaisse ── @@ -453,12 +460,17 @@ async def etat_financier_temps_reel( OperationBancaire.groupe_id == gid, OperationBancaire.type == "depot")) total_depots = float(result.scalar()) + result = await db.execute( + select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( + OperationBancaire.groupe_id == gid, OperationBancaire.type == "retrait")) + total_retraits = float(result.scalar()) + result = await db.execute( select(func.coalesce(func.sum(Depense.montant), 0)).where( Depense.groupe_id == gid, Depense.paye == True, Depense.methode == "COLLECTE")) depenses_cash = float(result.scalar()) - encaisse = total_collectes + total_ventes - total_depots - depenses_cash + encaisse = total_collectes + total_ventes + total_retraits - total_depots - depenses_cash # ── Solde banque ── filtre_depuis = dernier_releve.date_operation if dernier_releve else None @@ -466,24 +478,40 @@ async def etat_financier_temps_reel( OperationBancaire.groupe_id == gid, OperationBancaire.type == "depot") q_retraits = select(func.coalesce(func.sum(OperationBancaire.montant), 0)).where( OperationBancaire.groupe_id == gid, OperationBancaire.type == "retrait") + q_depenses_bancaires = select(func.coalesce(func.sum(Depense.montant), 0)).where( + Depense.groupe_id == gid, + Depense.paye == True, + Depense.methode.in_(["VIREMENT", "CHEQUE"]), + ) + q_contributions = select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where( + EnvoiContribution.groupe_id == gid, + EnvoiContribution.debitee == True, + ) if filtre_depuis: q_depots = q_depots.where(OperationBancaire.date_operation > filtre_depuis) q_retraits = q_retraits.where(OperationBancaire.date_operation > filtre_depuis) + q_depenses_bancaires = q_depenses_bancaires.where(Depense.date_confirmation > filtre_depuis) + q_contributions = q_contributions.where(EnvoiContribution.date_envoi > filtre_depuis) depots_depuis = float((await db.execute(q_depots)).scalar()) retraits_depuis = float((await db.execute(q_retraits)).scalar()) - solde_banque = solde_releve + depots_depuis - retraits_depuis + depenses_bancaires_depuis = float((await db.execute(q_depenses_bancaires)).scalar()) + contributions_depuis = float((await db.execute(q_contributions)).scalar()) + solde_banque = solde_releve + depots_depuis - retraits_depuis - depenses_bancaires_depuis - contributions_depuis # ── Réserves ── - reserve_prudente = 500.0 - mois_depuis = 0 - if groupe and groupe.cree_le: - from datetime import date as d - aujourdhui = d.today() - cree = groupe.cree_le.date() if hasattr(groupe.cree_le, 'date') else groupe.cree_le - mois_depuis = max(0, (aujourdhui.year - cree.year) * 12 + (aujourdhui.month - cree.month)) - reserve_anniversaire = mois_depuis * 20.0 + result = await db.execute( + select(Reserve).where( + Reserve.groupe_id == gid, + Reserve.active == True, + ).order_by(Reserve.nom) + ) + reserves_details = [ + {"id": str(r.id), "nom": r.nom, "montant": float(r.montant), "note": r.note} + for r in result.scalars().all() + ] + total_reserves = sum(r["montant"] for r in reserves_details) - disponible = solde_banque - total_engagements - reserve_prudente - reserve_anniversaire + disponible = solde_banque - total_engagements - total_reserves # ── Dernières transactions ── result = await db.execute( @@ -506,7 +534,7 @@ async def etat_financier_temps_reel( "date_releve": str(dernier_releve.date_operation) if dernier_releve else None, "solde_releve": round(solde_releve, 2), "solde_banque": round(solde_banque, 2), - "encaisse": round(max(0, encaisse), 2), + "encaisse": round(encaisse, 2), "disponible": round(disponible, 2), "revenus": { "collectes": round(total_collectes, 2), @@ -520,9 +548,8 @@ async def etat_financier_temps_reel( "total": round(total_depenses_payees + total_contributions, 2), }, "reserves": { - "prudente": reserve_prudente, - "anniversaire": round(reserve_anniversaire, 2), - "total": round(reserve_prudente + reserve_anniversaire, 2), + "details": reserves_details, + "total": round(total_reserves, 2), }, "resultat_net": round(revenus - total_depenses_payees - total_contributions, 2), "dernieres_collectes": dernieres_collectes, diff --git a/backend/app/schemas/phase1.py b/backend/app/schemas/phase1.py index b67d620..5abebbf 100644 --- a/backend/app/schemas/phase1.py +++ b/backend/app/schemas/phase1.py @@ -53,6 +53,29 @@ class OperationBancaireOut(BaseModel): model_config = {"from_attributes": True} +class ReserveCreate(BaseModel): + nom: str + montant: float + note: Optional[str] = None + + +class ReserveUpdate(BaseModel): + nom: Optional[str] = None + montant: Optional[float] = None + note: Optional[str] = None + + +class ReserveOut(BaseModel): + id: UUID + nom: str + montant: float + note: Optional[str] = None + active: bool = True + cree_le: datetime + + model_config = {"from_attributes": True} + + class SoldesOut(BaseModel): encaisse: float # cash reçu par trésorier, pas encore déposé compte_bancaire: float # dépôts - dépenses payées - contributions diff --git a/backend/app/schemas/phase7.py b/backend/app/schemas/phase7.py index e9502f7..9429bbf 100644 --- a/backend/app/schemas/phase7.py +++ b/backend/app/schemas/phase7.py @@ -21,7 +21,9 @@ class EnvoiContributionCreate(BaseModel): destination: str # district, region, bsg, intergroupe montant: float methode: str # DU, VIREMENT, CHEQUE - resolution_id: UUID # résolution adoptée qui autorise l'envoi + resolution_id: Optional[UUID] = None + proposeur_id: UUID + secondeur_id: UUID class EnvoiContributionOut(BaseModel): diff --git a/backend/app/schemas/phase8.py b/backend/app/schemas/phase8.py index 86ef565..be820f3 100644 --- a/backend/app/schemas/phase8.py +++ b/backend/app/schemas/phase8.py @@ -1,6 +1,6 @@ -"""Schémas Phase 8 — Votes (sondages) et parrainage (jumelages).""" +"""Schémas Phase 8 — Votes et sondages.""" from pydantic import BaseModel -from datetime import date, datetime +from datetime import datetime from uuid import UUID from typing import Optional @@ -52,27 +52,3 @@ class SondageUpdate(BaseModel): class VoteCreate(BaseModel): option_id: UUID - -# ── G7 Jumelage parrain/filleul ── -class JumelageCreate(BaseModel): - parrain_id: UUID - filleul_id: UUID - date_jumelage: date - note: Optional[str] = None - - -class JumelageOut(BaseModel): - id: UUID - parrain_prenom: str - filleul_prenom: str - date_jumelage: date - statut: str - note: Optional[str] = None - cree_par_prenom: Optional[str] = None - - model_config = {"from_attributes": True} - - -class JumelageUpdate(BaseModel): - statut: Optional[str] = None # actif, termine - note: Optional[str] = None diff --git a/backend/app/schemas/schemas.py b/backend/app/schemas/schemas.py index 3dd6d33..e268ad6 100644 --- a/backend/app/schemas/schemas.py +++ b/backend/app/schemas/schemas.py @@ -81,21 +81,6 @@ class PosteOut(BaseModel): informations: Optional[str] = None ordre_affichage: Optional[int] = None actif: bool = True - taches: list[dict] = [] - - model_config = {"from_attributes": True} - - -class TacheCreate(BaseModel): - nom: str - informations: Optional[str] = None - - -class TacheOut(BaseModel): - id: UUID - nom: str - informations: Optional[str] = None - postes: list[str] = [] # noms des postes associés model_config = {"from_attributes": True} @@ -117,6 +102,8 @@ class AffectationCreate(BaseModel): membre_id: UUID poste_id: UUID date_debut: date + proposeur_id: UUID + secondeur_id: UUID class TerminerMandatRequest(BaseModel): diff --git a/deploy/seed.py b/deploy/seed.py index 5150670..e0941b4 100644 --- a/deploy/seed.py +++ b/deploy/seed.py @@ -37,13 +37,13 @@ from app.models.config_repartition import ConfigRepartition from app.models.sondage import Sondage from app.models.option_sondage import OptionSondage from app.models.vote import Vote -from app.models.jumelage import Jumelage from app.models.sync_log import SyncLog from app.models.proposition import Proposition from app.models.operation_bancaire import OperationBancaire from app.models.evenement import Evenement from app.models.candidature import Candidature -from app.models.tache import Tache, PosteTache +from app.models.poste_module import PosteModule +from app.models.reserve import Reserve POSTES = [ diff --git a/docs/ANSIBLE.md b/docs/ANSIBLE.md new file mode 100644 index 0000000..0d732a0 --- /dev/null +++ b/docs/ANSIBLE.md @@ -0,0 +1,122 @@ +# Deploiement Ansible + +Ce depot peut installer l'application complete sur une Debian vanille avec un compte `ansible` sudoer sans mot de passe. + +Le playbook installe PostgreSQL, Node.js, Python, NGINX, le backend FastAPI, le frontend Vite compile, le service systemd, les sauvegardes quotidiennes et, optionnellement, le certificat Let's Encrypt. + +## Prerequis cote poste de controle + +```bash +python3 -m pip install --user ansible +ansible-galaxy collection install -r ansible/requirements.yml +node --version # Node.js 20 recommande pour construire le frontend PWA +npm --version +``` + +Le serveur cible doit etre joignable en SSH avec l'utilisateur `ansible`. +L'inventaire utilise explicitement cette cle locale: + +```bash +~/.ssh/id_ed25519_ansible_chezlepro +``` + +Pour preparer l'acces SSH: + +```bash +ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ansible_chezlepro -C "ansible@chezlepro" +ssh-copy-id -i ~/.ssh/id_ed25519_ansible_chezlepro.pub ansible@ADRESSE_IP_VM +ssh -i ~/.ssh/id_ed25519_ansible_chezlepro ansible@ADRESSE_IP_VM +``` + +## Configuration + +1. Modifier l'inventaire: + +```bash +nano ansible/inventory/hosts.yml +``` + +Remplacer `ansible_host` par l'adresse de la machine Debian si elle change. + +2. Creer le fichier de secrets: + +```bash +cp ansible/group_vars/vault.yml.example ansible/group_vars/vault.yml +openssl rand -base64 48 +nano ansible/group_vars/vault.yml +ansible-vault encrypt ansible/group_vars/vault.yml +``` + +Le fichier chiffre doit contenir au minimum: + +- `groupe_meditation_db_password` +- `groupe_meditation_jwt_secret` +- `groupe_meditation_first_member` + +3. Ajuster les variables non secretes au besoin: + +```bash +nano ansible/group_vars/all.yml +``` + +Par defaut, le domaine est `groupe-meditation.87-16.org`. + +## Installation + +Depuis la racine du depot: + +```bash +ansible-playbook ansible/site.yml --ask-vault-pass +``` + +Le playbook est concu pour etre relance. Il ne relance le seed que si la table `groupes` est absente ou vide. + +Playbooks utiles: + +```bash +ansible-playbook ansible/verify.yml --ask-vault-pass +ansible-playbook ansible/backup-now.yml --ask-vault-pass +``` + +## TLS + +Laissez d'abord `groupe_meditation_enable_tls: false`, pointez le DNS vers la machine, puis activez: + +```yaml +groupe_meditation_enable_tls: true +groupe_meditation_certbot_email: vous@example.com +``` + +Relancer ensuite: + +```bash +ansible-playbook ansible/site.yml --ask-vault-pass +``` + +## Verification + +```bash +curl -s http://groupe-meditation.87-16.org/api/health +ssh ansible@groupe-meditation.87-16.org 'sudo systemctl status groupe-meditation --no-pager' +ssh ansible@groupe-meditation.87-16.org 'sudo journalctl -u groupe-meditation -n 100 --no-pager' +``` + +## Ce que le playbook gere + +- Paquets Debian requis. +- PostgreSQL local avec utilisateur et base applicatifs. +- `.env` backend avec permissions restrictives. +- Copie du code depuis ce depot vers `/opt/groupe-meditation`. +- Environnement virtuel Python et dependances backend. +- Installation npm et build frontend sur le poste de controle, puis publication de `dist/` sur la VM. +- Publication des fichiers statiques dans `/var/www/groupe-meditation`. +- Service systemd `groupe-meditation`. +- Site NGINX reverse proxy + SPA. +- Sauvegarde quotidienne dans `/var/backups/groupe-meditation`. + +## Notes importantes + +- Le playbook ne modifie pas le code applicatif. +- `node_modules/`, `dist/` et `ansible/group_vars/vault.yml` sont ignores par Git. +- La sauvegarde Ansible injecte `PGPASSWORD` dans le script cron root pour que le dump PostgreSQL fonctionne en mode non interactif. +- Le seed utilise le script existant `deploy/seed.py` et lui fournit les valeurs configurees dans Ansible. diff --git a/docs/UTILISATEURS.md b/docs/UTILISATEURS.md new file mode 100644 index 0000000..c895bbe --- /dev/null +++ b/docs/UTILISATEURS.md @@ -0,0 +1,426 @@ +# Guide utilisateur + +Application du Groupe Méditation pour les membres, les serviteurs et l'exécutif du groupe. + +Ce guide explique quoi faire dans l'application, sans détails techniques. + +## 1. Accéder à l'application + +### Se connecter + +1. Ouvrir l'application dans le navigateur. +2. Choisir le groupe si plusieurs groupes sont affichés. +3. Entrer son courriel. +4. Entrer son PIN à 4 chiffres. +5. Cocher `Se souvenir de moi` seulement sur un appareil personnel. +6. Appuyer sur `Connexion`. + +Si `Se souvenir de moi` n'est pas coché, la session reste limitée à l'onglet ou à la session du navigateur. + +### Créer son compte + +Un nouveau membre doit avoir un code d'invitation. + +1. Sur l'écran de connexion, choisir `J'ai un code d'invitation`. +2. Entrer le code reçu. +3. Entrer les informations demandées. +4. Choisir un PIN à 4 chiffres. +5. Valider l'inscription. + +Le code d'invitation ne peut être utilisé qu'une seule fois et expire automatiquement. + +### Se déconnecter + +Depuis l'accueil, appuyer sur `Déconnexion`. + +## 2. Page d'accueil + +L'accueil affiche les modules disponibles pour le membre connecté. + +Les modules sont regroupés en trois sections : + +- `Mes tâches` : saisies et actions liées au poste du membre. +- `Consultation` : modules accessibles à tous les membres. +- `Gestion` : modules réservés à l'exécutif ou aux postes autorisés. + +Les accès dépendent des postes actifs du membre. Ils peuvent être ajustés dans `Gestion des postes`. + +## 3. Modules de consultation + +### Rapports PDF + +Permet de consulter ou produire les rapports du groupe : + +- finances; +- procès-verbaux; +- résolutions; +- rapports disponibles selon les données enregistrées. + +### Événements + +Affiche les événements du groupe et du district : + +- anniversaires de sobriété; +- événements créés par l'exécutif; +- résolutions adoptées. + +Les événements passés et futurs sont affichés. Les filtres peuvent être combinés : anniversaires, événements, résolutions. + +Les membres de l'exécutif peuvent créer un événement. + +### Littérature + +Permet de consulter l'inventaire de littérature : + +- titres disponibles; +- prix; +- quantités; +- alertes de stock bas. + +### Décisions en cours + +Affiche le suivi des décisions et propositions non terminées. + +### Votes ouverts + +Permet aux membres de voter dans les sondages ouverts. + +### Mon profil + +Permet de modifier : + +- prénom; +- nom; +- téléphone; +- courriel; +- date d'abstinence; +- PIN. + +Un courriel déjà utilisé dans le même groupe ne peut pas être repris. + +### Propositions + +Permet de : + +- soumettre une proposition; +- consulter les propositions; +- seconder une proposition; +- suivre le statut des résolutions. + +Une proposition suit normalement ce cycle : + +1. proposée; +2. secondée; +3. votée; +4. adoptée ou rejetée. + +Une proposition adoptée devient une résolution numérotée. + +### Postes à pourvoir + +Permet de consulter les postes vacants et de proposer sa candidature. + +Si un membre est nominé à un poste, il peut accepter ou refuser la nomination. + +### État financier + +Affiche une vue rapide : + +- solde bancaire; +- encaisse; +- montant disponible; +- résultat net; +- revenus; +- sorties; +- réserves; +- dernières collectes; +- dernières dépenses. + +## 4. Modules de saisie accessibles à tous + +### Collecte 7e Tradition + +Permet de saisir une collecte. + +Règles importantes : + +- la collecte doit être associée à une date de réunion; +- il ne peut pas y avoir deux collectes pour la même date; +- un autre membre doit confirmer le montant; +- le trésorier doit accuser réception si la collecte n'est pas déjà reçue automatiquement. + +Le module offre aussi une liste des collectes existantes. + +### Dépense + +Permet à un membre de soumettre une dépense. + +La dépense n'est pas considérée comme payée tant que le trésorier ne l'a pas confirmée. + +Le trésorier choisit la méthode : + +- `COLLECTE` : payé depuis l'encaisse; +- `CHEQUE` ou `VIREMENT` : engagement à confirmer lorsque le débit apparaît; +- `DU` : montant dû au membre. + +## 5. Trésorerie + +Module réservé au trésorier ou aux postes autorisés. + +### Soldes + +Affiche : + +- solde en banque; +- encaisse; +- montant disponible; +- réserves; +- engagements non débités; +- argent encore en circulation. + +### Réceptions + +Permet de traiter : + +- collectes à recevoir; +- ventes de littérature remises au trésorier; +- contributions à confirmer comme débitées. + +### Registre + +Affiche les transactions financières. + +On peut filtrer par : + +- mois; +- année; +- type de transaction. + +Types disponibles : + +- collectes; +- ventes de littérature; +- dépenses; +- contributions; +- dépôts bancaires; +- retraits bancaires. + +### Réserves + +Permet de créer, modifier et supprimer les réserves. + +Règle importante : le total des réserves ne peut jamais dépasser le solde en banque. + +### Opérations bancaires + +Permet d'enregistrer : + +- un dépôt : transfert de l'encaisse vers la banque; +- un retrait : transfert de la banque vers l'encaisse; +- un relevé : solde réel du compte bancaire à une date donnée. + +Le dépôt ne peut pas dépasser l'encaisse disponible. + +## 6. Envoi contributions + +Module réservé au trésorier. + +Permet d'enregistrer une contribution vers : + +- district; +- région; +- BSG; +- intergroupe. + +Le module demande : + +- destination; +- montant; +- méthode; +- proposeur; +- secondeur. + +Lors de l'enregistrement, l'application crée une résolution de type `contribution`. + +La contribution ne modifie pas les soldes immédiatement. Elle doit être confirmée comme débitée dans `Trésorerie` pour que le solde bancaire soit mis à jour. + +## 7. Procès-verbal, présences et rapport RSG + +### Rédiger PV + +Permet de préparer un procès-verbal de réunion d'affaires. + +Les propositions secondées et les décisions peuvent alimenter le contenu du PV. + +### Présences + +Permet de noter les membres présents à une réunion d'affaires. + +### Rapport RSG + +Permet au RSG ou au RSG substitut de saisir un rapport destiné au groupe. + +## 8. Littérature et jetons + +### Vente littérature + +Permet d'enregistrer une vente. + +Quand une vente est enregistrée : + +- le stock est ajusté; +- le montant devient à remettre au trésorier; +- le trésorier confirme ensuite la réception dans `Trésorerie`. + +### Ajuster inventaire + +Permet au responsable de la littérature d'ajuster les quantités ou d'ajouter des éléments. + +### Remise jeton + +Permet d'enregistrer une remise de jeton. + +### Commander jetons + +Permet de préparer ou suivre les commandes de jetons. + +## 9. Gestion des membres + +Module réservé à l'exécutif ou aux postes autorisés. + +Permet de : + +- voir les membres actifs; +- consulter une fiche membre; +- générer un code d'invitation; +- copier ou partager le code; +- désactiver un membre; +- appliquer le droit à l'oubli. + +### Désactiver un membre + +Le membre ne peut plus se connecter, mais son historique demeure. + +### Droit à l'oubli + +Les données personnelles du membre sont anonymisées. Cette action est irréversible. + +## 10. Gestion des mandats + +Regroupe les actions liées aux mandats : + +- affecter un poste; +- terminer un mandat; +- planifier les rotations. + +### Affecter un poste + +Une affectation demande : + +- le membre; +- le poste; +- la date de début; +- un proposeur; +- un secondeur. + +Une affectation crée automatiquement une résolution adoptée de type `nomination`. + +### Terminer un mandat + +Permet de fermer un mandat actif. + +La fin d'un mandat n'est pas automatique. Elle doit être enregistrée manuellement. + +## 11. Gestion des postes + +Permet de : + +- créer des postes; +- abolir des postes; +- gérer les candidatures; +- contrôler les accès aux modules selon les postes. + +Les accès ne sont pas accordés directement aux personnes : ils passent par les postes. + +## 12. Paramètres du groupe + +Permet de modifier les informations générales : + +- nom; +- district; +- région; +- adresse; +- jour de réunion; +- heure; +- format. + +## 13. Sondages + +### Lancer un sondage + +Permet à l'exécutif de créer un sondage. + +### Votes ouverts + +Permet aux membres de voter. + +## 14. Bonnes pratiques + +- Utiliser son propre compte, jamais celui d'un autre membre. +- Ne pas partager son PIN. +- Se déconnecter sur un appareil partagé. +- Saisir les collectes et dépenses dès que possible. +- Confirmer les débits seulement lorsque l'argent est réellement sorti du compte. +- Enregistrer les dépôts quand l'encaisse est réellement déposée à la banque. +- Garder les réserves à jour. +- Utiliser les propositions et résolutions pour conserver une trace claire des décisions. + +## 15. Problèmes fréquents + +### Je ne peux pas me connecter + +Vérifier : + +- le bon groupe est sélectionné; +- le courriel est exact; +- le PIN contient 4 chiffres; +- le compte n'a pas été désactivé. + +### Je ne vois pas un module + +Les modules dépendent des postes et des accès configurés. + +Demander à l'exécutif de vérifier : + +- vos mandats actifs; +- les accès du poste dans `Gestion des postes`. + +### Le dépôt est refusé + +Le montant du dépôt dépasse probablement l'encaisse disponible. + +### Une réserve est refusée + +Le total des réserves dépasserait le solde en banque. + +### Une contribution ou dépense ne change pas le solde + +Elle doit être confirmée comme débitée dans `Trésorerie`. + +### Une collecte ne peut pas être saisie + +Causes possibles : + +- date hors jour de réunion; +- collecte déjà enregistrée pour cette date; +- date future. + +## 16. Règles importantes de l'application + +- Les données sont séparées par groupe. +- Un membre se connecte avec son courriel et son PIN. +- Les actions financières sensibles sont réservées au trésorier ou à l'exécutif. +- Les résolutions adoptées sont numérotées automatiquement. +- Les mandats ne se terminent pas seuls. +- Les accès aux modules sont contrôlés par les postes. + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..c29a817 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6485 @@ +{ + "name": "groupe-meditation", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "groupe-meditation", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "vite": "^5.4.0", + "vite-plugin-pwa": "^0.20.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", + "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==", + "dev": true, + "dependencies": { + "jsonpointer": "^5.0.1", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", + "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.29.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.29.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", + "dev": true, + "dependencies": { + "serialize-javascript": "^7.0.3", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { + "version": "3.0.0-pre1", + "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", + "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==", + "dev": true, + "dependencies": { + "ejs": "^3.1.10", + "json5": "^2.2.3", + "magic-string": "^0.30.21", + "string.prototype.matchall": "^4.0.12" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.355", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.355.tgz", + "integrity": "sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "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", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "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", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "dev": true, + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smob": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", + "dev": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.47.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", + "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", + "dev": 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==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-pwa": { + "version": "0.20.5", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.5.tgz", + "integrity": "sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^0.2.6", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", + "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz", + "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-build": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz", + "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==", + "dev": true, + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "eta": "^4.5.1", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "pretty-bytes": "^5.3.0", + "rollup": "^4.53.3", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.1", + "workbox-broadcast-update": "7.4.1", + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-google-analytics": "7.4.1", + "workbox-navigation-preload": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-range-requests": "7.4.1", + "workbox-recipes": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1", + "workbox-streams": "7.4.1", + "workbox-sw": "7.4.1", + "workbox-window": "7.4.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz", + "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz", + "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==", + "dev": true + }, + "node_modules/workbox-expiration": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz", + "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz", + "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==", + "dev": true, + "dependencies": { + "workbox-background-sync": "7.4.1", + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz", + "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz", + "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz", + "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz", + "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==", + "dev": true, + "dependencies": { + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz", + "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz", + "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz", + "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==", + "dev": true, + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz", + "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==", + "dev": true + }, + "node_modules/workbox-window": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz", + "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/zustand": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 93ecae7..cbddf16 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -10,11 +10,8 @@ import Depense from './pages/Depense'; import Decisions from './pages/Decisions'; import Evenements from './pages/Evenements'; import Rapports from './pages/Rapports'; -import InviterMembre from './pages/InviterMembre'; import GererMembres from './pages/GererMembres'; -import AffecterPostes from './pages/AffecterPostes'; -import TerminerMandat from './pages/TerminerMandat'; -import PlanifierRotations from './pages/PlanifierRotations'; +import GestionMandats from './pages/GestionMandats'; import Parametres from './pages/Parametres'; import Presences from './pages/Presences'; import RedigerPV from './pages/RedigerPV'; @@ -28,7 +25,6 @@ import EnvoiContributions from './pages/EnvoiContributions'; import ConfigRepartition from './pages/ConfigRepartition'; import LancerSondage from './pages/LancerSondage'; import VotesOuverts from './pages/VotesOuverts'; -import JumelerParrain from './pages/JumelerParrain'; import Propositions from './pages/Propositions'; import Tresorerie from './pages/Tresorerie'; import Impliquer from './pages/Impliquer'; @@ -39,6 +35,8 @@ export default function App() { const { token, page, loadAccueil, loadGroupes } = useStore(); useEffect(() => { + const handleUnauthorized = () => useStore.getState().logout(); + window.addEventListener('gm:unauthorized', handleUnauthorized); loadGroupes(); if (token) { loadAccueil().then(() => { @@ -54,6 +52,7 @@ export default function App() { useStore.setState({ page: 'login' }); }); } + return () => window.removeEventListener('gm:unauthorized', handleUnauthorized); }, []); // Pages protégées @@ -68,13 +67,10 @@ export default function App() { case 'C1': return ; case 'C2': return ; case 'C5': return ; - case 'G1': return ; case 'G2': return ; - case 'G3': return ; - case 'G4': return ; - case 'G5': return ; case 'G9': return ; - case 'G10': return ; + case 'G11': return ; + case 'G12': return ; case 'M1': return ; case 'M2': return ; case 'M3': return ; @@ -87,7 +83,6 @@ export default function App() { case 'G8': return ; case 'G6': return ; case 'C6': return ; - case 'G7': return ; case 'C8': return ; case 'C9': return ; case 'C10': return ; diff --git a/frontend/src/pages/Accueil.jsx b/frontend/src/pages/Accueil.jsx index e8c2d7d..5cb0c52 100644 --- a/frontend/src/pages/Accueil.jsx +++ b/frontend/src/pages/Accueil.jsx @@ -39,7 +39,7 @@ function Section({ titre, modules }) {
{modules.map((m) => ( { - const navigable = ['C7', 'S1', 'S1T', 'S2', 'S3', 'S4', 'S5', 'C1', 'C2', 'C4', 'C5', 'C6', 'C8', 'C9', 'C10', 'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G9', 'G10', 'M1', 'M2', 'M3', 'M4', 'M5', 'M6']; + const navigable = ['C7', 'S1', 'S1T', 'S2', 'S3', 'S4', 'S5', 'C1', 'C2', 'C4', 'C5', 'C6', 'C8', 'C9', 'C10', 'G2', 'G6', 'G9', 'G11', 'G12', 'M1', 'M2', 'M3', 'M4', 'M5', 'M6']; if (m.code === 'C7') useStore.setState({ page: 'profil' }); else if (navigable.includes(m.code)) useStore.setState({ page: m.code }); else alert(`Module ${m.code} — ${m.nom}\n\n(Sera disponible dans les prochaines phases)`); @@ -75,11 +75,6 @@ export default function Accueil() { ))}
)} - {/* Lien aa87.org */} - - 📅 Événements du district → - {/* Modules */} diff --git a/frontend/src/pages/AffecterPostes.jsx b/frontend/src/pages/AffecterPostes.jsx index 3b2d3e4..7f105dc 100644 --- a/frontend/src/pages/AffecterPostes.jsx +++ b/frontend/src/pages/AffecterPostes.jsx @@ -3,11 +3,17 @@ import { api } from '../stores/api'; import PageHeader from '../components/PageHeader'; import SuccessCheck from '../components/SuccessCheck'; -export default function AffecterPostes() { +export default function AffecterPostes({ compact = false }) { const [postes, setPostes] = useState([]); const [membres, setMembres] = useState([]); const [affectations, setAffectations] = useState([]); - const [form, setForm] = useState({ membre_id: '', poste_id: '', date_debut: new Date().toISOString().slice(0, 10) }); + const [form, setForm] = useState({ + membre_id: '', + poste_id: '', + date_debut: new Date().toISOString().slice(0, 10), + proposeur_id: '', + secondeur_id: '', + }); const [loading, setLoading] = useState(true); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); @@ -26,8 +32,12 @@ export default function AffecterPostes() { }, []); const handleSubmit = async () => { - if (!form.membre_id || !form.poste_id) { - setError('Sélectionnez un membre et un poste'); + if (!form.membre_id || !form.poste_id || !form.proposeur_id || !form.secondeur_id) { + setError('Sélectionnez un membre, un poste, un proposeur et un secondeur'); + return; + } + if (form.proposeur_id === form.secondeur_id) { + setError('Le proposeur et le secondeur doivent être différents'); return; } setError(''); @@ -48,8 +58,8 @@ export default function AffecterPostes() { const postesVacants = postes.filter((p) => !postesOccupes.has(p.nom)); return ( -
- +
+ {!compact && }
{/* Affectations actuelles */} @@ -75,7 +85,7 @@ export default function AffecterPostes() { {postesVacants.length === 0 ? (
-

Tous les postes sont pourvus. Terminez d'abord un mandat (G4) pour libérer un poste.

+

Tous les postes sont pourvus. Terminez d'abord un mandat pour libérer un poste.

) : ( <> @@ -98,6 +108,24 @@ export default function AffecterPostes() { ))}
+ +
+ + +
+ +
+ + +
)} @@ -112,7 +140,7 @@ export default function AffecterPostes() { {error &&

{error}

} diff --git a/frontend/src/pages/Collecte.jsx b/frontend/src/pages/Collecte.jsx index 39fa31c..2eea910 100644 --- a/frontend/src/pages/Collecte.jsx +++ b/frontend/src/pages/Collecte.jsx @@ -16,6 +16,8 @@ export default function Collecte() { const [detenteurId, setDetenteurId] = useState(''); const [membres, setMembres] = useState([]); const [collectesMois, setCollectesMois] = useState([]); + const [collectesToutes, setCollectesToutes] = useState([]); + const [collecteSelectionneeId, setCollecteSelectionneeId] = useState(''); const [tab, setTab] = useState('mois'); const [loading, setLoading] = useState(false); const [msg, setMsg] = useState(''); @@ -27,13 +29,16 @@ export default function Collecte() { const charger = async () => { try { - const [mbrs, cols] = await Promise.all([ + const [mbrs, cols, toutes] = await Promise.all([ api.get('/membres/actifs'), api.get(`/collectes/?mois=${mois}&annee=${annee}&limite=50`), + api.get('/collectes/?limite=500'), ]); setMembres(mbrs); setCollectesMois(cols); + setCollectesToutes(toutes); if (membre && !detenteurId) setDetenteurId(membre.id); + if (!collecteSelectionneeId && toutes.length > 0) setCollecteSelectionneeId(toutes[0].id); } catch (e) { setError(e.message); } }; @@ -70,30 +75,42 @@ export default function Collecte() { }; const totalMois = collectesMois.reduce((sum, c) => sum + parseFloat(c.montant), 0); + const collecteSelectionnee = collectesToutes.find((c) => c.id === collecteSelectionneeId); const MOIS_NOMS = ['', 'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']; + const dateLocale = (d = new Date()) => { + const an = d.getFullYear(); + const mo = String(d.getMonth() + 1).padStart(2, '0'); + const jo = String(d.getDate()).padStart(2, '0'); + return `${an}-${mo}-${jo}`; + }; + + const labelDate = (d, avecAnnee = false) => { + const dt = new Date(d + 'T12:00:00'); + return dt.toLocaleDateString('fr-CA', { + weekday: 'long', + day: 'numeric', + month: 'long', + ...(avecAnnee ? { year: 'numeric' } : {}), + }); + }; + // Calculer les dernières dates de réunion pour le sélecteur const joursSemaine = { 'lundi': 1, 'mardi': 2, 'mercredi': 3, 'jeudi': 4, 'vendredi': 5, 'samedi': 6, 'dimanche': 0 }; const jourReunion = groupe?.jour_reunion ? joursSemaine[groupe.jour_reunion.toLowerCase()] : null; const getDatesReunion = () => { if (jourReunion === null) return []; + const datesAvecCollecte = new Set(collectesToutes.map((c) => c.date_collecte)); const dates = []; const d = new Date(); - for (let i = 0; i < 12; i++) { + for (let i = 0; i < 180; i++) { const test = new Date(d); test.setDate(d.getDate() - i); + const valeur = dateLocale(test); if (test.getDay() === jourReunion) { - dates.push(test.toISOString().slice(0, 10)); - } - } - // Aussi vérifier les 30 jours suivants dans le passé - for (let i = 12; i < 60; i++) { - const test = new Date(d); - test.setDate(d.getDate() - i); - if (test.getDay() === jourReunion) { - dates.push(test.toISOString().slice(0, 10)); + if (!datesAvecCollecte.has(valeur)) dates.push(valeur); } } return dates; @@ -101,6 +118,12 @@ export default function Collecte() { const datesReunion = getDatesReunion(); + useEffect(() => { + if (datesReunion.length > 0 && !datesReunion.includes(dateCollecte)) { + setDateCollecte(datesReunion[0]); + } + }, [collectesToutes, groupe?.jour_reunion]); + return (
@@ -134,6 +157,42 @@ export default function Collecte() {
+ {collectesToutes.length > 0 && ( +
+
+ + +
+ {collecteSelectionnee && ( +
+
+

{labelDate(collecteSelectionnee.date_collecte, true)}

+ + {STATUT[collecteSelectionnee.statut]?.label || collecteSelectionnee.statut} + +
+

{parseFloat(collecteSelectionnee.montant).toFixed(2)} $

+

+ Saisi par {collecteSelectionnee.saisi_par_prenom || '—'} · Chez {collecteSelectionnee.detenteur_prenom || '—'} +

+ {collecteSelectionnee.confirme_par_prenom && ( +

Confirmé par {collecteSelectionnee.confirme_par_prenom}

+ )} + {collecteSelectionnee.recu_par_prenom && ( +

Reçu par {collecteSelectionnee.recu_par_prenom}

+ )} +
+ )} +
+ )} + {collectesMois.length === 0 ? (

💰

@@ -181,18 +240,23 @@ export default function Collecte() { - ) : ( + ) : jourReunion === null ? ( setDateCollecte(e.target.value)} - max={new Date().toISOString().slice(0, 10)} + max={dateLocale()} className="w-full p-3 bg-gray-50 rounded-xl text-lg border-0 focus:ring-2 focus:ring-aa-blue" /> + ) : ( +
+ Aucun {groupe?.jour_reunion || 'jour de réunion'} sans collecte enregistrée dans les derniers mois. +
)} -

Saisie rétroactive permise aux dates de réunion

+

+ Seuls les {groupe?.jour_reunion || 'jours de réunion'} sans collecte enregistrée sont proposés. +

@@ -217,7 +281,7 @@ export default function Collecte() {
- diff --git a/frontend/src/pages/Depense.jsx b/frontend/src/pages/Depense.jsx index d6cc9de..a576297 100644 --- a/frontend/src/pages/Depense.jsx +++ b/frontend/src/pages/Depense.jsx @@ -65,7 +65,7 @@ export default function Depense() { formData.append('fichier', fichier); await fetch(`/api/depenses/${result.id}/preuve`, { method: 'POST', - headers: { 'Authorization': `Bearer ${localStorage.getItem('gm_token')}` }, + headers: { 'Authorization': `Bearer ${api.getToken()}` }, body: formData, }); } diff --git a/frontend/src/pages/EnvoiContributions.jsx b/frontend/src/pages/EnvoiContributions.jsx index 15fc9ba..179a502 100644 --- a/frontend/src/pages/EnvoiContributions.jsx +++ b/frontend/src/pages/EnvoiContributions.jsx @@ -17,7 +17,7 @@ const DESTINATIONS = [ export default function EnvoiContributions() { const [soldes, setSoldes] = useState(null); - const [resolutions, setResolutions] = useState([]); + const [membres, setMembres] = useState([]); const [envois, setEnvois] = useState([]); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); @@ -28,17 +28,18 @@ export default function EnvoiContributions() { destination: 'district', montant: '', methode: '', - resolution_id: '', + proposeur_id: '', + secondeur_id: '', }); useEffect(() => { Promise.all([ api.get('/collectes/soldes'), - api.get('/propositions/resolutions?type_proposition=contribution'), + api.get('/membres/actifs'), api.get('/contributions/?limite=10'), - ]).then(([s, r, e]) => { + ]).then(([s, m, e]) => { setSoldes(s); - setResolutions(r); + setMembres(m); setEnvois(e); if (s && s.disponible > 0) { setForm((f) => ({ ...f, montant: s.disponible.toFixed(2) })); @@ -51,10 +52,8 @@ export default function EnvoiContributions() { const handleSubmit = async () => { if (!form.montant || parseFloat(form.montant) <= 0) { setError('Montant invalide'); return; } if (!form.methode) { setError('Sélectionnez une méthode'); return; } - if (!form.resolution_id) { - setError('Une résolution adoptée est requise. Créez d\'abord une proposition de type « contribution », faites-la seconder et adopter.'); - return; - } + if (!form.proposeur_id || !form.secondeur_id) { setError('Sélectionnez un proposeur et un secondeur'); return; } + if (form.proposeur_id === form.secondeur_id) { setError('Le proposeur et le secondeur doivent être différents'); return; } setLoading(true); setError(''); try { await api.post('/contributions/', { @@ -62,17 +61,18 @@ export default function EnvoiContributions() { destination: form.destination, montant: parseFloat(form.montant), methode: form.methode, - resolution_id: form.resolution_id, + proposeur_id: form.proposeur_id, + secondeur_id: form.secondeur_id, }); setSuccess(true); } catch (e) { setError(e.message); setLoading(false); } }; - if (success) return ; + if (success) return ; return (
- +
{/* Somme disponible */} @@ -93,29 +93,30 @@ export default function EnvoiContributions() {
)} - {/* Résolution obligatoire */} -
- - {resolutions.length === 0 ? ( -
-

- Aucune résolution de type « contribution » disponible. -

-

- Créez une proposition de type « contribution » dans la section Propositions, faites-la seconder puis adopter lors d'une assemblée d'affaires. -

-
- ) : ( - update('proposeur_id', e.target.value)} className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue"> - - {resolutions.map((r) => ( - - ))} + + {membres.map((m) => )} - )} +
+
+ + +
@@ -161,9 +162,9 @@ export default function EnvoiContributions() { {error &&

{error}

} {/* Historique */} @@ -174,7 +175,9 @@ export default function EnvoiContributions() {

{e.destination_label}

-

{e.date_envoi} · {e.methode}

+

+ {e.date_envoi} · {e.methode} · {e.debitee ? 'Débité' : 'À débiter'} +

{e.montant.toFixed(2)} $

diff --git a/frontend/src/pages/EtatFinancier.jsx b/frontend/src/pages/EtatFinancier.jsx index fb6ef49..8e38369 100644 --- a/frontend/src/pages/EtatFinancier.jsx +++ b/frontend/src/pages/EtatFinancier.jsx @@ -42,7 +42,7 @@ export default function EtatFinancier() {
Total revenus - {data.revenus.total.toFixed(2)} $ + {formatMontant(data.revenus?.total)} $
@@ -54,36 +54,41 @@ export default function EtatFinancier() {
Total sorties - {data.sorties.total.toFixed(2)} $ + {formatMontant(data.sorties?.total)} $
{/* Réserves */}

Réserves

- - + {(data.reserves?.details || []).length > 0 ? ( + data.reserves.details.map((reserve) => ( + + )) + ) : ( +

Aucune réserve enregistrée.

+ )}
Total réserves - {data.reserves.total.toFixed(2)} $ + {formatMontant(data.reserves?.total)} $
{/* Dernières collectes */} - {data.dernieres_collectes.length > 0 && ( + {(data.dernieres_collectes || []).length > 0 && (

Dernières collectes

{data.dernieres_collectes.map((c, i) => (
{c.date} - +{c.montant.toFixed(2)} $ + +{formatMontant(c.montant)} $
))}
)} {/* Dernières dépenses */} - {data.dernieres_depenses.length > 0 && ( + {(data.dernieres_depenses || []).length > 0 && (

Dernières dépenses

{data.dernieres_depenses.map((d, i) => ( @@ -92,7 +97,7 @@ export default function EtatFinancier() {

{d.quoi}

{d.date} · {d.methode || '—'} · {d.paye ? '✓' : '⏳'}

- -{d.montant.toFixed(2)} $ + -{formatMontant(d.montant)} $ ))} @@ -113,7 +118,7 @@ function Bloc({ label, montant, couleur }) { return (

{label}

-

{montant.toFixed(2)} $

+

{formatMontant(montant)} $

); } @@ -122,7 +127,12 @@ function Ligne({ label, montant }) { return (
{label} - {montant.toFixed(2)} $ + {formatMontant(montant)} $
); } + +function formatMontant(montant) { + const valeur = Number(montant); + return Number.isFinite(valeur) ? valeur.toFixed(2) : '0.00'; +} diff --git a/frontend/src/pages/Evenements.jsx b/frontend/src/pages/Evenements.jsx index d0eac48..a348645 100644 --- a/frontend/src/pages/Evenements.jsx +++ b/frontend/src/pages/Evenements.jsx @@ -12,7 +12,7 @@ const TYPE_STYLE = { export default function Evenements() { const { postes } = useStore(); const [evenements, setEvenements] = useState([]); - const [filtre, setFiltre] = useState('tous'); + const [filtres, setFiltres] = useState(['anniversaire', 'adhoc', 'resolution']); const [loading, setLoading] = useState(true); const [showForm, setShowForm] = useState(false); const [error, setError] = useState(''); @@ -30,7 +30,7 @@ export default function Evenements() { const charger = async () => { try { - const data = await api.get('/evenements/?jours=90'); + const data = await api.get('/evenements/?jours=365&jours_avant=365'); setEvenements(data); } catch (e) { setError(e.message); } setLoading(false); @@ -52,11 +52,17 @@ export default function Evenements() { } catch (e) { setError(e.message); } }; - const filtres = evenements.filter((e) => filtre === 'tous' || e.type === filtre); + const toggleFiltre = (type) => { + setFiltres((actifs) => ( + actifs.includes(type) ? actifs.filter((t) => t !== type) : [...actifs, type] + )); + }; + + const evenementsFiltres = evenements.filter((e) => filtres.includes(e.type)); // Grouper par date const parDate = {}; - filtres.forEach((e) => { + evenementsFiltres.forEach((e) => { if (!parDate[e.date]) parDate[e.date] = []; parDate[e.date].push(e); }); @@ -65,7 +71,7 @@ export default function Evenements() { return (
- +
{msg &&
{msg}
} @@ -73,10 +79,10 @@ export default function Evenements() { {/* Filtres */}
- {[['tous', 'Tous'], ['anniversaire', '🎂 Anniv.'], ['adhoc', '⭐ Événements'], ['resolution', '📜 Résol.']].map(([k, label]) => ( - ))} {estExecutif && ( @@ -108,10 +114,10 @@ export default function Evenements() { )} {/* Liste groupée par date */} - {filtres.length === 0 ? ( + {evenementsFiltres.length === 0 ? (

📅

-

Aucun événement à venir

+

Aucun événement pour ces filtres

) : ( Object.entries(parDate).map(([dateStr, evs]) => { diff --git a/frontend/src/pages/GererMembres.jsx b/frontend/src/pages/GererMembres.jsx index 038f289..540ce29 100644 --- a/frontend/src/pages/GererMembres.jsx +++ b/frontend/src/pages/GererMembres.jsx @@ -8,6 +8,10 @@ export default function GererMembres() { const [detail, setDetail] = useState(null); const [confirmAction, setConfirmAction] = useState(null); const [msg, setMsg] = useState(''); + const [code, setCode] = useState(null); + const [expiration, setExpiration] = useState(null); + const [inviteLoading, setInviteLoading] = useState(false); + const [copied, setCopied] = useState(false); const charger = async () => { setLoading(true); @@ -20,6 +24,28 @@ export default function GererMembres() { useEffect(() => { charger(); }, []); + const genererInvitation = async () => { + setInviteLoading(true); + try { + const data = await api.post('/invitations/'); + setCode(data.code); + setExpiration(new Date(data.expire_le).toLocaleString('fr-CA')); + } catch (e) { alert(e.message); } + setInviteLoading(false); + }; + + const copierInvitation = () => { + navigator.clipboard.writeText(code).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + const partagerSMS = () => { + const texte = `Rejoins le Groupe Méditation sur l'app. Ton code d'invitation : ${code}`; + window.open(`sms:?body=${encodeURIComponent(texte)}`); + }; + const desactiver = async (id) => { try { const res = await api.patch(`/membres/${id}/desactiver`); @@ -122,9 +148,40 @@ export default function GererMembres() { return (
- -
+ +
{msg &&
{msg}
} + +
+
+
+

Inviter un membre

+

Code à usage unique, expiration automatique.

+
+ +
+ {code && ( +
+

Code d'invitation

+

{code}

+

Expire le {expiration}

+
+ + +
+
+ )} +
+ {loading ? (

Chargement...

) : ( diff --git a/frontend/src/pages/GererPostes.jsx b/frontend/src/pages/GererPostes.jsx index 18e0f3c..9efe96d 100644 --- a/frontend/src/pages/GererPostes.jsx +++ b/frontend/src/pages/GererPostes.jsx @@ -8,40 +8,39 @@ const CATEGORIES = [ { value: 'physique', label: 'Physique' }, ]; +const MODULES = [ + ['S1T', 'Trésorerie'], ['M4', 'Envoi contributions'], + ['M1', 'Rédiger PV'], ['M2', 'Présences'], ['M3', 'Rapport RSG'], + ['S2', 'Remise jeton'], ['M6', 'Commander jetons'], + ['S3', 'Vente littérature'], ['M5', 'Ajuster inventaire'], ['S4', 'Rapport réunion'], + ['G2', 'Gestion membres'], ['G6', 'Sondages'], ['G9', 'Paramètres'], + ['G11', 'Gestion mandats'], ['G12', 'Gestion postes'], +]; + export default function GererPostes() { const [postes, setPostes] = useState([]); - const [taches, setTaches] = useState([]); + const [modulesPostes, setModulesPostes] = useState({}); const [candidatures, setCandidatures] = useState([]); const [tab, setTab] = useState('postes'); const [showForm, setShowForm] = useState(false); - const [showTacheForm, setShowTacheForm] = useState(false); const [msg, setMsg] = useState(''); const [error, setError] = useState(''); - // Form poste const [nom, setNom] = useState(''); const [categorie, setCategorie] = useState('service'); const [description, setDescription] = useState(''); const [prerequis, setPrerequis] = useState(''); const [informations, setInformations] = useState(''); - // Form tache - const [tacheNom, setTacheNom] = useState(''); - const [tacheInfo, setTacheInfo] = useState(''); - - // Affectation tache - const [affectPosteId, setAffectPosteId] = useState(''); - const [affectTacheId, setAffectTacheId] = useState(''); - const charger = async () => { try { - const [p, t, c] = await Promise.all([ + const [p, mp, c] = await Promise.all([ api.get('/postes/?inclure_abolis=true'), - api.get('/postes/taches'), + api.get('/postes/modules'), api.get('/postes/candidatures'), ]); setPostes(p); - setTaches(t); + setModulesPostes(mp); setCandidatures(c); } catch (e) { setError(e.message); } }; @@ -84,49 +83,24 @@ export default function GererPostes() { }; const supprimerPermanent = async (id, nomPoste) => { - if (!confirm(`Supprimer DÉFINITIVEMENT le poste «${nomPoste}» ?\nCette action est irréversible.`)) return; + if (!confirm(`Supprimer définitivement le poste «${nomPoste}» ?`)) return; try { await api.request('DELETE', `/postes/${id}/permanent`); - flash('Poste supprimé définitivement'); + flash('Poste supprimé'); await charger(); } catch (e) { setError(e.message); } }; - const creerTache = async () => { - if (!tacheNom.trim()) { setError('Nom requis'); return; } + const toggleModule = async (posteId, code) => { + const actuel = new Set(modulesPostes[posteId] || []); + if (actuel.has(code)) actuel.delete(code); + else actuel.add(code); + const codes = [...actuel]; + setModulesPostes({ ...modulesPostes, [posteId]: codes }); try { - await api.post('/postes/taches', { nom: tacheNom.trim(), informations: tacheInfo.trim() || null }); - setTacheNom(''); setTacheInfo(''); setShowTacheForm(false); - flash('Tâche créée ✓'); - await charger(); - } catch (e) { setError(e.message); } - }; - - const supprimerTache = async (id, nomT) => { - if (!confirm(`Supprimer la tâche «${nomT}» ?`)) return; - try { - await api.request('DELETE', `/postes/taches/${id}`); - flash('Tâche supprimée'); - await charger(); - } catch (e) { setError(e.message); } - }; - - const affecterTache = async () => { - if (!affectPosteId || !affectTacheId) { setError('Sélectionnez un poste et une tâche'); return; } - try { - await api.post(`/postes/${affectPosteId}/taches/${affectTacheId}`); - setAffectTacheId(''); - flash('Tâche affectée ✓'); - await charger(); - } catch (e) { setError(e.message); } - }; - - const desaffecterTache = async (posteId, tacheId) => { - try { - await api.request('DELETE', `/postes/${posteId}/taches/${tacheId}`); - flash('Tâche retirée'); - await charger(); - } catch (e) { setError(e.message); } + await api.request('PUT', `/postes/${posteId}/modules`, codes); + flash('Accès mis à jour ✓'); + } catch (e) { setError(e.message); await charger(); } }; const traiterCandidature = async (id, statut) => { @@ -137,12 +111,11 @@ export default function GererPostes() { } catch (e) { setError(e.message); } }; - const postesActifs = postes.filter((p) => p.actif); const CAT_ICON = { executif: '🔵', service: '🟣', physique: '🟢' }; return (
- +
{msg &&
{msg}
} @@ -150,9 +123,9 @@ export default function GererPostes() {
{[ - ['postes', `📋 Postes (${postes.length})`], - ['taches', `🔧 Tâches (${taches.length})`], - ['candidatures', `🙋 Candid. (${candidatures.length})`], + ['postes', `Postes (${postes.length})`], + ['acces', 'Accès'], + ['candidatures', `Candidatures (${candidatures.length})`], ].map(([t, label]) => (
- {/* ── Postes ── */} {tab === 'postes' && (
- {showForm && (
- setNom(e.target.value)} - placeholder="Nom du poste" className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue" autoFocus /> + setNom(e.target.value)} placeholder="Nom du poste" + className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue" /> - setDescription(e.target.value)} - placeholder="Description" className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue" /> - setPrerequis(e.target.value)} - placeholder="Prérequis" className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue" /> -