diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index aafb123..5bd1669 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,16 +5,18 @@ name: Node.js CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] + +permissions: + contents: read permissions: contents: read jobs: build: - runs-on: ubuntu-latest strategy: @@ -23,12 +25,12 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run build --if-present - - run: npm test + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index ec63c28..6d5d3e4 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -4,7 +4,11 @@ on: push: branches: - main - workflow_dispatch: + workflow_dispatch: + +permissions: + contents: write + pull-requests: read permissions: contents: write diff --git a/.gitignore b/.gitignore index dbc949a..dd7c168 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ server.log # idea files .idea /generated/prisma +/examples diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..32e07b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "WillLuke.nextjs.hasPrompted": true +} \ No newline at end of file diff --git a/ProxmoxVE-Local.code-workspace b/ProxmoxVE-Local.code-workspace new file mode 100644 index 0000000..013094d --- /dev/null +++ b/ProxmoxVE-Local.code-workspace @@ -0,0 +1,13 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../ProxmoxVE-Frontend" + } + ], + "settings": { + "WillLuke.nextjs.hasPrompted": true + } +} \ No newline at end of file diff --git a/UPDATER_VERSION b/UPDATER_VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/UPDATER_VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/VERSION b/VERSION index 659914a..3153fa4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.8 +1.0.0-pre9 \ No newline at end of file diff --git a/json/files.txt b/json/files.txt deleted file mode 100644 index 651acf6..0000000 --- a/json/files.txt +++ /dev/null @@ -1,405 +0,0 @@ -2fauth.json -actualbudget.json -add-iptag.json -add-netbird-lxc.json -add-tailscale-lxc.json -adguard.json -adventurelog.json -agentdvr.json -all-templates.json -alpine-it-tools.json -alpine-tinyauth.json -alpine.json -apache-cassandra.json -apache-couchdb.json -apache-guacamole.json -apache-tika.json -apache-tomcat.json -apt-cacher-ng.json -archivebox.json -archlinux-vm.json -argus.json -aria2.json -asterisk.json -audiobookshelf.json -authelia.json -autobrr.json -autocaliweb.json -babybuddy.json -backrest.json -baikal.json -bar-assistant.json -barcode-buddy.json -bazarr.json -beszel.json -bitmagnet.json -blocky.json -booklore.json -bookstack.json -bunkerweb.json -bytestash.json -caddy.json -casaos.json -changedetection.json -channels.json -checkmk.json -clean-lxcs.json -clean-orphaned-lvm.json -cleanuparr.json -cloudflare-ddns.json -cloudflared.json -cloudreve.json -cockpit.json -coder-code-server.json -commafeed.json -configarr.json -convertx.json -copyparty.json -cosmos.json -crafty-controller.json -cron-update-lxcs.json -cronicle.json -cross-seed.json -crowdsec.json -cryptpad.json -daemonsync.json -dashy.json -debian-13-vm.json -debian-vm.json -debian.json -deconz.json -deluge.json -docker-vm.json -docker.json -dockge.json -docmost.json -documenso.json -dolibarr.json -dotnetaspwebapi.json -duplicati.json -elementsynapse.json -emby.json -emqx.json -ersatztv.json -esphome.json -evcc.json -excalidraw.json -execute.json -fenrus.json -fhem.json -filebrowser-quantum.json -filebrowser.json -fileflows.json -firefly.json -flaresolverr.json -flowiseai.json -fluid-calendar.json -forgejo.json -freepbx.json -freshrss.json -frigate.json.bak -fstrim.json -fumadocs.json -gatus.json -ghost.json -ghostfolio.json -gitea-mirror.json -gitea.json -glance.json -glances.json -globaleaks.json -glpi.json -go2rtc.json -goaway.json -gokapi.json -gotify.json -grafana.json -graylog.json -grist.json -grocy.json -guardian.json -habitica.json -haos-vm.json -headscale.json -healthchecks.json -heimdall-dashboard.json -hev-socks5-server.json -hivemq.json -homarr.json -homeassistant.json -homebox.json -homebridge.json -homepage.json -homer.json -hortusfox.json -host-backup.json -huntarr.json -hyperhdr.json -hyperion.json -immich.json -influxdb.json -inspircd.json -inventree.json -iobroker.json -itsm-ng.json -iventoy.json -jackett.json -jeedom.json -jellyfin.json -jellyseerr.json -jenkins.json -joplin-server.json -jupyternotebook.json -kapowarr.json -karakeep.json -kasm.json -kavita.json -kernel-clean.json -kernel-pin.json -keycloak.json -kimai.json -koillection.json -kometa.json -komga.json -komodo.json -kubo.json -lazylibrarian.json -leantime.json -librespeed-rust.json -libretranslate.json -lidarr.json -limesurvey.json -linkstack.json -linkwarden.json -listmonk.json -litellm.json -lldap.json -lubelogger.json -lxc-delete.json -lyrionmusicserver.json -mafl.json -magicmirror.json -managemydamnlife.json -mariadb.json -matterbridge.json -mattermost.json -mealie.json -mediamanager.json -mediamtx.json -medusa.json -meilisearch.json -memos.json -meshcentral.json -metadata.json -metube.json -microcode.json -mikrotik-routeros.json -minarca.json -minio.json -mongodb.json -monica.json -monitor-all.json -motioneye.json -mqtt.json -myip.json -mylar3.json -myspeed.json -mysql.json -n8n.json -navidrome.json -neo4j.json -netbox.json -netdata.json -nextcloud-vm.json -nextcloudpi.json -nextpvr.json -nginxproxymanager.json -nic-offloading-fix.json -nocodb.json -node-red.json -nodebb.json -notifiarr.json -npmplus.json -ntfy.json -nxwitness.json -nzbget.json -oauth2-proxy.json -octoprint.json -odoo.json -olivetin.json -ollama.json -omada.json -ombi.json -omv.json -onedev.json -onlyoffice.json -open-archiver.json -opengist.json -openhab.json -openobserve.json -openproject.json -openwebui.json -openwrt-vm.json -openziti-controller.json -openziti-tunnel.json -ots.json -outline.json -overseerr.json -owncast.json -owncloud-vm.json -pairdrop.json -palmr.json -paperless-ai.json -paperless-gpt.json -paperless-ngx.json -part-db.json -paymenter.json -pbs-microcode.json -pbs4-upgrade.json -peanut.json -pelican-panel.json -pelican-wings.json -pf2etools.json -photoprism.json -phpipam.json -phpmyadmin.json -pialert.json -pihole.json -pimox-haos-vm.json -planka.json -plant-it.json -plex.json -pocketbase.json -pocketid.json -podman-homeassistant.json -podman.json -post-pbs-install.json -post-pmg-install.json -post-pve-install.json -postgresql.json -privatebin.json -projectsend.json -prometheus-alertmanager.json -prometheus-blackbox-exporter.json -prometheus-paperless-ngx-exporter.json -prometheus-pve-exporter.json -prometheus.json -prowlarr.json -proxmox-backup-server.json -proxmox-datacenter-manager.json -proxmox-mail-gateway.json -ps5-mqtt.json -pterodactyl-panel.json -pterodactyl-wings.json -pulse.json -pve-privilege-converter.json -pve-scripts-local.json -qbittorrent.json -rabbitmq.json -radarr.json -radicale.json -rclone.json -rdtclient.json -reactive-resume.json -readeck.json -recyclarr.json -redis.json -redlib.json -resiliosync.json -revealjs.json -runtipi.json -rustdeskserver.json -rwmarkable.json -sabnzbd.json -salt.json -scaling-governor.json -scraparr.json -searxng.json -seelf.json -semaphore.json -sftpgo.json -shinobi.json -signoz.json -silverbullet.json -slskd.json -smokeping.json -snipeit.json -sonarqube.json -sonarr.json -spoolman.json -sqlserver2022.json -stirling-pdf.json -streamlink-webui.json -stylus.json -suwayomi-server.json -swizzin.json -syncthing.json -tandoor.json -tasmoadmin.json -tasmocompiler.json -tautulli.json -tdarr.json -teamspeak-server.json -technitiumdns.json -teddycloud.json -telegraf.json -the-lounge.json -threadfin.json -tianji.json -traccar.json -tracktor.json -traefik.json -transmission.json -trilium.json -tududi.json -tunarr.json -turnkey.json -twingate-connector.json -typesense.json -ubuntu.json -ubuntu2204-vm.json -ubuntu2404-vm.json -ubuntu2504-vm.json -uhf.json -umami.json -umbrel-os-vm.json -umlautadaptarr.json -unbound.json -unifi.json -unmanic.json -update-lxcs.json -update-repo.json -upsnap.json -uptimekuma.json -urbackupserver.json -vaultwarden.json -verdaccio.json -versions.json -victoriametrics.json -vikunja.json -wallos.json -warracker.json -wastebin.json -watcharr.json -watchyourlan.json -wavelog.json -wazuh.json -web-check.json -webmin.json -wger.json -whisparr.json -wikijs.json -wireguard.json -wizarr.json -wordpress.json -yt-dlp-webui.json -yunohost.json -zabbix.json -zammad.json -zerotier-one.json -zigbee2mqtt.json -zipline.json -zitadel.json -zoraxy.json -zot-registry.json -zwave-js-ui.json diff --git a/package-lock.json b/package-lock.json index 18a61e0..4bbf33d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,44 +1,44 @@ { "name": "pve-scripts-local", - "version": "0.5.7", + "version": "1.0.0-pre6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pve-scripts-local", - "version": "0.5.7", + "version": "1.0.0-pre6", "hasInstallScript": true, "dependencies": { - "@prisma/adapter-better-sqlite3": "^7.6.0", - "@prisma/client": "^7.6.0", + "@prisma/adapter-better-sqlite3": "^7.8.0", + "@prisma/client": "^7.8.0", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.4", "@t3-oss/env-nextjs": "^0.13.11", "@tailwindcss/typography": "^0.5.19", - "@tanstack/react-query": "^5.96.0", - "@trpc/client": "^11.16.0", - "@trpc/react-query": "^11.16.0", - "@trpc/server": "^11.16.0", + "@tanstack/react-query": "^5.100.6", + "@trpc/client": "^11.17.0", + "@trpc/react-query": "^11.17.0", + "@trpc/server": "^11.17.0", "@types/react-syntax-highlighter": "^15.5.13", "@types/ws": "^8.18.1", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", - "axios": "^1.14.0", + "axios": "^1.15.2", "bcryptjs": "^3.0.3", - "better-sqlite3": "^12.8.0", + "better-sqlite3": "^12.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cron-validator": "^1.4.0", - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "jsonwebtoken": "^9.0.3", - "lucide-react": "^1.7.0", - "next": ">=16.2.1", + "lucide-react": "^1.14.0", + "next": "16.2.4", "node-cron": "^4.2.1", "node-pty": "^1.1.0", "pocketbase": "^0.26.8", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.1", "refractor": "^5.0.0", @@ -47,39 +47,40 @@ "strip-ansi": "^7.2.0", "superjson": "^2.2.6", "tailwind-merge": "^3.5.0", - "vite": "^8.0.3", + "vite": "^8.0.10", "ws": "^8.20.0", "zod": "^4.3.6" }, "devDependencies": { - "@tailwindcss/postcss": "^4.2.2", + "@tailwindcss/postcss": "^4.2.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/bcryptjs": "^3.0.0", "@types/better-sqlite3": "^7.6.13", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^24.10.9", + "@types/node": "^25.6.0", "@types/node-cron": "^3.0.11", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "@typescript-eslint/eslint-plugin": "^8.59.1", + "@typescript-eslint/parser": "^8.59.1", "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.2", - "@vitest/ui": "^4.1.2", - "baseline-browser-mapping": "^2.10.13", - "eslint": "^10.1.0", - "eslint-config-next": "^16.2.1", - "jsdom": "^29.0.1", - "next": ">=16.2.1", - "postcss": "^8.5.8", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "prisma": "^7.6.0", - "tailwindcss": "^4.2.2", + "@vitest/coverage-v8": "^4.1.5", + "@vitest/ui": "^4.1.5", + "baseline-browser-mapping": "^2.10.24", + "eslint": "^10.2.1", + "eslint-config-next": "^16.2.4", + "jsdom": "^29.1.0", + "next": "16.2.4", + "postcss": "^8.5.12", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "prisma": "^7.8.0", + "tailwindcss": "^4.2.4", "tsx": "^4.21.0", - "typescript": "^6.0.2", - "typescript-eslint": "^8.58.0", - "vitest": "^4.1.2" + "typescript": "^6.0.3", + "vitest": "^4.1.5" }, "engines": { "node": ">=24.0.0" @@ -106,57 +107,47 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", - "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.1.1", - "@csstools/css-color-parser": "^4.0.2", + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.6" + "@csstools/css-tokenizer": "^4.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.3.tgz", - "integrity": "sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, "license": "MIT", "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7" + "is-potential-custom-element-name": "^1.0.1" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -181,6 +172,13 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/compat-data": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", @@ -222,6 +220,16 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", @@ -256,6 +264,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -359,9 +377,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -438,29 +456,6 @@ "specificity": "bin/cli.js" } }, - "node_modules/@clack/core": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", - "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", - "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "0.5.0", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, "node_modules/@csstools/color-helpers": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", @@ -482,9 +477,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", - "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", "dev": true, "funding": [ { @@ -506,9 +501,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", - "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, "funding": [ { @@ -523,7 +518,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.1.1" + "@csstools/css-calc": "^3.2.0" }, "engines": { "node": ">=20.19.0" @@ -557,9 +552,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", - "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", "dev": true, "funding": [ { @@ -632,22 +627,20 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", - "dev": true, + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", "optional": true, "dependencies": { @@ -655,16 +648,457 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -684,19 +1118,6 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -708,13 +1129,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.3", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" }, @@ -722,62 +1143,23 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/config-array/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, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1" + "@eslint/core": "^1.2.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -788,9 +1170,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -798,13 +1180,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { @@ -830,31 +1212,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -862,48 +1244,62 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@hono/node-server": { - "version": "1.19.12", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.12.tgz", - "integrity": "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.0.tgz", + "integrity": "sha512-n3GfHwwCvHCkGmOwKfxUPOlbfzuO64Sbc5XC4NGPIXxkuOnJrdgExdRKmHfF924r914WRJPT397GdqLvdYTeyQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -933,9 +1329,9 @@ } }, "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", "optional": true, @@ -1494,16 +1890,16 @@ } }, "node_modules/@next/env": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.1.tgz", - "integrity": "sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", "dev": true, "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.1.tgz", - "integrity": "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.4.tgz", + "integrity": "sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA==", "dev": true, "license": "MIT", "dependencies": { @@ -1511,9 +1907,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.1.tgz", - "integrity": "sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" ], @@ -1528,9 +1924,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.1.tgz", - "integrity": "sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ "x64" ], @@ -1545,9 +1941,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.1.tgz", - "integrity": "sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ "arm64" ], @@ -1562,9 +1958,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.1.tgz", - "integrity": "sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ "arm64" ], @@ -1579,9 +1975,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.1.tgz", - "integrity": "sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ "x64" ], @@ -1596,9 +1992,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.1.tgz", - "integrity": "sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ "x64" ], @@ -1613,9 +2009,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.1.tgz", - "integrity": "sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" ], @@ -1630,9 +2026,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.1.tgz", - "integrity": "sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" ], @@ -1695,9 +2091,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1711,22 +2107,22 @@ "license": "MIT" }, "node_modules/@prisma/adapter-better-sqlite3": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-better-sqlite3/-/adapter-better-sqlite3-7.6.0.tgz", - "integrity": "sha512-V0bU2nbFwwqCgzIEFDCmywNoqH9ilCYa6S32hGQxIgRbkgFC2eTl4+AJ6G+jfP0BZs3LXUirreoGNbSY/zbUGQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-better-sqlite3/-/adapter-better-sqlite3-7.8.0.tgz", + "integrity": "sha512-9p0HvQNaRoOaUForDcp1MPIjylmCamYQjQpEogpdesLr14g3NwAsYR3bHL9wFi8tn21TPDVQPzcZi+SxXEokSA==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.6.0", + "@prisma/driver-adapter-utils": "7.8.0", "better-sqlite3": "^12.6.0" } }, "node_modules/@prisma/client": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.6.0.tgz", - "integrity": "sha512-7Pe/1ayh3GgWPEg4mmT4ax77LJ1wC+XlnIFvQ94bLP2DsUnOpnruQQR3Jw7r+Frthk94QqDNxo3FjSg8h9PXeQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", + "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.6.0" + "@prisma/client-runtime-utils": "7.8.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -1745,28 +2141,28 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.6.0.tgz", - "integrity": "sha512-fD7jlqubsZvVODKvsp9lOpXVecx2aWGxC2l35Ioz2t+teUJ5CfR0SAMsi7UkU1VvaZmmm+DS6BdujF622nY7tQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", + "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.6.0.tgz", - "integrity": "sha512-MuAz1MK4PeG5/03YzfzX3CnFVHQ6qePGwUpQRzPzX5tT0ffJ3Tzi9zJZbBc+VzEGFCM8ghW/gTVDR85Syjt+Yw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", + "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "c12": "3.1.0", + "c12": "3.3.4", "deepmerge-ts": "7.1.5", "effect": "3.20.0", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.6.0.tgz", - "integrity": "sha512-LpHr3qos4lQZ6sxwjStf59YBht7m9/QF7NSQsMH6qGENWZu2w3UkQUGn1h5iRkDjnWRj3VHykOu9qFhps4ADvA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", + "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -1795,66 +2191,73 @@ "zeptomatch": "2.1.0" } }, + "node_modules/@prisma/dev/node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.6.0.tgz", - "integrity": "sha512-D8j3p0RnhLuufMaRLX6QqtGgPC5Ao3l5oFP6Q5AL0rTHi4vna+NzGEipwCsfvcSvaGFCbsH3lsTMbb4WvY+ovA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.6.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/engines": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.6.0.tgz", - "integrity": "sha512-Sn5edRzhHqgRV2M+A0eIbY442B4mReWWf3pKs/LKreYgW7oa/up8JtK/s4iv/EQA097cyboZ08mmkpbLp+tZ3w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", + "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.6.0", - "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", - "@prisma/fetch-engine": "7.6.0", - "@prisma/get-platform": "7.6.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/fetch-engine": "7.8.0", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711.tgz", - "integrity": "sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw==", + "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", + "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.6.0.tgz", - "integrity": "sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.6.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.6.0.tgz", - "integrity": "sha512-N575Ni95c3FkduWY/eKTHqNYgNbceZ1tQaSknVtJjpKmiiBXmniESn/GTxsDvICC4ZeiNrXxioGInzQrCdx16w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", + "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.6.0", - "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", - "@prisma/get-platform": "7.6.0" + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/get-platform": "7.8.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.6.0.tgz", - "integrity": "sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.6.0" + "@prisma/debug": "7.8.0" } }, "node_modules/@prisma/get-platform": { @@ -1899,9 +2302,9 @@ } }, "node_modules/@prisma/streams-local/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "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, "license": "MIT", "dependencies": { @@ -2556,9 +2959,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", "cpu": [ "arm64" ], @@ -2572,9 +2975,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", "cpu": [ "arm64" ], @@ -2588,9 +2991,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", "cpu": [ "x64" ], @@ -2604,9 +3007,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", "cpu": [ "x64" ], @@ -2620,9 +3023,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", "cpu": [ "arm" ], @@ -2636,9 +3039,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", "cpu": [ "arm64" ], @@ -2652,9 +3055,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", "cpu": [ "arm64" ], @@ -2668,9 +3071,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", "cpu": [ "ppc64" ], @@ -2684,9 +3087,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", "cpu": [ "s390x" ], @@ -2700,9 +3103,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", "cpu": [ "x64" ], @@ -2716,9 +3119,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", "cpu": [ "x64" ], @@ -2732,9 +3135,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", "cpu": [ "arm64" ], @@ -2748,25 +3151,27 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "license": "MIT", "optional": true, "dependencies": { @@ -2782,9 +3187,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", "cpu": [ "arm64" ], @@ -2798,9 +3203,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", "cpu": [ "x64" ], @@ -2900,9 +3305,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", - "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", + "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", "dev": true, "license": "MIT", "dependencies": { @@ -2912,37 +3317,37 @@ "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.2" + "tailwindcss": "4.2.4" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", - "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", + "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-x64": "4.2.2", - "@tailwindcss/oxide-freebsd-x64": "4.2.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-x64-musl": "4.2.2", - "@tailwindcss/oxide-wasm32-wasi": "4.2.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + "@tailwindcss/oxide-android-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-x64": "4.2.4", + "@tailwindcss/oxide-freebsd-x64": "4.2.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-x64-musl": "4.2.4", + "@tailwindcss/oxide-wasm32-wasi": "4.2.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", - "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", + "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", "cpu": [ "arm64" ], @@ -2957,9 +3362,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", - "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", + "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", "cpu": [ "arm64" ], @@ -2974,9 +3379,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", - "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", + "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", "cpu": [ "x64" ], @@ -2991,9 +3396,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", - "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", + "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", "cpu": [ "x64" ], @@ -3008,9 +3413,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", - "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", + "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", "cpu": [ "arm" ], @@ -3025,9 +3430,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", - "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", + "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", "cpu": [ "arm64" ], @@ -3042,9 +3447,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", - "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", + "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", "cpu": [ "arm64" ], @@ -3059,9 +3464,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", - "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", + "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", "cpu": [ "x64" ], @@ -3076,9 +3481,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", - "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", + "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", "cpu": [ "x64" ], @@ -3093,9 +3498,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", - "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", + "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3187,9 +3592,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", - "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", + "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", "cpu": [ "arm64" ], @@ -3204,9 +3609,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", - "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", + "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", "cpu": [ "x64" ], @@ -3221,17 +3626,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", - "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.4.tgz", + "integrity": "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.2.2", - "@tailwindcss/oxide": "4.2.2", + "@tailwindcss/node": "4.2.4", + "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", - "tailwindcss": "4.2.2" + "tailwindcss": "4.2.4" } }, "node_modules/@tailwindcss/typography": { @@ -3247,9 +3652,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.96.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.0.tgz", - "integrity": "sha512-sfO3uQeol1BU7cRP6NYY7nAiX3GiNY20lI/dtSbKLwcIkYw/X+w/tEsQAkc544AfIhBX/IvH/QYtPHrPhyAKGw==", + "version": "5.100.6", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.6.tgz", + "integrity": "sha512-Os2CPUr98to98RYm+D4qGqGkiffn7MGSyl2547a4MljVkHE30AMJRqTiyCqBfMwzAx/I91vCkAxp5tHSla6Twg==", "license": "MIT", "funding": { "type": "github", @@ -3257,12 +3662,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.96.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.96.0.tgz", - "integrity": "sha512-6qbjdm1K5kizVKv9TNqhIN3doq2anRhdF2XaFMFSn4m8L22S69RV+FilvlyVT4RoJyMxtPU5rs4RpdFa/PEC7A==", + "version": "5.100.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.6.tgz", + "integrity": "sha512-uVSrps0PV16Cxmcn2rvL+dUhwTpTUtiRW347AEeYxMZXO2pZe9ja7E24PAMGoQ5u2g89DD8u4QhOviBk+RN8RA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.96.0" + "@tanstack/query-core": "5.100.6" }, "funding": { "type": "github", @@ -3292,13 +3697,6 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, "node_modules/@testing-library/react": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", @@ -3342,9 +3740,9 @@ } }, "node_modules/@trpc/client": { - "version": "11.16.0", - "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.16.0.tgz", - "integrity": "sha512-TxIzm7OoK3baKZ0XCbuMUbI3GhgjcbKHIc4nWVKaRpCRnbSh0T31BT6fTPYwtnA/Nur8pBCGqC2B4J5hEPiPFQ==", + "version": "11.17.0", + "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.17.0.tgz", + "integrity": "sha512-KpJBFrbKTDeVCFv/3ckL1XBBH5Yssn8hethI/rUy7GIpTj+VzjtPjykDqJpzobuVOz+d26cXCSu1t4I6MYI5Zg==", "funding": [ "https://trpc.io/sponsor" ], @@ -3353,30 +3751,30 @@ "intent": "bin/intent.js" }, "peerDependencies": { - "@trpc/server": "11.16.0", + "@trpc/server": "11.17.0", "typescript": ">=5.7.2" } }, "node_modules/@trpc/react-query": { - "version": "11.16.0", - "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.16.0.tgz", - "integrity": "sha512-ah6ULOu4k7lCEFAEoEgqg14YPVu1lnCVVYcz8TP7avPPKwtcB0CbFKYve3ElRP+V5DoBT75Q+G66bRdR+rQ5fg==", + "version": "11.17.0", + "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.17.0.tgz", + "integrity": "sha512-AGcl5YAF8NnhBmyJ6PqJqKb1M5VTGSoNRNqJ3orct4o4epdcg0GWhW+qT9q6gPzs/2ImIwYCdfFpgNGdZ9yLHA==", "funding": [ "https://trpc.io/sponsor" ], "license": "MIT", "peerDependencies": { "@tanstack/react-query": "^5.80.3", - "@trpc/client": "11.16.0", - "@trpc/server": "11.16.0", + "@trpc/client": "11.17.0", + "@trpc/server": "11.17.0", "react": ">=18.2.0", "typescript": ">=5.7.2" } }, "node_modules/@trpc/server": { - "version": "11.16.0", - "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.16.0.tgz", - "integrity": "sha512-XgGuUMddrUTd04+za/WE5GFuZ1/YU9XQG0t3VL5WOIu2JspkOlq6k4RYEiqS6HSJt+S0RXaPdIoE2anIP/BBRQ==", + "version": "11.17.0", + "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.17.0.tgz", + "integrity": "sha512-jbAOUe0PpUTCYqziyu+8vYXZdDXPudZgnEhWCQ2NjKnVEjfE93RqHTt1oycZJv/HNf51YlRXfEEwSIAbb161rw==", "funding": [ "https://trpc.io/sponsor" ], @@ -3431,9 +3829,9 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -3518,12 +3916,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/node-cron": { @@ -3534,9 +3932,9 @@ "license": "MIT" }, "node_modules/@types/prismjs": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", - "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", "license": "MIT" }, "node_modules/@types/react": { @@ -3583,17 +3981,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", - "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/type-utils": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3606,32 +4004,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.0", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", - "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -3647,14 +4035,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", - "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.0", - "@typescript-eslint/types": "^8.58.0", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -3669,14 +4057,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", - "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3687,9 +4075,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", - "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "license": "MIT", "engines": { @@ -3704,15 +4092,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", - "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3729,9 +4117,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", - "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "license": "MIT", "engines": { @@ -3743,16 +4131,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", - "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.0", - "@typescript-eslint/tsconfig-utils": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3770,69 +4158,17 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/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, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", - "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3847,13 +4183,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", - "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3864,6 +4200,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -4166,14 +4515,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", + "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.5", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -4187,8 +4536,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.2", - "vitest": "4.1.2" + "@vitest/browser": "4.1.5", + "vitest": "4.1.5" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4196,24 +4545,17 @@ } } }, - "node_modules/@vitest/coverage-v8/node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4222,13 +4564,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.2", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4249,9 +4591,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { @@ -4262,13 +4604,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -4276,14 +4618,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4292,9 +4634,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -4302,13 +4644,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.2.tgz", - "integrity": "sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.5.tgz", + "integrity": "sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.5", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -4320,17 +4662,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.2" + "vitest": "4.1.5" } }, "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", + "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4383,9 +4725,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4399,6 +4741,18 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -4412,13 +4766,13 @@ } }, "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/array-buffer-byte-length": { @@ -4610,13 +4964,6 @@ "js-tokens": "^10.0.0" } }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -4660,9 +5007,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.3.tgz", + "integrity": "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==", "dev": true, "license": "MPL-2.0", "engines": { @@ -4670,9 +5017,9 @@ } }, "node_modules/axios": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", - "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", @@ -4701,11 +5048,14 @@ } }, "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==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -4728,9 +5078,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", - "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4750,22 +5100,16 @@ } }, "node_modules/better-result": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.7.0.tgz", - "integrity": "sha512-7zrmXjAK8u8Z6SOe4R65XObOR5X+Y2I/VVku3t5cPOGQ8/WsBcfFmfnIPiEl5EBMDOzPHRwbiPbMtQBKYdw7RA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.0.tgz", + "integrity": "sha512-NHwGDGVbRlWDOce3CwcfGIrcNR9zY37ut3SVwQVfv57DZdVhxjhA4mfaHN1n8QwWnRAR4iErpW1X/eaiaUaFYg==", "dev": true, - "license": "MIT", - "dependencies": { - "@clack/prompts": "^0.11.0" - }, - "bin": { - "better-result": "bin/cli.mjs" - } + "license": "MIT" }, "node_modules/better-sqlite3": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", - "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz", + "integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4807,14 +5151,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -4895,27 +5241,27 @@ "license": "BSD-3-Clause" }, "node_modules/c12": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^4.0.3", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^16.6.1", - "exsolve": "^1.0.7", - "giget": "^2.0.0", - "jiti": "^2.4.2", + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", - "perfect-debounce": "^1.0.0", - "pkg-types": "^2.2.0", - "rc9": "^2.1.2" + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" }, "peerDependencies": { - "magicast": "^0.3.5" + "magicast": "*" }, "peerDependenciesMeta": { "magicast": { @@ -4923,29 +5269,16 @@ } } }, - "node_modules/c12/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "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": { @@ -4986,9 +5319,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001782", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz", - "integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "dev": true, "funding": [ { @@ -5080,16 +5413,16 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -5101,16 +5434,6 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -5175,16 +5498,6 @@ "dev": true, "license": "MIT" }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.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", @@ -5367,9 +5680,9 @@ "license": "MIT" }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -5457,9 +5770,9 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "dev": true, "license": "MIT" }, @@ -5539,10 +5852,17 @@ "node": ">=0.10.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5586,9 +5906,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.329", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.329.tgz", - "integrity": "sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==", + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", "dev": true, "license": "ISC" }, @@ -5619,27 +5939,27 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" } }, "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -5659,9 +5979,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -5746,37 +6066,37 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", + "get-intrinsic": "^1.3.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", "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -5838,6 +6158,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5862,18 +6224,18 @@ } }, "node_modules/eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", - "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.3", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.1", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5918,13 +6280,13 @@ } }, "node_modules/eslint-config-next": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.1.tgz", - "integrity": "sha512-qhabwjQZ1Mk53XzXvmogf8KQ0tG0CQXF0CZ56+2/lVhmObgmaqj7x5A1DSrWdZd3kwI7GTPGUjFne+krRxYmFg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.4.tgz", + "integrity": "sha512-A6ekXYFj/YQxBPMl45g3e+U8zJo+X2+ZQwcz34pPKjpc/3S4roBA2Rd9xWB4FKuSxhofo1/95WjzmUY+wHrOhg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.2.1", + "@next/eslint-plugin-next": "16.2.4", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", @@ -5944,29 +6306,16 @@ } } }, - "node_modules/eslint-config-next/node_modules/globals": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -6076,6 +6425,24 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -6086,6 +6453,29 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", @@ -6116,14 +6506,35 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 0.4" + "node": "*" } }, "node_modules/eslint-plugin-react": { @@ -6160,9 +6571,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6176,25 +6587,48 @@ "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "semver": "bin/semver.js" } }, "node_modules/eslint-scope": { @@ -6217,6 +6651,19 @@ } }, "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", @@ -6229,43 +6676,14 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 4" } }, "node_modules/espree": { @@ -6286,6 +6704,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -6498,6 +6929,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -6576,9 +7024,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -6814,9 +7262,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -6827,19 +7275,11 @@ } }, "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", + "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", "dev": true, "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" - }, "bin": { "giget": "dist/cli.mjs" } @@ -6863,6 +7303,19 @@ "node": ">=10.13.0" } }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -6993,9 +7446,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7107,9 +7560,9 @@ "license": "CC0-1.0" }, "node_modules/hono": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", - "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "dev": true, "license": "MIT", "engines": { @@ -7153,6 +7606,23 @@ "dev": true, "license": "MIT" }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7174,9 +7644,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -7341,19 +7811,6 @@ "semver": "^7.7.1" } }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7815,35 +8272,35 @@ } }, "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==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", + "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7", - "parse5": "^8.0.0", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", - "undici": "^7.24.5", + "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", @@ -7863,9 +8320,9 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -7941,18 +8398,6 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -8371,6 +8816,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -8396,9 +8848,9 @@ } }, "node_modules/lru.min": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", - "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "dev": true, "license": "MIT", "engines": { @@ -8412,9 +8864,9 @@ } }, "node_modules/lucide-react": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", - "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -8458,19 +8910,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -8519,9 +8958,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -9410,16 +9849,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -9474,44 +9916,17 @@ "node": ">= 8.0" } }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "dev": true, "license": "MIT", "dependencies": { - "lru-cache": "^7.14.1" + "lru.min": "^1.1.0" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "node": ">=8.0.0" } }, "node_modules/nanoid": { @@ -9562,13 +9977,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz", - "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", "dev": true, "license": "MIT", "dependencies": { - "@next/env": "16.2.1", + "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -9582,14 +9997,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.1", - "@next/swc-darwin-x64": "16.2.1", - "@next/swc-linux-arm64-gnu": "16.2.1", - "@next/swc-linux-arm64-musl": "16.2.1", - "@next/swc-linux-x64-gnu": "16.2.1", - "@next/swc-linux-x64-musl": "16.2.1", - "@next/swc-win32-arm64-msvc": "16.2.1", - "@next/swc-win32-x64-msvc": "16.2.1", + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { @@ -9645,9 +10060,9 @@ } }, "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -9656,18 +10071,6 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -9683,12 +10086,34 @@ "node": ">=6.0.0" } }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/node-pty": { "version": "1.1.0", @@ -9701,34 +10126,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nypm": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", - "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "citty": "^0.2.0", - "pathe": "^2.0.3", - "tinyexec": "^1.0.2" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/nypm/node_modules/citty": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", - "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, "license": "MIT" }, @@ -9976,13 +10376,13 @@ "license": "MIT" }, "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^6.0.0" + "entities": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -10023,9 +10423,9 @@ "license": "MIT" }, "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "dev": true, "license": "MIT" }, @@ -10049,14 +10449,14 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", "dev": true, "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.2.4", + "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, @@ -10077,9 +10477,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "funding": [ { "type": "opencollective", @@ -10135,6 +10535,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -10168,9 +10569,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -10184,9 +10585,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", - "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.0.tgz", + "integrity": "sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==", "dev": true, "license": "MIT", "engines": { @@ -10263,16 +10664,16 @@ } }, "node_modules/prisma": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.6.0.tgz", - "integrity": "sha512-OKJIPT81K3+F+AayIkY/Y3mkF2NWoFh7lZApaaqPYy7EHILKdO0VsmGkP+hDKYTySHsFSyLWXm/JgcR1B8fY1Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", + "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.6.0", + "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", - "@prisma/engines": "7.6.0", + "@prisma/engines": "7.8.0", "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" @@ -10317,13 +10718,6 @@ "react-is": "^16.13.1" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -10363,9 +10757,9 @@ } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -10435,47 +10829,45 @@ "rc": "cli.js" } }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc9": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", "dev": true, "license": "MIT", "dependencies": { - "defu": "^6.1.4", - "destr": "^2.0.3" + "defu": "^6.1.6", + "destr": "^2.0.5" } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.5" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -10504,9 +10896,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", - "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -10607,13 +10999,13 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -10781,13 +11173,16 @@ } }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10833,13 +11228,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" @@ -10848,27 +11243,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", "license": "MIT" }, "node_modules/run-parallel": { @@ -10896,15 +11291,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "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, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -10997,13 +11392,15 @@ "license": "MIT" }, "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, + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/seq-queue": { @@ -11113,20 +11510,6 @@ "@img/sharp-win32-x64": "0.34.5" } }, - "node_modules/sharp/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11171,14 +11554,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -11306,13 +11689,6 @@ "node": ">=18" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -11357,9 +11733,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -11528,18 +11904,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11563,6 +11927,15 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -11661,16 +12034,16 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", - "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", + "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "dev": true, "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { @@ -11717,9 +12090,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", "engines": { @@ -11727,13 +12100,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -11742,23 +12115,6 @@ "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==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -11782,22 +12138,22 @@ } }, "node_modules/tldts": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.26.tgz", - "integrity": "sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==", + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz", + "integrity": "sha512-JIXCerhudr/N6OWLwLF1HVsTTUo7ry6qHa5eWZEkiMuxsIiAACL55tGLfqfHfoH7QaMQUW8fngD7u7TxWexYQg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.26" + "tldts-core": "^7.0.29" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.26.tgz", - "integrity": "sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew==", + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.29.tgz", + "integrity": "sha512-W99NuU7b1DcG3uJ3v9k9VztCH3WialNbBkBft5wCs8V8mexu0XQqaZEYb9l9RNNzK8+3EJ9PKWB0/RUtTQ/o+Q==", "dev": true, "license": "MIT" }, @@ -11935,490 +12291,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", - "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", - "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", - "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", - "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", - "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", - "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", - "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", - "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", - "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", - "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", - "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", - "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", - "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", - "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", - "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", - "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", - "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", - "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", - "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", - "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", - "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", - "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", - "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", - "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", - "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", - "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.0", - "@esbuild/android-arm": "0.27.0", - "@esbuild/android-arm64": "0.27.0", - "@esbuild/android-x64": "0.27.0", - "@esbuild/darwin-arm64": "0.27.0", - "@esbuild/darwin-x64": "0.27.0", - "@esbuild/freebsd-arm64": "0.27.0", - "@esbuild/freebsd-x64": "0.27.0", - "@esbuild/linux-arm": "0.27.0", - "@esbuild/linux-arm64": "0.27.0", - "@esbuild/linux-ia32": "0.27.0", - "@esbuild/linux-loong64": "0.27.0", - "@esbuild/linux-mips64el": "0.27.0", - "@esbuild/linux-ppc64": "0.27.0", - "@esbuild/linux-riscv64": "0.27.0", - "@esbuild/linux-s390x": "0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/netbsd-arm64": "0.27.0", - "@esbuild/netbsd-x64": "0.27.0", - "@esbuild/openbsd-arm64": "0.27.0", - "@esbuild/openbsd-x64": "0.27.0", - "@esbuild/openharmony-arm64": "0.27.0", - "@esbuild/sunos-x64": "0.27.0", - "@esbuild/win32-arm64": "0.27.0", - "@esbuild/win32-ia32": "0.27.0", - "@esbuild/win32-x64": "0.27.0" - } - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -12523,9 +12395,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12537,16 +12409,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", - "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.0", - "@typescript-eslint/parser": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -12580,9 +12452,9 @@ } }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -12590,9 +12462,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unified": { @@ -12654,9 +12526,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -12851,16 +12723,16 @@ } }, "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" @@ -12877,7 +12749,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -12940,19 +12812,19 @@ } }, "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -12980,10 +12852,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -13007,6 +12881,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -13034,13 +12914,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest/node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -13173,9 +13046,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7b29b0d..0d2e4d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pve-scripts-local", - "version": "0.5.8", + "version": "1.0.0-pre9", "private": true, "type": "module", "scripts": { @@ -25,36 +25,36 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@prisma/adapter-better-sqlite3": "^7.6.0", - "@prisma/client": "^7.6.0", + "@prisma/adapter-better-sqlite3": "^7.8.0", + "@prisma/client": "^7.8.0", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.4", "@t3-oss/env-nextjs": "^0.13.11", "@tailwindcss/typography": "^0.5.19", - "@tanstack/react-query": "^5.96.0", - "@trpc/client": "^11.16.0", - "@trpc/react-query": "^11.16.0", - "@trpc/server": "^11.16.0", + "@tanstack/react-query": "^5.100.9", + "@trpc/client": "^11.17.0", + "@trpc/react-query": "^11.17.0", + "@trpc/server": "^11.17.0", "@types/react-syntax-highlighter": "^15.5.13", "@types/ws": "^8.18.1", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", - "axios": "^1.14.0", + "axios": "^1.16.0", "bcryptjs": "^3.0.3", - "better-sqlite3": "^12.8.0", + "better-sqlite3": "^12.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cron-validator": "^1.4.0", - "dotenv": "^17.3.1", + "dotenv": "^17.4.2", "jsonwebtoken": "^9.0.3", - "lucide-react": "^1.7.0", - "next": ">=16.2.1", + "lucide-react": "^1.14.0", + "next": "16.2.4", "node-cron": "^4.2.1", "node-pty": "^1.1.0", "pocketbase": "^0.26.8", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.1", "refractor": "^5.0.0", @@ -63,39 +63,40 @@ "strip-ansi": "^7.2.0", "superjson": "^2.2.6", "tailwind-merge": "^3.5.0", - "vite": "^8.0.3", + "vite": "^8.0.10", "ws": "^8.20.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { - "@tailwindcss/postcss": "^4.2.2", + "@tailwindcss/postcss": "^4.2.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/bcryptjs": "^3.0.0", "@types/better-sqlite3": "^7.6.13", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^24.10.9", + "@types/node": "^25.6.0", "@types/node-cron": "^3.0.11", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.2", - "@vitest/ui": "^4.1.2", - "baseline-browser-mapping": "^2.10.13", - "eslint": "^10.1.0", - "eslint-config-next": "^16.2.1", - "jsdom": "^29.0.1", - "next": ">=16.2.1", - "postcss": "^8.5.8", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "prisma": "^7.6.0", - "tailwindcss": "^4.2.2", + "@vitest/coverage-v8": "^4.1.5", + "@vitest/ui": "^4.1.5", + "baseline-browser-mapping": "^2.10.27", + "eslint": "^10.3.0", + "eslint-config-next": "^16.2.4", + "jsdom": "^29.1.1", + "next": "16.2.4", + "postcss": "^8.5.14", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "prisma": "^7.8.0", + "tailwindcss": "^4.2.4", "tsx": "^4.21.0", - "typescript": "^6.0.2", - "typescript-eslint": "^8.58.0", - "vitest": "^4.1.2" + "typescript": "^6.0.3", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "@typescript-eslint/parser": "^8.59.2", + "vitest": "^4.1.5" }, "ct3aMetadata": { "initVersion": "7.39.3" @@ -110,4 +111,4 @@ "@hono/node-server": ">=1.19.10", "lodash": "^4.17.23" } -} +} \ No newline at end of file diff --git a/pre-release-updater.sh b/pre-release-updater.sh new file mode 100644 index 0000000..8d9653d --- /dev/null +++ b/pre-release-updater.sh @@ -0,0 +1,395 @@ +#!/bin/bash + +# ProxmoxVE-Local Pre-Release Updater +# ==================================== +# This script allows users to upgrade from stable (e.g. v0.5.8) to pre-release +# versions (v1.0.0-pre1, v1.0.0-pre2, etc.) for testing purposes. +# +# Installation: +# curl -fsSL https://github.com/community-scripts/ProxmoxVE-Local/releases/download/v1.0.0-pre1/pre-release-updater.sh -o /opt/pve-local-prerelease-updater.sh +# chmod +x /opt/pve-local-prerelease-updater.sh +# +# Usage: +# bash /opt/pve-local-prerelease-updater.sh +# +# The script will: +# 1. Fetch all available pre-releases from GitHub +# 2. Display them (newest first) with a numbered menu +# 3. Let the user pick which pre-release to install +# 4. Download and install the selected pre-release + +set -euo pipefail + +# --- Configuration ----------------------------------------------------------- +REPO_OWNER="community-scripts" +REPO_NAME="ProxmoxVE-Local" +GITHUB_API="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}" +INSTALL_DIR="/opt/ProxmoxVE-Local" +BACKUP_DIR="/tmp/pve-prerelease-backup-$(date +%Y%m%d-%H%M%S)" +SERVICE_NAME="pve-scripts-local" + +# --- Colors ------------------------------------------------------------------ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# --- Helper Functions -------------------------------------------------------- +msg() { echo -e "${BLUE}[INFO]${NC} $1"; } +msg_ok() { echo -e "${GREEN}[OK]${NC} $1"; } +msg_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +msg_err() { echo -e "${RED}[ERROR]${NC} $1" >&2; } + +header() { + echo "" + echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║${NC} ${BOLD}ProxmoxVE-Local Pre-Release Updater${NC} ${CYAN}║${NC}" + echo -e "${CYAN}║${NC} Test upcoming releases before they go stable ${CYAN}║${NC}" + echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" +} + +# --- Dependency Check -------------------------------------------------------- +check_dependencies() { + local missing=() + for cmd in curl jq tar node npm; do + if ! command -v "$cmd" &>/dev/null; then + missing+=("$cmd") + fi + done + + if [ ${#missing[@]} -ne 0 ]; then + msg_err "Missing dependencies: ${missing[*]}" + msg_err "Install them with: apt-get install -y ${missing[*]}" + exit 1 + fi +} + +# --- Get Current Version ----------------------------------------------------- +get_current_version() { + if [ -f "${INSTALL_DIR}/VERSION" ]; then + cat "${INSTALL_DIR}/VERSION" | tr -d '[:space:]' + else + echo "unknown" + fi +} + +# --- Fetch Pre-Releases from GitHub ------------------------------------------ +fetch_prereleases() { + msg "Fetching available pre-releases from GitHub..." >&2 + + local curl_opts="-fsSL --connect-timeout 15 --max-time 30" + + # Add token if available + if [ -n "${GITHUB_TOKEN:-}" ]; then + curl_opts="$curl_opts -H \"Authorization: token $GITHUB_TOKEN\"" + fi + + local releases_json + if ! releases_json=$(eval "curl $curl_opts \"${GITHUB_API}/releases?per_page=20\""); then + msg_err "Failed to fetch releases from GitHub API" + msg_err "Check your internet connection or try again later." + exit 1 + fi + + # Validate JSON + if ! echo "$releases_json" | jq empty 2>/dev/null; then + msg_err "Invalid response from GitHub API" + exit 1 + fi + + # Filter pre-releases only, sort newest first (already sorted by API) + local prereleases + prereleases=$(echo "$releases_json" | jq -r '[.[] | select(.prerelease == true)] | sort_by(.published_at) | reverse') + + local count + count=$(echo "$prereleases" | jq 'length') + + if [ "$count" -eq 0 ]; then + msg_warn "No pre-releases found." >&2 + msg "There are currently no pre-release versions available for testing." >&2 + exit 0 + fi + + echo "$prereleases" +} + +# --- Display Menu ------------------------------------------------------------ +display_menu() { + local prereleases="$1" + local current_version="$2" + local count + count=$(echo "$prereleases" | jq 'length') + + echo -e "${BOLD}Currently installed:${NC} v${current_version}" + echo "" + echo -e "${BOLD}Available Pre-Releases:${NC} (newest first)" + echo -e "────────────────────────────────────────────────────" + printf " ${BOLD}%-4s %-20s %-12s %s${NC}\n" "#" "Version" "Date" "Notes" + echo -e "────────────────────────────────────────────────────" + + for ((i = 0; i < count; i++)); do + local tag_name published_at name + tag_name=$(echo "$prereleases" | jq -r ".[$i].tag_name") + published_at=$(echo "$prereleases" | jq -r ".[$i].published_at // .[$i].created_at" | cut -d'T' -f1) + name=$(echo "$prereleases" | jq -r ".[$i].name // .[$i].tag_name" | head -c 30) + + local marker="" + if [ "$tag_name" = "v${current_version}" ] || [ "$tag_name" = "${current_version}" ]; then + marker=" ${GREEN}← installed${NC}" + fi + + printf " ${CYAN}%-4s${NC} %-20s %-12s %s%b\n" "$((i + 1))" "$tag_name" "$published_at" "$name" "$marker" + done + + echo -e "────────────────────────────────────────────────────" + echo "" +} + +# --- User Selection ---------------------------------------------------------- +get_user_choice() { + local count="$1" + + while true; do + echo -ne "${BOLD}Select a pre-release to install [1-${count}] (or 'q' to quit): ${NC}" >&2 + read -r choice + + if [ "$choice" = "q" ] || [ "$choice" = "Q" ]; then + msg "Aborted by user." >&2 + exit 0 + fi + + # Validate numeric input + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$count" ]; then + echo "$choice" + return 0 + fi + + msg_warn "Invalid choice. Please enter a number between 1 and ${count}." >&2 + done +} + +# --- Backup ------------------------------------------------------------------ +backup_current() { + if [ ! -d "$INSTALL_DIR" ]; then + msg "No existing installation found, skipping backup." + return 0 + fi + + msg "Backing up current installation..." + mkdir -p "$BACKUP_DIR" + + # Backup critical data + if [ -d "${INSTALL_DIR}/data" ]; then + cp -r "${INSTALL_DIR}/data" "$BACKUP_DIR/data" + fi + if [ -f "${INSTALL_DIR}/.env" ]; then + cp "${INSTALL_DIR}/.env" "$BACKUP_DIR/.env" + fi + if [ -d "${INSTALL_DIR}/prisma" ]; then + # Backup the SQLite database if it exists + find "${INSTALL_DIR}/prisma" -name "*.db" -exec cp {} "$BACKUP_DIR/" \; 2>/dev/null || true + fi + + # Backup downloaded scripts so updates never wipe user downloads + for dir in ct tools vm vw; do + if [ -d "${INSTALL_DIR}/scripts/${dir}" ]; then + cp -r "${INSTALL_DIR}/scripts/${dir}" "$BACKUP_DIR/scripts-${dir}" + fi + done + + msg_ok "Backup created at: $BACKUP_DIR" +} + +restore_downloaded_scripts() { + for dir in ct tools vm vw; do + if [ -d "$BACKUP_DIR/scripts-${dir}" ]; then + mkdir -p "${INSTALL_DIR}/scripts" + rm -rf "${INSTALL_DIR}/scripts/${dir}" 2>/dev/null || true + cp -r "$BACKUP_DIR/scripts-${dir}" "${INSTALL_DIR}/scripts/${dir}" + msg_ok "Restored downloaded scripts directory: scripts/${dir}" + fi + done +} + +# --- Install Pre-Release ----------------------------------------------------- +install_prerelease() { + local tag_name="$1" + + msg "Installing pre-release: ${tag_name}..." + + local download_url="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${tag_name}.tar.gz" + local temp_dir="/tmp/pve-prerelease-$$" + + # Create temp directory + mkdir -p "$temp_dir" + trap "rm -rf $temp_dir" EXIT + + # Download + msg "Downloading ${tag_name}..." + if ! curl -fsSL --connect-timeout 30 --max-time 300 --retry 3 -o "$temp_dir/release.tar.gz" "$download_url"; then + msg_err "Failed to download release. Check that the tag '${tag_name}' exists." + exit 1 + fi + + # Verify download + if [ ! -s "$temp_dir/release.tar.gz" ]; then + msg_err "Downloaded file is empty." + exit 1 + fi + + msg_ok "Downloaded ($(du -h "$temp_dir/release.tar.gz" | cut -f1))" + + # Extract + msg "Extracting..." + if ! tar -xzf "$temp_dir/release.tar.gz" -C "$temp_dir"; then + msg_err "Failed to extract release archive." + exit 1 + fi + + # Find extracted directory + local extracted_dir + extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -1) + if [ -z "$extracted_dir" ]; then + msg_err "Could not find extracted directory." + exit 1 + fi + + # Stop service if running + local service_was_running=false + if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then + msg "Stopping ${SERVICE_NAME} service..." + systemctl stop "$SERVICE_NAME" + service_was_running=true + fi + + # Install: sync files (preserve data, .env, node_modules) + msg "Installing files to ${INSTALL_DIR}..." + mkdir -p "$INSTALL_DIR" + + # Use rsync if available, otherwise manual copy + if command -v rsync &>/dev/null; then + rsync -a --delete \ + --exclude='data/' \ + --exclude='.env' \ + --exclude='node_modules/' \ + --exclude='scripts/ct/' \ + --exclude='scripts/tools/' \ + --exclude='scripts/vm/' \ + --exclude='scripts/vw/' \ + --exclude='prisma/*.db' \ + --exclude='prisma/*.db-journal' \ + "$extracted_dir/" "$INSTALL_DIR/" + else + # Manual approach: remove old files (except protected), copy new + find "$INSTALL_DIR" -mindepth 1 \ + ! -path "${INSTALL_DIR}/data*" \ + ! -path "${INSTALL_DIR}/.env" \ + ! -path "${INSTALL_DIR}/node_modules*" \ + ! -name "*.db" \ + ! -name "*.db-journal" \ + -delete 2>/dev/null || true + cp -r "$extracted_dir/"* "$INSTALL_DIR/" + fi + + # Always restore user-downloaded scripts after copying release files + restore_downloaded_scripts + + msg_ok "Files installed" + + # Install dependencies (dev deps needed for prisma generate & build) + msg "Installing npm dependencies..." + cd "$INSTALL_DIR" + if ! npm ci 2>/dev/null; then + msg_warn "npm ci failed, trying npm install..." + npm install + fi + msg_ok "Dependencies installed" + + # Run prisma migrations + msg "Running database migrations..." + npx prisma migrate deploy 2>/dev/null || msg_warn "Migration skipped (may already be up to date)" + msg_ok "Database ready" + + # Build + msg "Building application..." + if ! npm run build; then + msg_err "Build failed! Check the logs above." + msg_warn "Your backup is at: $BACKUP_DIR" + exit 1 + fi + msg_ok "Build complete" + + # Restart service if it was running + if [ "$service_was_running" = true ]; then + msg "Restarting ${SERVICE_NAME} service..." + systemctl start "$SERVICE_NAME" + msg_ok "Service restarted" + fi + + echo "" + echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║${NC} ${BOLD}Pre-release ${tag_name} installed successfully!${NC} ${GREEN}║${NC}" + echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + msg "Backup location: $BACKUP_DIR" + msg "To rollback, restore the backup and run: systemctl restart ${SERVICE_NAME}" + echo "" +} + +# --- Main -------------------------------------------------------------------- +main() { + header + + # Must run as root + if [ "$(id -u)" -ne 0 ]; then + msg_err "This script must be run as root (use sudo)." + exit 1 + fi + + check_dependencies + + local current_version + current_version=$(get_current_version) + + # Fetch pre-releases + local prereleases + prereleases=$(fetch_prereleases) + + local count + count=$(echo "$prereleases" | jq 'length') + + # Display menu + display_menu "$prereleases" "$current_version" + + # Get user choice + local choice + choice=$(get_user_choice "$count") + + # Get selected tag + local selected_tag + selected_tag=$(echo "$prereleases" | jq -r ".[$(($choice - 1))].tag_name") + + # Confirm + echo "" + echo -e "${YELLOW}You are about to update:${NC}" + echo -e " From: ${BOLD}v${current_version}${NC}" + echo -e " To: ${BOLD}${selected_tag}${NC}" + echo "" + echo -ne "${BOLD}Continue? [y/N]: ${NC}" + read -r confirm + + if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then + msg "Aborted by user." + exit 0 + fi + + # Backup and install + backup_current + install_prerelease "$selected_tag" +} + +main "$@" diff --git a/prisma/migrations/20260401113636_add_notes_and_presets/migration.sql b/prisma/migrations/20260401113636_add_notes_and_presets/migration.sql new file mode 100644 index 0000000..a150b48 --- /dev/null +++ b/prisma/migrations/20260401113636_add_notes_and_presets/migration.sql @@ -0,0 +1,37 @@ +-- CreateTable +CREATE TABLE "script_notes" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "script_slug" TEXT NOT NULL, + "title" TEXT NOT NULL DEFAULT '', + "content" TEXT NOT NULL, + "is_shared" BOOLEAN NOT NULL DEFAULT false, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "server_presets" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "server_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "cpu" INTEGER, + "ram" INTEGER, + "disk" INTEGER, + "privileged" BOOLEAN NOT NULL DEFAULT false, + "bridge" TEXT, + "vlan" TEXT, + "dns" TEXT, + "ssh" BOOLEAN NOT NULL DEFAULT false, + "nesting" BOOLEAN NOT NULL DEFAULT true, + "fuse" BOOLEAN NOT NULL DEFAULT false, + "apt_proxy_addr" TEXT, + "apt_proxy_on" BOOLEAN NOT NULL DEFAULT false, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL +); + +-- CreateIndex +CREATE INDEX "script_notes_script_slug_idx" ON "script_notes"("script_slug"); + +-- CreateIndex +CREATE INDEX "server_presets_server_id_idx" ON "server_presets"("server_id"); diff --git a/prisma/migrations/20260429000000_add_pbs_username/migration.sql b/prisma/migrations/20260429000000_add_pbs_username/migration.sql new file mode 100644 index 0000000..5877bb0 --- /dev/null +++ b/prisma/migrations/20260429000000_add_pbs_username/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable: add pbs_username to pbs_storage_credentials with default 'root@pam' +ALTER TABLE "pbs_storage_credentials" ADD COLUMN "pbs_username" TEXT NOT NULL DEFAULT 'root@pam'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index de112ad..55fff9f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -123,6 +123,7 @@ model PBSStorageCredential { storage_name String pbs_ip String pbs_datastore String + pbs_username String @default("root@pam") pbs_password String pbs_fingerprint String created_at DateTime @default(now()) @@ -146,3 +147,39 @@ model Repository { @@map("repositories") } + +model ScriptNote { + id Int @id @default(autoincrement()) + script_slug String + title String @default("") + content String + is_shared Boolean @default(false) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@index([script_slug]) + @@map("script_notes") +} + +model ServerPreset { + id Int @id @default(autoincrement()) + server_id Int + name String + cpu Int? + ram Int? + disk Int? + privileged Boolean @default(false) + bridge String? + vlan String? + dns String? + ssh Boolean @default(false) + nesting Boolean @default(true) + fuse Boolean @default(false) + apt_proxy_addr String? + apt_proxy_on Boolean @default(false) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@index([server_id]) + @@map("server_presets") +} diff --git a/public/favicon.png b/public/favicon.png deleted file mode 100644 index 5e4b11b..0000000 Binary files a/public/favicon.png and /dev/null differ diff --git a/public/favicon/android-chrome-192x192.png b/public/favicon/android-chrome-192x192.png new file mode 100644 index 0000000..80ef9de Binary files /dev/null and b/public/favicon/android-chrome-192x192.png differ diff --git a/public/favicon/android-chrome-512x512.png b/public/favicon/android-chrome-512x512.png new file mode 100644 index 0000000..2241107 Binary files /dev/null and b/public/favicon/android-chrome-512x512.png differ diff --git a/public/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000..1d51093 Binary files /dev/null and b/public/favicon/apple-touch-icon.png differ diff --git a/public/favicon/favicon-16x16.png b/public/favicon/favicon-16x16.png new file mode 100644 index 0000000..d9608ce Binary files /dev/null and b/public/favicon/favicon-16x16.png differ diff --git a/public/favicon/favicon-32x32.png b/public/favicon/favicon-32x32.png new file mode 100644 index 0000000..7fe97ad Binary files /dev/null and b/public/favicon/favicon-32x32.png differ diff --git a/public/favicon/favicon.ico b/public/favicon/favicon.ico new file mode 100644 index 0000000..4d20147 Binary files /dev/null and b/public/favicon/favicon.ico differ diff --git a/public/favicon/favicon.png b/public/favicon/favicon.png new file mode 100644 index 0000000..f6e74d5 Binary files /dev/null and b/public/favicon/favicon.png differ diff --git a/public/favicon/site.webmanifest b/public/favicon/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/public/favicon/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..efb680a Binary files /dev/null and b/public/logo.png differ diff --git a/restore.log b/restore.log deleted file mode 100644 index 0f654fb..0000000 --- a/restore.log +++ /dev/null @@ -1,10 +0,0 @@ -Starting restore... -Reading container configuration... -Stopping container... -Destroying container... -Logging into PBS... -Downloading backup from PBS... -Packing backup folder... -Restoring container... -Cleaning up temporary files... -Restore completed successfully diff --git a/scripts/cache-logos.ts b/scripts/cache-logos.ts index 409a94b..e1aada1 100644 --- a/scripts/cache-logos.ts +++ b/scripts/cache-logos.ts @@ -21,7 +21,7 @@ async function main() { console.log(`[cache-logos] Caching ${entries.length} logos...`); - const result = await cacheLogos(entries); + const result = await cacheLogos(entries, { verbose: true }); console.log( `[cache-logos] Done: ${result.downloaded} downloaded, ${result.skipped} already cached, ${result.errors} errors`, ); diff --git a/scripts/core/alpine-install.func b/scripts/core/alpine-install.func index 0e0c798..1dd388d 100644 --- a/scripts/core/alpine-install.func +++ b/scripts/core/alpine-install.func @@ -4,7 +4,7 @@ # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE if ! command -v curl >/dev/null 2>&1; then - apk update && apk add curl >/dev/null 2>&1 + apk update && apk add curl >/dev/null 2>&1 fi source "$(dirname "${BASH_SOURCE[0]}")/core.func" source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func" @@ -14,8 +14,8 @@ catch_errors # Persist diagnostics setting inside container (exported from build.func) # so addon scripts running later can find the user's choice if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then - mkdir -p /usr/local/community-scripts - echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics + mkdir -p /usr/local/community-scripts + echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics fi # Get LXC IP address (must be called INSIDE container, after network is up) @@ -33,208 +33,217 @@ get_lxc_ip # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set # ------------------------------------------------------------------------------ post_progress_to_api() { - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - local progress_status="${1:-configuring}" + local progress_status="${1:-configuring}" - curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ - -H "Content-Type: application/json" \ - -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true + curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true } # This function enables IPv6 if it's not disabled and sets verbose mode verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE + set_std_mode # Set STD mode based on VERBOSE - if [ "${IPV6_METHOD:-}" = "disable" ]; then - msg_info "Disabling IPv6 (this may affect some services)" - $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1 - $STD sysctl -w net.ipv6.conf.default.disable_ipv6=1 - $STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1 - mkdir -p /etc/sysctl.d - $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null </dev/null <&2 -en "${CROSS}${RD} No Network! " - sleep $RETRY_EVERY - i=$((i - 1)) - done + msg_info "Setting up Container OS" + while [ $i -gt 0 ]; do + if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" != "" ]; then + break + fi + echo 1>&2 -en "${CROSS}${RD} No Network! " + sleep $RETRY_EVERY + i=$((i - 1)) + done - if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then - echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" - echo -e "${NETWORK}Check Network Settings" - exit 121 - fi - msg_ok "Set up Container OS" - msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}" - post_progress_to_api + if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then + echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" + echo -e "${NETWORK}Check Network Settings" + exit 121 + fi + msg_ok "Set up Container OS" + msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}" + post_progress_to_api } # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected network_check() { - set +e - trap - ERR - ipv4_connected=false + set +e + trap - ERR + ipv4_connected=false - # Check IPv4 connectivity to Cloudflare, Google & Quad9 DNS servers - if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then - msg_ok "IPv4 Internet Connected" - ipv4_connected=true - else - msg_error "IPv4 Internet Not Connected" - fi + # Check IPv4 connectivity to Cloudflare, Google & Quad9 DNS servers + if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then + msg_ok "IPv4 Internet Connected" + ipv4_connected=true + else + msg_error "IPv4 Internet Not Connected" + fi - if [[ $ipv4_connected == false ]]; then - read -r -p "No Internet detected, would you like to continue anyway? " prompt - if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then - echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" - else - echo -e "${NETWORK}Check Network Settings" - exit 122 - fi - fi + if [[ $ipv4_connected == false ]]; then + read -r -p "No Internet detected, would you like to continue anyway? " prompt + if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then + echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" + else + echo -e "${NETWORK}Check Network Settings" + exit 122 + fi + fi - # DNS resolution checks for GitHub-related domains - GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") - GIT_STATUS="Git DNS:" - DNS_FAILED=false + # DNS resolution checks for GitHub-related domains + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" + DNS_FAILED=false - for HOST in "${GIT_HOSTS[@]}"; do - RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) - if [[ -z "$RESOLVEDIP" ]]; then - GIT_STATUS+="$HOST:($DNSFAIL)" - DNS_FAILED=true - else - GIT_STATUS+=" $HOST:($DNSOK)" - fi - done + for HOST in "${GIT_HOSTS[@]}"; do + RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) + if [[ -z "$RESOLVEDIP" ]]; then + GIT_STATUS+="$HOST:($DNSFAIL)" + DNS_FAILED=true + else + GIT_STATUS+=" $HOST:($DNSOK)" + fi + done - if [[ "$DNS_FAILED" == true ]]; then - fatal "$GIT_STATUS" - else - msg_ok "$GIT_STATUS" - fi + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + fi - set -e - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR + set -e + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # This function updates the Container OS by running apk upgrade with mirror fallback update_os() { - msg_info "Updating Container OS" - if ! $STD apk -U upgrade; then - msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." - local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" - local apk_ok=false - for m in $(printf '%s\n' $alpine_mirrors | shuf); do - if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then - msg_custom "${INFO}" "${YW}" "Attempting mirror: ${m}" - cat </etc/apk/repositories + msg_info "Updating Container OS" + if ! $STD apk -U upgrade; then + msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." + local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" + local apk_ok=false + for m in $(printf '%s\n' $alpine_mirrors | shuf); do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${m}" + cat </etc/apk/repositories http://$m/alpine/latest-stable/main http://$m/alpine/latest-stable/community EOF - if $STD apk -U upgrade; then - msg_ok "CDN set to ${m}: tests passed" - apk_ok=true - break - else - msg_warn "Mirror ${m} failed" - fi - fi - done - if [[ "$apk_ok" != true ]]; then - msg_error "All Alpine mirrors failed. Check network or try again later." - exit 1 - fi - fi - local tools_content - tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { - msg_error "Failed to download tools.func" - exit 115 - } - source /dev/stdin <<<"$tools_content" - if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then - msg_error "tools.func loaded but incomplete — missing expected functions" - exit 115 - fi - msg_ok "Updated Container OS" - post_progress_to_api + if $STD apk -U upgrade; then + msg_ok "CDN set to ${m}: tests passed" + apk_ok=true + break + else + msg_warn "Mirror ${m} failed" + fi + fi + done + if [[ "$apk_ok" != true ]]; then + msg_error "All Alpine mirrors failed. Check network or try again later." + exit 1 + fi + fi + local tools_content + local _tools_local + _tools_local="$(dirname "${BASH_SOURCE[0]}")/tools.func" + if [[ -f "$_tools_local" ]]; then + tools_content=$(cat "$_tools_local") || { + msg_error "Failed to load tools.func from local" + exit 115 + } + else + tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { + msg_error "Failed to download tools.func" + exit 115 + } + fi + source /dev/stdin <<<"$tools_content" + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then + msg_error "tools.func loaded but incomplete — missing expected functions" + exit 115 + fi + msg_ok "Updated Container OS" + post_progress_to_api } # This function modifies the message of the day (motd) and SSH settings motd_ssh() { - echo "export TERM='xterm-256color'" >>/root/.bashrc + echo "export TERM='xterm-256color'" >>/root/.bashrc - PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" - echo "echo -e \"\"" >"$PROFILE_FILE" - echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" - echo "echo \"\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE" + PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" + echo "echo -e \"\"" >"$PROFILE_FILE" + echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" + echo "echo \"\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE" - # Configure SSH if enabled - if [[ "${SSH_ROOT}" == "yes" ]]; then - # Enable sshd service - $STD rc-update add sshd - # Allow root login via SSH - sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config - # Start the sshd service - $STD /etc/init.d/sshd start - fi - post_progress_to_api + # Configure SSH if enabled + if [[ "${SSH_ROOT}" == "yes" ]]; then + # Enable sshd service + $STD rc-update add sshd + # Allow root login via SSH + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + # Start the sshd service + $STD /etc/init.d/sshd start + fi + post_progress_to_api } # Validate Timezone for some LXC's validate_tz() { - [[ -f "/usr/share/zoneinfo/$1" ]] + [[ -f "/usr/share/zoneinfo/$1" ]] } # This function customizes the container and enables passwordless login for the root user customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" - passwd -d root >/dev/null 2>&1 + if [[ "$PASSWORD" == "" ]]; then + msg_info "Customizing Container" + passwd -d root >/dev/null 2>&1 - # Ensure agetty is available - apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1 + # Ensure agetty is available + apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1 - # Create persistent autologin boot script - mkdir -p /etc/local.d - cat <<'EOF' >/etc/local.d/autologin.start + # Create persistent autologin boot script + mkdir -p /etc/local.d + cat <<'EOF' >/etc/local.d/autologin.start #!/bin/sh sed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab kill -HUP 1 EOF - touch /root/.hushlogin + touch /root/.hushlogin - chmod +x /etc/local.d/autologin.start - rc-update add local >/dev/null 2>&1 + chmod +x /etc/local.d/autologin.start + rc-update add local >/dev/null 2>&1 - # Apply autologin immediately for current session - /etc/local.d/autologin.start + # Apply autologin immediately for current session + /etc/local.d/autologin.start - msg_ok "Customized Container" - fi + msg_ok "Customized Container" + fi - echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update - chmod +x /usr/bin/update - post_progress_to_api + echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + chmod +x /usr/bin/update + post_progress_to_api } diff --git a/scripts/core/api.func b/scripts/core/api.func index 6e820e5..e3d9978 100644 --- a/scripts/core/api.func +++ b/scripts/core/api.func @@ -59,52 +59,52 @@ STATUS_TIMEOUT=10 # - Skips detection if REPO_SOURCE is already set (e.g., by environment) # ------------------------------------------------------------------------------ detect_repo_source() { - # Allow explicit override via environment - [[ -n "${REPO_SOURCE:-}" ]] && return 0 + # Allow explicit override via environment + [[ -n "${REPO_SOURCE:-}" ]] && return 0 - local content="" owner_repo="" + local content="" owner_repo="" - # Method 1: Read from /proc/$$/cmdline - # When invoked via: bash -c "$(curl -fsSL https://.../ct/app.sh)" - # the full CT/VM script content is in /proc/$$/cmdline (same PID through source chain) - if [[ -r /proc/$$/cmdline ]]; then - content=$(tr '\0' ' ' /dev/null) || true - fi + # Method 1: Read from /proc/$$/cmdline + # When invoked via: bash -c "$(curl -fsSL https://.../ct/app.sh)" + # the full CT/VM script content is in /proc/$$/cmdline (same PID through source chain) + if [[ -r /proc/$$/cmdline ]]; then + content=$(tr '\0' ' ' /dev/null) || true + fi - # Method 2: Read from the original script file (bash ct/app.sh / bash vm/app.sh) - if [[ -z "$content" ]] || ! echo "$content" | grep -qE 'githubusercontent\.com|community-scripts\.org' 2>/dev/null; then - if [[ -f "$0" ]] && [[ "$0" != *bash* ]]; then - content=$(head -10 "$0" 2>/dev/null) || true - fi - fi + # Method 2: Read from the original script file (bash ct/app.sh / bash vm/app.sh) + if [[ -z "$content" ]] || ! echo "$content" | grep -qE 'githubusercontent\.com|community-scripts\.org' 2>/dev/null; then + if [[ -f "$0" ]] && [[ "$0" != *bash* ]]; then + content=$(head -10 "$0" 2>/dev/null) || true + fi + fi - # Extract owner/repo from URL patterns found in the script content - if [[ -n "$content" ]]; then - # GitHub raw URL: raw.githubusercontent.com/OWNER/REPO/... - owner_repo=$(echo "$content" | grep -oE 'raw\.githubusercontent\.com/[^/]+/[^/]+' | head -1 | sed 's|raw\.githubusercontent\.com/||') || true + # Extract owner/repo from URL patterns found in the script content + if [[ -n "$content" ]]; then + # GitHub raw URL: raw.githubusercontent.com/OWNER/REPO/... + owner_repo=$(echo "$content" | grep -oE 'raw\.githubusercontent\.com/[^/]+/[^/]+' | head -1 | sed 's|raw\.githubusercontent\.com/||') || true - # Gitea URL: git.community-scripts.org/OWNER/REPO/... - if [[ -z "$owner_repo" ]]; then - owner_repo=$(echo "$content" | grep -oE 'git\.community-scripts\.org/[^/]+/[^/]+' | head -1 | sed 's|git\.community-scripts\.org/||') || true - fi - fi + # Gitea URL: git.community-scripts.org/OWNER/REPO/... + if [[ -z "$owner_repo" ]]; then + owner_repo=$(echo "$content" | grep -oE 'git\.community-scripts\.org/[^/]+/[^/]+' | head -1 | sed 's|git\.community-scripts\.org/||') || true + fi + fi - # Map detected owner/repo to canonical repo_source value - case "$owner_repo" in - community-scripts/ProxmoxVE) REPO_SOURCE="ProxmoxVE" ;; - community-scripts/ProxmoxVED) REPO_SOURCE="ProxmoxVED" ;; - "") - # No URL detected — use hardcoded fallback - # This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev - REPO_SOURCE="ProxmoxVE" - ;; - *) - # Fork or unknown repo - REPO_SOURCE="external" - ;; - esac + # Map detected owner/repo to canonical repo_source value + case "$owner_repo" in + community-scripts/ProxmoxVE) REPO_SOURCE="ProxmoxVE" ;; + community-scripts/ProxmoxVED) REPO_SOURCE="ProxmoxVED" ;; + "") + # No URL detected — use hardcoded fallback + # This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev + REPO_SOURCE="ProxmoxVE" + ;; + *) + # Fork or unknown repo + REPO_SOURCE="external" + ;; + esac - export REPO_SOURCE + export REPO_SOURCE } # Run detection immediately when api.func is sourced @@ -119,7 +119,7 @@ detect_repo_source # # - Maps numeric exit codes to human-readable error descriptions # - Canonical source of truth for ALL exit code mappings -# - Used by both api.func (telemetry) and error-handler.func (error display) +# - Used by both api.func (telemetry) and error_handler.func (error display) # - Supports: # * Generic/Shell errors (1-3, 10, 124-132, 134, 137, 139, 141, 143-146) # * curl/wget errors (4-8, 16, 18, 22-28, 30, 32-36, 39, 44-48, 51-52, 55-57, 59, 61, 63, 75, 78-79, 92, 95) @@ -138,204 +138,204 @@ detect_repo_source # - Returns description string for given exit code # ------------------------------------------------------------------------------ explain_exit_code() { - local code="$1" - case "$code" in - # --- Generic / Shell --- - 1) echo "General error / Operation not permitted" ;; - 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; - 3) echo "General syntax or argument error" ;; - 10) echo "Docker / privileged mode required (unsupported environment)" ;; + local code="$1" + case "$code" in + # --- Generic / Shell --- + 1) echo "General error / Operation not permitted" ;; + 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; + 3) echo "General syntax or argument error" ;; + 10) echo "Docker / privileged mode required (unsupported environment)" ;; - # --- curl / wget errors (commonly seen in downloads) --- - 4) echo "curl: Feature not supported or protocol error" ;; - 5) echo "curl: Could not resolve proxy" ;; - 6) echo "curl: DNS resolution failed (could not resolve host)" ;; - 7) echo "curl: Failed to connect (network unreachable / host down)" ;; - 8) echo "curl: Server reply error (FTP/SFTP or apk untrusted key)" ;; - 16) echo "curl: HTTP/2 framing layer error" ;; - 18) echo "curl: Partial file (transfer not completed)" ;; - 22) echo "curl: HTTP error returned (404, 429, 500+)" ;; - 23) echo "curl: Write error (disk full or permissions)" ;; - 24) echo "curl: Write to local file failed" ;; - 25) echo "curl: Upload failed" ;; - 26) echo "curl: Read error on local file (I/O)" ;; - 27) echo "curl: Out of memory (memory allocation failed)" ;; - 28) echo "curl: Operation timeout (network slow or server not responding)" ;; - 30) echo "curl: FTP port command failed" ;; - 32) echo "curl: FTP SIZE command failed" ;; - 33) echo "curl: HTTP range error" ;; - 34) echo "curl: HTTP post error" ;; - 35) echo "curl: SSL/TLS handshake failed (certificate error)" ;; - 36) echo "curl: FTP bad download resume" ;; - 39) echo "curl: LDAP search failed" ;; - 44) echo "curl: Internal error (bad function call order)" ;; - 45) echo "curl: Interface error (failed to bind to specified interface)" ;; - 46) echo "curl: Bad password entered" ;; - 47) echo "curl: Too many redirects" ;; - 48) echo "curl: Unknown command line option specified" ;; - 51) echo "curl: SSL peer certificate or SSH host key verification failed" ;; - 52) echo "curl: Empty reply from server (got nothing)" ;; - 55) echo "curl: Failed sending network data" ;; - 56) echo "curl: Receive error (connection reset by peer)" ;; - 57) echo "curl: Unrecoverable poll/select error (system I/O failure)" ;; - 59) echo "curl: Couldn't use specified SSL cipher" ;; - 61) echo "curl: Bad/unrecognized transfer encoding" ;; - 63) echo "curl: Maximum file size exceeded" ;; - 75) echo "Temporary failure (retry later)" ;; - 78) echo "curl: Remote file not found (404 on FTP/file)" ;; - 79) echo "curl: SSH session error (key exchange/auth failed)" ;; - 92) echo "curl: HTTP/2 stream error (protocol violation)" ;; - 95) echo "curl: HTTP/3 layer error" ;; + # --- curl / wget errors (commonly seen in downloads) --- + 4) echo "curl: Feature not supported or protocol error" ;; + 5) echo "curl: Could not resolve proxy" ;; + 6) echo "curl: DNS resolution failed (could not resolve host)" ;; + 7) echo "curl: Failed to connect (network unreachable / host down)" ;; + 8) echo "curl: Server reply error (FTP/SFTP or apk untrusted key)" ;; + 16) echo "curl: HTTP/2 framing layer error" ;; + 18) echo "curl: Partial file (transfer not completed)" ;; + 22) echo "curl: HTTP error returned (404, 429, 500+)" ;; + 23) echo "curl: Write error (disk full or permissions)" ;; + 24) echo "curl: Write to local file failed" ;; + 25) echo "curl: Upload failed" ;; + 26) echo "curl: Read error on local file (I/O)" ;; + 27) echo "curl: Out of memory (memory allocation failed)" ;; + 28) echo "curl: Operation timeout (network slow or server not responding)" ;; + 30) echo "curl: FTP port command failed" ;; + 32) echo "curl: FTP SIZE command failed" ;; + 33) echo "curl: HTTP range error" ;; + 34) echo "curl: HTTP post error" ;; + 35) echo "curl: SSL/TLS handshake failed (certificate error)" ;; + 36) echo "curl: FTP bad download resume" ;; + 39) echo "curl: LDAP search failed" ;; + 44) echo "curl: Internal error (bad function call order)" ;; + 45) echo "curl: Interface error (failed to bind to specified interface)" ;; + 46) echo "curl: Bad password entered" ;; + 47) echo "curl: Too many redirects" ;; + 48) echo "curl: Unknown command line option specified" ;; + 51) echo "curl: SSL peer certificate or SSH host key verification failed" ;; + 52) echo "curl: Empty reply from server (got nothing)" ;; + 55) echo "curl: Failed sending network data" ;; + 56) echo "curl: Receive error (connection reset by peer)" ;; + 57) echo "curl: Unrecoverable poll/select error (system I/O failure)" ;; + 59) echo "curl: Couldn't use specified SSL cipher" ;; + 61) echo "curl: Bad/unrecognized transfer encoding" ;; + 63) echo "curl: Maximum file size exceeded" ;; + 75) echo "Temporary failure (retry later)" ;; + 78) echo "curl: Remote file not found (404 on FTP/file)" ;; + 79) echo "curl: SSH session error (key exchange/auth failed)" ;; + 92) echo "curl: HTTP/2 stream error (protocol violation)" ;; + 95) echo "curl: HTTP/3 layer error" ;; - # --- Package manager / APT / DPKG --- - 100) echo "APT: Package manager error (broken packages / dependency problems)" ;; - 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; - 102) echo "APT: Lock held by another process (dpkg/apt still running)" ;; + # --- Package manager / APT / DPKG --- + 100) echo "APT: Package manager error (broken packages / dependency problems)" ;; + 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; + 102) echo "APT: Lock held by another process (dpkg/apt still running)" ;; - # --- Script Validation & Setup (103-123) --- - 103) echo "Validation: Shell is not Bash" ;; - 104) echo "Validation: Not running as root (or invoked via sudo)" ;; - 105) echo "Validation: Proxmox VE version not supported" ;; - 106) echo "Validation: Architecture not supported (ARM / PiMox)" ;; - 107) echo "Validation: Kernel key parameters unreadable" ;; - 108) echo "Validation: Kernel key limits exceeded" ;; - 109) echo "Proxmox: No available container ID after max attempts" ;; - 110) echo "Proxmox: Failed to apply default.vars" ;; - 111) echo "Proxmox: App defaults file not available" ;; - 112) echo "Proxmox: Invalid install menu option" ;; - 113) echo "LXC: Under-provisioned — user aborted update" ;; - 114) echo "LXC: Storage too low — user aborted update" ;; - 115) echo "Download: install.func download failed or incomplete" ;; - 116) echo "Proxmox: Default bridge vmbr0 not found" ;; - 117) echo "LXC: Container did not reach running state" ;; - 118) echo "LXC: No IP assigned to container after timeout" ;; - 119) echo "Proxmox: No valid storage for rootdir content" ;; - 120) echo "Proxmox: No valid storage for vztmpl content" ;; - 121) echo "LXC: Container network not ready (no IP after retries)" ;; - 122) echo "LXC: No internet connectivity — user declined to continue" ;; - 123) echo "LXC: Local IP detection failed" ;; + # --- Script Validation & Setup (103-123) --- + 103) echo "Validation: Shell is not Bash" ;; + 104) echo "Validation: Not running as root (or invoked via sudo)" ;; + 105) echo "Validation: Proxmox VE version not supported" ;; + 106) echo "Validation: Architecture not supported (ARM / PiMox)" ;; + 107) echo "Validation: Kernel key parameters unreadable" ;; + 108) echo "Validation: Kernel key limits exceeded" ;; + 109) echo "Proxmox: No available container ID after max attempts" ;; + 110) echo "Proxmox: Failed to apply default.vars" ;; + 111) echo "Proxmox: App defaults file not available" ;; + 112) echo "Proxmox: Invalid install menu option" ;; + 113) echo "LXC: Under-provisioned — user aborted update" ;; + 114) echo "LXC: Storage too low — user aborted update" ;; + 115) echo "Download: install.func download failed or incomplete" ;; + 116) echo "Proxmox: Default bridge vmbr0 not found" ;; + 117) echo "LXC: Container did not reach running state" ;; + 118) echo "LXC: No IP assigned to container after timeout" ;; + 119) echo "Proxmox: No valid storage for rootdir content" ;; + 120) echo "Proxmox: No valid storage for vztmpl content" ;; + 121) echo "LXC: Container network not ready (no IP after retries)" ;; + 122) echo "LXC: No internet connectivity — user declined to continue" ;; + 123) echo "LXC: Local IP detection failed" ;; - # --- BSD sysexits.h (64-78) --- - 64) echo "Usage error (wrong arguments)" ;; - 65) echo "Data format error (bad input data)" ;; - 66) echo "Input file not found (cannot open input)" ;; - 67) echo "User not found (addressee unknown)" ;; - 68) echo "Host not found (hostname unknown)" ;; - 69) echo "Service unavailable" ;; - 70) echo "Internal software error" ;; - 71) echo "System error (OS-level failure)" ;; - 72) echo "Critical OS file missing" ;; - 73) echo "Cannot create output file" ;; - 74) echo "I/O error" ;; - 76) echo "Remote protocol error" ;; - 77) echo "Permission denied" ;; + # --- BSD sysexits.h (64-78) --- + 64) echo "Usage error (wrong arguments)" ;; + 65) echo "Data format error (bad input data)" ;; + 66) echo "Input file not found (cannot open input)" ;; + 67) echo "User not found (addressee unknown)" ;; + 68) echo "Host not found (hostname unknown)" ;; + 69) echo "Service unavailable" ;; + 70) echo "Internal software error" ;; + 71) echo "System error (OS-level failure)" ;; + 72) echo "Critical OS file missing" ;; + 73) echo "Cannot create output file" ;; + 74) echo "I/O error" ;; + 76) echo "Remote protocol error" ;; + 77) echo "Permission denied" ;; - # --- Common shell/system errors --- - 124) echo "Command timed out (timeout command)" ;; - 125) echo "Command failed to start (Docker daemon or execution error)" ;; - 126) echo "Command invoked cannot execute (permission problem?)" ;; - 127) echo "Command not found" ;; - 128) echo "Invalid argument to exit" ;; - 129) echo "Killed by SIGHUP (terminal closed / hangup)" ;; - 130) echo "Aborted by user (SIGINT)" ;; - 131) echo "Killed by SIGQUIT (core dumped)" ;; - 132) echo "Killed by SIGILL (illegal CPU instruction)" ;; - 134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;; - 137) echo "Killed (SIGKILL / Out of memory?)" ;; - 139) echo "Segmentation fault (core dumped)" ;; - 141) echo "Broken pipe (SIGPIPE - output closed prematurely)" ;; - 143) echo "Terminated (SIGTERM)" ;; - 144) echo "Killed by signal 16 (SIGUSR1 / SIGSTKFLT)" ;; - 146) echo "Killed by signal 18 (SIGTSTP)" ;; + # --- Common shell/system errors --- + 124) echo "Command timed out (timeout command)" ;; + 125) echo "Command failed to start (Docker daemon or execution error)" ;; + 126) echo "Command invoked cannot execute (permission problem?)" ;; + 127) echo "Command not found" ;; + 128) echo "Invalid argument to exit" ;; + 129) echo "Killed by SIGHUP (terminal closed / hangup)" ;; + 130) echo "Aborted by user (SIGINT)" ;; + 131) echo "Killed by SIGQUIT (core dumped)" ;; + 132) echo "Killed by SIGILL (illegal CPU instruction)" ;; + 134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;; + 137) echo "Killed (SIGKILL / Out of memory?)" ;; + 139) echo "Segmentation fault (core dumped)" ;; + 141) echo "Broken pipe (SIGPIPE - output closed prematurely)" ;; + 143) echo "Terminated (SIGTERM)" ;; + 144) echo "Killed by signal 16 (SIGUSR1 / SIGSTKFLT)" ;; + 146) echo "Killed by signal 18 (SIGTSTP)" ;; - # --- Systemd / Service errors (150-154) --- - 150) echo "Systemd: Service failed to start" ;; - 151) echo "Systemd: Service unit not found" ;; - 152) echo "Permission denied (EACCES)" ;; - 153) echo "Build/compile failed (make/gcc/cmake)" ;; - 154) echo "Node.js: Native addon build failed (node-gyp)" ;; - # --- Python / pip / uv (160-162) --- - 160) echo "Python: Virtualenv / uv environment missing or broken" ;; - 161) echo "Python: Dependency resolution failed" ;; - 162) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;; + # --- Systemd / Service errors (150-154) --- + 150) echo "Systemd: Service failed to start" ;; + 151) echo "Systemd: Service unit not found" ;; + 152) echo "Permission denied (EACCES)" ;; + 153) echo "Build/compile failed (make/gcc/cmake)" ;; + 154) echo "Node.js: Native addon build failed (node-gyp)" ;; + # --- Python / pip / uv (160-162) --- + 160) echo "Python: Virtualenv / uv environment missing or broken" ;; + 161) echo "Python: Dependency resolution failed" ;; + 162) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;; - # --- PostgreSQL (170-173) --- - 170) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;; - 171) echo "PostgreSQL: Authentication failed (bad user/password)" ;; - 172) echo "PostgreSQL: Database does not exist" ;; - 173) echo "PostgreSQL: Fatal error in query / syntax" ;; + # --- PostgreSQL (170-173) --- + 170) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;; + 171) echo "PostgreSQL: Authentication failed (bad user/password)" ;; + 172) echo "PostgreSQL: Database does not exist" ;; + 173) echo "PostgreSQL: Fatal error in query / syntax" ;; - # --- MySQL / MariaDB (180-183) --- - 180) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;; - 181) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;; - 182) echo "MySQL/MariaDB: Database does not exist" ;; - 183) echo "MySQL/MariaDB: Fatal error in query / syntax" ;; + # --- MySQL / MariaDB (180-183) --- + 180) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;; + 181) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;; + 182) echo "MySQL/MariaDB: Database does not exist" ;; + 183) echo "MySQL/MariaDB: Fatal error in query / syntax" ;; - # --- MongoDB (190-193) --- - 190) echo "MongoDB: Connection failed (server not running)" ;; - 191) echo "MongoDB: Authentication failed (bad user/password)" ;; - 192) echo "MongoDB: Database not found" ;; - 193) echo "MongoDB: Fatal query error" ;; + # --- MongoDB (190-193) --- + 190) echo "MongoDB: Connection failed (server not running)" ;; + 191) echo "MongoDB: Authentication failed (bad user/password)" ;; + 192) echo "MongoDB: Database not found" ;; + 193) echo "MongoDB: Fatal query error" ;; - # --- Proxmox Custom Codes (200-231) --- - 200) echo "Proxmox: Failed to create lock file" ;; - 203) echo "Proxmox: Missing CTID variable" ;; - 204) echo "Proxmox: Missing PCT_OSTYPE variable" ;; - 205) echo "Proxmox: Invalid CTID (<100)" ;; - 206) echo "Proxmox: CTID already in use" ;; - 207) echo "Proxmox: Password contains unescaped special characters" ;; - 208) echo "Proxmox: Invalid configuration (DNS/MAC/Network format)" ;; - 209) echo "Proxmox: Container creation failed" ;; - 210) echo "Proxmox: Cluster not quorate" ;; - 211) echo "Proxmox: Timeout waiting for template lock" ;; - 212) echo "Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)" ;; - 213) echo "Proxmox: Storage type does not support 'rootdir' content" ;; - 214) echo "Proxmox: Not enough storage space" ;; - 215) echo "Proxmox: Container created but not listed (ghost state)" ;; - 216) echo "Proxmox: RootFS entry missing in config" ;; - 217) echo "Proxmox: Storage not accessible" ;; - 218) echo "Proxmox: Template file corrupted or incomplete" ;; - 219) echo "Proxmox: CephFS does not support containers - use RBD" ;; - 220) echo "Proxmox: Unable to resolve template path" ;; - 221) echo "Proxmox: Template file not readable" ;; - 222) echo "Proxmox: Template download failed" ;; - 223) echo "Proxmox: Template not available after download" ;; - 224) echo "Proxmox: PBS storage is for backups only" ;; - 225) echo "Proxmox: No template available for OS/Version" ;; - 226) echo "Proxmox: VM disk import or post-creation setup failed" ;; - 231) echo "Proxmox: LXC stack upgrade failed" ;; + # --- Proxmox Custom Codes (200-231) --- + 200) echo "Proxmox: Failed to create lock file" ;; + 203) echo "Proxmox: Missing CTID variable" ;; + 204) echo "Proxmox: Missing PCT_OSTYPE variable" ;; + 205) echo "Proxmox: Invalid CTID (<100)" ;; + 206) echo "Proxmox: CTID already in use" ;; + 207) echo "Proxmox: Password contains unescaped special characters" ;; + 208) echo "Proxmox: Invalid configuration (DNS/MAC/Network format)" ;; + 209) echo "Proxmox: Container creation failed" ;; + 210) echo "Proxmox: Cluster not quorate" ;; + 211) echo "Proxmox: Timeout waiting for template lock" ;; + 212) echo "Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)" ;; + 213) echo "Proxmox: Storage type does not support 'rootdir' content" ;; + 214) echo "Proxmox: Not enough storage space" ;; + 215) echo "Proxmox: Container created but not listed (ghost state)" ;; + 216) echo "Proxmox: RootFS entry missing in config" ;; + 217) echo "Proxmox: Storage not accessible" ;; + 218) echo "Proxmox: Template file corrupted or incomplete" ;; + 219) echo "Proxmox: CephFS does not support containers - use RBD" ;; + 220) echo "Proxmox: Unable to resolve template path" ;; + 221) echo "Proxmox: Template file not readable" ;; + 222) echo "Proxmox: Template download failed" ;; + 223) echo "Proxmox: Template not available after download" ;; + 224) echo "Proxmox: PBS storage is for backups only" ;; + 225) echo "Proxmox: No template available for OS/Version" ;; + 226) echo "Proxmox: VM disk import or post-creation setup failed" ;; + 231) echo "Proxmox: LXC stack upgrade failed" ;; - # --- Tools & Addon Scripts (232-238) --- - 232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;; - 233) echo "Tools: Application not installed (update prerequisite missing)" ;; - 234) echo "Tools: No LXC containers found or available" ;; - 235) echo "Tools: Backup or restore operation failed" ;; - 236) echo "Tools: Required hardware not detected" ;; - 237) echo "Tools: Dependency package installation failed" ;; - 238) echo "Tools: OS or distribution not supported for this addon" ;; + # --- Tools & Addon Scripts (232-238) --- + 232) echo "Tools: Wrong execution environment (run on PVE host, not inside LXC)" ;; + 233) echo "Tools: Application not installed (update prerequisite missing)" ;; + 234) echo "Tools: No LXC containers found or available" ;; + 235) echo "Tools: Backup or restore operation failed" ;; + 236) echo "Tools: Required hardware not detected" ;; + 237) echo "Tools: Dependency package installation failed" ;; + 238) echo "Tools: OS or distribution not supported for this addon" ;; - # --- Node.js / npm / pnpm / yarn (239-249) --- - 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; - 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; - 245) echo "Node.js: Invalid command-line option" ;; - 246) echo "Node.js: Internal JavaScript Parse Error" ;; - 247) echo "Node.js: Fatal internal error" ;; - 248) echo "Node.js: Invalid C++ addon / N-API failure" ;; - 249) echo "npm/pnpm/yarn: Unknown fatal error" ;; + # --- Node.js / npm / pnpm / yarn (239-249) --- + 239) echo "npm/Node.js: Unexpected runtime error or dependency failure" ;; + 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; + 245) echo "Node.js: Invalid command-line option" ;; + 246) echo "Node.js: Internal JavaScript Parse Error" ;; + 247) echo "Node.js: Fatal internal error" ;; + 248) echo "Node.js: Invalid C++ addon / N-API failure" ;; + 249) echo "npm/pnpm/yarn: Unknown fatal error" ;; - # --- Application Install/Update Errors (250-254) --- - 250) echo "App: Download failed or version not determined" ;; - 251) echo "App: File extraction failed (corrupt or incomplete archive)" ;; - 252) echo "App: Required file or resource not found" ;; - 253) echo "App: Data migration required — update aborted" ;; - 254) echo "App: User declined prompt or input timed out" ;; + # --- Application Install/Update Errors (250-254) --- + 250) echo "App: Download failed or version not determined" ;; + 251) echo "App: File extraction failed (corrupt or incomplete archive)" ;; + 252) echo "App: Required file or resource not found" ;; + 253) echo "App: Data migration required — update aborted" ;; + 254) echo "App: User declined prompt or input timed out" ;; - # --- DPKG --- - 255) echo "DPKG: Fatal internal error" ;; + # --- DPKG --- + 255) echo "DPKG: Fatal internal error" ;; - # --- Default --- - *) echo "Unknown error" ;; - esac + # --- Default --- + *) echo "Unknown error" ;; + esac } # ------------------------------------------------------------------------------ @@ -346,12 +346,12 @@ explain_exit_code() { # - Handles backslashes, quotes, newlines, tabs, and carriage returns # ------------------------------------------------------------------------------ json_escape() { - # Escape a string for safe JSON embedding using awk (handles any input size). - # Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n - printf '%s' "$1" | - sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | - tr -d '\000-\010\013\014\016-\037\177\r' | - awk ' + # Escape a string for safe JSON embedding using awk (handles any input size). + # Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n + printf '%s' "$1" | + sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | + tr -d '\000-\010\013\014\016-\037\177\r' | + awk ' BEGIN { ORS = "" } { gsub(/\\/, "\\\\") # backslash → \\ @@ -370,39 +370,39 @@ json_escape() { # - Handles container paths that don't exist on the host # ------------------------------------------------------------------------------ get_error_text() { - local logfile="" - if declare -f get_active_logfile >/dev/null 2>&1; then - logfile=$(get_active_logfile) - elif [[ -n "${INSTALL_LOG:-}" ]]; then - logfile="$INSTALL_LOG" - elif [[ -n "${BUILD_LOG:-}" ]]; then - logfile="$BUILD_LOG" - fi + local logfile="" + if declare -f get_active_logfile >/dev/null 2>&1; then + logfile=$(get_active_logfile) + elif [[ -n "${INSTALL_LOG:-}" ]]; then + logfile="$INSTALL_LOG" + elif [[ -n "${BUILD_LOG:-}" ]]; then + logfile="$BUILD_LOG" + fi - # If logfile is inside container (e.g. /root/.install-*), try the host copy - if [[ -n "$logfile" && ! -s "$logfile" ]]; then - # Try combined log: /tmp/--.log - if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then - local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log" - if [[ -s "$combined_log" ]]; then - logfile="$combined_log" - fi - fi - fi + # If logfile is inside container (e.g. /root/.install-*), try the host copy + if [[ -n "$logfile" && ! -s "$logfile" ]]; then + # Try combined log: /tmp/--.log + if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then + local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log" + if [[ -s "$combined_log" ]]; then + logfile="$combined_log" + fi + fi + fi - # Also try BUILD_LOG as fallback if primary log is empty/missing - if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then - logfile="$BUILD_LOG" - fi + # Also try BUILD_LOG as fallback if primary log is empty/missing + if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then + logfile="$BUILD_LOG" + fi - # Try SILENT_LOGFILE as last resort (captures $STD command output) - if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then - logfile="$SILENT_LOGFILE" - fi + # Try SILENT_LOGFILE as last resort (captures $STD command output) + if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then + logfile="$SILENT_LOGFILE" + fi - if [[ -n "$logfile" && -s "$logfile" ]]; then - tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' - fi + if [[ -n "$logfile" && -s "$logfile" ]]; then + tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' + fi } # ------------------------------------------------------------------------------ @@ -415,50 +415,50 @@ get_error_text() { # - Used for the error telemetry field (full trace instead of 20 lines) # ------------------------------------------------------------------------------ get_full_log() { - local max_bytes="${1:-122880}" # 120KB default - local logfile="" + local max_bytes="${1:-122880}" # 120KB default + local logfile="" - # Ensure logs are available on host (pulls from container if needed) - if declare -f ensure_log_on_host >/dev/null 2>&1; then - ensure_log_on_host - fi + # Ensure logs are available on host (pulls from container if needed) + if declare -f ensure_log_on_host >/dev/null 2>&1; then + ensure_log_on_host + fi - # Try combined log first (most complete) - if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then - local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log" - if [[ -s "$combined_log" ]]; then - logfile="$combined_log" - fi - fi + # Try combined log first (most complete) + if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then + local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log" + if [[ -s "$combined_log" ]]; then + logfile="$combined_log" + fi + fi - # Fall back to INSTALL_LOG - if [[ -z "$logfile" || ! -s "$logfile" ]]; then - if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then - logfile="$INSTALL_LOG" - fi - fi + # Fall back to INSTALL_LOG + if [[ -z "$logfile" || ! -s "$logfile" ]]; then + if [[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]]; then + logfile="$INSTALL_LOG" + fi + fi - # Fall back to BUILD_LOG - if [[ -z "$logfile" || ! -s "$logfile" ]]; then - if [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then - logfile="$BUILD_LOG" - fi - fi + # Fall back to BUILD_LOG + if [[ -z "$logfile" || ! -s "$logfile" ]]; then + if [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then + logfile="$BUILD_LOG" + fi + fi - # Fall back to SILENT_LOGFILE (captures $STD command output) - if [[ -z "$logfile" || ! -s "$logfile" ]]; then - if [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then - logfile="$SILENT_LOGFILE" - fi - fi + # Fall back to SILENT_LOGFILE (captures $STD command output) + if [[ -z "$logfile" || ! -s "$logfile" ]]; then + if [[ -n "${SILENT_LOGFILE:-}" && -s "${SILENT_LOGFILE}" ]]; then + logfile="$SILENT_LOGFILE" + fi + fi - if [[ -n "$logfile" && -s "$logfile" ]]; then - # Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR) - sed 's/\r$//' "$logfile" 2>/dev/null | - sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | - sed -E 's/([0-9]{1,3}\.)[0-9]{1,3}\.[0-9]{1,3}/\1x.x/g' | - head -c "$max_bytes" - fi + if [[ -n "$logfile" && -s "$logfile" ]]; then + # Strip ANSI codes, carriage returns, and anonymize IP addresses (GDPR) + sed 's/\r$//' "$logfile" 2>/dev/null | + sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | + sed -E 's/([0-9]{1,3}\.)[0-9]{1,3}\.[0-9]{1,3}/\1x.x/g' | + head -c "$max_bytes" + fi } # ------------------------------------------------------------------------------ @@ -473,18 +473,18 @@ get_full_log() { # - Returns structured error string via stdout # ------------------------------------------------------------------------------ build_error_string() { - local exit_code="${1:-1}" - local log_text="${2:-}" - local explanation - explanation=$(explain_exit_code "$exit_code") + local exit_code="${1:-1}" + local log_text="${2:-}" + local explanation + explanation=$(explain_exit_code "$exit_code") - if [[ -n "$log_text" ]]; then - # Structured format: header + separator + log lines - printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text" - else - # No log available - just the explanation with exit code - printf 'exit_code=%s | %s' "$exit_code" "$explanation" - fi + if [[ -n "$log_text" ]]; then + # Structured format: header + separator + log lines + printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text" + else + # No log available - just the explanation with exit code + printf 'exit_code=%s | %s' "$exit_code" "$explanation" + fi } # ============================================================================== @@ -499,35 +499,35 @@ build_error_string() { # - Used for GPU analytics # ------------------------------------------------------------------------------ detect_gpu() { - GPU_VENDOR="unknown" - GPU_MODEL="" - GPU_PASSTHROUGH="unknown" + GPU_VENDOR="unknown" + GPU_MODEL="" + GPU_PASSTHROUGH="unknown" - local gpu_line - gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1 || true) + local gpu_line + gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1 || true) - if [[ -n "$gpu_line" ]]; then - # Extract model: everything after the colon, clean up - GPU_MODEL=$(echo "$gpu_line" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64) + if [[ -n "$gpu_line" ]]; then + # Extract model: everything after the colon, clean up + GPU_MODEL=$(echo "$gpu_line" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64) - # Detect vendor and passthrough type - if echo "$gpu_line" | grep -qi "Intel"; then - GPU_VENDOR="intel" - GPU_PASSTHROUGH="igpu" - elif echo "$gpu_line" | grep -qi "AMD\|ATI"; then - GPU_VENDOR="amd" - if echo "$gpu_line" | grep -qi "Radeon RX\|Radeon Pro"; then - GPU_PASSTHROUGH="dgpu" - else - GPU_PASSTHROUGH="igpu" - fi - elif echo "$gpu_line" | grep -qi "NVIDIA"; then - GPU_VENDOR="nvidia" - GPU_PASSTHROUGH="dgpu" - fi - fi + # Detect vendor and passthrough type + if echo "$gpu_line" | grep -qi "Intel"; then + GPU_VENDOR="intel" + GPU_PASSTHROUGH="igpu" + elif echo "$gpu_line" | grep -qi "AMD\|ATI"; then + GPU_VENDOR="amd" + if echo "$gpu_line" | grep -qi "Radeon RX\|Radeon Pro"; then + GPU_PASSTHROUGH="dgpu" + else + GPU_PASSTHROUGH="igpu" + fi + elif echo "$gpu_line" | grep -qi "NVIDIA"; then + GPU_VENDOR="nvidia" + GPU_PASSTHROUGH="dgpu" + fi + fi - export GPU_VENDOR GPU_MODEL GPU_PASSTHROUGH + export GPU_VENDOR GPU_MODEL GPU_PASSTHROUGH } # ------------------------------------------------------------------------------ @@ -538,29 +538,29 @@ detect_gpu() { # - Used for CPU analytics # ------------------------------------------------------------------------------ detect_cpu() { - CPU_VENDOR="unknown" - CPU_MODEL="" + CPU_VENDOR="unknown" + CPU_MODEL="" - if [[ -f /proc/cpuinfo ]]; then - local vendor_id - vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ' || true) + if [[ -f /proc/cpuinfo ]]; then + local vendor_id + vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ' || true) - case "$vendor_id" in - GenuineIntel) CPU_VENDOR="intel" ;; - AuthenticAMD) CPU_VENDOR="amd" ;; - *) - # ARM doesn't have vendor_id, check for CPU implementer - if grep -qi "CPU implementer" /proc/cpuinfo 2>/dev/null; then - CPU_VENDOR="arm" - fi - ;; - esac + case "$vendor_id" in + GenuineIntel) CPU_VENDOR="intel" ;; + AuthenticAMD) CPU_VENDOR="amd" ;; + *) + # ARM doesn't have vendor_id, check for CPU implementer + if grep -qi "CPU implementer" /proc/cpuinfo 2>/dev/null; then + CPU_VENDOR="arm" + fi + ;; + esac - # Extract model name and clean it up - CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64 || true) - fi + # Extract model name and clean it up + CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64 || true) + fi - export CPU_VENDOR CPU_MODEL + export CPU_VENDOR CPU_MODEL } # ------------------------------------------------------------------------------ @@ -572,20 +572,20 @@ detect_cpu() { # - Returns empty if not available or if speed is "Unknown" (nested VMs) # ------------------------------------------------------------------------------ detect_ram() { - RAM_SPEED="" + RAM_SPEED="" - if command -v dmidecode &>/dev/null; then - # Get configured memory speed (actual running speed) - # Use || true to handle "Unknown" values in nested VMs (no numeric match) - RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1) || true + if command -v dmidecode &>/dev/null; then + # Get configured memory speed (actual running speed) + # Use || true to handle "Unknown" values in nested VMs (no numeric match) + RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1) || true - # Fallback to Speed: if Configured not available - if [[ -z "$RAM_SPEED" ]]; then - RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1) || true - fi - fi + # Fallback to Speed: if Configured not available + if [[ -z "$RAM_SPEED" ]]; then + RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1) || true + fi + fi - export RAM_SPEED + export RAM_SPEED } # ------------------------------------------------------------------------------ @@ -608,59 +608,59 @@ detect_ram() { # - Never blocks or fails script execution # ------------------------------------------------------------------------------ post_to_api() { - # Prevent duplicate submissions (post_to_api is called from multiple places) - [[ "${POST_TO_API_DONE:-}" == "true" ]] && return 0 + # Prevent duplicate submissions (post_to_api is called from multiple places) + [[ "${POST_TO_API_DONE:-}" == "true" ]] && return 0 - # Silent fail - telemetry should never break scripts - command -v curl &>/dev/null || { - [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] curl not found, skipping" >&2 - return 0 - } - [[ "${DIAGNOSTICS:-no}" == "no" ]] && { - [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] DIAGNOSTICS=no, skipping" >&2 - return 0 - } - [[ -z "${RANDOM_UUID:-}" ]] && { - [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] RANDOM_UUID empty, skipping" >&2 - return 0 - } + # Silent fail - telemetry should never break scripts + command -v curl &>/dev/null || { + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] curl not found, skipping" >&2 + return 0 + } + [[ "${DIAGNOSTICS:-no}" == "no" ]] && { + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] DIAGNOSTICS=no, skipping" >&2 + return 0 + } + [[ -z "${RANDOM_UUID:-}" ]] && { + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] RANDOM_UUID empty, skipping" >&2 + return 0 + } - [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP" >&2 + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] post_to_api() DIAGNOSTICS=$DIAGNOSTICS RANDOM_UUID=$RANDOM_UUID NSAPP=$NSAPP" >&2 - # Set type for later status updates (preserve if already set, e.g. turnkey) - TELEMETRY_TYPE="${TELEMETRY_TYPE:-lxc}" + # Set type for later status updates (preserve if already set, e.g. turnkey) + TELEMETRY_TYPE="${TELEMETRY_TYPE:-lxc}" - local pve_version="" - if command -v pveversion &>/dev/null; then - pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true - fi + local pve_version="" + if command -v pveversion &>/dev/null; then + pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true + fi - # Detect GPU if not already set - if [[ -z "${GPU_VENDOR:-}" ]]; then - detect_gpu - fi - local gpu_vendor="${GPU_VENDOR:-unknown}" - local gpu_model - gpu_model=$(json_escape "${GPU_MODEL:-}") - local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" + # Detect GPU if not already set + if [[ -z "${GPU_VENDOR:-}" ]]; then + detect_gpu + fi + local gpu_vendor="${GPU_VENDOR:-unknown}" + local gpu_model + gpu_model=$(json_escape "${GPU_MODEL:-}") + local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" - # Detect CPU if not already set - if [[ -z "${CPU_VENDOR:-}" ]]; then - detect_cpu - fi - local cpu_vendor="${CPU_VENDOR:-unknown}" - local cpu_model - cpu_model=$(json_escape "${CPU_MODEL:-}") + # Detect CPU if not already set + if [[ -z "${CPU_VENDOR:-}" ]]; then + detect_cpu + fi + local cpu_vendor="${CPU_VENDOR:-unknown}" + local cpu_model + cpu_model=$(json_escape "${CPU_MODEL:-}") - # Detect RAM if not already set - if [[ -z "${RAM_SPEED:-}" ]]; then - detect_ram - fi - local ram_speed="${RAM_SPEED:-}" + # Detect RAM if not already set + if [[ -z "${RAM_SPEED:-}" ]]; then + detect_ram + fi + local ram_speed="${RAM_SPEED:-}" - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat <&2 - [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2 + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Sending to: $TELEMETRY_URL" >&2 + [[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] Payload: $JSON_PAYLOAD" >&2 - # Send initial "installing" record with retry. - # This record MUST exist for all subsequent updates to succeed. - local http_code="" attempt - local _post_success=false - for attempt in 1 2 3; do - if [[ "${DEV_MODE:-}" == "true" ]]; then - http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ - -H "Content-Type: application/json" \ - -d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || http_code="000" - echo "[DEBUG] post_to_api attempt $attempt HTTP=$http_code" >&2 - else - http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ - -H "Content-Type: application/json" \ - -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" - fi - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - _post_success=true - break - fi - [[ "$attempt" -lt 3 ]] && sleep 1 - done + # Send initial "installing" record with retry. + # This record MUST exist for all subsequent updates to succeed. + local http_code="" attempt + local _post_success=false + for attempt in 1 2 3; do + if [[ "${DEV_MODE:-}" == "true" ]]; then + http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" -o /dev/stderr 2>&1) || http_code="000" + echo "[DEBUG] post_to_api attempt $attempt HTTP=$http_code" >&2 + else + http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" + fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + _post_success=true + break + fi + [[ "$attempt" -lt 3 ]] && sleep 1 + done - # Only mark done if at least one attempt succeeded. - # If all 3 failed, POST_TO_API_DONE stays false so post_update_to_api - # and on_exit() know the initial record was never created. - # The server has fallback logic to create a new record on status updates, - # so subsequent calls can still succeed even without the initial record. - POST_TO_API_DONE=${_post_success} + # Only mark done if at least one attempt succeeded. + # If all 3 failed, POST_TO_API_DONE stays false so post_update_to_api + # and on_exit() know the initial record was never created. + # The server has fallback logic to create a new record on status updates, + # so subsequent calls can still succeed even without the initial record. + POST_TO_API_DONE=${_post_success} } # ------------------------------------------------------------------------------ @@ -733,53 +733,53 @@ EOF # - Never blocks or fails script execution # ------------------------------------------------------------------------------ post_to_api_vm() { - # Read diagnostics setting from file - if [[ -f /usr/local/community-scripts/diagnostics ]]; then - DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true - fi + # Read diagnostics setting from file + if [[ -f /usr/local/community-scripts/diagnostics ]]; then + DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true + fi - # Silent fail - telemetry should never break scripts - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + # Silent fail - telemetry should never break scripts + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - # Set type for later status updates - TELEMETRY_TYPE="vm" + # Set type for later status updates + TELEMETRY_TYPE="vm" - local pve_version="" - if command -v pveversion &>/dev/null; then - pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true - fi + local pve_version="" + if command -v pveversion &>/dev/null; then + pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true + fi - # Detect GPU if not already set - if [[ -z "${GPU_VENDOR:-}" ]]; then - detect_gpu - fi - local gpu_vendor="${GPU_VENDOR:-unknown}" - local gpu_model - gpu_model=$(json_escape "${GPU_MODEL:-}") - local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" + # Detect GPU if not already set + if [[ -z "${GPU_VENDOR:-}" ]]; then + detect_gpu + fi + local gpu_vendor="${GPU_VENDOR:-unknown}" + local gpu_model + gpu_model=$(json_escape "${GPU_MODEL:-}") + local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" - # Detect CPU if not already set - if [[ -z "${CPU_VENDOR:-}" ]]; then - detect_cpu - fi - local cpu_vendor="${CPU_VENDOR:-unknown}" - local cpu_model - cpu_model=$(json_escape "${CPU_MODEL:-}") + # Detect CPU if not already set + if [[ -z "${CPU_VENDOR:-}" ]]; then + detect_cpu + fi + local cpu_vendor="${CPU_VENDOR:-unknown}" + local cpu_model + cpu_model=$(json_escape "${CPU_MODEL:-}") - # Detect RAM if not already set - if [[ -z "${RAM_SPEED:-}" ]]; then - detect_ram - fi - local ram_speed="${RAM_SPEED:-}" + # Detect RAM if not already set + if [[ -z "${RAM_SPEED:-}" ]]; then + detect_ram + fi + local ram_speed="${RAM_SPEED:-}" - # Remove 'G' suffix from disk size - local DISK_SIZE_API="${DISK_SIZE%G}" + # Remove 'G' suffix from disk size + local DISK_SIZE_API="${DISK_SIZE%G}" - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat </dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - _post_success=true - break - fi - [[ "$attempt" -lt 3 ]] && sleep 1 - done + # Send initial "installing" record with retry (must succeed for updates to work) + local http_code="" attempt + local _post_success=false + for attempt in 1 2 3; do + http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + _post_success=true + break + fi + [[ "$attempt" -lt 3 ]] && sleep 1 + done - POST_TO_API_DONE=${_post_success} + POST_TO_API_DONE=${_post_success} } # ------------------------------------------------------------------------------ @@ -836,17 +836,17 @@ EOF # - Can be called multiple times safely # ------------------------------------------------------------------------------ post_progress_to_api() { - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - local progress_status="${1:-configuring}" - local app_name="${NSAPP:-${app:-unknown}}" - local telemetry_type="${TELEMETRY_TYPE:-lxc}" + local progress_status="${1:-configuring}" + local app_name="${NSAPP:-${app:-unknown}}" + local telemetry_type="${TELEMETRY_TYPE:-lxc}" - curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \ - -H "Content-Type: application/json" \ - -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${telemetry_type}\",\"nsapp\":\"${app_name}\",\"status\":\"${progress_status}\"}" &>/dev/null || true + curl -fsS -m 5 -X POST "${TELEMETRY_URL:-https://telemetry.community-scripts.org/telemetry}" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${telemetry_type}\",\"nsapp\":\"${app_name}\",\"status\":\"${progress_status}\"}" &>/dev/null || true } # ------------------------------------------------------------------------------ @@ -865,111 +865,111 @@ post_progress_to_api() { # - Never blocks or fails script execution # ------------------------------------------------------------------------------ post_update_to_api() { - # Silent fail - telemetry should never break scripts - command -v curl &>/dev/null || return 0 + # Silent fail - telemetry should never break scripts + command -v curl &>/dev/null || return 0 - # Support "force" mode (3rd arg) to bypass duplicate check for retries after cleanup - local force="${3:-}" - POST_UPDATE_DONE=${POST_UPDATE_DONE:-false} - if [[ "$POST_UPDATE_DONE" == "true" && "$force" != "force" ]]; then - return 0 - fi + # Support "force" mode (3rd arg) to bypass duplicate check for retries after cleanup + local force="${3:-}" + POST_UPDATE_DONE=${POST_UPDATE_DONE:-false} + if [[ "$POST_UPDATE_DONE" == "true" && "$force" != "force" ]]; then + return 0 + fi - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - local status="${1:-failed}" - local raw_exit_code="${2:-1}" - local exit_code=0 error="" pb_status error_category="" + local status="${1:-failed}" + local raw_exit_code="${2:-1}" + local exit_code=0 error="" pb_status error_category="" - # Get GPU info (if detected) - local gpu_vendor="${GPU_VENDOR:-unknown}" - local gpu_model - gpu_model=$(json_escape "${GPU_MODEL:-}") - local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" + # Get GPU info (if detected) + local gpu_vendor="${GPU_VENDOR:-unknown}" + local gpu_model + gpu_model=$(json_escape "${GPU_MODEL:-}") + local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}" - # Get CPU info (if detected) - local cpu_vendor="${CPU_VENDOR:-unknown}" - local cpu_model - cpu_model=$(json_escape "${CPU_MODEL:-}") + # Get CPU info (if detected) + local cpu_vendor="${CPU_VENDOR:-unknown}" + local cpu_model + cpu_model=$(json_escape "${CPU_MODEL:-}") - # Get RAM info (if detected) - local ram_speed="${RAM_SPEED:-}" + # Get RAM info (if detected) + local ram_speed="${RAM_SPEED:-}" - # Map status to telemetry values: installing, success, failed, unknown - case "$status" in - done | success) - pb_status="success" - exit_code=0 - error="" - error_category="" - ;; - failed) - pb_status="failed" - ;; - *) - pb_status="unknown" - ;; - esac + # Map status to telemetry values: installing, success, failed, unknown + case "$status" in + done | success) + pb_status="success" + exit_code=0 + error="" + error_category="" + ;; + failed) + pb_status="failed" + ;; + *) + pb_status="unknown" + ;; + esac - # For failed/unknown status, resolve exit code and error description - local short_error="" medium_error="" - if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then - if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then - exit_code="$raw_exit_code" - else - exit_code=1 - fi - # Get full installation log for error field - local log_text="" - log_text=$(get_full_log 122880) || true # 120KB max - if [[ -z "$log_text" ]]; then - # Fallback to last 20 lines - log_text=$(get_error_text) - fi - local full_error - full_error=$(build_error_string "$exit_code" "$log_text") - error=$(json_escape "$full_error") - short_error=$(json_escape "$(explain_exit_code "$exit_code")") - error_category=$(categorize_error "$exit_code") - [[ -z "$error" ]] && error="Unknown error" + # For failed/unknown status, resolve exit code and error description + local short_error="" medium_error="" + if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then + if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then + exit_code="$raw_exit_code" + else + exit_code=1 + fi + # Get full installation log for error field + local log_text="" + log_text=$(get_full_log 122880) || true # 120KB max + if [[ -z "$log_text" ]]; then + # Fallback to last 20 lines + log_text=$(get_error_text) + fi + local full_error + full_error=$(build_error_string "$exit_code" "$log_text") + error=$(json_escape "$full_error") + short_error=$(json_escape "$(explain_exit_code "$exit_code")") + error_category=$(categorize_error "$exit_code") + [[ -z "$error" ]] && error="Unknown error" - # Build medium error for attempt 2: explanation + last 100 log lines (≤16KB) - # This is the critical middle ground between full 120KB log and generic-only description - local medium_log="" - medium_log=$(get_full_log 16384) || true # 16KB max - if [[ -z "$medium_log" ]]; then - medium_log=$(get_error_text) || true - fi - local medium_full - medium_full=$(build_error_string "$exit_code" "$medium_log") - medium_error=$(json_escape "$medium_full") - [[ -z "$medium_error" ]] && medium_error="$short_error" - fi + # Build medium error for attempt 2: explanation + last 100 log lines (≤16KB) + # This is the critical middle ground between full 120KB log and generic-only description + local medium_log="" + medium_log=$(get_full_log 16384) || true # 16KB max + if [[ -z "$medium_log" ]]; then + medium_log=$(get_error_text) || true + fi + local medium_full + medium_full=$(build_error_string "$exit_code" "$medium_log") + medium_error=$(json_escape "$medium_full") + [[ -z "$medium_error" ]] && medium_error="$short_error" + fi - # Calculate duration if timer was started - local duration=0 - if [[ -n "${INSTALL_START_TIME:-}" ]]; then - duration=$(($(date +%s) - INSTALL_START_TIME)) - fi + # Calculate duration if timer was started + local duration=0 + if [[ -n "${INSTALL_START_TIME:-}" ]]; then + duration=$(($(date +%s) - INSTALL_START_TIME)) + fi - # Get PVE version - local pve_version="" - if command -v pveversion &>/dev/null; then - pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true - fi + # Get PVE version + local pve_version="" + if command -v pveversion &>/dev/null; then + pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true + fi - local http_code="" + local http_code="" - # Strip 'G' suffix from disk size (VMs set DISK_SIZE=32G) - local DISK_SIZE_API="${DISK_SIZE:-0}" - DISK_SIZE_API="${DISK_SIZE_API%G}" - [[ ! "$DISK_SIZE_API" =~ ^[0-9]+$ ]] && DISK_SIZE_API=0 + # Strip 'G' suffix from disk size (VMs set DISK_SIZE=32G) + local DISK_SIZE_API="${DISK_SIZE:-0}" + DISK_SIZE_API="${DISK_SIZE_API%G}" + [[ ! "$DISK_SIZE_API" =~ ^[0-9]+$ ]] && DISK_SIZE_API=0 - # ── Attempt 1: Full payload with complete error text (includes full log) ── - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat </dev/null) || http_code="000" + http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - POST_UPDATE_DONE=true - return 0 - fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + POST_UPDATE_DONE=true + return 0 + fi - # ── Attempt 2: Medium error text (truncated log ≤16KB instead of full 120KB) ── - sleep 1 - local RETRY_PAYLOAD - RETRY_PAYLOAD=$( - cat </dev/null) || http_code="000" + http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$RETRY_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - POST_UPDATE_DONE=true - return 0 - fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + POST_UPDATE_DONE=true + return 0 + fi - # ── Attempt 3: Minimal payload with medium error (bare minimum to set status) ── - sleep 2 - local MINIMAL_PAYLOAD - MINIMAL_PAYLOAD=$( - cat </dev/null) || http_code="000" + http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$MINIMAL_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - POST_UPDATE_DONE=true - return 0 - fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + POST_UPDATE_DONE=true + return 0 + fi - # All 3 attempts failed — do NOT set POST_UPDATE_DONE=true. - # This allows the EXIT trap (on_exit in error-handler.func) to retry. - # No infinite loop risk: EXIT trap fires exactly once. + # All 3 attempts failed — do NOT set POST_UPDATE_DONE=true. + # This allows the EXIT trap (on_exit in error_handler.func) to retry. + # No infinite loop risk: EXIT trap fires exactly once. } # ============================================================================== @@ -1096,65 +1096,65 @@ EOF # - Used to group errors in dashboard # ------------------------------------------------------------------------------ categorize_error() { - # Allow build.func to override category based on log analysis (exit code 1 subclassification) - if [[ -n "${ERROR_CATEGORY_OVERRIDE:-}" ]]; then - echo "$ERROR_CATEGORY_OVERRIDE" - return - fi + # Allow build.func to override category based on log analysis (exit code 1 subclassification) + if [[ -n "${ERROR_CATEGORY_OVERRIDE:-}" ]]; then + echo "$ERROR_CATEGORY_OVERRIDE" + return + fi - local code="$1" - case "$code" in - # Network errors (curl/wget) - 6 | 7 | 22 | 35) echo "network" ;; + local code="$1" + case "$code" in + # Network errors (curl/wget) + 6 | 7 | 22 | 35) echo "network" ;; - # Docker / Privileged mode required - 10) echo "config" ;; + # Docker / Privileged mode required + 10) echo "config" ;; - # Timeout errors - 28 | 124 | 211) echo "timeout" ;; + # Timeout errors + 28 | 124 | 211) echo "timeout" ;; - # Storage errors (Proxmox storage) - 214 | 217 | 219 | 224) echo "storage" ;; + # Storage errors (Proxmox storage) + 214 | 217 | 219 | 224) echo "storage" ;; - # Dependency/Package errors (APT, DPKG, pip, commands) - 100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;; + # Dependency/Package errors (APT, DPKG, pip, commands) + 100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;; - # Permission errors - 126 | 152) echo "permission" ;; + # Permission errors + 126 | 152) echo "permission" ;; - # Configuration errors (Proxmox config, invalid args) - 128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;; + # Configuration errors (Proxmox config, invalid args) + 128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;; - # Proxmox container/template errors - 200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;; + # Proxmox container/template errors + 200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;; - # Service/Systemd errors - 150 | 151 | 153 | 154) echo "service" ;; + # Service/Systemd errors + 150 | 151 | 153 | 154) echo "service" ;; - # Database errors (PostgreSQL, MySQL, MongoDB) - 170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;; + # Database errors (PostgreSQL, MySQL, MongoDB) + 170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;; - # Node.js / JavaScript runtime errors - 243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;; + # Node.js / JavaScript runtime errors + 243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;; - # Python environment errors - # (already covered: 160-162 under dependency) + # Python environment errors + # (already covered: 160-162 under dependency) - # Aborted by user (SIGHUP=terminal closed, SIGINT=Ctrl+C, SIGTERM=killed) - 129 | 130 | 143) echo "user_aborted" ;; + # Aborted by user (SIGHUP=terminal closed, SIGINT=Ctrl+C, SIGTERM=killed) + 129 | 130 | 143) echo "user_aborted" ;; - # Resource errors (OOM, SIGKILL, SIGABRT) - 134 | 137) echo "resource" ;; + # Resource errors (OOM, SIGKILL, SIGABRT) + 134 | 137) echo "resource" ;; - # Signal/Process errors (SIGPIPE, SIGSEGV) - 139 | 141) echo "signal" ;; + # Signal/Process errors (SIGPIPE, SIGSEGV) + 139 | 141) echo "signal" ;; - # Shell errors (general error, syntax error) - 1 | 2) echo "shell" ;; + # Shell errors (general error, syntax error) + 1 | 2) echo "shell" ;; - # Default - truly unknown - *) echo "unknown" ;; - esac + # Default - truly unknown + *) echo "unknown" ;; + esac } # ------------------------------------------------------------------------------ @@ -1165,8 +1165,8 @@ categorize_error() { # - Sets INSTALL_START_TIME global variable # ------------------------------------------------------------------------------ start_install_timer() { - INSTALL_START_TIME=$(date +%s) - export INSTALL_START_TIME + INSTALL_START_TIME=$(date +%s) + export INSTALL_START_TIME } # ------------------------------------------------------------------------------ @@ -1176,12 +1176,12 @@ start_install_timer() { # - Returns 0 if timer was not started # ------------------------------------------------------------------------------ get_install_duration() { - if [[ -z "${INSTALL_START_TIME:-}" ]]; then - echo "0" - return - fi - local now=$(date +%s) - echo $((now - INSTALL_START_TIME)) + if [[ -z "${INSTALL_START_TIME:-}" ]]; then + echo "0" + return + fi + local now=$(date +%s) + echo $((now - INSTALL_START_TIME)) } # ------------------------------------------------------------------------------ @@ -1193,18 +1193,18 @@ get_install_duration() { # * $1: exit_code from the script # ------------------------------------------------------------------------------ _telemetry_report_exit() { - local ec="${1:-0}" - local status="success" - [[ "$ec" -ne 0 ]] && status="failed" + local ec="${1:-0}" + local status="success" + [[ "$ec" -ne 0 ]] && status="failed" - # Lazy name resolution: use explicit name, fall back to $APP, then "unknown" - local name="${TELEMETRY_TOOL_NAME:-${APP:-unknown}}" + # Lazy name resolution: use explicit name, fall back to $APP, then "unknown" + local name="${TELEMETRY_TOOL_NAME:-${APP:-unknown}}" - if [[ "${TELEMETRY_TOOL_TYPE:-pve}" == "addon" ]]; then - post_addon_to_api "$name" "$status" "$ec" - else - post_tool_to_api "$name" "$status" "$ec" - fi + if [[ "${TELEMETRY_TOOL_TYPE:-pve}" == "addon" ]]; then + post_addon_to_api "$name" "$status" "$ec" + else + post_tool_to_api "$name" "$status" "$ec" + fi } # ------------------------------------------------------------------------------ @@ -1224,21 +1224,21 @@ _telemetry_report_exit() { # init_tool_telemetry "" "addon" # uses $APP at exit time # ------------------------------------------------------------------------------ init_tool_telemetry() { - local name="${1:-}" - local type="${2:-pve}" + local name="${1:-}" + local type="${2:-pve}" - [[ -n "$name" ]] && TELEMETRY_TOOL_NAME="$name" - TELEMETRY_TOOL_TYPE="$type" + [[ -n "$name" ]] && TELEMETRY_TOOL_NAME="$name" + TELEMETRY_TOOL_TYPE="$type" - # Read diagnostics opt-in/opt-out - if [[ -f /usr/local/community-scripts/diagnostics ]]; then - DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true - fi + # Read diagnostics opt-in/opt-out + if [[ -f /usr/local/community-scripts/diagnostics ]]; then + DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics 2>/dev/null | awk -F'=' '{print $2}') || true + fi - start_install_timer + start_install_timer - # EXIT trap: automatically report telemetry when script ends - trap '_telemetry_report_exit "$?"' EXIT + # EXIT trap: automatically report telemetry when script ends + trap '_telemetry_report_exit "$?"' EXIT } # ------------------------------------------------------------------------------ @@ -1252,40 +1252,40 @@ init_tool_telemetry() { # - For PVE host tools, not container installations # ------------------------------------------------------------------------------ post_tool_to_api() { - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - local tool_name="${1:-unknown}" - local status="${2:-success}" - local exit_code="${3:-0}" - local error="" error_category="" - local uuid duration + local tool_name="${1:-unknown}" + local status="${2:-success}" + local exit_code="${3:-0}" + local error="" error_category="" + local uuid duration - # Generate UUID for this tool execution - uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "tool-$(date +%s)") - duration=$(get_install_duration) + # Generate UUID for this tool execution + uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "tool-$(date +%s)") + duration=$(get_install_duration) - # Map status - [[ "$status" == "done" ]] && status="success" + # Map status + [[ "$status" == "done" ]] && status="success" - if [[ "$status" == "failed" ]]; then - [[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1 - local error_text="" - error_text=$(get_error_text) - local full_error - full_error=$(build_error_string "$exit_code" "$error_text") - error=$(json_escape "$full_error") - error_category=$(categorize_error "$exit_code") - fi + if [[ "$status" == "failed" ]]; then + [[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1 + local error_text="" + error_text=$(get_error_text) + local full_error + full_error=$(build_error_string "$exit_code" "$error_text") + error=$(json_escape "$full_error") + error_category=$(categorize_error "$exit_code") + fi - local pve_version="" - if command -v pveversion &>/dev/null; then - pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true - fi + local pve_version="" + if command -v pveversion &>/dev/null; then + pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true + fi - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat </dev/null || true + curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" &>/dev/null || true } # ------------------------------------------------------------------------------ @@ -1318,42 +1318,42 @@ EOF # - For addons installed inside containers # ------------------------------------------------------------------------------ post_addon_to_api() { - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - local addon_name="${1:-unknown}" - local status="${2:-success}" - local exit_code="${3:-0}" - local error="" error_category="" - local uuid duration + local addon_name="${1:-unknown}" + local status="${2:-success}" + local exit_code="${3:-0}" + local error="" error_category="" + local uuid duration - # Generate UUID for this addon installation - uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "addon-$(date +%s)") - duration=$(get_install_duration) + # Generate UUID for this addon installation + uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || echo "addon-$(date +%s)") + duration=$(get_install_duration) - # Map status - [[ "$status" == "done" ]] && status="success" + # Map status + [[ "$status" == "done" ]] && status="success" - if [[ "$status" == "failed" ]]; then - [[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1 - local error_text="" - error_text=$(get_error_text) - local full_error - full_error=$(build_error_string "$exit_code" "$error_text") - error=$(json_escape "$full_error") - error_category=$(categorize_error "$exit_code") - fi + if [[ "$status" == "failed" ]]; then + [[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1 + local error_text="" + error_text=$(get_error_text) + local full_error + full_error=$(build_error_string "$exit_code" "$error_text") + error=$(json_escape "$full_error") + error_category=$(categorize_error "$exit_code") + fi - # Detect OS info - local os_type="" os_version="" - if [[ -f /etc/os-release ]]; then - os_type=$(grep "^ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true) - os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true) - fi + # Detect OS info + local os_type="" os_version="" + if [[ -f /etc/os-release ]]; then + os_type=$(grep "^ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true) + os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true) + fi - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat </dev/null || true + curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" &>/dev/null || true } # ------------------------------------------------------------------------------ @@ -1389,63 +1389,63 @@ EOF # * GPU info (if detect_gpu was called) # ------------------------------------------------------------------------------ post_update_to_api_extended() { - # Silent fail - telemetry should never break scripts - command -v curl &>/dev/null || return 0 + # Silent fail - telemetry should never break scripts + command -v curl &>/dev/null || return 0 - # Prevent duplicate submissions - POST_UPDATE_DONE=${POST_UPDATE_DONE:-false} - [[ "$POST_UPDATE_DONE" == "true" ]] && return 0 + # Prevent duplicate submissions + POST_UPDATE_DONE=${POST_UPDATE_DONE:-false} + [[ "$POST_UPDATE_DONE" == "true" ]] && return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - local status="${1:-failed}" - local raw_exit_code="${2:-1}" - local exit_code=0 error="" pb_status error_category="" - local duration gpu_vendor gpu_passthrough + local status="${1:-failed}" + local raw_exit_code="${2:-1}" + local exit_code=0 error="" pb_status error_category="" + local duration gpu_vendor gpu_passthrough - # Get duration - duration=$(get_install_duration) + # Get duration + duration=$(get_install_duration) - # Get GPU info (if detected) - gpu_vendor="${GPU_VENDOR:-}" - gpu_passthrough="${GPU_PASSTHROUGH:-}" + # Get GPU info (if detected) + gpu_vendor="${GPU_VENDOR:-}" + gpu_passthrough="${GPU_PASSTHROUGH:-}" - # Map status to telemetry values - case "$status" in - done | success) - pb_status="success" - exit_code=0 - error="" - error_category="" - ;; - failed) - pb_status="failed" - ;; - *) - pb_status="unknown" - ;; - esac + # Map status to telemetry values + case "$status" in + done | success) + pb_status="success" + exit_code=0 + error="" + error_category="" + ;; + failed) + pb_status="failed" + ;; + *) + pb_status="unknown" + ;; + esac - # For failed/unknown status, resolve exit code and error description - if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then - if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then - exit_code="$raw_exit_code" - else - exit_code=1 - fi - local error_text="" - error_text=$(get_error_text) - local full_error - full_error=$(build_error_string "$exit_code" "$error_text") - error=$(json_escape "$full_error") - error_category=$(categorize_error "$exit_code") - [[ -z "$error" ]] && error="Unknown error" - fi + # For failed/unknown status, resolve exit code and error description + if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then + if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then + exit_code="$raw_exit_code" + else + exit_code=1 + fi + local error_text="" + error_text=$(get_error_text) + local full_error + full_error=$(build_error_string "$exit_code" "$error_text") + error=$(json_escape "$full_error") + error_category=$(categorize_error "$exit_code") + [[ -z "$error" ]] && error="Unknown error" + fi - local JSON_PAYLOAD - JSON_PAYLOAD=$( - cat </dev/null) || http_code="000" + local http_code + http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - POST_UPDATE_DONE=true - return 0 - fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + POST_UPDATE_DONE=true + return 0 + fi - # Retry with minimal payload - sleep 1 - http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ - -H "Content-Type: application/json" \ - -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-unknown}\",\"status\":\"${pb_status}\",\"exit_code\":${exit_code},\"install_duration\":${duration:-0}}" \ - -o /dev/null 2>/dev/null) || http_code="000" + # Retry with minimal payload + sleep 1 + http_code=$(curl -sS -w "%{http_code}" -m "${STATUS_TIMEOUT}" -X POST "${TELEMETRY_URL}" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"${TELEMETRY_TYPE:-lxc}\",\"nsapp\":\"${NSAPP:-unknown}\",\"status\":\"${pb_status}\",\"exit_code\":${exit_code},\"install_duration\":${duration:-0}}" \ + -o /dev/null 2>/dev/null) || http_code="000" - if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then - POST_UPDATE_DONE=true - return 0 - fi + if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then + POST_UPDATE_DONE=true + return 0 + fi - # Do NOT set POST_UPDATE_DONE=true — let EXIT trap retry + # Do NOT set POST_UPDATE_DONE=true — let EXIT trap retry } diff --git a/scripts/core/build.func b/scripts/core/build.func index 9c82bef..9f59f50 100755 --- a/scripts/core/build.func +++ b/scripts/core/build.func @@ -18,7 +18,7 @@ # # Usage: # - Sourced automatically by CT creation scripts -# - Requires core.func and error-handler.func to be loaded first +# - Requires core.func and error_handler.func to be loaded first # # ============================================================================== @@ -986,13 +986,23 @@ base_settings() { # Runtime check: Verify APT cacher is reachable if configured if [[ -n "$APT_CACHER_IP" && "$APT_CACHER" == "yes" ]]; then - if ! curl -s --connect-timeout 2 "http://${APT_CACHER_IP}:3142" >/dev/null 2>&1; then - msg_warn "APT Cacher configured but not reachable at ${APT_CACHER_IP}:3142" + local _check_host _check_port _check_url + _check_host=$(echo "$APT_CACHER_IP" | sed -e 's|https\?://||' -e 's|/.*||' | cut -d: -f1) + _check_port=$(echo "$APT_CACHER_IP" | sed -e 's|https\?://||' -e 's|/.*||' | cut -s -d: -f2) + if [[ "$APT_CACHER_IP" =~ ^https?:// ]]; then + _check_url="$APT_CACHER_IP" + _check_port="${_check_port:-80}" + else + _check_port="${_check_port:-3142}" + _check_url="http://${APT_CACHER_IP}:${_check_port}" + fi + if ! curl -s --connect-timeout 2 "${_check_url}" >/dev/null 2>&1; then + msg_warn "APT Cacher configured but not reachable at ${_check_url}" msg_custom "⚠️" "${YW}" "Disabling APT Cacher for this installation" APT_CACHER="" APT_CACHER_IP="" else - msg_ok "APT Cacher verified at ${APT_CACHER_IP}:3142" + msg_ok "APT Cacher verified at ${_check_url}" fi fi @@ -1044,7 +1054,7 @@ load_vars_file() { # Allowed var_* keys local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu var_keyctl + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain @@ -1199,6 +1209,13 @@ load_vars_file() { continue fi ;; + var_apt_cacher_ip) + # Allow: plain IP/hostname, http://host, https://host:port + if [[ -n "$var_val" ]] && ! [[ "$var_val" =~ ^(https?://)?[a-zA-Z0-9._-]+(:[0-9]+)?(/.*)?$ ]]; then + msg_warn "Invalid APT Cacher address '$var_val' in $file, ignoring" + continue + fi + ;; var_container_storage | var_template_storage) # Validate that the storage exists and is active on the current node local _storage_status @@ -1238,7 +1255,7 @@ default_var_settings() { # Allowed var_* keys (alphabetically sorted) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu var_keyctl + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage @@ -1311,9 +1328,11 @@ var_ipv6_method=none var_ssh=no # var_ssh_authorized_key= -# APT cacher (optional - with example) +# APT cacher (optional - IP or URL) # var_apt_cacher=yes # var_apt_cacher_ip=192.168.1.10 +# var_apt_cacher_ip=http://proxy.local +# var_apt_cacher_ip=https://proxy.local:443 # Features/Tags/verbosity var_fuse=no @@ -1331,6 +1350,10 @@ var_verbose=no # Security (root PW) – empty => autologin # var_pw= + +# GitHub Personal Access Token (optional – avoids API rate limits during installs) +# Create at https://github.com/settings/tokens – read-only public access is sufficient +# var_github_token=ghp_your_token_here EOF # Now choose storages (always prompt unless just one exists) @@ -1368,6 +1391,11 @@ EOF VERBOSE="no" fi + # 4) Map var_github_token → GITHUB_TOKEN (only if not already set in environment) + if [[ -z "${GITHUB_TOKEN:-}" && -n "${var_github_token:-}" ]]; then + export GITHUB_TOKEN="${var_github_token}" + fi + # 4) Apply base settings and show summary METHOD="mydefaults-global" base_settings "$VERBOSE" @@ -1400,7 +1428,7 @@ get_app_defaults_path() { if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) declare -ag VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_gateway var_hostname var_ipv6_method var_mac var_mtu var_net var_ns var_os var_pw var_ram var_tags var_tun var_unprivileged var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage @@ -2526,7 +2554,7 @@ advanced_settings() { # Ask for IP if enabled if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ --title "APT CACHER IP" \ - --inputbox "\nEnter APT Cacher-NG server IP address:" 10 58 "$_apt_cacher_ip" \ + --inputbox "\nEnter APT Cacher-NG IP or URL:\n(e.g. 192.168.1.10, http://host, https://host:443)" 12 62 "$_apt_cacher_ip" \ 3>&1 1>&2 2>&3); then _apt_cacher_ip="$result" fi @@ -3525,11 +3553,18 @@ build_container() { fi # IP (always required, default dhcp) - NET_STRING+=",ip=${NET:-dhcp}" + # Guard against the literal word "static" being passed as the IP value – + # that happens when var_net=static is exported without an actual address. + if [[ "$NET" == "static" || -z "$NET" ]]; then + msg_warn "NET value '${NET:-empty}' is not a valid IP/CIDR – falling back to dhcp" + NET="dhcp" + fi + NET_STRING+=",ip=${NET}" # Gateway if [[ -n "$GATE" ]]; then case "$GATE" in + ,gw=) ;; ,gw=*) NET_STRING+="$GATE" ;; *) NET_STRING+=",gw=$GATE" ;; esac @@ -3584,17 +3619,37 @@ build_container() { fi # Build PCT_OPTIONS as string for export - TEMP_DIR=$(mktemp -d) - pushd "$TEMP_DIR" >/dev/null - local _func_url + local _func_file + local SCRIPT_DIR + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ "$var_os" == "alpine" ]; then - _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func" + _func_file="${SCRIPT_DIR}/alpine-install.func" else - _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func" + _func_file="${SCRIPT_DIR}/install.func" + fi + if [[ -f "$_func_file" ]]; then + # Concatenate core dependencies + install func so the container has + # everything via a single `source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"`. + # install.func has `source $(dirname BASH_SOURCE)/core.func` which + # resolves to /dev/core.func when sourced from stdin — this avoids that. + local _core_file="${SCRIPT_DIR}/core.func" + local _err_file="${SCRIPT_DIR}/error-handler.func" + local _combined="" + [[ -f "$_core_file" ]] && _combined+="$(cat "$_core_file")"$'\n' + [[ -f "$_err_file" ]] && _combined+="$(cat "$_err_file")"$'\n' + _combined+="$(cat "$_func_file")" + export FUNCTIONS_FILE_PATH="$_combined" + else + local _func_url + if [ "$var_os" == "alpine" ]; then + _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func" + else + _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func" + fi + export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")" fi - export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")" if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then - msg_error "Failed to download install functions from: $_func_url" + msg_error "Failed to load install functions" exit 115 fi @@ -3998,7 +4053,7 @@ EOF # Wait for IP assignment (IPv4 or IPv6) local ip_in_lxc="" - for i in {1..20}; do + for i in {1..60}; do # Try IPv4 first ip_in_lxc=$(pct exec "$CTID" -- ip -4 addr show dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) # Fallback to IPv6 if IPv4 not available @@ -4006,11 +4061,18 @@ EOF ip_in_lxc=$(pct exec "$CTID" -- ip -6 addr show dev eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n1) fi [ -n "$ip_in_lxc" ] && break - sleep 1 + # Progressive backoff: 1s for first 20, 2s for next 20, 3s for last 20 + if [ "$i" -le 20 ]; then + sleep 1 + elif [ "$i" -le 40 ]; then + sleep 2 + else + sleep 3 + fi done if [ -z "$ip_in_lxc" ]; then - msg_error "No IP assigned to CT $CTID after 20s" + msg_error "No IP assigned to CT $CTID after 60 attempts" msg_custom "🔧" "${YW}" "Troubleshooting:" echo " • Verify bridge ${BRG} exists and has connectivity" echo " • Check if DHCP server is reachable (if using DHCP)" @@ -4300,7 +4362,13 @@ EOF # that sends "configuring" status AFTER the host already reported "failed" export CONTAINER_INSTALLING=true - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + # Use local install script if available, fallback to remote download + local _local_install="${SCRIPT_DIR}/../install/${var_install}.sh" + if [[ -f "$_local_install" ]]; then + lxc-attach -n "$CTID" -- bash -c "$(cat "$_local_install")" + else + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + fi local lxc_exit=$? unset CONTAINER_INSTALLING @@ -4623,7 +4691,7 @@ EOF if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" if pct exec "$CTID" -- bash -c " - source "$(dirname \"\${BASH_SOURCE[0]}\")/install.func" + source /tmp/scripts/core/install.func declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true " >/dev/null 2>&1; then local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) @@ -4695,7 +4763,11 @@ EOF # Re-run install script in existing container (don't destroy/recreate) set +Eeuo pipefail trap - ERR - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + if [[ -f "$_local_install" ]]; then + lxc-attach -n "$CTID" -- bash -c "$(cat "$_local_install")" + else + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + fi local apt_retry_exit=$? set -Eeuo pipefail trap 'error_handler' ERR @@ -5241,9 +5313,10 @@ create_lxc_container() { exit 205 } if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then - unset CTID - msg_error "Cannot use ID that is already in use." - exit 206 + msg_warn "Container/VM ID $CTID is already in use (detected late). Reassigning..." + CTID=$(get_valid_container_id "$((CTID + 1))") + export CTID + msg_ok "Reassigned to container ID $CTID" fi # Report installation start to API early - captures failures in storage/template/create @@ -5719,30 +5792,77 @@ create_lxc_container() { if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..." - # Check if template issue - retry with fresh download - if grep -qiE 'unable to open|corrupt|invalid' "$LOGFILE"; then - msg_info "Template may be corrupted – re-downloading" - rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 - msg_ok "Template re-downloaded" - fi + # Check if CTID collision (race condition: ID claimed between validation and creation) + if grep -qiE 'already exists|already in use' "$LOGFILE"; then + local old_ctid="$CTID" + CTID=$(get_valid_container_id "$((CTID + 1))") + export CTID + msg_warn "Container ID $old_ctid was claimed by another process. Retrying with ID $CTID" + LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" + if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then + msg_ok "Container successfully created with new ID $CTID" + else + msg_error "Container creation failed even with new ID $CTID. See $LOGFILE" + _flush_pct_log + exit 209 + fi + else + # Not a CTID collision - check if template issue and retry with fresh download + if grep -qiE 'unable to open|corrupt|invalid' "$LOGFILE"; then + msg_info "Template may be corrupted – re-downloading" + rm -f "$TEMPLATE_PATH" + pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 + msg_ok "Template re-downloaded" + fi - # Retry after repair - if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then - # Fallback to local storage if not already on local - if [[ "$TEMPLATE_STORAGE" != "local" ]]; then - msg_info "Retrying container creation with fallback to local storage" - LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" - if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then - msg_ok "Trying local storage fallback" - msg_info "Downloading template to local" - pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 - msg_ok "Template downloaded to local" + # Retry after repair + if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then + # Fallback to local storage if not already on local + if [[ "$TEMPLATE_STORAGE" != "local" ]]; then + msg_info "Retrying container creation with fallback to local storage" + LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" + if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then + msg_ok "Trying local storage fallback" + msg_info "Downloading template to local" + pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 + msg_ok "Template downloaded to local" + else + msg_ok "Trying local storage fallback" + fi + if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then + # Local fallback also failed - check for LXC stack version issue + if grep -qiE 'unsupported .* version' "$LOGFILE"; then + msg_warn "pct reported 'unsupported version' – LXC stack might be too old for this template" + offer_lxc_stack_upgrade_and_maybe_retry "yes" + rc=$? + case $rc in + 0) : ;; # success - container created, continue + 2) + msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve" + _flush_pct_log + exit 231 + ;; + 3) + msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE" + _flush_pct_log + exit 231 + ;; + esac + else + msg_error "Container creation failed. See $LOGFILE" + if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then + set -x + pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE" + set +x + fi + _flush_pct_log + exit 209 + fi + else + msg_ok "Container successfully created using local fallback." + fi else - msg_ok "Trying local storage fallback" - fi - if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then - # Local fallback also failed - check for LXC stack version issue + # Already on local storage and still failed - check LXC stack version if grep -qiE 'unsupported .* version' "$LOGFILE"; then msg_warn "pct reported 'unsupported version' – LXC stack might be too old for this template" offer_lxc_stack_upgrade_and_maybe_retry "yes" @@ -5770,50 +5890,28 @@ create_lxc_container() { _flush_pct_log exit 209 fi - else - msg_ok "Container successfully created using local fallback." fi else - # Already on local storage and still failed - check LXC stack version - if grep -qiE 'unsupported .* version' "$LOGFILE"; then - msg_warn "pct reported 'unsupported version' – LXC stack might be too old for this template" - offer_lxc_stack_upgrade_and_maybe_retry "yes" - rc=$? - case $rc in - 0) : ;; # success - container created, continue - 2) - msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve" - _flush_pct_log - exit 231 - ;; - 3) - msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE" - _flush_pct_log - exit 231 - ;; - esac - else - msg_error "Container creation failed. See $LOGFILE" - if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then - set -x - pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE" - set +x - fi - _flush_pct_log - exit 209 - fi + msg_ok "Container successfully created after template repair." fi - else - msg_ok "Container successfully created after template repair." - fi + fi # close CTID collision else-branch fi - # Verify container exists - pct list | awk '{print $1}' | grep -qx "$CTID" || { - msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE" + # Verify container exists (allow up to 10s for pmxcfs sync in clusters) + local _pct_visible=false + for _pct_check in {1..10}; do + if pct list | awk '{print $1}' | grep -qx "$CTID"; then + _pct_visible=true + break + fi + sleep 1 + done + if [[ "$_pct_visible" != true ]]; then + msg_error "Container ID $CTID not listed in 'pct list' after 10s. See $LOGFILE" + msg_custom "🔧" "${YW}" "This can happen in clusters with pmxcfs sync delays." _flush_pct_log exit 215 - } + fi # Verify config rootfs grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || { @@ -5853,6 +5951,12 @@ create_lxc_container() { # ------------------------------------------------------------------------------ description() { IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) + local script_slug script_url donate_url + + script_slug="${SCRIPT_SLUG:-${NSAPP}}" + script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')" + script_url="https://community-scripts.org/scripts/${script_slug}" + donate_url="https://community-scripts.org/donate" # Generate LXC Description DESCRIPTION=$( @@ -5865,8 +5969,14 @@ description() {

${APP} LXC

- - spend Coffee + + Sponsoring and donations + +

+ +

+ + Open script page

@@ -5959,9 +6069,9 @@ ensure_log_on_host() { # TRAP MANAGEMENT # ============================================================================== # All traps (ERR, EXIT, INT, TERM, HUP) are set by catch_errors() in -# error-handler.func — called at the top of this file after sourcing. +# error_handler.func — called at the top of this file after sourcing. # -# Do NOT set duplicate traps here. The handlers in error-handler.func +# Do NOT set duplicate traps here. The handlers in error_handler.func # (on_exit, on_interrupt, on_terminate, on_hangup, error_handler) already: # - Send telemetry via post_update_to_api / _send_abort_telemetry # - Stop orphaned containers via _stop_container_if_installing diff --git a/scripts/core/core.func b/scripts/core/core.func index 38d918a..bceb949 100644 --- a/scripts/core/core.func +++ b/scripts/core/core.func @@ -143,7 +143,7 @@ ensure_profile_loaded() { # Source all profile.d scripts to ensure PATH is complete if [[ -d /etc/profile.d ]]; then for script in /etc/profile.d/*.sh; do - [[ -r "$script" ]] && source "$script" + [[ -r "$script" ]] && source "$script" || true done fi @@ -527,29 +527,23 @@ silent() { fi if [[ $rc -ne 0 ]]; then - # Source explain_exit_code if needed - if ! declare -f explain_exit_code >/dev/null 2>&1; then - if ! source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func"; then - explain_exit_code() { echo "unknown (error-handler.func download failed)"; } - fi - fi + # Return instead of exit so that callers can use `$STD cmd || true` + # or `if $STD cmd; then ...` to handle errors gracefully. + # When no || / if is used, set -e + ERR trap will still catch it + # and error_handler() will display the error and exit. + # + # Set flag so error_handler knows to show log tail from silent's logfile + export _SILENT_FAILED_RC="$rc" + export _SILENT_FAILED_CMD="$cmd" + export _SILENT_FAILED_LINE="$caller_line" + export _SILENT_FAILED_LOG="$logfile" - local explanation - explanation="$(explain_exit_code "$rc")" - - printf "\e[?25h" - msg_error "in line ${caller_line}: exit code ${rc} (${explanation})" - msg_custom "→" "${YWB}" "${cmd}" - - if [[ -s "$logfile" ]]; then - echo -e "\n${TAB}--- Last 20 lines of log ---" - tail -n 20 "$logfile" - echo -e "${TAB}-----------------------------------" - echo -e "${TAB}📋 Full log: ${logfile}\n" - fi - - exit "$rc" + return "$rc" fi + + # Clear stale flags on success (prevents false positives if a previous + # $STD cmd || true failed and a later non-silent command triggers error_handler) + unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true } # ------------------------------------------------------------------------------ diff --git a/scripts/core/error-handler.func b/scripts/core/error-handler.func index b65ec49..d20ce55 100644 --- a/scripts/core/error-handler.func +++ b/scripts/core/error-handler.func @@ -15,7 +15,7 @@ # - Initialization function for trap setup # # Usage: -# source <(curl -fsSL .../error-handler.func) +# source <(curl -fsSL .../error_handler.func) # catch_errors # # ------------------------------------------------------------------------------ @@ -236,6 +236,16 @@ error_handler() { command="${command//\$STD/}" + # If error originated from silent(), use its captured metadata + # This provides the actual command and line number instead of "silent ..." + if [[ -n "${_SILENT_FAILED_RC:-}" ]]; then + exit_code="$_SILENT_FAILED_RC" + command="$_SILENT_FAILED_CMD" + line_number="$_SILENT_FAILED_LINE" + # Clear flags to prevent stale data on subsequent errors + unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE + fi + if [[ "$exit_code" -eq 0 ]]; then return 0 fi @@ -279,8 +289,12 @@ error_handler() { fi # Get active log file (BUILD_LOG or INSTALL_LOG) + # Prefer silent()'s logfile when available (contains the actual command output) local active_log="" - if declare -f get_active_logfile >/dev/null 2>&1; then + if [[ -n "${_SILENT_FAILED_LOG:-}" && -s "${_SILENT_FAILED_LOG}" ]]; then + active_log="$_SILENT_FAILED_LOG" + unset _SILENT_FAILED_LOG + elif declare -f get_active_logfile >/dev/null 2>&1; then active_log="$(get_active_logfile)" elif [[ -n "${SILENT_LOGFILE:-}" ]]; then active_log="$SILENT_LOGFILE" diff --git a/scripts/core/install.func b/scripts/core/install.func index 0870b20..365ef6d 100755 --- a/scripts/core/install.func +++ b/scripts/core/install.func @@ -28,20 +28,25 @@ # ============================================================================== if ! command -v curl >/dev/null 2>&1; then - printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 - apt update >/dev/null 2>&1 - apt install -y curl >/dev/null 2>&1 + printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 + apt update >/dev/null 2>&1 + apt install -y curl >/dev/null 2>&1 +fi +# Only source sibling files when running directly (not via stdin/FUNCTIONS_FILE_PATH). +# When sourced as `source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"`, BASH_SOURCE[0] is +# /dev/stdin — dirname resolves to /dev, which doesn't contain these files. +if [[ "${BASH_SOURCE[0]}" != "/dev/stdin" && "${BASH_SOURCE[0]}" != "stdin" ]]; then + source "$(dirname "${BASH_SOURCE[0]}")/core.func" + source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func" fi -source "$(dirname "${BASH_SOURCE[0]}")/core.func" -source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func" load_functions catch_errors # Persist diagnostics setting inside container (exported from build.func) # so addon scripts running later can find the user's choice if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then - mkdir -p /usr/local/community-scripts - echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics + mkdir -p /usr/local/community-scripts + echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics fi # Get LXC IP address (must be called INSIDE container, after network is up) @@ -59,15 +64,15 @@ get_lxc_ip # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set # ------------------------------------------------------------------------------ post_progress_to_api() { - command -v curl &>/dev/null || return 0 - [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 - [[ -z "${RANDOM_UUID:-}" ]] && return 0 + command -v curl &>/dev/null || return 0 + [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0 + [[ -z "${RANDOM_UUID:-}" ]] && return 0 - local progress_status="${1:-configuring}" + local progress_status="${1:-configuring}" - curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ - -H "Content-Type: application/json" \ - -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true + curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \ + -H "Content-Type: application/json" \ + -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true } # ============================================================================== @@ -82,20 +87,20 @@ post_progress_to_api() { # - Sets verbose mode via set_std_mode() # ------------------------------------------------------------------------------ verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE + set_std_mode # Set STD mode based on VERBOSE - if [ "${IPV6_METHOD:-}" = "disable" ]; then - msg_info "Disabling IPv6 (this may affect some services)" - mkdir -p /etc/sysctl.d - $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null </dev/null </dev/null) || true - fi + # Fix Debian 13 LXC template bug where / is owned by nobody + # Only attempt in privileged containers (unprivileged cannot chown /) + if [[ "$(stat -c '%U' /)" != "root" ]]; then + (chown root:root / 2>/dev/null) || true + fi - for ((i = RETRY_NUM; i > 0; i--)); do - if [ "$(hostname -I)" != "" ]; then - break - fi - echo 1>&2 -en "${CROSS}${RD} No Network! " - sleep $RETRY_EVERY - done - if [ "$(hostname -I)" = "" ]; then - echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" - echo -e "${NETWORK}Check Network Settings" - exit 121 - fi - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - systemctl disable -q --now systemd-networkd-wait-online.service - msg_ok "Set up Container OS" - #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" - msg_ok "Network Connected: ${BL}$(hostname -I)" - post_progress_to_api + for ((i = RETRY_NUM; i > 0; i--)); do + if [ "$(hostname -I)" != "" ]; then + break + fi + echo 1>&2 -en "${CROSS}${RD} No Network! " + sleep $RETRY_EVERY + done + if [ "$(hostname -I)" = "" ]; then + echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" + echo -e "${NETWORK}Check Network Settings" + exit 121 + fi + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + systemctl disable -q --now systemd-networkd-wait-online.service + msg_ok "Set up Container OS" + #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" + msg_ok "Network Connected: ${BL}$(hostname -I)" + post_progress_to_api } # ------------------------------------------------------------------------------ @@ -148,62 +153,62 @@ setting_up_container() { # - Uses fatal() on DNS resolution failure for critical hosts # ------------------------------------------------------------------------------ network_check() { - set +e - trap - ERR - ipv4_connected=false - ipv6_connected=false - sleep 1 + set +e + trap - ERR + ipv4_connected=false + ipv6_connected=false + sleep 1 - # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. - if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then - msg_ok "IPv4 Internet Connected" - ipv4_connected=true - else - msg_error "IPv4 Internet Not Connected" - fi + # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. + if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then + msg_ok "IPv4 Internet Connected" + ipv4_connected=true + else + msg_error "IPv4 Internet Not Connected" + fi - # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. - if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then - msg_ok "IPv6 Internet Connected" - ipv6_connected=true - else - msg_error "IPv6 Internet Not Connected" - fi + # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. + if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then + msg_ok "IPv6 Internet Connected" + ipv6_connected=true + else + msg_error "IPv6 Internet Not Connected" + fi - # If both IPv4 and IPv6 checks fail, prompt the user - if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then - read -r -p "No Internet detected, would you like to continue anyway? " prompt - if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then - echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" - else - echo -e "${NETWORK}Check Network Settings" - exit 122 - fi - fi + # If both IPv4 and IPv6 checks fail, prompt the user + if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then + read -r -p "No Internet detected, would you like to continue anyway? " prompt + if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then + echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" + else + echo -e "${NETWORK}Check Network Settings" + exit 122 + fi + fi - # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) - GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") - GIT_STATUS="Git DNS:" - DNS_FAILED=false + # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" + DNS_FAILED=false - for HOST in "${GIT_HOSTS[@]}"; do - RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) - if [[ -z "$RESOLVEDIP" ]]; then - GIT_STATUS+="$HOST:($DNSFAIL)" - DNS_FAILED=true - else - GIT_STATUS+=" $HOST:($DNSOK)" - fi - done + for HOST in "${GIT_HOSTS[@]}"; do + RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) + if [[ -z "$RESOLVEDIP" ]]; then + GIT_STATUS+="$HOST:($DNSFAIL)" + DNS_FAILED=true + else + GIT_STATUS+=" $HOST:($DNSOK)" + fi + done - if [[ "$DNS_FAILED" == true ]]; then - fatal "$GIT_STATUS" - else - msg_ok "$GIT_STATUS" - fi + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + fi - set -e - trap 'error_handler' ERR + set -e + trap 'error_handler' ERR } # ============================================================================== @@ -220,161 +225,161 @@ network_check() { # - Detects hash mismatch, SSL errors, and generic apt failures # ------------------------------------------------------------------------------ apt_update_safe() { - if $STD apt-get update; then - return 0 - fi + if $STD apt-get update; then + return 0 + fi - local failed_mirror - failed_mirror=$(grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null || echo "unknown") - msg_warn "apt-get update failed (${failed_mirror}), trying alternate mirrors..." + local failed_mirror + failed_mirror=$(grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null || echo "unknown") + msg_warn "apt-get update failed (${failed_mirror}), trying alternate mirrors..." - local distro - distro=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") + local distro + distro=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") - local eu_mirrors us_mirrors ap_mirrors - if [[ "$distro" == "ubuntu" ]]; then - eu_mirrors="de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de" - us_mirrors="us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu" - ap_mirrors="au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au" - else - eu_mirrors="ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net" - us_mirrors="ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com" - ap_mirrors="ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in" - fi + local eu_mirrors us_mirrors ap_mirrors + if [[ "$distro" == "ubuntu" ]]; then + eu_mirrors="de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de" + us_mirrors="us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu" + ap_mirrors="au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au" + else + eu_mirrors="ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net" + us_mirrors="ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com" + ap_mirrors="ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in" + fi - local tz regional others - tz=$(cat /etc/timezone 2>/dev/null || echo "UTC") - case "$tz" in - Europe/* | Arctic/*) - regional="$eu_mirrors" - others="$us_mirrors $ap_mirrors" - ;; - America/*) - regional="$us_mirrors" - others="$eu_mirrors $ap_mirrors" - ;; - Asia/* | Australia/* | Pacific/*) - regional="$ap_mirrors" - others="$eu_mirrors $us_mirrors" - ;; - *) - regional="" - others="$eu_mirrors $us_mirrors $ap_mirrors" - ;; - esac + local tz regional others + tz=$(cat /etc/timezone 2>/dev/null || echo "UTC") + case "$tz" in + Europe/* | Arctic/*) + regional="$eu_mirrors" + others="$us_mirrors $ap_mirrors" + ;; + America/*) + regional="$us_mirrors" + others="$eu_mirrors $ap_mirrors" + ;; + Asia/* | Australia/* | Pacific/*) + regional="$ap_mirrors" + others="$eu_mirrors $us_mirrors" + ;; + *) + regional="" + others="$eu_mirrors $us_mirrors $ap_mirrors" + ;; + esac - echo 'Acquire::By-Hash "no";' >/etc/apt/apt.conf.d/99no-by-hash + echo 'Acquire::By-Hash "no";' >/etc/apt/apt.conf.d/99no-by-hash - _try_apt_mirror() { - local m=$1 - for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do - [[ -f "$src" ]] && sed -i "s|URIs: http[s]*://[^/]*/|URIs: http://${m}/|g; s|deb http[s]*://[^/]*/|deb http://${m}/|g" "$src" - done - rm -rf /var/lib/apt/lists/* - local out - out=$(apt-get update 2>&1) - if echo "$out" | grep -qi "hashsum\|hash sum"; then - msg_warn "Mirror ${m} failed (hash mismatch)" - return 1 - elif echo "$out" | grep -qi "SSL\|certificate"; then - msg_warn "Mirror ${m} failed (SSL/certificate error)" - return 1 - elif echo "$out" | grep -q "^E:"; then - msg_warn "Mirror ${m} failed (apt-get update error)" - return 1 - else - msg_ok "CDN set to ${m}: tests passed" - return 0 - fi - } + _try_apt_mirror() { + local m=$1 + for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do + [[ -f "$src" ]] && sed -i "s|URIs: http[s]*://[^/]*/|URIs: http://${m}/|g; s|deb http[s]*://[^/]*/|deb http://${m}/|g" "$src" + done + rm -rf /var/lib/apt/lists/* + local out + out=$(apt-get update 2>&1) + if echo "$out" | grep -qi "hashsum\|hash sum"; then + msg_warn "Mirror ${m} failed (hash mismatch)" + return 1 + elif echo "$out" | grep -qi "SSL\|certificate"; then + msg_warn "Mirror ${m} failed (SSL/certificate error)" + return 1 + elif echo "$out" | grep -q "^E:"; then + msg_warn "Mirror ${m} failed (apt-get update error)" + return 1 + else + msg_ok "CDN set to ${m}: tests passed" + return 0 + fi + } - _scan_reachable() { - local result="" - for m in $1; do - if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then - result="$result $m" - fi - done - echo "$result" | xargs - } + _scan_reachable() { + local result="" + for m in $1; do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + result="$result $m" + fi + done + echo "$result" | xargs + } - local apt_ok=false + local apt_ok=false - # Phase 1: Scan global mirrors first (independent of local CDN issues) - local others_ok - others_ok=$(_scan_reachable "$others") - local others_pick - others_pick=$(printf '%s\n' $others_ok | shuf | head -3 | xargs) + # Phase 1: Scan global mirrors first (independent of local CDN issues) + local others_ok + others_ok=$(_scan_reachable "$others") + local others_pick + others_pick=$(printf '%s\n' $others_ok | shuf | head -3 | xargs) - for mirror in $others_pick; do - msg_custom "${INFO}" "${YW}" "Attempting mirror: ${mirror}" - if _try_apt_mirror "$mirror"; then - apt_ok=true - break - fi - done + for mirror in $others_pick; do + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${mirror}" + if _try_apt_mirror "$mirror"; then + apt_ok=true + break + fi + done - # Phase 2: Try primary mirror - if [[ "$apt_ok" != true ]]; then - local primary - if [[ "$distro" == "ubuntu" ]]; then - primary="archive.ubuntu.com" - else - primary="ftp.debian.org" - fi - if timeout 2 bash -c "echo >/dev/tcp/$primary/80" 2>/dev/null; then - msg_custom "${INFO}" "${YW}" "Attempting mirror: ${primary}" - if _try_apt_mirror "$primary"; then - apt_ok=true - fi - fi - fi + # Phase 2: Try primary mirror + if [[ "$apt_ok" != true ]]; then + local primary + if [[ "$distro" == "ubuntu" ]]; then + primary="archive.ubuntu.com" + else + primary="ftp.debian.org" + fi + if timeout 2 bash -c "echo >/dev/tcp/$primary/80" 2>/dev/null; then + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${primary}" + if _try_apt_mirror "$primary"; then + apt_ok=true + fi + fi + fi - # Phase 3: Fall back to regional mirrors - if [[ "$apt_ok" != true ]]; then - local regional_ok - regional_ok=$(_scan_reachable "$regional") - local regional_pick - regional_pick=$(printf '%s\n' $regional_ok | shuf | head -3 | xargs) + # Phase 3: Fall back to regional mirrors + if [[ "$apt_ok" != true ]]; then + local regional_ok + regional_ok=$(_scan_reachable "$regional") + local regional_pick + regional_pick=$(printf '%s\n' $regional_ok | shuf | head -3 | xargs) - for mirror in $regional_pick; do - msg_custom "${INFO}" "${YW}" "Attempting mirror: ${mirror}" - if _try_apt_mirror "$mirror"; then - apt_ok=true - break - fi - done - fi + for mirror in $regional_pick; do + msg_custom "${INFO}" "${YW}" "Attempting mirror: ${mirror}" + if _try_apt_mirror "$mirror"; then + apt_ok=true + break + fi + done + fi - # Phase 4: All auto mirrors failed, prompt user - if [[ "$apt_ok" != true ]]; then - msg_warn "Multiple mirrors failed (possible CDN synchronization issue)." - if [[ "$distro" == "ubuntu" ]]; then - msg_warn "Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors" - else - msg_warn "Find Debian mirrors at: https://www.debian.org/mirror/list" - fi - local custom_mirror - while true; do - read -rp " Enter a mirror hostname (or 'skip' to abort): " custom_mirror /etc/apt/apt.conf.d/00aptproxy - cat </usr/local/bin/apt-proxy-detect.sh + msg_info "Updating Container OS" + if [[ "$CACHER" == "yes" ]]; then + echo 'Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";' >/etc/apt/apt.conf.d/00aptproxy + local _proxy_raw="${CACHER_IP}" + local _proxy_host _proxy_port _proxy_url + # Parse host and port from URL or plain IP/hostname + _proxy_host=$(echo "$_proxy_raw" | sed -e 's|https\?://||' -e 's|/.*||' | cut -d: -f1) + _proxy_port=$(echo "$_proxy_raw" | sed -e 's|https\?://||' -e 's|/.*||' | cut -s -d: -f2) + if [[ "$_proxy_raw" =~ ^https?:// ]]; then + # Full URL provided — use as-is for proxy output, extract port for nc check + _proxy_url="$_proxy_raw" + _proxy_port="${_proxy_port:-80}" + else + # Legacy: plain IP or hostname — default to http + port 3142 + _proxy_port="${_proxy_port:-3142}" + _proxy_url="http://${_proxy_raw}:${_proxy_port}" + fi + cat </usr/local/bin/apt-proxy-detect.sh #!/bin/bash -if nc -w1 -z "${CACHER_IP}" 3142; then - echo -n "http://${CACHER_IP}:3142" +if nc -w1 -z "${_proxy_host}" ${_proxy_port}; then + echo -n "${_proxy_url}" else echo -n "DIRECT" fi EOF - chmod +x /usr/local/bin/apt-proxy-detect.sh - fi - apt_update_safe - $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - msg_ok "Updated Container OS" - post_progress_to_api + chmod +x /usr/local/bin/apt-proxy-detect.sh + fi + apt_update_safe + $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + msg_ok "Updated Container OS" + post_progress_to_api - source "$(dirname "${BASH_SOURCE[0]}")/tools.func" + local tools_content + local _tools_local + _tools_local="$(dirname "${BASH_SOURCE[0]}")/tools.func" + if [[ -f "$_tools_local" ]]; then + tools_content=$(cat "$_tools_local") || { + msg_error "Failed to load tools.func from local" + exit 115 + } + else + tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { + msg_error "Failed to download tools.func" + exit 115 + } + fi + source /dev/stdin <<<"$tools_content" + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then + msg_error "tools.func loaded but incomplete — missing expected functions" + exit 115 + fi } # ============================================================================== @@ -428,26 +465,26 @@ EOF # - Configures TERM environment variable for better terminal support # ------------------------------------------------------------------------------ motd_ssh() { - # Set terminal to 256-color mode - grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc + # Set terminal to 256-color mode + grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc - PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" - echo "echo -e \"\"" >"$PROFILE_FILE" - echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" - echo "echo \"\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" + PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" + echo "echo -e \"\"" >"$PROFILE_FILE" + echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" + echo "echo \"\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" - # Disable default MOTD scripts - chmod -x /etc/update-motd.d/* + # Disable default MOTD scripts + chmod -x /etc/update-motd.d/* - if [[ "${SSH_ROOT}" == "yes" ]]; then - sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config - systemctl restart sshd - fi - post_progress_to_api + if [[ "${SSH_ROOT}" == "yes" ]]; then + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + systemctl restart sshd + fi + post_progress_to_api } # ============================================================================== @@ -464,27 +501,27 @@ motd_ssh() { # - Sets proper permissions on SSH directories and key files # ------------------------------------------------------------------------------ customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" - GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" - mkdir -p "$(dirname "$GETTY_OVERRIDE")" - cat <"$GETTY_OVERRIDE" + if [[ "$PASSWORD" == "" ]]; then + msg_info "Customizing Container" + GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" + mkdir -p "$(dirname "$GETTY_OVERRIDE")" + cat <"$GETTY_OVERRIDE" [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF - systemctl daemon-reload - systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')" - msg_ok "Customized Container" - fi - echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update - chmod +x /usr/bin/update + systemctl daemon-reload + systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')" + msg_ok "Customized Container" + fi + echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + chmod +x /usr/bin/update - if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then - mkdir -p /root/.ssh - echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys - chmod 700 /root/.ssh - chmod 600 /root/.ssh/authorized_keys - fi - post_progress_to_api + if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then + mkdir -p /root/.ssh + echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys + chmod 700 /root/.ssh + chmod 600 /root/.ssh/authorized_keys + fi + post_progress_to_api } diff --git a/scripts/core/tools.func b/scripts/core/tools.func index 52c6cb3..eb64a9a 100644 --- a/scripts/core/tools.func +++ b/scripts/core/tools.func @@ -1117,15 +1117,90 @@ is_package_installed() { fi } +# ------------------------------------------------------------------------------ +# validate_github_token() +# Checks a GitHub token via the /user endpoint. +# Prints a status message and returns: +# 0 - token is valid +# 1 - token is invalid / expired (HTTP 401) +# 2 - token has no public repo scope (HTTP 200 but missing scope) +# 3 - network/API error +# Also reports expiry date if the token carries an x-oauth-expiry header. +# ------------------------------------------------------------------------------ +validate_github_token() { + local token="${1:-${GITHUB_TOKEN:-}}" + [[ -z "$token" ]] && return 3 + + local response headers http_code expiry_date scopes + headers=$(mktemp) + response=$(curl -sSL -w "%{http_code}" \ + -D "$headers" \ + -o /dev/null \ + -H "Authorization: Bearer $token" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/user" 2>/dev/null) || { + rm -f "$headers" + return 3 + } + http_code="$response" + + # Read expiry header (fine-grained PATs carry this) + expiry_date=$(grep -i '^github-authentication-token-expiration:' "$headers" | + sed 's/.*: *//' | tr -d '\r\n' || true) + # Read token scopes (classic PATs) + scopes=$(grep -i '^x-oauth-scopes:' "$headers" | + sed 's/.*: *//' | tr -d '\r\n' || true) + rm -f "$headers" + + case "$http_code" in + 200) + if [[ -n "$expiry_date" ]]; then + msg_ok "GitHub token is valid (expires: $expiry_date)." + else + msg_ok "GitHub token is valid (no expiry / fine-grained PAT)." + fi + # Warn if classic PAT has no public_repo scope + if [[ -n "$scopes" && "$scopes" != *"public_repo"* && "$scopes" != *"repo"* ]]; then + msg_warn "Token has no 'public_repo' scope - private repos and some release APIs may fail." + return 2 + fi + return 0 + ;; + 401) + msg_error "GitHub token is invalid or expired (HTTP 401)." + return 1 + ;; + *) + msg_warn "GitHub token validation returned HTTP $http_code - treating as valid." + return 0 + ;; + esac +} + # ------------------------------------------------------------------------------ # Prompt user to enter a GitHub Personal Access Token (PAT) interactively # Returns 0 if a valid token was provided, 1 otherwise # ------------------------------------------------------------------------------ prompt_for_github_token() { if [[ ! -t 0 ]]; then + # Non-interactive: pick up var_github_token if set (from default.vars / app.vars / env) + if [[ -z "${GITHUB_TOKEN:-}" && -n "${var_github_token:-}" ]]; then + export GITHUB_TOKEN="${var_github_token}" + msg_ok "GitHub token loaded from var_github_token." + return 0 + fi return 1 fi + # Prefer var_github_token when already set and no interactive override needed + if [[ -z "${GITHUB_TOKEN:-}" && -n "${var_github_token:-}" ]]; then + export GITHUB_TOKEN="${var_github_token}" + msg_ok "GitHub token loaded from var_github_token." + validate_github_token || true + return 0 + fi + local reply read -rp "${TAB}Would you like to enter a GitHub Personal Access Token (PAT)? [y/N]: " reply reply="${reply:-n}" @@ -1147,10 +1222,16 @@ prompt_for_github_token() { msg_warn "Token must not contain spaces. Please try again." continue fi - break + # Validate before accepting + export GITHUB_TOKEN="$token" + if validate_github_token "$token"; then + break + else + msg_warn "Please enter a valid token, or press Ctrl+C to abort." + unset GITHUB_TOKEN + fi done - export GITHUB_TOKEN="$token" msg_ok "GitHub token has been set." return 0 } @@ -2860,7 +2941,7 @@ function fetch_and_deploy_codeberg_release() { while ((attempt < ${#api_timeouts[@]})); do resp=$(curl --connect-timeout 10 --max-time "${api_timeouts[$attempt]}" -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break - ((attempt++)) + attempt=$((attempt + 1)) if ((attempt < ${#api_timeouts[@]})); then msg_warn "API request timed out after ${api_timeouts[$((attempt - 1))]}s, retrying... (attempt $((attempt + 1))/${#api_timeouts[@]})" fi @@ -3370,7 +3451,8 @@ function fetch_and_deploy_gh_release() { if prompt_for_github_token; then header=(-H "Authorization: token $GITHUB_TOKEN") retry_delay=2 - attempt=0 + attempt=1 + continue fi fi else @@ -4458,7 +4540,7 @@ function setup_hwaccel() { local in_ct="${CTTYPE:-0}" - # ═════════════════════════════════════════════════════�����═════════════════════ + # ═══════════════════════════════════════════════════════════════════════════ # Process Selected GPUs # ═══════════════════════════════════════════════════════════════════════════ for idx in "${SELECTED_INDICES[@]}"; do diff --git a/scripts/tools/addon/arcane.sh b/scripts/tools/addon/arcane.sh new file mode 100644 index 0000000..b63b830 --- /dev/null +++ b/scripts/tools/addon/arcane.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: summoningpixels +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/getarcaneapp/arcane +if ! command -v curl &>/dev/null; then + printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 + apt-get update >/dev/null 2>&1 + apt-get install -y curl >/dev/null 2>&1 +fi +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "arcane" "addon" + +# Enable error handling +set -Eeuo pipefail +trap 'error_handler' ERR + +# ============================================================================== +# CONFIGURATION +# ============================================================================== +APP="Arcane" +APP_TYPE="addon" +INSTALL_PATH="/opt/arcane" +COMPOSE_FILE="${INSTALL_PATH}/compose.yaml" +ENV_FILE="${INSTALL_PATH}/.env" +DEFAULT_PORT=3552 + +# Initialize all core functions (colors, formatting, icons, STD mode) +load_functions + +# ============================================================================== +# HEADER +# ============================================================================== +function header_info { + clear + cat <<"EOF" + ___ ____ _________ _ ________ + / | / __ \/ ____/ | / | / / ____/ + / /| | / /_/ / / / /| | / |/ / __/ + / ___ |/ _, _/ /___/ ___ |/ /| / /___ +/_/ |_/_/ |_|\____/_/ |_/_/ |_/_____/ + +EOF +} + +# ============================================================================== +# UNINSTALL +# ============================================================================== +function uninstall() { + msg_info "Uninstalling ${APP}" + + if [[ -f "$COMPOSE_FILE" ]]; then + msg_info "Stopping and removing Docker containers" + cd "$INSTALL_PATH" + $STD docker compose down --volumes --remove-orphans + msg_ok "Stopped and removed Docker containers" + fi + + rm -rf "$INSTALL_PATH" + rm -f "/usr/local/bin/update_arcane" + msg_ok "${APP} has been uninstalled" +} + +# ============================================================================== +# UPDATE +# ============================================================================== +function update() { + msg_info "Pulling latest ${APP} image" + cd "$INSTALL_PATH" + $STD docker compose pull + msg_ok "Pulled latest image" + + msg_info "Restarting ${APP}" + $STD docker compose up -d --remove-orphans + msg_ok "Restarted ${APP}" + + msg_ok "Updated successfully" + exit +} + +# ============================================================================== +# CHECK DOCKER +# ============================================================================== +function check_docker() { + if ! command -v docker &>/dev/null; then + msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting." + exit 10 + fi + if ! docker compose version &>/dev/null; then + msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting." + exit 10 + fi + msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available" +} + +# ============================================================================== +# INSTALL +# ============================================================================== +function install() { + check_docker + + msg_info "Creating install directory" + mkdir -p "$INSTALL_PATH" + msg_ok "Created ${INSTALL_PATH}" + + # Generate secrets and config values + local ENCRYPTION_KEY JWT_SECRET PROJ_DIR + ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32) + JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32) + PROJ_DIR="/etc/arcane/projects" + + msg_info "Creating stacks directory" + mkdir -p "$PROJ_DIR" + msg_ok "Created ${PROJ_DIR}" + + msg_info "Downloading Docker Compose file" + curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/docker/examples/compose.basic.yaml" -o "$COMPOSE_FILE" + msg_ok "Downloaded Docker Compose file" + + msg_info "Downloading .env file" + curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/.env.example" -o "$ENV_FILE" + chmod 600 "$ENV_FILE" + msg_ok "Downloaded .env file" + + msg_info "Configuring compose and env files" + sed -i '/^[[:space:]]*#/!s|/host/path/to/projects|'"$PROJ_DIR"'|g' "$COMPOSE_FILE" + sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY='"$ENCRYPTION_KEY"'|g' "$COMPOSE_FILE" + sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|JWT_SECRET='"$JWT_SECRET"'|g' "$COMPOSE_FILE" + sed -i '/^[[:space:]]*#/!s|APP_URL=.*|APP_URL=http://localhost:'"$DEFAULT_PORT"'|g' "$ENV_FILE" + sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|#&|g' "$ENV_FILE" + sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|#&|g' "$ENV_FILE" + msg_ok "Configured compose and env files" + + msg_info "Starting ${APP}" + cd "$INSTALL_PATH" + $STD docker compose up -d + msg_ok "Started ${APP}" + + # Create update script + msg_info "Creating update script" + cat <<'UPDATEEOF' >/usr/local/bin/update_arcane +#!/usr/bin/env bash +# Arcane Update Script +type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/arcane.sh)" +UPDATEEOF + chmod +x /usr/local/bin/update_arcane + msg_ok "Created update script (/usr/local/bin/update_arcane)" + + echo "" + msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}" + echo "" + echo -e "Arcane Credentials" + echo -e "==================" + echo -e "User: arcane" + echo -e "Password: arcane-admin" + echo "" + msg_warn "On first access, you'll be prompted to change your password." +} + +# ============================================================================== +# MAIN +# ============================================================================== + +# Handle type=update (called from update script) +if [[ "${type:-}" == "update" ]]; then + header_info + if [[ -f "$COMPOSE_FILE" ]]; then + update + else + msg_error "${APP} is not installed. Nothing to update." + exit 233 + fi + exit 0 +fi + +header_info +get_lxc_ip + +# Check if already installed +if [[ -f "$COMPOSE_FILE" ]]; then + msg_warn "${APP} is already installed." + echo "" + + echo -n "${TAB}Uninstall ${APP}? (y/N): " + read -r uninstall_prompt + if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then + uninstall + exit 0 + fi + + echo -n "${TAB}Update ${APP}? (y/N): " + read -r update_prompt + if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then + update + exit 0 + fi + + msg_warn "No action selected. Exiting." + exit 0 +fi + +# Fresh installation +msg_warn "${APP} is not installed." +echo "" +echo -e "${TAB}${INFO} This will install:" +echo -e "${TAB} - Arcane (via Docker Compose)" +echo "" + +echo -n "${TAB}Install ${APP}? (y/N): " +read -r install_prompt +if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then + install +else + msg_warn "Installation cancelled. Exiting." + exit 0 +fi diff --git a/server.js b/server.js index edfb108..2926b47 100644 --- a/server.js +++ b/server.js @@ -79,6 +79,7 @@ const handle = app.getRequestHandler(); * @property {boolean} [isShell] * @property {boolean} [isBackup] * @property {boolean} [isClone] + * @property {boolean} [executeInContainer] * @property {string} [containerId] * @property {string} [storage] * @property {string} [backupStorage] @@ -317,7 +318,7 @@ class ScriptExecutionHandler { * @param {WebSocketMessage} message */ async handleMessage(ws, message) { - const { action, scriptPath, executionId, input, mode, server, isUpdate, isShell, isBackup, isClone, containerId, storage, backupStorage, cloneCount, hostnames, containerType, envVars } = message; + const { action, scriptPath, executionId, input, mode, server, isUpdate, isShell, isBackup, isClone, executeInContainer, containerId, storage, backupStorage, cloneCount, hostnames, containerType, envVars } = message; switch (action) { case 'start': @@ -332,9 +333,11 @@ class ScriptExecutionHandler { } else if (isBackup && containerId && storage) { await this.startBackupExecution(ws, containerId, executionId, storage, mode, resolved); } else if (isUpdate && containerId) { - await this.startUpdateExecution(ws, containerId, executionId, mode, resolved, backupStorage); + await this.startUpdateExecution(ws, containerId, executionId, mode, resolved, backupStorage, envVars); } else if (isShell && containerId) { await this.startShellExecution(ws, containerId, executionId, mode, resolved, containerType); + } else if (executeInContainer && containerId) { + await this.startInContainerScriptExecution(ws, scriptPath, executionId, mode, resolved, envVars, containerId, containerType ?? 'lxc'); } else { await this.startScriptExecution(ws, scriptPath, executionId, mode, resolved, envVars); } @@ -552,6 +555,171 @@ class ScriptExecutionHandler { } } + + /** + * Execute a script INSIDE a container via `pct exec` (LXC) or `qm guest exec` (VM). + * For SSH mode the scripts folder is already transferred to /tmp/scripts on the PVE host by + * startSSHScriptExecution; we re-use that mechanism and then run the script inside the CT. + * For local mode we pipe the script through `pct exec`. + * + * @param {ExtendedWebSocket} ws + * @param {string} scriptPath + * @param {string} executionId + * @param {string} mode + * @param {ServerInfo|null} server + * @param {Object} envVars + * @param {string} containerId + * @param {'lxc'|'vm'} containerType + */ + async startInContainerScriptExecution(ws, scriptPath, executionId, mode = 'local', server = null, envVars = {}, containerId, containerType = 'lxc') { + /** @type {number|null} */ + let installationId = null; + try { + if (this.activeExecutions.has(executionId)) { + this.sendMessage(ws, { type: 'error', data: 'Script execution already running', timestamp: Date.now() }); + return; + } + + const scriptName = scriptPath.split('/').pop() ?? scriptPath.split('\\').pop() ?? 'Unknown Script'; + const serverId = server ? (server.id ?? null) : null; + installationId = await this.createInstallationRecord(scriptName, scriptPath, mode, serverId); + + // Build env-var export prefix + const envExports = Object.entries(envVars ?? {}) + .map(([k, v]) => `export ${k}=${JSON.stringify(String(v))}`) + .join('; '); + const envPrefix = envExports ? `${envExports}; ` : ''; + + if (mode === 'ssh' && server) { + // Transfer scripts folder to PVE host, then exec inside container + const sshService = getSSHExecutionService(); + this.sendMessage(ws, { type: 'start', data: `Connecting to ${server.ip}…`, timestamp: Date.now() }); + + const relScript = scriptPath.replace(/^scripts[/\\]/, ''); + const remoteScript = `/tmp/scripts/${relScript}`; + + // Transfer scripts folder silently — suppress verbose rsync file listing + this.sendMessage(ws, { type: 'output', data: 'Syncing scripts…\r\n', timestamp: Date.now() }); + await sshService.transferScriptsFolder(server, + () => {}, // suppress rsync stdout (verbose file listing) + (/** @type {string} */ err) => { + // Ignore harmless SSH host-key / known-hosts notices from rsync stderr + if (!err.includes('Warning:') && !err.includes('Permanently added')) { + this.sendMessage(ws, { type: 'error', data: err, timestamp: Date.now() }); + } + } + ); + + let inContainerCmd; + + if (containerType === 'lxc') { + // For LXC we must copy the script from host (/tmp/scripts/...) into the container first. + // Otherwise pct exec tries to run a host path that does not exist inside the CT. + const remoteTarget = `/tmp/${scriptName}`; + const pushCmd = `pct push ${containerId} ${remoteScript} ${remoteTarget}`; + + // Run pct push silently (no terminal output needed for this bookkeeping step) + await new Promise((resolve, reject) => { + sshService.executeCommand(server, pushCmd, + () => {}, // suppress push output + () => {}, // suppress push stderr + (/** @type {number} */ code) => { + if (code === 0) resolve(true); + else reject(new Error(`pct push failed with exit code ${code}`)); + }, + ); + }); + + inContainerCmd = `pct exec ${containerId} -- bash -c "chmod +x ${remoteTarget}; ${envPrefix}bash ${remoteTarget}"`; + } else { + // VM execution currently relies on guest exec and assumes script path exists in guest context. + inContainerCmd = `qm guest exec ${containerId} -- bash -c "${envPrefix}bash ${remoteScript}"`; + } + + this.activeExecutions.set(executionId, { process: null, ws, installationId, outputBuffer: '' }); + + // executeCommand resolves immediately with the PTY process — store it so + // interactive input (y/n prompts etc.) can be forwarded via sendInputToProcess. + const execResult = await sshService.executeCommand(server, inContainerCmd, + (/** @type {string} */ data) => { + const execution = this.activeExecutions.get(executionId); + if (execution) execution.outputBuffer += data; + this.sendMessage(ws, { type: 'output', data, timestamp: Date.now() }); + }, + (/** @type {string} */ err) => { + this.sendMessage(ws, { type: 'error', data: err, timestamp: Date.now() }); + }, + async (/** @type {number} */ exitCode) => { + const execution = this.activeExecutions.get(executionId); + if (installationId && execution) { + await this.updateInstallationRecord(installationId, { + status: exitCode === 0 ? 'success' : 'failed', + output_log: execution.outputBuffer + }); + } + this.sendMessage(ws, { type: 'end', data: `Finished with code: ${exitCode}`, timestamp: Date.now() }); + this.activeExecutions.delete(executionId); + } + ); + + // Attach the real PTY process so keyboard input can reach interactive prompts + const storedExec = this.activeExecutions.get(executionId); + if (storedExec && execResult && /** @type {any} */(execResult).process) { + storedExec.process = /** @type {any} */(execResult).process; + } + return; + } + + // Local mode: pipe the script content into `pct exec -- bash` + const scriptsDir = join(process.cwd(), 'scripts'); + const resolvedPath = resolve(scriptPath); + if (!resolvedPath.startsWith(resolve(scriptsDir))) { + this.sendMessage(ws, { type: 'error', data: 'Script path outside scripts directory', timestamp: Date.now() }); + if (installationId) await this.updateInstallationRecord(installationId, { status: 'failed' }); + return; + } + + const bashCmd = `${envPrefix}bash ${resolvedPath}`; + const args = containerType === 'lxc' + ? ['exec', containerId, '--', 'bash', '-c', bashCmd] + : ['guest', 'exec', containerId, '--', 'bash', '-c', bashCmd]; + const cmd = containerType === 'lxc' ? 'pct' : 'qm'; + + const childProcess = ptySpawn(cmd, args, { + cwd: scriptsDir, + name: 'xterm-256color', + cols: 80, + rows: 24, + env: { ...process.env, TERM: 'xterm-256color' } + }); + + this.activeExecutions.set(executionId, { process: childProcess, ws, installationId, outputBuffer: '' }); + this.sendMessage(ws, { type: 'start', data: `Executing inside container ${containerId}…`, timestamp: Date.now() }); + + childProcess.onData((data) => { + const execution = this.activeExecutions.get(executionId); + if (execution) execution.outputBuffer += data; + this.sendMessage(ws, { type: 'output', data, timestamp: Date.now() }); + }); + + childProcess.onExit(async (e) => { + const execution = this.activeExecutions.get(executionId); + if (installationId && execution) { + await this.updateInstallationRecord(installationId, { + status: e.exitCode === 0 ? 'success' : 'failed', + output_log: execution.outputBuffer + }); + } + this.sendMessage(ws, { type: 'end', data: `Script finished with code: ${e.exitCode}`, timestamp: Date.now() }); + this.activeExecutions.delete(executionId); + }); + + } catch (error) { + this.sendMessage(ws, { type: 'error', data: `Failed to start in-container script: ${error instanceof Error ? error.message : String(error)}`, timestamp: Date.now() }); + if (installationId) await this.updateInstallationRecord(installationId, { status: 'failed' }); + } + } + /** * Start SSH script execution * @param {ExtendedWebSocket} ws @@ -1303,7 +1471,7 @@ class ScriptExecutionHandler { * @param {ServerInfo|undefined} server * @param {string} [backupStorage] - Optional storage to backup to before update */ - async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = undefined, backupStorage = undefined) { + async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = undefined, backupStorage = undefined, envVars = {}) { try { // If backup storage is provided, run backup first if (backupStorage && mode === 'ssh' && server) { @@ -1364,9 +1532,9 @@ class ScriptExecutionHandler { }); if (mode === 'ssh' && server) { - await this.startSSHUpdateExecution(ws, containerId, executionId, server); + await this.startSSHUpdateExecution(ws, containerId, executionId, server, envVars); } else { - await this.startLocalUpdateExecution(ws, containerId, executionId); + await this.startLocalUpdateExecution(ws, containerId, executionId, envVars); } } catch (error) { @@ -1384,7 +1552,7 @@ class ScriptExecutionHandler { * @param {string} containerId * @param {string} executionId */ - async startLocalUpdateExecution(ws, containerId, executionId) { + async startLocalUpdateExecution(ws, containerId, executionId, envVars = {}) { const { spawn } = await import('node-pty'); // Create a shell process that will run pct enter and then update @@ -1411,9 +1579,24 @@ class ScriptExecutionHandler { }); }); + // Build env export commands (e.g. for PHS_SILENT=1) + const envExports = Object.entries(envVars) + .filter(([key]) => key.startsWith('PHS_') || key.startsWith('var_')) + .map(([key, value]) => { + const safeValue = String(value) + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"'); + return `export ${key}="${safeValue}"`; + }) + .join('; '); + // Send the update command after a delay to ensure we're in the container setTimeout(() => { - childProcess.write('update\n'); + if (envExports) { + childProcess.write(`${envExports}; update\n`); + } else { + childProcess.write('update\n'); + } }, 4000); // Handle process exit @@ -1435,7 +1618,7 @@ class ScriptExecutionHandler { * @param {string} executionId * @param {ServerInfo} server */ - async startSSHUpdateExecution(ws, containerId, executionId, server) { + async startSSHUpdateExecution(ws, containerId, executionId, server, envVars = {}) { const sshService = getSSHExecutionService(); try { @@ -1476,9 +1659,24 @@ class ScriptExecutionHandler { ws }); + // Build env export commands (e.g. for PHS_SILENT=1) + const envExports = Object.entries(envVars) + .filter(([key]) => key.startsWith('PHS_') || key.startsWith('var_')) + .map(([key, value]) => { + const safeValue = String(value) + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"'); + return `export ${key}="${safeValue}"`; + }) + .join('; '); + // Send the update command after a delay to ensure we're in the container setTimeout(() => { - /** @type {any} */ (execution).process.write('update\n'); + if (envExports) { + /** @type {any} */ (execution).process.write(`${envExports}; update\n`); + } else { + /** @type {any} */ (execution).process.write('update\n'); + } }, 4000); } catch (error) { diff --git a/src/app/_components/AppearanceButton.tsx b/src/app/_components/AppearanceButton.tsx new file mode 100644 index 0000000..f3978ec --- /dev/null +++ b/src/app/_components/AppearanceButton.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useState } from "react"; +import { AppearanceModal } from "./AppearanceModal"; +import { Button } from "./ui/button"; +import { Paintbrush } from "lucide-react"; + +export function AppearanceButton() { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + + setIsOpen(false)} /> + + ); +} diff --git a/src/app/_components/AppearanceModal.tsx b/src/app/_components/AppearanceModal.tsx new file mode 100644 index 0000000..ecfb67d --- /dev/null +++ b/src/app/_components/AppearanceModal.tsx @@ -0,0 +1,216 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "./ui/button"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; +import { useTheme } from "./ThemeProvider"; +import { + Sun, + Moon, + Type, + Maximize2, + Minimize2, + X, + Paintbrush, +} from "lucide-react"; + +type TextSize = "small" | "medium" | "large"; +type LayoutWidth = "default" | "full"; + +function loadAppearance(): { textSize: TextSize; layoutWidth: LayoutWidth } { + if (typeof window === "undefined") + return { textSize: "medium", layoutWidth: "default" }; + try { + const ts = localStorage.getItem("pve-text-size"); + const lw = localStorage.getItem("pve-layout-width"); + return { + textSize: + ts === "small" || ts === "medium" || ts === "large" ? ts : "medium", + layoutWidth: lw === "full" ? "full" : "default", + }; + } catch { + return { textSize: "medium", layoutWidth: "default" }; + } +} + +function applyTextSize(size: TextSize) { + const root = document.documentElement; + root.classList.remove( + "text-size-small", + "text-size-medium", + "text-size-large", + ); + root.classList.add(`text-size-${size}`); + localStorage.setItem("pve-text-size", size); +} + +function applyLayoutWidth(width: LayoutWidth) { + const root = document.documentElement; + root.style.setProperty( + "--layout-max-w", + width === "full" ? "1800px" : "1440px", + ); + localStorage.setItem("pve-layout-width", width); +} + +interface AppearanceModalProps { + isOpen: boolean; + onClose: () => void; +} + +export function AppearanceModal({ isOpen, onClose }: AppearanceModalProps) { + const zIndex = useRegisterModal(isOpen, { + id: "appearance-modal", + allowEscape: true, + onClose, + }); + const { theme, setTheme } = useTheme(); + const [textSize, setTextSize] = useState("medium"); + const [layoutWidth, setLayoutWidth] = useState("default"); + + useEffect(() => { + if (isOpen) { + const a = loadAppearance(); + setTextSize(a.textSize); + setLayoutWidth(a.layoutWidth); + } + }, [isOpen]); + + if (!isOpen) return null; + + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) onClose(); + }; + + return ( + +
+
+ {/* Header */} +
+
+
+ +
+

+ Appearance +

+
+ +
+ + {/* Content */} +
+ {/* Theme */} +
+

+ Theme +

+
+ {[ + { value: "light" as const, label: "Light", Icon: Sun }, + { value: "dark" as const, label: "Dark", Icon: Moon }, + ].map(({ value, label, Icon }) => ( + + ))} +
+
+ + {/* Text Size */} +
+

+ + Text Size +

+
+ {[ + { value: "small" as const, label: "Small" }, + { value: "medium" as const, label: "Medium" }, + { value: "large" as const, label: "Large" }, + ].map(({ value, label }) => ( + + ))} +
+
+ + {/* Layout Width */} +
+

+ Layout Width +

+
+ {[ + { + value: "default" as const, + label: "Default", + sub: "1440px", + Icon: Minimize2, + }, + { + value: "full" as const, + label: "Wide", + sub: "1800px", + Icon: Maximize2, + }, + ].map(({ value, label, sub, Icon }) => ( + + ))} +
+
+
+
+
+
+ ); +} diff --git a/src/app/_components/AuthGuard.tsx b/src/app/_components/AuthGuard.tsx index 4a0aacf..a5b17a0 100644 --- a/src/app/_components/AuthGuard.tsx +++ b/src/app/_components/AuthGuard.tsx @@ -1,57 +1,35 @@ -'use client'; +"use client"; -import { useState, useEffect, type ReactNode } from 'react'; -import { useAuth } from './AuthProvider'; -import { AuthModal } from './AuthModal'; -import { SetupModal } from './SetupModal'; +import { useState, type ReactNode } from "react"; +import { useAuth } from "./AuthProvider"; +import { AuthModal } from "./AuthModal"; +import { SetupModal } from "./SetupModal"; interface AuthGuardProps { children: ReactNode; } -interface AuthConfig { - username: string | null; - enabled: boolean; - hasCredentials: boolean; - setupCompleted: boolean; -} - export function AuthGuard({ children }: AuthGuardProps) { - const { isAuthenticated, isLoading } = useAuth(); - const [authConfig, setAuthConfig] = useState(null); - const [configLoading, setConfigLoading] = useState(true); - const [setupCompleted, setSetupCompleted] = useState(false); + const { + isAuthenticated, + isLoading, + setupCompleted, + authEnabled, + refreshConfig, + } = useAuth(); + const [localSetupCompleted, setLocalSetupCompleted] = useState(false); const handleSetupComplete = async () => { - setSetupCompleted(true); - // Refresh auth config without reloading the page - await fetchAuthConfig(); + setLocalSetupCompleted(true); + await refreshConfig(); }; - const fetchAuthConfig = async () => { - try { - const response = await fetch('/api/settings/auth-credentials'); - if (response.ok) { - const config = await response.json() as AuthConfig; - setAuthConfig(config); - } - } catch (error) { - console.error('Error fetching auth config:', error); - } finally { - setConfigLoading(false); - } - }; - - useEffect(() => { - void fetchAuthConfig(); - }, []); - - // Show loading while checking auth status - if (isLoading || configLoading) { + // Show loading while AuthProvider is still checking + if (isLoading || setupCompleted === null) { return ( -
+
-
+

Loading...

@@ -59,12 +37,12 @@ export function AuthGuard({ children }: AuthGuardProps) { } // Show setup modal if setup has not been completed yet - if (authConfig && !authConfig.setupCompleted && !setupCompleted) { + if (!setupCompleted && !localSetupCompleted) { return ; } // Show auth modal if auth is enabled but user is not authenticated - if (authConfig && authConfig.enabled && !isAuthenticated) { + if (authEnabled && !isAuthenticated) { return ; } diff --git a/src/app/_components/AuthModal.tsx b/src/app/_components/AuthModal.tsx index 271509f..f8472b0 100644 --- a/src/app/_components/AuthModal.tsx +++ b/src/app/_components/AuthModal.tsx @@ -1,21 +1,25 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { useAuth } from './AuthProvider'; -import { Lock, User, AlertCircle } from 'lucide-react'; -import { useRegisterModal } from './modal/ModalStackProvider'; +import { useState } from "react"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { useAuth } from "./AuthProvider"; +import { Lock, User, AlertCircle } from "lucide-react"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; interface AuthModalProps { isOpen: boolean; } export function AuthModal({ isOpen }: AuthModalProps) { - useRegisterModal(isOpen, { id: 'auth-modal', allowEscape: false, onClose: () => null }); + const zIndex = useRegisterModal(isOpen, { + id: "auth-modal", + allowEscape: false, + onClose: () => null, + }); const { login } = useAuth(); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -25,89 +29,102 @@ export function AuthModal({ isOpen }: AuthModalProps) { setError(null); const success = await login(username, password); - + if (!success) { - setError('Invalid username or password'); + setError("Invalid username or password"); } - + setIsLoading(false); }; if (!isOpen) return null; return ( -
-
- {/* Header */} -
-
- -

Authentication Required

+ +
+
+ {/* Header */} +
+
+ +

+ Authentication Required +

+
+
+ + {/* Content */} +
+

+ Please enter your credentials to access the application. +

+ +
+
+ +
+ + setUsername(e.target.value)} + disabled={isLoading} + className="pl-10" + required + /> +
+
+ +
+ +
+ + setPassword(e.target.value)} + disabled={isLoading} + className="pl-10" + required + /> +
+
+ + {error && ( +
+ + {error} +
+ )} + + +
- - {/* Content */} -
-

- Please enter your credentials to access the application. -

- -
-
- -
- - setUsername(e.target.value)} - disabled={isLoading} - className="pl-10" - required - /> -
-
- -
- -
- - setPassword(e.target.value)} - disabled={isLoading} - className="pl-10" - required - /> -
-
- - {error && ( -
- - {error} -
- )} - - -
-
-
+ ); } diff --git a/src/app/_components/AuthProvider.tsx b/src/app/_components/AuthProvider.tsx index 0888081..22466fe 100644 --- a/src/app/_components/AuthProvider.tsx +++ b/src/app/_components/AuthProvider.tsx @@ -14,9 +14,12 @@ interface AuthContextType { username: string | null; isLoading: boolean; expirationTime: number | null; + setupCompleted: boolean | null; + authEnabled: boolean | null; login: (username: string, password: string) => Promise; logout: () => void; checkAuth: () => Promise; + refreshConfig: () => Promise; } const AuthContext = createContext(undefined); @@ -30,6 +33,8 @@ export function AuthProvider({ children }: AuthProviderProps) { const [username, setUsername] = useState(null); const [isLoading, setIsLoading] = useState(true); const [expirationTime, setExpirationTime] = useState(null); + const [setupCompleted, setSetupCompleted] = useState(null); + const [authEnabled, setAuthEnabled] = useState(null); const checkAuthInternal = async (retryCount = 0) => { try { @@ -41,6 +46,9 @@ export function AuthProvider({ children }: AuthProviderProps) { enabled: boolean; }; + setSetupCompleted(setupData.setupCompleted); + setAuthEnabled(setupData.enabled); + // If setup is not completed or auth is disabled, don't verify if (!setupData.setupCompleted || !setupData.enabled) { setIsAuthenticated(false); @@ -99,6 +107,22 @@ export function AuthProvider({ children }: AuthProviderProps) { return checkAuthInternal(0); }, []); + const refreshConfig = useCallback(async () => { + try { + const response = await fetch("/api/settings/auth-credentials"); + if (response.ok) { + const data = (await response.json()) as { + setupCompleted: boolean; + enabled: boolean; + }; + setSetupCompleted(data.setupCompleted); + setAuthEnabled(data.enabled); + } + } catch (error) { + console.error("Error refreshing auth config:", error); + } + }, []); + const login = async ( username: string, password: string, @@ -158,9 +182,12 @@ export function AuthProvider({ children }: AuthProviderProps) { username, isLoading, expirationTime, + setupCompleted, + authEnabled, login, logout, checkAuth, + refreshConfig, }} > {children} diff --git a/src/app/_components/BackupWarningModal.tsx b/src/app/_components/BackupWarningModal.tsx index d5e3f5f..2146520 100644 --- a/src/app/_components/BackupWarningModal.tsx +++ b/src/app/_components/BackupWarningModal.tsx @@ -2,7 +2,7 @@ import { Button } from "./ui/button"; import { AlertTriangle } from "lucide-react"; -import { useRegisterModal } from "./modal/ModalStackProvider"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; interface BackupWarningModalProps { isOpen: boolean; @@ -15,7 +15,7 @@ export function BackupWarningModal({ onClose, onProceed, }: BackupWarningModalProps) { - useRegisterModal(isOpen, { + const zIndex = useRegisterModal(isOpen, { id: "backup-warning-modal", allowEscape: true, onClose, @@ -24,51 +24,56 @@ export function BackupWarningModal({ if (!isOpen) return null; return ( -
-
- {/* Header */} -
-
- -

- Backup Failed -

+ +
+
+ {/* Header */} +
+
+ +

+ Backup Failed +

+
-
- {/* Content */} -
-

- The backup failed, but you can still proceed with the update if you - wish. -
-
- Warning: Proceeding - without a backup means you won't be able to restore the - container if something goes wrong during the update. -

+ {/* Content */} +
+

+ The backup failed, but you can still proceed with the update if + you wish. +
+
+ Warning: Proceeding + without a backup means you won't be able to restore the + container if something goes wrong during the update. +

- {/* Action Buttons */} -
- - + {/* Action Buttons */} +
+ + +
-
+
); } diff --git a/src/app/_components/BackupsTab.tsx b/src/app/_components/BackupsTab.tsx index 5048e8a..a249d39 100644 --- a/src/app/_components/BackupsTab.tsx +++ b/src/app/_components/BackupsTab.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect } from "react"; +import { createPortal } from "react-dom"; import { api } from "~/trpc/react"; import { Button } from "./ui/button"; import { Badge } from "./ui/badge"; @@ -13,6 +14,8 @@ import { Server, CheckCircle, AlertCircle, + Plus, + ArrowLeft, } from "lucide-react"; import { DropdownMenu, @@ -22,6 +25,8 @@ import { } from "./ui/dropdown-menu"; import { ConfirmationModal } from "./ConfirmationModal"; import { LoadingModal } from "./LoadingModal"; +import { useShell } from "./ShellContext"; +import type { Server as ServerType } from "~/types/server"; interface Backup { id: number; @@ -47,6 +52,8 @@ export function BackupsTab() { const [expandedContainers, setExpandedContainers] = useState>( new Set(), ); + const shell = useShell(); + const [servers, setServers] = useState([]); const [hasAutoDiscovered, setHasAutoDiscovered] = useState(false); const [restoreConfirmOpen, setRestoreConfirmOpen] = useState(false); const [selectedBackup, setSelectedBackup] = useState<{ @@ -58,6 +65,17 @@ export function BackupsTab() { const [restoreError, setRestoreError] = useState(null); const [shouldPollRestore, setShouldPollRestore] = useState(false); + // Create-backup dialog state + // mode "existing": opened from container row (serverId + containerIds pre-filled) + // mode "new": opened from header button (pick server → containers → storage) + const [createDialog, setCreateDialog] = useState<{ + mode: "existing" | "new"; + step: "server" | "containers" | "storage"; + serverId: number | null; + containerIds: string[]; // multi-select + } | null>(null); + const [selectedStorage, setSelectedStorage] = useState(""); + const { data: backupsData, refetch: refetchBackups, @@ -69,6 +87,41 @@ export function BackupsTab() { }, }); + // Storages for create-backup dialog (shown once at least one container is selected) + const storagesQuery = api.installedScripts.getBackupStorages.useQuery( + { serverId: createDialog?.serverId ?? 0 }, + { + enabled: + (createDialog?.serverId ?? 0) > 0 && + (createDialog?.containerIds.length ?? 0) > 0, + }, + ); + + // Containers for new-backup dialog (shown when server is picked) + const containersQuery = api.installedScripts.listContainersOnServer.useQuery( + { serverId: createDialog?.serverId ?? 0 }, + { + enabled: + createDialog?.mode === "new" && + createDialog.step === "containers" && + (createDialog?.serverId ?? 0) > 0, + }, + ); + + // Estimate backup size from selected containers (sum of configured disk sizes) + const resourceTemplatesQuery = + api.installedScripts.getContainersResourceTemplates.useQuery( + { + serverId: createDialog?.serverId ?? 0, + containerIds: createDialog?.containerIds ?? [], + }, + { + enabled: + (createDialog?.serverId ?? 0) > 0 && + (createDialog?.containerIds.length ?? 0) > 0, + }, + ); + // Poll for restore progress const { data: restoreLogsData } = api.backups.getRestoreProgress.useQuery( undefined, @@ -154,6 +207,16 @@ export function BackupsTab() { ? restoreProgress[restoreProgress.length - 1] : "Restoring backup..."; + // Load servers for the create-backup dialog + useEffect(() => { + fetch("/api/servers") + .then((r) => r.json()) + .then((data: ServerType[]) => setServers(data)) + .catch(() => { + /* ignore */ + }); + }, []); + // Auto-discover backups when tab is first opened useEffect(() => { if (!hasAutoDiscovered && !isLoading && backupsData) { @@ -169,6 +232,38 @@ export function BackupsTab() { discoverMutation.mutate(); }; + const handleOpenCreateBackup = (containerId: string, serverId: number) => { + setCreateDialog({ + mode: "existing", + step: "storage", + serverId, + containerIds: [containerId], + }); + setSelectedStorage(""); + }; + + const handleStartBackup = () => { + if ( + !createDialog?.containerIds.length || + !createDialog?.serverId || + !selectedStorage + ) + return; + const server = servers.find((s) => s.id === createDialog.serverId); + if (!server) return; + for (const cid of createDialog.containerIds) { + shell.open({ + containerId: cid, + server, + containerType: "lxc", + backupStorage: selectedStorage, + onComplete: () => discoverMutation.mutate(), + }); + } + setCreateDialog(null); + setSelectedStorage(""); + }; + const handleRestoreClick = (backup: Backup, containerId: string) => { setSelectedBackup({ backup, containerId }); setRestoreConfirmOpen(true); @@ -253,16 +348,34 @@ export function BackupsTab() { Discovered backups grouped by container ID

- +
+ + +
{/* Loading state */} @@ -308,11 +421,11 @@ export function BackupsTab() { className="bg-card border-border overflow-hidden rounded-lg border shadow-sm" > {/* Container header - collapsible */} -
-
- + + {/* Create Backup button — only shown when server is known */} + {(container.backups[0]?.server_id ?? 0) > 0 && ( + + )} +
{/* Container content - backups list */} {isExpanded && ( @@ -546,6 +677,400 @@ export function BackupsTab() {
)} + + {/* ── Create Backup Dialog ── */} + {createDialog && + (() => { + // Build typed container list once + type CT = { + id: string; + name: string; + status: string; + type: "CT" | "VM"; + }; + const lxcItems: CT[] = (containersQuery.data?.lxc ?? []).map((c) => ({ + id: c.id, + name: c.name, + status: c.status, + type: "CT" as const, + })); + const vmItems: CT[] = (containersQuery.data?.vm ?? []).map((c) => ({ + id: c.id, + name: c.name, + status: c.status, + type: "VM" as const, + })); + const allContainers: CT[] = [...lxcItems, ...vmItems]; + + const isServerStep = + createDialog.mode === "new" && createDialog.step === "server"; + const isContainerStep = + createDialog.mode === "new" && createDialog.step === "containers"; + const isStorageStep = createDialog.step === "storage"; + + const templates: Record< + string, + { cpu: number | null; ramMB: number | null; diskGB: number | null } + > = resourceTemplatesQuery.data?.templates ?? {}; + const estimatedBackupGb = createDialog.containerIds.reduce( + (sum, id) => sum + (templates[id]?.diskGB ?? 0), + 0, + ); + + const storageOptions = + storagesQuery.data?.storages?.filter((s) => s.supportsBackup) ?? []; + + const stepLabel = + createDialog.mode === "existing" + ? `Backup CT ${createDialog.containerIds[0]} — select storage` + : isServerStep + ? "Select Server" + : isStorageStep + ? `${createDialog.containerIds.length} container${createDialog.containerIds.length > 1 ? "s" : ""} selected — select storage` + : "Select Containers"; + + return createPortal( +
+
+
+ {/* Header */} +
+ {createDialog.mode === "new" && + createDialog.step !== "server" && ( + + )} +
+

+ {createDialog.mode === "existing" + ? "Create Backup" + : "New Backup"} +

+

+ {stepLabel} +

+
+ +
+ + {/* Body */} +
+ {isServerStep && ( +
+

+ Choose a server +

+ {servers.length === 0 ? ( +

+ No servers configured. +

+ ) : ( + servers.map((srv) => ( + + )) + )} +
+ )} + + {isContainerStep && ( +
+

+ Select containers (multiple allowed) +

+ {containersQuery.isLoading ? ( +
+ + + Loading containers… + +
+ ) : allContainers.length === 0 ? ( +

+ No containers found on this server. +

+ ) : ( +
+ {allContainers.map((ct) => { + const selected = + createDialog.containerIds.includes(ct.id); + return ( + + ); + })} +
+ )} +
+ )} + + {isStorageStep && ( +
+ {createDialog.mode === "new" && ( +
+ {createDialog.containerIds.map((id) => ( + + CT {id} + + ))} +
+ )} + + {createDialog.containerIds.length > 0 && ( +
+ + Estimated maximum backup size:{" "} + + + {estimatedBackupGb > 0 + ? `${estimatedBackupGb} GB` + : "Unknown"} + + {resourceTemplatesQuery.isLoading && ( + + (calculating…) + + )} +
+ )} + +

+ Select backup storage +

+ + {storagesQuery.isLoading ? ( +
+ + + Loading storages… + +
+ ) : storageOptions.length === 0 ? ( +

+ No backup-capable storages found on this server. +

+ ) : ( +
+ {storageOptions.map((storage) => { + const availableGb = + typeof storage.availableGB === "number" + ? storage.availableGB + : null; + const lowSpace = + availableGb != null && + estimatedBackupGb > 0 && + estimatedBackupGb > availableGb; + return ( + + ); + })} +
+ )} +
+ )} +
+ + {/* Footer */} +
+ +
+ {isContainerStep && ( + + )} + {isStorageStep && ( + + )} +
+
+
+
+
, + document.body, + ); + })()}
); } diff --git a/src/app/_components/Badge.tsx b/src/app/_components/Badge.tsx index cbf2ff5..4a6abbd 100644 --- a/src/app/_components/Badge.tsx +++ b/src/app/_components/Badge.tsx @@ -1,93 +1,107 @@ -'use client'; +"use client"; -import React from 'react'; +import React from "react"; interface BadgeProps { - variant: 'type' | 'updateable' | 'privileged' | 'status' | 'note' | 'execution-mode'; + variant: + | "type" + | "updateable" + | "privileged" + | "status" + | "note" + | "execution-mode"; type?: string; - noteType?: 'info' | 'warning' | 'error'; - status?: 'success' | 'failed' | 'in_progress'; - executionMode?: 'local' | 'ssh'; + noteType?: "info" | "warning" | "error"; + status?: "success" | "failed" | "in_progress"; + executionMode?: "local" | "ssh"; children: React.ReactNode; className?: string; } -export function Badge({ variant, type, noteType, status, executionMode, children, className = '' }: BadgeProps) { +export function Badge({ + variant, + type, + noteType, + status, + executionMode, + children, + className = "", +}: BadgeProps) { const getTypeStyles = (scriptType: string) => { switch (scriptType.toLowerCase()) { - case 'ct': - return 'bg-primary/10 text-primary border-primary/20'; - case 'addon': - return 'bg-primary/10 text-primary border-primary/20'; - case 'vm': - return 'bg-success/10 text-success border-success/20'; - case 'pve': - return 'bg-warning/10 text-warning border-warning/20'; + case "ct": + return "bg-primary/10 text-primary border-primary/20"; + case "addon": + return "bg-primary/10 text-primary border-primary/20"; + case "vm": + return "bg-success/10 text-success border-success/20"; + case "pve": + return "bg-warning/10 text-warning border-warning/20"; default: - return 'bg-muted text-muted-foreground border-border'; + return "bg-muted text-muted-foreground border-border"; } }; const getVariantStyles = () => { switch (variant) { - case 'type': - return `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${type ? getTypeStyles(type) : getTypeStyles('unknown')}`; - - case 'updateable': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success/10 text-success border border-success/20'; - - case 'privileged': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-destructive/10 text-destructive border border-destructive/20'; - - case 'status': + case "type": + return `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${type ? getTypeStyles(type) : getTypeStyles("unknown")}`; + + case "updateable": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success/10 text-success border border-success/20"; + + case "privileged": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-destructive/10 text-destructive border border-destructive/20"; + + case "status": switch (status) { - case 'success': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success/10 text-success border border-success/20'; - case 'failed': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-error/10 text-error border border-error/20'; - case 'in_progress': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-warning/10 text-warning border border-warning/20'; + case "success": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success/10 text-success border border-success/20"; + case "failed": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-error/10 text-error border border-error/20"; + case "in_progress": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-warning/10 text-warning border border-warning/20"; default: - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border'; + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border"; } - - case 'execution-mode': + + case "execution-mode": switch (executionMode) { - case 'local': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20'; - case 'ssh': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20'; + case "local": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20"; + case "ssh": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20"; default: - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border'; + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border"; } - - case 'note': + + case "note": switch (noteType) { - case 'warning': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-warning/10 text-warning border border-warning/20'; - case 'error': - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-destructive/10 text-destructive border border-destructive/20'; + case "warning": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-warning/10 text-warning border border-warning/20"; + case "error": + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-destructive/10 text-destructive border border-destructive/20"; default: - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20'; + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20"; } - + default: - return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border'; + return "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border"; } }; // Format the text for type badges const formatText = () => { - if (variant === 'type' && type) { + if (variant === "type" && type) { switch (type.toLowerCase()) { - case 'ct': - return 'LXC'; - case 'addon': - return 'ADDON'; - case 'vm': - return 'VM'; - case 'pve': - return 'PVE'; + case "ct": + return "LXC"; + case "addon": + return "ADDON"; + case "vm": + return "VM"; + case "pve": + return "PVE"; default: return type.toUpperCase(); } @@ -96,14 +110,18 @@ export function Badge({ variant, type, noteType, status, executionMode, children }; return ( - - {formatText()} - + {formatText()} ); } // Convenience components for common use cases -export const TypeBadge = ({ type, className }: { type: string; className?: string }) => ( +export const TypeBadge = ({ + type, + className, +}: { + type: string; + className?: string; +}) => ( {type} @@ -121,20 +139,60 @@ export const PrivilegedBadge = ({ className }: { className?: string }) => ( ); -export const StatusBadge = ({ status, children, className }: { status: 'success' | 'failed' | 'in_progress'; children: React.ReactNode; className?: string }) => ( +export const StatusBadge = ({ + status, + children, + className, +}: { + status: "success" | "failed" | "in_progress"; + children: React.ReactNode; + className?: string; +}) => ( {children} ); -export const ExecutionModeBadge = ({ mode, children, className }: { mode: 'local' | 'ssh'; children: React.ReactNode; className?: string }) => ( +export const ExecutionModeBadge = ({ + mode, + children, + className, +}: { + mode: "local" | "ssh"; + children: React.ReactNode; + className?: string; +}) => ( {children} ); -export const NoteBadge = ({ noteType, children, className }: { noteType: 'info' | 'warning' | 'error'; children: React.ReactNode; className?: string }) => ( +export const NoteBadge = ({ + noteType, + children, + className, +}: { + noteType: "info" | "warning" | "error"; + children: React.ReactNode; + className?: string; +}) => ( {children} -); \ No newline at end of file +); + +export const DevBadge = ({ className }: { className?: string }) => ( + + DEV + +); + +export const ArmBadge = ({ className }: { className?: string }) => ( + + ARM + +); diff --git a/src/app/_components/CategorySidebar.tsx b/src/app/_components/CategorySidebar.tsx index 61cb723..895745d 100644 --- a/src/app/_components/CategorySidebar.tsx +++ b/src/app/_components/CategorySidebar.tsx @@ -1,195 +1,164 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import { ContextualHelpIcon } from './ContextualHelpIcon'; +import { useState } from "react"; +import { ContextualHelpIcon } from "./ContextualHelpIcon"; interface CategorySidebarProps { categories: string[]; categoryCounts: Record; + categoryDevCounts?: Record; totalScripts: number; selectedCategory: string | null; onCategorySelect: (category: string | null) => void; + showDevScripts?: boolean; } // Icon mapping for categories -const CategoryIcon = ({ iconName, className = "w-5 h-5" }: { iconName: string; className?: string }) => { - const iconMap: Record = { - server: ( - - - - ), - monitor: ( - - - - ), - box: ( - - - - ), - shield: ( - - - - ), - "shield-check": ( - - - - ), - key: ( - - - - ), - archive: ( - - - - ), - database: ( - - - - ), - "chart-bar": ( - - - - ), - template: ( - - - - ), - "folder-open": ( - - - - ), - "document-text": ( - - - - ), - film: ( - - - - ), - download: ( - - - - ), - "video-camera": ( - - - - ), - home: ( - - - - ), - wifi: ( - - - - ), - "chat-alt": ( - - - - ), - clock: ( - - - - ), - code: ( - - - - ), - "external-link": ( - - - - ), - sparkles: ( - - - - ), - "currency-dollar": ( - - - - ), - puzzle: ( - - - - ), - office: ( - - - - ), - }; +const categoryIconColorMap: Record = { + server: "text-blue-500", + monitor: "text-sky-400", + box: "text-orange-400", + shield: "text-green-500", + "shield-check": "text-green-500", + key: "text-yellow-500", + archive: "text-amber-400", + database: "text-indigo-500", + "chart-bar": "text-emerald-500", + template: "text-violet-500", + "folder-open": "text-cyan-500", + "document-text": "text-slate-400", + film: "text-rose-500", + download: "text-cyan-500", + "video-camera": "text-pink-500", + home: "text-lime-500", + wifi: "text-fuchsia-500", + "chat-alt": "text-sky-500", + clock: "text-orange-500", + code: "text-green-400", + "external-link": "text-blue-400", + sparkles: "text-purple-500", + "currency-dollar": "text-emerald-400", + puzzle: "text-pink-400", + office: "text-stone-500", +}; - return iconMap[iconName] ?? ( - - +const iconPaths: Record = { + server: + "M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01", + monitor: + "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z", + box: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4", + shield: + "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z", + "shield-check": + "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z", + key: "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1 0 21 9z", + archive: + "M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4", + database: + "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4", + "chart-bar": + "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z", + template: + "M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z", + "folder-open": + "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z", + "document-text": + "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z", + film: "M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2m0 0V1.5a.5.5 0 01.5-.5h1a.5.5 0 01.5.5V4m-3 0H9m3 0v16a1 1 0 01-1 1H8a1 1 0 01-1-1V4m6 0h2a2 2 0 012 2v12a2 2 0 01-2 2h-2V4z", + download: + "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z", + "video-camera": + "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z", + home: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6", + wifi: "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0", + "chat-alt": + "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z", + clock: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z", + code: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4", + "external-link": + "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14", + sparkles: + "M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z", + "currency-dollar": + "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1", + puzzle: + "M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z", + office: + "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4", +}; + +const FALLBACK_PATH = + "M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zM21 5a2 2 0 00-2-2h-4a2 2 0 00-2 2v12a4 4 0 004 4 4 4 0 004-4V5z"; + +const CategoryIcon = ({ + iconName, + className = "w-5 h-5", +}: { + iconName: string; + className?: string; +}) => { + return ( + + ); }; -export function CategorySidebar({ - categories, - categoryCounts, - totalScripts, - selectedCategory, - onCategorySelect +export function CategorySidebar({ + categories, + categoryCounts, + categoryDevCounts, + totalScripts, + selectedCategory, + onCategorySelect, + showDevScripts = false, }: CategorySidebarProps) { const [isCollapsed, setIsCollapsed] = useState(false); // Category to icon mapping (based on metadata.json) const categoryIconMapping: Record = { - 'Proxmox & Virtualization': 'server', - 'Operating Systems': 'monitor', - 'Containers & Docker': 'box', - 'Network & Firewall': 'shield', - 'Adblock & DNS': 'shield-check', - 'Authentication & Security': 'key', - 'Backup & Recovery': 'archive', - 'Databases': 'database', - 'Monitoring & Analytics': 'chart-bar', - 'Dashboards & Frontends': 'template', - 'Files & Downloads': 'folder-open', - 'Documents & Notes': 'document-text', - 'Media & Streaming': 'film', - '*Arr Suite': 'download', - 'NVR & Cameras': 'video-camera', - 'IoT & Smart Home': 'home', - 'ZigBee, Z-Wave & Matter': 'wifi', - 'MQTT & Messaging': 'chat-alt', - 'Automation & Scheduling': 'clock', - 'AI / Coding & Dev-Tools': 'code', - 'Webservers & Proxies': 'external-link', - 'Bots & ChatOps': 'sparkles', - 'Finance & Budgeting': 'currency-dollar', - 'Gaming & Leisure': 'puzzle', - 'Business & ERP': 'office', - 'Miscellaneous': 'box' + "Proxmox & Virtualization": "server", + "Operating Systems": "monitor", + "Containers & Docker": "box", + "Network & Firewall": "shield", + "Adblock & DNS": "shield-check", + "Authentication & Security": "key", + "Backup & Recovery": "archive", + Databases: "database", + "Monitoring & Analytics": "chart-bar", + "Dashboards & Frontends": "template", + "Files & Downloads": "folder-open", + "Documents & Notes": "document-text", + "Media & Streaming": "film", + "*Arr Suite": "download", + "NVR & Cameras": "video-camera", + "IoT & Smart Home": "home", + "ZigBee, Z-Wave & Matter": "wifi", + "MQTT & Messaging": "chat-alt", + "Automation & Scheduling": "clock", + "AI / Coding & Dev-Tools": "code", + "Webservers & Proxies": "external-link", + "Bots & ChatOps": "sparkles", + "Finance & Budgeting": "currency-dollar", + "Gaming & Leisure": "puzzle", + "Business & ERP": "office", + Miscellaneous: "box", }; // Filter categories to only show those with scripts, then sort by count (descending) and alphabetically const sortedCategories = categories - .map(category => [category, categoryCounts[category] ?? 0] as const) + .map((category) => [category, categoryCounts[category] ?? 0] as const) .filter(([, count]) => count > 0) // Only show categories with at least one script .sort(([a, countA], [b, countB]) => { if (countB !== countA) return countB - countA; @@ -197,34 +166,48 @@ export function CategorySidebar({ }); return ( -
+
{/* Header */} -
+
{!isCollapsed && ( -
+
-

Categories

-

{totalScripts} Total scripts

+

+ Categories +

+

+ {totalScripts} Total scripts +

- +
)}
@@ -236,24 +219,26 @@ export function CategorySidebar({ {/* "All Categories" option */} @@ -261,33 +246,46 @@ export function CategorySidebar({ {/* Individual Categories */} {sortedCategories.map(([category, count]) => { const isSelected = selectedCategory === category; - + return ( ); })} @@ -297,32 +295,34 @@ export function CategorySidebar({ {/* Collapsed state - show only icons with counters and tooltips */} {isCollapsed && ( -
+
{/* "All Categories" option */}
- + {/* Tooltip */} -
+
All Categories ({totalScripts})
@@ -330,32 +330,34 @@ export function CategorySidebar({ {/* Individual Categories */} {sortedCategories.map(([category, count]) => { const isSelected = selectedCategory === category; - + return (
- + {/* Tooltip */} -
+
{category} ({count})
@@ -365,4 +367,4 @@ export function CategorySidebar({ )}
); -} \ No newline at end of file +} diff --git a/src/app/_components/CloneCountInputModal.tsx b/src/app/_components/CloneCountInputModal.tsx index eb32ac5..a9ae245 100644 --- a/src/app/_components/CloneCountInputModal.tsx +++ b/src/app/_components/CloneCountInputModal.tsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; -import { useState, useEffect } from 'react'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Copy, X } from 'lucide-react'; -import { useRegisterModal } from './modal/ModalStackProvider'; +import { useState, useEffect } from "react"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { Copy, X } from "lucide-react"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; interface CloneCountInputModalProps { isOpen: boolean; @@ -17,11 +17,15 @@ export function CloneCountInputModal({ isOpen, onClose, onSubmit, - storageName + storageName, }: CloneCountInputModalProps) { const [cloneCount, setCloneCount] = useState(1); - - useRegisterModal(isOpen, { id: 'clone-count-input-modal', allowEscape: true, onClose }); + + const zIndex = useRegisterModal(isOpen, { + id: "clone-count-input-modal", + allowEscape: true, + onClose, + }); useEffect(() => { if (isOpen) { @@ -44,86 +48,97 @@ export function CloneCountInputModal({ }; return ( -
-
- {/* Header */} -
-
- -

Clone Count

-
- -
- - {/* Content */} -
-

- How many clones would you like to create? -

- - {storageName && ( -
-

Storage:

-

{storageName}

+ +
+
+ {/* Header */} +
+
+ +

+ Clone Count +

- )} - -
- - { - const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value >= 1 && value <= 100) { - setCloneCount(value); - } else if (e.target.value === '') { - setCloneCount(1); - } - }} - className="w-full" - placeholder="1" - /> -

- Enter a number between 1 and 100 -

-
- - {/* Action Buttons */} -
-
+ + {/* Content */} +
+

+ How many clones would you like to create? +

+ + {storageName && ( +
+

Storage:

+

+ {storageName} +

+
+ )} + +
+ + { + const value = parseInt(e.target.value, 10); + if (!isNaN(value) && value >= 1 && value <= 100) { + setCloneCount(value); + } else if (e.target.value === "") { + setCloneCount(1); + } + }} + className="w-full" + placeholder="1" + /> +

+ Enter a number between 1 and 100 +

+
+ + {/* Action Buttons */} +
+ + +
+
-
+
); } - diff --git a/src/app/_components/ConfigurationModal.tsx b/src/app/_components/ConfigurationModal.tsx index f4ac0b4..07ce6b4 100644 --- a/src/app/_components/ConfigurationModal.tsx +++ b/src/app/_components/ConfigurationModal.tsx @@ -6,7 +6,7 @@ import type { Script } from "~/types/script"; import type { Server } from "~/types/server"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; -import { useRegisterModal } from "./modal/ModalStackProvider"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; export type EnvVars = Record; @@ -27,7 +27,7 @@ export function ConfigurationModal({ server, mode, }: ConfigurationModalProps) { - useRegisterModal(isOpen, { + const zIndex = useRegisterModal(isOpen, { id: "configuration-modal", allowEscape: true, onClose, @@ -63,6 +63,25 @@ export function ConfigurationModal({ // Advanced mode state const [advancedVars, setAdvancedVars] = useState({}); + // Server presets + const [showSavePreset, setShowSavePreset] = useState(false); + const [presetName, setPresetName] = useState(""); + const { data: presetsData, refetch: refetchPresets } = + api.serverPresets.getByServerId.useQuery( + { serverId: server?.id ?? 0 }, + { enabled: !!server?.id && isOpen && mode === "advanced" }, + ); + const createPresetMutation = api.serverPresets.create.useMutation({ + onSuccess: () => { + void refetchPresets(); + setShowSavePreset(false); + setPresetName(""); + }, + }); + const deletePresetMutation = api.serverPresets.delete.useMutation({ + onSuccess: () => void refetchPresets(), + }); + // Discovered SSH keys on the Proxmox host (advanced mode only) const [discoveredSshKeys, setDiscoveredSshKeys] = useState([]); const [discoveredSshKeysLoading, setDiscoveredSshKeysLoading] = @@ -107,6 +126,7 @@ export function ConfigurationModal({ var_ns: "", // Identity + var_ctid: "", var_hostname: slug, var_pw: "", var_tags: "community-script", @@ -129,6 +149,7 @@ export function ConfigurationModal({ var_verbose: "no", var_apt_cacher: "no", var_apt_cacher_ip: "", + var_github_token: "", // Storage var_container_storage: "", @@ -138,6 +159,30 @@ export function ConfigurationModal({ } }, [actualScript, server, mode, resources, slug]); + // Load persistent APT proxy settings for advanced mode + useEffect(() => { + if (!isOpen || mode !== "advanced") return; + let cancelled = false; + fetch("/api/settings/apt-proxy") + .then((res) => res.json() as Promise<{ enabled: boolean; ip: string }>) + .then((data) => { + if (cancelled) return; + if (data.enabled && data.ip) { + setAdvancedVars((prev) => ({ + ...prev, + var_apt_cacher: "yes", + var_apt_cacher_ip: data.ip, + })); + } + }) + .catch(() => { + /* ignore */ + }); + return () => { + cancelled = true; + }; + }, [isOpen, mode]); + // Discover SSH keys on the Proxmox host when advanced mode is open useEffect(() => { if (!server?.id || !isOpen || mode !== "advanced") { @@ -367,10 +412,16 @@ export function ConfigurationModal({ // Advanced mode: all vars envVars = { ...advancedVars }; - // If network mode is static and var_ip is set, replace var_net with the CIDR - if (envVars.var_net === "static" && envVars.var_ip) { - envVars.var_net = envVars.var_ip as string; - delete envVars.var_ip; // Remove the temporary var_ip + // If network mode is static and var_ip is set, replace var_net with the CIDR. + // If var_ip is missing (user left it blank), remove var_net so the script + // defaults to dhcp rather than receiving the literal string "static". + if (envVars.var_net === "static") { + if (envVars.var_ip) { + envVars.var_net = envVars.var_ip as string; + } else { + delete envVars.var_net; + } + delete envVars.var_ip; } // Format password correctly: if var_pw is set, format it as "-password " @@ -438,951 +489,1156 @@ export function ConfigurationModal({ } }; + const applyPreset = ( + preset: NonNullable["presets"][number], + ) => { + setAdvancedVars((prev) => ({ + ...prev, + ...(preset.cpu != null && { var_cpu: preset.cpu }), + ...(preset.ram != null && { var_ram: preset.ram }), + ...(preset.disk != null && { var_disk: preset.disk }), + var_unprivileged: preset.privileged ? 0 : 1, + ...(preset.bridge && { var_brg: preset.bridge }), + ...(preset.vlan && { var_vlan: preset.vlan }), + ...(preset.dns && { var_ns: preset.dns }), + var_ssh: preset.ssh ? "yes" : "no", + var_nesting: preset.nesting ? 1 : 0, + var_fuse: preset.fuse ? 1 : 0, + ...(preset.apt_proxy_on && + preset.apt_proxy_addr && { + var_apt_cacher: "yes", + var_apt_cacher_ip: preset.apt_proxy_addr, + }), + })); + }; + + const saveCurrentAsPreset = () => { + if (!server?.id || !presetName.trim()) return; + createPresetMutation.mutate({ + serverId: server.id, + name: presetName.trim(), + cpu: + typeof advancedVars.var_cpu === "number" + ? advancedVars.var_cpu + : undefined, + ram: + typeof advancedVars.var_ram === "number" + ? advancedVars.var_ram + : undefined, + disk: + typeof advancedVars.var_disk === "number" + ? advancedVars.var_disk + : undefined, + privileged: advancedVars.var_unprivileged === 0, + bridge: + typeof advancedVars.var_brg === "string" + ? advancedVars.var_brg + : undefined, + vlan: + typeof advancedVars.var_vlan === "string" + ? advancedVars.var_vlan + : undefined, + dns: + typeof advancedVars.var_ns === "string" + ? advancedVars.var_ns + : undefined, + ssh: advancedVars.var_ssh === "yes", + nesting: advancedVars.var_nesting === 1, + fuse: advancedVars.var_fuse === 1, + aptProxyAddr: + typeof advancedVars.var_apt_cacher_ip === "string" + ? advancedVars.var_apt_cacher_ip + : undefined, + aptProxyOn: advancedVars.var_apt_cacher === "yes", + }); + }; + if (!isOpen) return null; const rootfsStorages = rootfsStoragesData?.storages ?? []; const templateStorages = templateStoragesData?.storages ?? []; return ( -
-
- {/* Header */} -
-

- {mode === "default" - ? "Default Configuration" - : "Advanced Configuration"} -

- -
+ + + + +
- {/* Content */} -
- {mode === "default" ? ( - /* Default Mode */ -
-
- - - {rootfsStorages.length === 0 && ( -

- Could not fetch storages. Script will use default selection. -

+ {/* Content */} +
+ {mode === "default" ? ( + /* Default Mode */ +
+
+ + + {rootfsStorages.length === 0 && ( +

+ Could not fetch storages. Script will use default + selection. +

+ )} +
+ +
+

+ Default Values +

+
+

Hostname: {slug}

+

Bridge: vmbr0

+

Network: DHCP

+

IPv6: Auto

+

SSH: Disabled

+

Nesting: Enabled

+

CPU: {resources?.cpu ?? 1}

+

RAM: {resources?.ram ?? 1024} MB

+

Disk: {resources?.hdd ?? 4} GB

+
+
+
+ ) : ( + /* Advanced Mode */ +
+ {/* Server Presets */} + {(presetsData?.presets?.length ?? 0) > 0 && ( +
+

+ Load Preset +

+
+ {presetsData?.presets.map((preset) => ( +
+ + +
+ ))} +
+
)} -
-
-

- Default Values -

-
-

Hostname: {slug}

-

Bridge: vmbr0

-

Network: DHCP

-

IPv6: Auto

-

SSH: Disabled

-

Nesting: Enabled

-

CPU: {resources?.cpu ?? 1}

-

RAM: {resources?.ram ?? 1024} MB

-

Disk: {resources?.hdd ?? 4} GB

-
-
-
- ) : ( - /* Advanced Mode */ -
- {/* Resources */} -
-

- Resources -

-
-
- - - updateAdvancedVar( - "var_cpu", - parseInt(e.target.value) || 1, - ) - } - className={errors.var_cpu ? "border-destructive" : ""} - /> - {errors.var_cpu && ( -

- {errors.var_cpu} -

- )} -
-
- - - updateAdvancedVar( - "var_ram", - parseInt(e.target.value) || 1024, - ) - } - className={errors.var_ram ? "border-destructive" : ""} - /> - {errors.var_ram && ( -

- {errors.var_ram} -

- )} -
-
- - - updateAdvancedVar( - "var_disk", - parseInt(e.target.value) || 4, - ) - } - className={errors.var_disk ? "border-destructive" : ""} - /> - {errors.var_disk && ( -

- {errors.var_disk} -

- )} -
-
- - -
-
-
- - {/* Network */} -
-

- Network -

-
-
- - setPresetName(e.target.value)} + placeholder="Preset name..." + className="max-w-[200px]" + onKeyDown={(e) => + e.key === "Enter" && saveCurrentAsPreset() } - }} - className="border-input bg-background text-foreground focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none" + /> + + + + ) : ( +
- {(advancedVars.var_net === "static" || - (typeof advancedVars.var_net === "string" && - advancedVars.var_net.includes("/"))) && ( + Save Current as Preset + + )} +
+ + {/* Resources */} +
+

+ Resources +

+
+ updateAdvancedVar( + "var_cpu", + parseInt(e.target.value) || 1, + ) + } + className={errors.var_cpu ? "border-destructive" : ""} + /> + {errors.var_cpu && ( +

+ {errors.var_cpu} +

+ )} +
+
+ + + updateAdvancedVar( + "var_ram", + parseInt(e.target.value) || 1024, + ) + } + className={errors.var_ram ? "border-destructive" : ""} + /> + {errors.var_ram && ( +

+ {errors.var_ram} +

+ )} +
+
+ + + updateAdvancedVar( + "var_disk", + parseInt(e.target.value) || 4, + ) + } + className={errors.var_disk ? "border-destructive" : ""} + /> + {errors.var_disk && ( +

+ {errors.var_disk} +

+ )} +
+
+ + +
+
+
+ + {/* Network */} +
+

+ Network +

+
+
+ +
- )} -
- - - updateAdvancedVar("var_brg", e.target.value) - } - placeholder="vmbr0" - /> -
-
- - - updateAdvancedVar("var_gateway", e.target.value) - } - placeholder="Auto" - className={errors.var_gateway ? "border-destructive" : ""} - /> - {errors.var_gateway && ( -

- {errors.var_gateway} -

+ {(advancedVars.var_net === "static" || + (typeof advancedVars.var_net === "string" && + advancedVars.var_net.includes("/"))) && ( +
+ + { + // Store in var_ip temporarily, will be moved to var_net on confirm + updateAdvancedVar("var_ip", e.target.value); + }} + placeholder="10.10.10.1/24" + className={errors.var_ip ? "border-destructive" : ""} + /> + {errors.var_ip && ( +

+ {errors.var_ip} +

+ )} +
)} -
-
- - -
- {advancedVars.var_ipv6_method === "static" && (
- updateAdvancedVar("var_ipv6_static", e.target.value) + updateAdvancedVar("var_brg", e.target.value) } - placeholder="2001:db8::1/64" + placeholder="vmbr0" + /> +
+
+ + + updateAdvancedVar("var_gateway", e.target.value) + } + placeholder="Auto" className={ - errors.var_ipv6_static ? "border-destructive" : "" + errors.var_gateway ? "border-destructive" : "" } /> - {errors.var_ipv6_static && ( + {errors.var_gateway && (

- {errors.var_ipv6_static} + {errors.var_gateway}

)}
- )} -
- - - updateAdvancedVar( - "var_vlan", - e.target.value ? parseInt(e.target.value) : "", - ) - } - placeholder="None" - className={errors.var_vlan ? "border-destructive" : ""} - /> - {errors.var_vlan && ( -

- {errors.var_vlan} -

+
+ + +
+ {advancedVars.var_ipv6_method === "static" && ( +
+ + + updateAdvancedVar("var_ipv6_static", e.target.value) + } + placeholder="2001:db8::1/64" + className={ + errors.var_ipv6_static ? "border-destructive" : "" + } + /> + {errors.var_ipv6_static && ( +

+ {errors.var_ipv6_static} +

+ )} +
)} -
-
- - - updateAdvancedVar( - "var_mtu", - e.target.value ? parseInt(e.target.value) : 1500, - ) - } - placeholder="1500" - className={errors.var_mtu ? "border-destructive" : ""} - /> - {errors.var_mtu && ( -

- {errors.var_mtu} -

- )} -
-
- - - updateAdvancedVar("var_mac", e.target.value) - } - placeholder="Auto" - className={errors.var_mac ? "border-destructive" : ""} - /> - {errors.var_mac && ( -

- {errors.var_mac} -

- )} -
-
- - - updateAdvancedVar("var_ns", e.target.value) - } - placeholder="Auto" - className={errors.var_ns ? "border-destructive" : ""} - /> - {errors.var_ns && ( -

- {errors.var_ns} -

- )} -
-
-
- - {/* Identity & Metadata */} -
-

- Identity & Metadata -

-
-
- - - updateAdvancedVar("var_hostname", e.target.value) - } - placeholder={slug} - /> -
-
- - - updateAdvancedVar("var_pw", e.target.value) - } - placeholder="Random (empty = auto-login)" - /> -
-
- - - updateAdvancedVar("var_tags", e.target.value) - } - placeholder="e.g. tag1; tag2" - /> -
-
-
- - {/* SSH Access */} -
-

- SSH Access -

-
-
- - -
-
- - {discoveredSshKeysLoading && ( -

- Detecting SSH keys... -

- )} - {discoveredSshKeysError && !discoveredSshKeysLoading && ( -

- Could not detect keys on host -

- )} - {discoveredSshKeys.length > 0 && - !discoveredSshKeysLoading && ( -
- - -
+
+ + + updateAdvancedVar( + "var_vlan", + e.target.value ? parseInt(e.target.value) : "", + ) + } + placeholder="None" + className={errors.var_vlan ? "border-destructive" : ""} + /> + {errors.var_vlan && ( +

+ {errors.var_vlan} +

)} - - updateAdvancedVar( - "var_ssh_authorized_key", - e.target.value, - ) - } - placeholder="Or paste a public key: ssh-rsa AAAA..." - /> +
+
+ + + updateAdvancedVar( + "var_mtu", + e.target.value ? parseInt(e.target.value) : 1500, + ) + } + placeholder="1500" + className={errors.var_mtu ? "border-destructive" : ""} + /> + {errors.var_mtu && ( +

+ {errors.var_mtu} +

+ )} +
+
+ + + updateAdvancedVar("var_mac", e.target.value) + } + placeholder="Auto" + className={errors.var_mac ? "border-destructive" : ""} + /> + {errors.var_mac && ( +

+ {errors.var_mac} +

+ )} +
+
+ + + updateAdvancedVar("var_ns", e.target.value) + } + placeholder="Auto" + className={errors.var_ns ? "border-destructive" : ""} + /> + {errors.var_ns && ( +

+ {errors.var_ns} +

+ )} +
-
- {/* Container Features */} -
-

- Container Features -

-
-
- - -
-
- - -
-
- - -
-
- - -

- For Tailscale, WireGuard, OpenVPN -

-
-
- - -
-
- - - updateAdvancedVar("var_mount_fs", e.target.value) - } - placeholder="nfs,cifs" - /> -
-
- - -
-
-
- - {/* System Configuration */} -
-

- System Configuration -

-
-
- - - updateAdvancedVar("var_timezone", e.target.value) - } - placeholder="System" - /> -
-
- - -
-
- - -
-
- - - updateAdvancedVar("var_apt_cacher_ip", e.target.value) - } - placeholder="192.168.1.10 or apt-cacher.internal" - className={ - errors.var_apt_cacher_ip ? "border-destructive" : "" - } - /> - {errors.var_apt_cacher_ip && ( -

- {errors.var_apt_cacher_ip} -

- )} -
-
-
- - {/* Storage Selection */} -
-

- Storage Selection -

-
-
- - - {rootfsStorages.length === 0 && ( + {/* Identity & Metadata */} +
+

+ Identity & Metadata +

+
+
+ + + updateAdvancedVar("var_ctid", e.target.value) + } + placeholder="Auto (next available)" + />

- Could not fetch storages. Leave empty for auto - selection. + Leave empty for auto-assignment

- )} +
+
+ + + updateAdvancedVar("var_hostname", e.target.value) + } + placeholder={slug} + /> +
+
+ + + updateAdvancedVar("var_pw", e.target.value) + } + placeholder="Random (empty = auto-login)" + /> +
+
+ + + updateAdvancedVar("var_tags", e.target.value) + } + placeholder="e.g. tag1; tag2" + /> +
-
- - - {templateStorages.length === 0 && ( +
+ + {/* SSH Access */} +
+

+ SSH Access +

+
+
+ + +
+
+ + {discoveredSshKeysLoading && ( +

+ Detecting SSH keys... +

+ )} + {discoveredSshKeysError && !discoveredSshKeysLoading && ( +

+ Could not detect keys on host +

+ )} + {discoveredSshKeys.length > 0 && + !discoveredSshKeysLoading && ( +
+ + +
+ )} + + updateAdvancedVar( + "var_ssh_authorized_key", + e.target.value, + ) + } + placeholder="Or paste a public key: ssh-rsa AAAA..." + /> +
+
+
+ + {/* Container Features */} +
+

+ Container Features +

+
+
+ + +
+
+ + +
+
+ + +
+
+ +

- Could not fetch storages. Leave empty for auto - selection. + For Tailscale, WireGuard, OpenVPN

- )} +
+
+ + +
+
+ + + updateAdvancedVar("var_mount_fs", e.target.value) + } + placeholder="nfs,cifs" + /> +
+
+ + +
+
+
+ + {/* System Configuration */} +
+

+ System Configuration +

+
+
+ + + updateAdvancedVar("var_timezone", e.target.value) + } + placeholder="System" + /> +
+
+ + +
+
+ + +
+
+ + + updateAdvancedVar("var_apt_cacher_ip", e.target.value) + } + placeholder="192.168.1.10 or apt-cacher.internal" + className={ + errors.var_apt_cacher_ip ? "border-destructive" : "" + } + /> + {errors.var_apt_cacher_ip && ( +

+ {errors.var_apt_cacher_ip} +

+ )} +
+
+ + + updateAdvancedVar("var_github_token", e.target.value) + } + placeholder="ghp_... (optional)" + autoComplete="off" + /> +

+ Passed as GITHUB_TOKEN to avoid API rate limits +

+
+
+
+ + {/* Storage Selection */} +
+

+ Storage Selection +

+
+
+ + + {rootfsStorages.length === 0 && ( +

+ Could not fetch storages. Leave empty for auto + selection. +

+ )} +
+
+ + + {templateStorages.length === 0 && ( +

+ Could not fetch storages. Leave empty for auto + selection. +

+ )} +
+ )} + + {/* Action Buttons */} +
+ +
- )} - - {/* Action Buttons */} -
- -
-
+ ); } diff --git a/src/app/_components/ConfirmationModal.tsx b/src/app/_components/ConfirmationModal.tsx index cfe8b69..d089314 100644 --- a/src/app/_components/ConfirmationModal.tsx +++ b/src/app/_components/ConfirmationModal.tsx @@ -1,9 +1,9 @@ -'use client'; +"use client"; -import { useMemo, useState } from 'react'; -import { Button } from './ui/button'; -import { AlertTriangle, Info } from 'lucide-react'; -import { useRegisterModal } from './modal/ModalStackProvider'; +import { useMemo, useState } from "react"; +import { Button } from "./ui/button"; +import { AlertTriangle, Info } from "lucide-react"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; interface ConfirmationModalProps { isOpen: boolean; @@ -11,7 +11,7 @@ interface ConfirmationModalProps { onConfirm: () => void; title: string; message: string; - variant: 'simple' | 'danger'; + variant: "simple" | "danger"; confirmText?: string; // What the user must type for danger variant confirmButtonText?: string; cancelButtonText?: string; @@ -25,14 +25,18 @@ export function ConfirmationModal({ message, variant, confirmText, - confirmButtonText = 'Confirm', - cancelButtonText = 'Cancel' + confirmButtonText = "Confirm", + cancelButtonText = "Cancel", }: ConfirmationModalProps) { - const [typedText, setTypedText] = useState(''); - const isDanger = variant === 'danger'; + const [typedText, setTypedText] = useState(""); + const isDanger = variant === "danger"; const allowEscape = useMemo(() => !isDanger, [isDanger]); - useRegisterModal(isOpen, { id: 'confirmation-modal', allowEscape, onClose }); + const zIndex = useRegisterModal(isOpen, { + id: "confirmation-modal", + allowEscape, + onClose, + }); if (!isOpen) return null; const isConfirmEnabled = isDanger ? typedText === confirmText : true; @@ -40,75 +44,84 @@ export function ConfirmationModal({ const handleConfirm = () => { if (isConfirmEnabled) { onConfirm(); - setTypedText(''); // Reset for next time + setTypedText(""); // Reset for next time } }; const handleClose = () => { onClose(); - setTypedText(''); // Reset when closing + setTypedText(""); // Reset when closing }; return ( -
-
- {/* Header */} -
-
- {isDanger ? ( - - ) : ( - - )} -

{title}

-
-
- - {/* Content */} -
-

- {message} -

- - {/* Type-to-confirm input for danger variant */} - {isDanger && confirmText && ( -
- - setTypedText(e.target.value)} - className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring" - placeholder={`Type "${confirmText}" here`} - autoComplete="off" - /> + +
+
+ {/* Header */} +
+
+ {isDanger ? ( + + ) : ( + + )} +

+ {title} +

- )} +
- {/* Action Buttons */} -
- - + {/* Content */} +
+

{message}

+ + {/* Type-to-confirm input for danger variant */} + {isDanger && confirmText && ( +
+ + setTypedText(e.target.value)} + className="border-input bg-background text-foreground placeholder:text-muted-foreground focus:ring-ring focus:border-ring w-full rounded-md border px-3 py-2 focus:ring-2 focus:outline-none" + placeholder={`Type "${confirmText}" here`} + autoComplete="off" + /> +
+ )} + + {/* Action Buttons */} +
+ + +
-
+
); } diff --git a/src/app/_components/DiffViewer.tsx b/src/app/_components/DiffViewer.tsx index 48a5b61..3ba9660 100644 --- a/src/app/_components/DiffViewer.tsx +++ b/src/app/_components/DiffViewer.tsx @@ -1,7 +1,7 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import { api } from '~/trpc/react'; +import { useState } from "react"; +import { api } from "~/trpc/react"; interface DiffViewerProps { scriptSlug: string; @@ -10,13 +10,18 @@ interface DiffViewerProps { onClose: () => void; } -export function DiffViewer({ scriptSlug, filePath, isOpen, onClose }: DiffViewerProps) { +export function DiffViewer({ + scriptSlug, + filePath, + isOpen, + onClose, +}: DiffViewerProps) { const [isLoading, setIsLoading] = useState(false); // Get diff content const { data: diffData, refetch } = api.scripts.getScriptDiff.useQuery( { slug: scriptSlug, filePath }, - { enabled: isOpen && !!scriptSlug && !!filePath } + { enabled: isOpen && !!scriptSlug && !!filePath }, ); const handleBackdropClick = (e: React.MouseEvent) => { @@ -36,27 +41,31 @@ export function DiffViewer({ scriptSlug, filePath, isOpen, onClose }: DiffViewer const renderDiffLine = (line: string, index: number) => { const lineNumberMatch = /^([+-]?\d+):/.exec(line); const lineNumber = lineNumberMatch?.[1]; - const content = line.replace(/^[+-]?\d+:\s*/, ''); - const isAdded = line.startsWith('+'); - const isRemoved = line.startsWith('-'); + const content = line.replace(/^[+-]?\d+:\s*/, ""); + const isAdded = line.startsWith("+"); + const isRemoved = line.startsWith("-"); return (
-
+
{lineNumber}
- - {isAdded ? '+' : isRemoved ? '-' : ' '} + + {isAdded ? "+" : isRemoved ? "-" : " "} {content}
@@ -66,82 +75,117 @@ export function DiffViewer({ scriptSlug, filePath, isOpen, onClose }: DiffViewer return (
-
+
{/* Header */} -
+
-

Script Diff

-

{filePath}

+

Script Diff

+

{filePath}

{/* Legend */} -
+
-
+
Added (Remote)
-
+
Removed (Local)
-
+
Unchanged
{/* Diff Content */} -
+
{diffData?.success ? ( diffData.diff ? ( -
- {diffData.diff.split('\n').map((line, index) => - line.trim() ? renderDiffLine(line, index) : null - )} +
+ {diffData.diff + .split("\n") + .map((line, index) => + line.trim() ? renderDiffLine(line, index) : null, + )}
) : ( -
- - +
+ +

No differences found

-

The local and remote files are identical

+

+ The local and remote files are identical +

) ) : diffData?.error ? ( -
- - +
+ +

Error loading diff

{diffData.error}

) : ( -
-
+
+

Loading diff...

)} diff --git a/src/app/_components/DownloadedScriptsTab.tsx b/src/app/_components/DownloadedScriptsTab.tsx index 3a38020..3b7b1d8 100644 --- a/src/app/_components/DownloadedScriptsTab.tsx +++ b/src/app/_components/DownloadedScriptsTab.tsx @@ -1,6 +1,12 @@ "use client"; -import React, { useState, useRef, useEffect } from "react"; +import React, { + useState, + useRef, + useEffect, + useCallback, + useMemo, +} from "react"; import { api } from "~/trpc/react"; import { ScriptCard } from "./ScriptCard"; import { ScriptCardList } from "./ScriptCardList"; @@ -12,21 +18,10 @@ import { ConfirmationModal } from "./ConfirmationModal"; import { Button } from "./ui/button"; import { RefreshCw } from "lucide-react"; import type { ScriptCard as ScriptCardType } from "~/types/script"; -import type { Server } from "~/types/server"; import { getDefaultFilters, mergeFiltersWithDefaults } from "./filterUtils"; +import { useShell } from "./ShellContext"; -interface DownloadedScriptsTabProps { - onInstallScript?: ( - scriptPath: string, - scriptName: string, - mode?: "local" | "ssh", - server?: Server, - ) => void; -} - -export function DownloadedScriptsTab({ - onInstallScript, -}: DownloadedScriptsTabProps) { +export function DownloadedScriptsTab() { const [selectedSlug, setSelectedSlug] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedCategory, setSelectedCategory] = useState(null); @@ -34,6 +29,8 @@ export function DownloadedScriptsTab({ const [filters, setFilters] = useState(getDefaultFilters()); const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false); const [isLoadingFilters, setIsLoadingFilters] = useState(true); + const filtersInitRef = useRef(false); + const viewModeInitRef = useRef(false); const [updateAllConfirmOpen, setUpdateAllConfirmOpen] = useState(false); const [updateResult, setUpdateResult] = useState<{ successCount: number; @@ -53,35 +50,37 @@ export function DownloadedScriptsTab({ data: localScriptsData, isLoading: localLoading, error: localError, + refetch: refetchLocal, } = api.scripts.getAllDownloadedScripts.useQuery(); const { data: scriptData } = api.scripts.getScriptBySlug.useQuery( { slug: selectedSlug ?? "" }, { enabled: !!selectedSlug }, ); - const loadMultipleScriptsMutation = api.scripts.loadMultipleScripts.useMutation({ - onSuccess: (data) => { - void utils.scripts.getAllDownloadedScripts.invalidate(); - void utils.scripts.getScriptCardsWithCategories.invalidate(); - setUpdateResult({ - successCount: data.successful?.length ?? 0, - failCount: data.failed?.length ?? 0, - failed: (data.failed ?? []).map((f) => ({ - slug: f.slug, - error: f.error ?? "Unknown error", - })), - }); - setTimeout(() => setUpdateResult(null), 8000); - }, - onError: (error) => { - setUpdateResult({ - successCount: 0, - failCount: 1, - failed: [{ slug: "Request failed", error: error.message }], - }); - setTimeout(() => setUpdateResult(null), 8000); - }, - }); + const loadMultipleScriptsMutation = + api.scripts.loadMultipleScripts.useMutation({ + onSuccess: (data) => { + void utils.scripts.getAllDownloadedScripts.invalidate(); + void utils.scripts.getScriptCardsWithCategories.invalidate(); + setUpdateResult({ + successCount: data.successful?.length ?? 0, + failCount: data.failed?.length ?? 0, + failed: (data.failed ?? []).map((f) => ({ + slug: f.slug, + error: f.error ?? "Unknown error", + })), + }); + setTimeout(() => setUpdateResult(null), 8000); + }, + onError: (error) => { + setUpdateResult({ + successCount: 0, + failCount: 1, + failed: [{ slug: "Request failed", error: error.message }], + }); + setTimeout(() => setUpdateResult(null), 8000); + }, + }); // Load SAVE_FILTER setting, saved filters, and view mode on component mount useEffect(() => { @@ -135,6 +134,11 @@ export function DownloadedScriptsTab({ // Save filters when they change (if SAVE_FILTER is enabled) useEffect(() => { if (!saveFiltersEnabled || isLoadingFilters) return; + // Skip the first fire after load — values haven't changed yet + if (!filtersInitRef.current) { + filtersInitRef.current = true; + return; + } const saveFilters = async () => { try { @@ -158,6 +162,11 @@ export function DownloadedScriptsTab({ // Save view mode when it changes useEffect(() => { if (isLoadingFilters) return; + // Skip the first fire after load — value hasn't changed yet + if (!viewModeInitRef.current) { + viewModeInitRef.current = true; + return; + } const saveViewMode = async () => { try { @@ -336,13 +345,11 @@ export function DownloadedScriptsTab({ scripts = scripts.filter((script) => { if (!script) return false; const scriptType = (script.type ?? "").toLowerCase(); - - // Map non-standard types to standard categories - const mappedType = scriptType === "turnkey" ? "ct" : scriptType; - - return filters.selectedTypes.some( - (type) => type.toLowerCase() === mappedType, - ); + return filters.selectedTypes.some((type) => { + const t = type.toLowerCase(); + if (t === "ct") return scriptType === "ct" || scriptType === "lxc"; + return scriptType === t; + }); }); } @@ -376,6 +383,21 @@ export function DownloadedScriptsTab({ compareValue = (a.name ?? "").localeCompare(b.name ?? ""); } break; + case "updated": + // Sort by date_created as a proxy (JSON doesn't have updated date) + // For downloaded scripts, treat more recent date_created as "recently updated" + const aUpdated = a?.date_created ?? ""; + const bUpdated = b?.date_created ?? ""; + if (aUpdated && bUpdated) { + compareValue = aUpdated.localeCompare(bUpdated); + } else if (aUpdated && !bUpdated) { + compareValue = -1; + } else if (!aUpdated && bUpdated) { + compareValue = 1; + } else { + compareValue = (a.name ?? "").localeCompare(b.name ?? ""); + } + break; default: compareValue = (a.name ?? "").localeCompare(b.name ?? ""); } @@ -432,6 +454,61 @@ export function DownloadedScriptsTab({ setSelectedSlug(null); }; + const { open: openShell } = useShell(); + const { data: installedScriptsData } = + api.installedScripts.getAllInstalledScripts.useQuery(); + + const normalizeSlug = (s?: string): string => + (s ?? "") + .toLowerCase() + .replace(/\.(sh|bash|py|js|ts)$/i, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + + const installedContainerMap = useMemo((): Map => { + const map = new Map(); + const scripts = (installedScriptsData as any)?.scripts ?? []; + for (const s of scripts) { + if (!s.container_id || s.status === "failed") continue; + const key = normalizeSlug(s.script_name); + if (!map.has(key)) map.set(key, s); + } + return map; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [installedScriptsData]); + + const handleShellClick = useCallback( + (script: ScriptCardType) => { + const container = installedContainerMap.get(normalizeSlug(script.slug)); + if (!container) return; + const server = + container.server_id && container.server_user + ? { + id: container.server_id, + name: container.server_name ?? "", + ip: container.server_ip ?? "", + user: container.server_user, + password: container.server_password ?? undefined, + auth_type: (container.server_auth_type ?? "password") as any, + ssh_key: container.server_ssh_key ?? undefined, + ssh_key_passphrase: + container.server_ssh_key_passphrase ?? undefined, + ssh_port: container.server_ssh_port ?? 22, + created_at: null, + updated_at: null, + } + : undefined; + openShell({ + containerId: container.container_id, + containerName: container.script_name, + server, + containerType: container.is_vm ? "vm" : "lxc", + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [installedContainerMap, openShell], + ); + const handleUpdateAllClick = () => { setUpdateResult(null); setUpdateAllConfirmOpen(true); @@ -541,6 +618,19 @@ export function DownloadedScriptsTab({
{/* Update all downloaded scripts */}
+
@@ -657,6 +753,11 @@ export function DownloadedScriptsTab({ key={uniqueKey} script={script} onClick={handleCardClick} + onShell={ + installedContainerMap.has(normalizeSlug(script.slug)) + ? () => handleShellClick(script) + : undefined + } /> ); })} @@ -677,6 +778,11 @@ export function DownloadedScriptsTab({ key={uniqueKey} script={script} onClick={handleCardClick} + onShell={ + installedContainerMap.has(normalizeSlug(script.slug)) + ? () => handleShellClick(script) + : undefined + } /> ); })} @@ -687,7 +793,8 @@ export function DownloadedScriptsTab({ script={scriptData?.success ? scriptData.script : null} isOpen={isModalOpen} onClose={handleCloseModal} - onInstallScript={onInstallScript} + orderedSlugs={filteredScripts.map((s) => s.slug)} + onSelectSlug={(slug) => setSelectedSlug(slug)} /> { if (isOpen) { @@ -36,54 +40,63 @@ export function ErrorModal({ if (!isOpen) return null; return ( -
-
- {/* Header */} -
-
- {type === 'success' ? ( - - ) : ( - + +
+
+ {/* Header */} +
+
+ {type === "success" ? ( + + ) : ( + + )} +

{title}

+
+
+ + {/* Content */} +
+

{message}

+ {details && ( +
+

+ {type === "success" ? "Details:" : "Error Details:"} +

+
+                  {details}
+                
+
)} -

{title}

+
+ + {/* Footer */} +
+
- - {/* Content */} -
-

{message}

- {details && ( -
-

- {type === 'success' ? 'Details:' : 'Error Details:'} -

-
-                {details}
-              
-
- )} -
- - {/* Footer */} -
- -
-
+ ); } diff --git a/src/app/_components/ExecutionModeModal.tsx b/src/app/_components/ExecutionModeModal.tsx index fce78ca..32eb35b 100644 --- a/src/app/_components/ExecutionModeModal.tsx +++ b/src/app/_components/ExecutionModeModal.tsx @@ -1,32 +1,47 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import type { Server } from '../../types/server'; -import type { Script } from '../../types/script'; -import { Button } from './ui/button'; -import { ColorCodedDropdown } from './ColorCodedDropdown'; -import { SettingsModal } from './SettingsModal'; -import { ConfigurationModal, type EnvVars } from './ConfigurationModal'; -import { useRegisterModal } from './modal/ModalStackProvider'; +"use client"; +import { useState, useEffect } from "react"; +import type { Server } from "../../types/server"; +import type { Script } from "../../types/script"; +import { Button } from "./ui/button"; +import { ColorCodedDropdown } from "./ColorCodedDropdown"; +import { SettingsModal } from "./SettingsModal"; +import { ConfigurationModal, type EnvVars } from "./ConfigurationModal"; +import { useRegisterModal, ModalPortal } from "./modal/ModalStackProvider"; interface ExecutionModeModalProps { isOpen: boolean; onClose: () => void; - onExecute: (mode: 'local' | 'ssh', server?: Server, envVars?: EnvVars) => void; + onExecute: ( + mode: "local" | "ssh", + server?: Server, + envVars?: EnvVars, + ) => void; scriptName: string; script?: Script | null; } -export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName, script }: ExecutionModeModalProps) { - useRegisterModal(isOpen, { id: 'execution-mode-modal', allowEscape: true, onClose }); +export function ExecutionModeModal({ + isOpen, + onClose, + onExecute, + scriptName, + script, +}: ExecutionModeModalProps) { + const zIndex = useRegisterModal(isOpen, { + id: "execution-mode-modal", + allowEscape: true, + onClose, + }); const [servers, setServers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [selectedServer, setSelectedServer] = useState(null); const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [configModalOpen, setConfigModalOpen] = useState(false); - const [configMode, setConfigMode] = useState<'default' | 'advanced'>('default'); + const [configMode, setConfigMode] = useState<"default" | "advanced">( + "default", + ); useEffect(() => { if (isOpen) { @@ -52,26 +67,26 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName, scr setLoading(true); setError(null); try { - const response = await fetch('/api/servers'); + const response = await fetch("/api/servers"); if (!response.ok) { - throw new Error('Failed to fetch servers'); + throw new Error("Failed to fetch servers"); } const data = await response.json(); // Sort servers by name alphabetically - const sortedServers = (data as Server[]).sort((a, b) => - (a.name ?? '').localeCompare(b.name ?? '') + const sortedServers = (data as Server[]).sort((a, b) => + (a.name ?? "").localeCompare(b.name ?? ""), ); setServers(sortedServers); } catch (err) { - setError(err instanceof Error ? err.message : 'An error occurred'); + setError(err instanceof Error ? err.message : "An error occurred"); } finally { setLoading(false); } }; - const handleConfigModeSelect = (mode: 'default' | 'advanced') => { + const handleConfigModeSelect = (mode: "default" | "advanced") => { if (!selectedServer) { - setError('Please select a server first'); + setError("Please select a server first"); return; } setConfigMode(mode); @@ -81,7 +96,7 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName, scr const handleConfigConfirm = (envVars: EnvVars) => { if (!selectedServer) return; setConfigModalOpen(false); - onExecute('ssh', selectedServer, envVars); + onExecute("ssh", selectedServer, envVars); onClose(); }; @@ -90,158 +105,127 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName, scr setError(null); // Clear error when server is selected }; - if (!isOpen) return null; return ( <> -
-
- {/* Header */} -
-

Select Server

- -
- - {/* Content */} -
- {error && ( -
-
-
- - - -
-
-

{error}

-
-
-
- )} - - {loading ? ( -
-
-

Loading servers...

-
- ) : servers.length === 0 ? ( -
-

No servers configured

-

Add servers in Settings to execute scripts

- -
- ) : servers.length === 1 ? ( - /* Single Server Confirmation View */ -
-
-

- Install Script Confirmation -

-

- Do you want to install "{scriptName}" on the following server? -

-
- -
-
+ + + +
+ + {/* Content */} +
+ {error && ( +
+
-
+ + +
-
-

- {selectedServer?.name ?? 'Unnamed Server'} -

-

- {selectedServer?.ip} -

+
+

{error}

+ )} - {/* Configuration Mode Selection */} -
-

- Choose configuration mode: + {loading ? ( +

+
+

+ Loading servers...

-
- - -
- - {/* Action Buttons */} -
+ ) : servers.length === 0 ? ( +
+

No servers configured

+

+ Add servers in Settings to execute scripts +

-
- ) : ( - /* Multiple Servers Selection View */ -
-
-

- Select server to execute "{scriptName}" -

-
+ ) : servers.length === 1 ? ( + /* Single Server Confirmation View */ +
+
+

+ Install Script Confirmation +

+

+ Do you want to install "{scriptName}" on the + following server? +

+
- {/* Server Selection */} -
- - -
+
+
+
+
+
+
+

+ {selectedServer?.name ?? "Unnamed Server"} +

+

+ {selectedServer?.ip} +

+
+
+
- {/* Configuration Mode Selection - only show when server is selected */} - {selectedServer && ( -
-

+ {/* Configuration Mode Selection */} +

+

Choose configuration mode:

- )} - {/* Action Buttons */} -
- + {/* Action Buttons */} +
+ +
-
- )} + ) : ( + /* Multiple Servers Selection View */ +
+
+

+ Select server to execute "{scriptName}" +

+
+ + {/* Server Selection */} +
+ + +
+ + {/* Configuration Mode Selection - only show when server is selected */} + {selectedServer && ( +
+

+ Choose configuration mode: +

+
+ + +
+
+ )} + + {/* Action Buttons */} +
+ +
+
+ )} +
-
+ {/* Server Settings Modal */} - {/* Configuration Modal */} diff --git a/src/app/_components/FilterBar.tsx b/src/app/_components/FilterBar.tsx index eb55b47..d751ab4 100644 --- a/src/app/_components/FilterBar.tsx +++ b/src/app/_components/FilterBar.tsx @@ -6,24 +6,41 @@ import { ContextualHelpIcon } from "./ContextualHelpIcon"; import { Package, Monitor, - Wrench, - Server, FileText, Calendar, RefreshCw, Filter, GitBranch, + Layers, + TrendingUp, + Sparkles, + Clock, + TrendingDown, + FlaskConical, + Cpu, + Settings2, } from "lucide-react"; import { api } from "~/trpc/react"; import { getDefaultFilters } from "./filterUtils"; +export type QuickFilter = + | "all" + | "popular" + | "new" + | "updated" + | "unpopular" + | "dev" + | "arm"; + export interface FilterState { searchQuery: string; showUpdatable: boolean | null; // null = all, true = only updatable, false = only non-updatable selectedTypes: string[]; // Array of selected types: 'lxc', 'vm', 'addon', 'pve' selectedRepositories: string[]; // Array of selected repository URLs - sortBy: "name" | "created"; // Sort criteria (removed 'updated') + sortBy: "name" | "created" | "updated"; // Sort criteria sortOrder: "asc" | "desc"; // Sort direction + quickFilter: QuickFilter; + selectedCategory: string | null; // null = all categories } interface FilterBarProps { @@ -34,13 +51,17 @@ interface FilterBarProps { updatableCount?: number; saveFiltersEnabled?: boolean; isLoadingFilters?: boolean; + categories?: string[]; + categoryCounts?: Record; + showDevScripts?: boolean; } const SCRIPT_TYPES = [ { value: "ct", label: "LXC Container", Icon: Package }, { value: "vm", label: "Virtual Machine", Icon: Monitor }, - { value: "addon", label: "Add-on", Icon: Wrench }, - { value: "pve", label: "PVE Host", Icon: Server }, + { value: "pve", label: "PVE Tools", Icon: Settings2 }, + { value: "addon", label: "Addons", Icon: Sparkles }, + { value: "turnkey", label: "TurnKey", Icon: FileText }, ]; export function FilterBar({ @@ -51,9 +72,13 @@ export function FilterBar({ updatableCount = 0, saveFiltersEnabled = false, isLoadingFilters = false, + categories = [], + categoryCounts = {}, + showDevScripts = false, }: FilterBarProps) { const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false); const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false); + const [isCategoryDropdownOpen, setIsCategoryDropdownOpen] = useState(false); const [isMinimized, setIsMinimized] = useState(false); // Fetch enabled repositories @@ -86,8 +111,10 @@ export function FilterBar({ filters.showUpdatable !== null || filters.selectedTypes.length > 0 || filters.selectedRepositories.length > 0 || + filters.selectedCategory !== null || filters.sortBy !== "name" || - filters.sortOrder !== "asc"; + filters.sortOrder !== "asc" || + filters.quickFilter !== "all"; const getUpdatableButtonText = () => { if (filters.showUpdatable === null) return "Updatable: All"; @@ -158,6 +185,35 @@ export function FilterBar({ {/* Filter Content - Conditionally rendered based on minimized state */} {!isMinimized && !isLoadingFilters && ( <> + {/* Quick Filters */} +
+ {[ + { key: "all" as const, label: "All", Icon: Layers }, + { key: "new" as const, label: "New", Icon: Sparkles }, + { key: "updated" as const, label: "Updated", Icon: Clock }, + ...(showDevScripts + ? [{ key: "dev" as const, label: "In Dev", Icon: FlaskConical }] + : []), + { key: "arm" as const, label: "ARM", Icon: Cpu }, + ].map(({ key, label, Icon }) => { + const isActive = filters.quickFilter === key; + return ( + + ); + })} +
+ {/* Search Bar */}
@@ -319,6 +375,84 @@ export function FilterBar({ )}
+ {/* Repository Filter Buttons - Only show if more than one enabled repo */} + {categories.length > 0 && ( +
+ + + {isCategoryDropdownOpen && ( +
+
+ + {categories.map((cat) => ( + + ))} +
+
+ )} +
+ )} + {/* Repository Filter Buttons - Only show if more than one enabled repo */} {enabledRepos.length > 1 && enabledRepos.map((repo: { id: number; url: string }) => { @@ -372,7 +506,11 @@ export function FilterBar({ )} - {filters.sortBy === "name" ? "By Name" : "By Created Date"} + {filters.sortBy === "name" + ? "By Name" + : filters.sortBy === "created" + ? "By Created Date" + : "By Updated Date"} By Created Date +
)} @@ -541,12 +693,13 @@ export function FilterBar({ )} {/* Click outside to close dropdowns */} - {(isTypeDropdownOpen || isSortDropdownOpen) && ( + {(isTypeDropdownOpen || isSortDropdownOpen || isCategoryDropdownOpen) && (
{ setIsTypeDropdownOpen(false); setIsSortDropdownOpen(false); + setIsCategoryDropdownOpen(false); }} /> )} diff --git a/src/app/_components/FloatingShell.tsx b/src/app/_components/FloatingShell.tsx new file mode 100644 index 0000000..9d8a548 --- /dev/null +++ b/src/app/_components/FloatingShell.tsx @@ -0,0 +1,323 @@ +"use client"; + +import { useState, useRef, useCallback, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { + Minus, + Maximize2, + Minimize2, + X, + Terminal as TerminalIcon, + HardDrive, + ChevronUp, +} from "lucide-react"; +import { Terminal } from "./Terminal"; +import { useShell } from "./ShellContext"; +import type { ShellEntry } from "./ShellContext"; + +const WIN_W = 820; +const WIN_H = 520; +const STAGGER = 28; // px offset for each subsequent window + +// ── Single floating window ──────────────────────────────────────────────────── +function FloatingShellWindow({ + entry, + stackIndex, + onClose, + onMinimize, + isVisible, +}: { + entry: ShellEntry; + stackIndex: number; + onClose: () => void; + onMinimize: () => void; + isVisible: boolean; +}) { + const { session } = entry; + const [isMaximized, setIsMaximized] = useState(false); + const [pos, setPos] = useState<{ x: number; y: number } | null>(null); + const windowRef = useRef(null); + const dragRef = useRef<{ + startMouseX: number; + startMouseY: number; + startWinX: number; + startWinY: number; + } | null>(null); + + // Set initial staggered position on mount + useEffect(() => { + const baseX = Math.max( + 0, + Math.min( + window.innerWidth / 2 - WIN_W / 2 + stackIndex * STAGGER, + window.innerWidth - WIN_W, + ), + ); + const baseY = Math.max( + 0, + Math.min( + window.innerHeight / 2 - WIN_H / 2 + stackIndex * STAGGER, + window.innerHeight - WIN_H - 40, + ), + ); + setPos({ x: baseX, y: baseY }); + }, []); // only on mount + + const handleDragStart = useCallback( + (e: React.MouseEvent) => { + if (isMaximized) return; + if ((e.target as HTMLElement).closest("button")) return; + e.preventDefault(); + + const rect = windowRef.current?.getBoundingClientRect(); + const startWinX = rect?.left ?? window.innerWidth / 2 - WIN_W / 2; + const startWinY = rect?.top ?? window.innerHeight / 2 - WIN_H / 2; + + dragRef.current = { + startMouseX: e.clientX, + startMouseY: e.clientY, + startWinX, + startWinY, + }; + + const onMove = (ev: MouseEvent) => { + if (!dragRef.current) return; + const dx = ev.clientX - dragRef.current.startMouseX; + const dy = ev.clientY - dragRef.current.startMouseY; + setPos({ + x: Math.max( + 0, + Math.min(window.innerWidth - WIN_W, dragRef.current.startWinX + dx), + ), + y: Math.max( + 0, + Math.min(window.innerHeight - 40, dragRef.current.startWinY + dy), + ), + }); + }; + + const onUp = () => { + dragRef.current = null; + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + }; + + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + }, + [isMaximized], + ); + + const isGenericTerminal = !!session.terminal; + const isBackupTask = !isGenericTerminal && !!session.backupStorage; + + const title = + session.title ?? + (isGenericTerminal + ? (session.terminal?.scriptPath.split("/").pop() ?? "Terminal") + : isBackupTask + ? `Backup CT ${session.containerId} → ${session.backupStorage}` + : session.containerName + ? `${session.containerName}${session.containerId ? ` (${session.containerId})` : ""}` + : `Shell${session.containerId ? ` — ${session.containerId}` : ""}`); + + const terminalProps = isGenericTerminal + ? { + scriptPath: session.terminal!.scriptPath, + onClose, + mode: session.terminal!.mode ?? (session.terminal!.server ? ("ssh" as const) : ("local" as const)), + server: session.terminal!.server, + isUpdate: session.terminal!.isUpdate, + isShell: session.terminal!.isShell, + isBackup: session.terminal!.isBackup, + isClone: session.terminal!.isClone, + executeInContainer: session.terminal!.executeInContainer, + containerId: session.terminal!.containerId, + storage: session.terminal!.storage, + backupStorage: session.terminal!.backupStorage, + executionId: session.terminal!.executionId, + cloneCount: session.terminal!.cloneCount, + hostnames: session.terminal!.hostnames, + containerType: session.terminal!.containerType, + envVars: session.terminal!.envVars, + } + : isBackupTask + ? { + scriptPath: `backup-${session.containerId}-${session.backupStorage}`, + onClose, + mode: "ssh" as const, + server: session.server, + isBackup: true, + containerId: session.containerId, + storage: session.backupStorage, + } + : { + scriptPath: `shell-${session.containerId}`, + onClose, + mode: session.server ? ("ssh" as const) : ("local" as const), + server: session.server, + isShell: true, + containerId: session.containerId, + containerType: session.containerType ?? "lxc", + }; + + const headerIcon = isBackupTask ? ( + + ) : ( + + ); + + const vmHint = + !isGenericTerminal && !isBackupTask && session.containerType === "vm" && ( +

+ VM shell uses the Proxmox serial console. The VM must have a serial port + configured (e.g.{" "} + + qm set {session.containerId} -serial0 socket + + ). Detach with Ctrl+O. +

+ ); + + const windowStyle: React.CSSProperties = isMaximized + ? { inset: 0, width: "100vw", height: "100vh", borderRadius: 0 } + : pos + ? { left: pos.x, top: pos.y, width: WIN_W, height: WIN_H } + : { + left: "50%", + top: "50%", + transform: "translate(-50%, -50%)", + width: WIN_W, + height: WIN_H, + }; + + return ( +
+ {/* Header — drag handle */} +
+
+ {headerIcon} + {title} + {!isBackupTask && session.containerType === "vm" && ( + + VM + + )} +
+
+ + {isMaximized ? ( + + ) : ( + + )} + +
+
+ {vmHint} +
+ +
+
+ ); +} + +// ── Root component rendered in app-shell ────────────────────────────────────── +export function FloatingShell() { + const { sessions, close, minimize, restore } = useShell(); + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + if (!mounted) return null; + + const openSessions = sessions.filter((e) => e.state === "open"); + const minimizedSessions = sessions.filter((e) => e.state === "minimized"); + + return createPortal( + <> + {/* All windows — minimized ones are hidden via CSS to preserve terminal state */} + {sessions.map((entry, idx) => { + const isVisible = entry.state === "open"; + const stackIndex = openSessions.indexOf(entry); + return ( + = 0 ? stackIndex : idx} + isVisible={isVisible} + onClose={() => { + close(entry.id); + entry.session.onComplete?.(); + }} + onMinimize={() => minimize(entry.id)} + /> + ); + })} + + {/* Minimised pills — stacked bottom-right */} + {minimizedSessions.length > 0 && ( +
+ {minimizedSessions.map((entry) => { + const isBackup = !!entry.session.backupStorage; + const label = + entry.session.title ?? + (isBackup + ? `Backup CT ${entry.session.containerId}` + : entry.session.containerName + ? `${entry.session.containerName}${entry.session.containerId ? ` (${entry.session.containerId})` : ""}` + : entry.session.terminal + ? (entry.session.title ?? (entry.session.terminal.scriptPath.split("/").pop() ?? "Terminal")) + : `Shell${entry.session.containerId ? ` — ${entry.session.containerId}` : ""}`); + return ( + + ); + })} +
+ )} + , + document.body, + ); +} diff --git a/src/app/_components/Footer.tsx b/src/app/_components/Footer.tsx index 7d3b5e1..33ea787 100644 --- a/src/app/_components/Footer.tsx +++ b/src/app/_components/Footer.tsx @@ -1,8 +1,8 @@ -'use client'; +"use client"; -import { api } from '~/trpc/react'; -import { Button } from './ui/button'; -import { ExternalLink, FileText } from 'lucide-react'; +import { api } from "~/trpc/react"; +import { Button } from "./ui/button"; +import { ExternalLink, FileText, Heart } from "lucide-react"; interface FooterProps { onOpenReleaseNotes: () => void; @@ -12,48 +12,83 @@ export function Footer({ onOpenReleaseNotes }: FooterProps) { const { data: versionData } = api.version.getCurrentVersion.useQuery(); return ( -