diff --git a/README.md b/README.md index c537b12..0a5a143 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Edit `charts/plugin-hub/values.yaml` (or pass `--set` flags): | `service.port` | Service port | `8080` | | `plugin.displayName` | Display name in the UI | `Plugin Hub` | | `plugin.enabled` | Enable/disable the plugin | `true` | -| `everestAPIURL` | OpenEverest API server URL (in-cluster autodiscovered when empty) | `""` | +| `everestAPIURL` | OpenEverest API server URL (defaults to in-cluster DNS `everest.everest-system.svc.cluster.local:8080` when empty) | `""` | | `hubIndexURL` | Override the catalog index URL | official hub when empty | | `cacheTTLSeconds` | Catalog cache TTL (seconds) | `300` when empty | diff --git a/backend/main.go b/backend/main.go index 60ac637..aac5b1a 100644 --- a/backend/main.go +++ b/backend/main.go @@ -79,11 +79,15 @@ func everestAPIURL() string { if v := os.Getenv("EVEREST_API_URL"); v != "" { return strings.TrimRight(v, "/") } - host := os.Getenv("EVEREST_SERVICE_HOST") - port := os.Getenv("EVEREST_SERVICE_PORT") - if host != "" && port != "" { - return fmt.Sprintf("http://%s:%s", host, port) - } + // NOTE: we deliberately do NOT consult the Kubernetes-injected + // EVEREST_SERVICE_HOST / EVEREST_SERVICE_PORT env vars. Those are a + // point-in-time snapshot of the everest Service's ClusterIP taken by + // kubelet when this pod started and are never refreshed. If the everest + // Service is deleted and recreated (e.g. during a redeploy) it gets a new + // ClusterIP, the snapshot goes stale, and every request dials a dead IP + // until this pod is restarted. The stable in-cluster DNS name always + // resolves to the live ClusterIP, so we rely on it (overridable via + // EVEREST_API_URL for non-standard deployments). return defaultEverestService } diff --git a/charts/plugin-hub/values.yaml b/charts/plugin-hub/values.yaml index 305bea9..763466a 100644 --- a/charts/plugin-hub/values.yaml +++ b/charts/plugin-hub/values.yaml @@ -29,9 +29,10 @@ plugin: icon: "/icon.png" # Optional override for the Everest API URL. When left empty the backend -# auto-discovers the everest Service via Kubernetes-injected env vars -# (EVEREST_SERVICE_HOST / EVEREST_SERVICE_PORT). Only set this for local dev -# or non-standard deployments. +# targets the stable in-cluster DNS name +# (http://everest.everest-system.svc.cluster.local:8080), which always +# resolves to the live ClusterIP. Only set this for local dev or when everest +# runs under a non-standard name/namespace. everestAPIURL: "" # Override the catalog index URL. Defaults to the official OpenEverest hub. diff --git a/package-lock.json b/package-lock.json index 636fccd..dc2fdad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,49 +14,34 @@ "devDependencies": { "@openeverest/plugin-sdk": "^0.1.0", "@types/react": "^18.3.28", - "@vitejs/plugin-react-swc": "^3.5.0", + "@vitejs/plugin-react-swc": "^4.3.3", "typescript": "^5.2.2", - "vite": "^5.4.0" + "vite": "^8.2.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@openeverest/plugin-sdk": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@openeverest/plugin-sdk/-/plugin-sdk-0.1.0.tgz", + "integrity": "sha512-wWaTVh6OnhMi5NVfAcGLQ/1XXBnY0cjL50YJCm/MXS5HcvylAq9DDf27WJsQAYRqWs84REizt3Gjffsw3dejPw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" + "peerDependencies": { + "react": "^18.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], + "node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", "cpu": [ "arm64" ], @@ -67,30 +52,13 @@ "android" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", "cpu": [ "arm64" ], @@ -101,13 +69,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", "cpu": [ "x64" ], @@ -118,30 +86,13 @@ "darwin" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", "cpu": [ "x64" ], @@ -152,13 +103,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", "cpu": [ "arm" ], @@ -169,355 +120,17 @@ "linux" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@openeverest/plugin-sdk": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@openeverest/plugin-sdk/-/plugin-sdk-0.1.0.tgz", - "integrity": "sha512-wWaTVh6OnhMi5NVfAcGLQ/1XXBnY0cjL50YJCm/MXS5HcvylAq9DDf27WJsQAYRqWs84REizt3Gjffsw3dejPw==", - "dev": true, - "peerDependencies": { - "react": "^18.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", - "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", - "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", - "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", - "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", - "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", - "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", - "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", - "cpu": [ - "arm" - ], - "dev": true, "libc": [ "glibc" ], @@ -525,46 +138,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", - "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", - "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", - "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", "cpu": [ "arm64" ], @@ -576,84 +158,19 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", - "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", - "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", - "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", - "cpu": [ - "ppc64" ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", - "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", - "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", - "cpu": [ - "riscv64" - ], - "dev": true, "libc": [ "glibc" ], @@ -661,29 +178,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", - "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", - "cpu": [ - "riscv64" ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", - "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", "cpu": [ "s390x" ], @@ -695,12 +198,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", - "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", "cpu": [ "x64" ], @@ -712,43 +218,35 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", - "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", - "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ - "openbsd" - ] + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", - "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", "cpu": [ "arm64" ], @@ -757,12 +255,15 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", - "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", "cpu": [ "arm64" ], @@ -771,26 +272,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", - "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", - "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", "cpu": [ "x64" ], @@ -799,32 +289,28 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", - "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.41.tgz", - "integrity": "sha512-03nQq/082QRJJiOvp3FGbgxTGyyxMxohPTjhk/W9bD2J0tk4ukITI7goOhOO2WbaHn/lsPmo/zf8+DIXhwpgYQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.47.tgz", + "integrity": "sha512-FbsO5JcfOjfH38W/rohBRBweJeERsAuIP4f377lmkmxTcq9exjtx4SkRuZY5CdfhR2CBVwDIJegBpJDffwNsOg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.26" + "@swc/types": "^0.1.27" }, "engines": { "node": ">=10" @@ -834,18 +320,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.41", - "@swc/core-darwin-x64": "1.15.41", - "@swc/core-linux-arm-gnueabihf": "1.15.41", - "@swc/core-linux-arm64-gnu": "1.15.41", - "@swc/core-linux-arm64-musl": "1.15.41", - "@swc/core-linux-ppc64-gnu": "1.15.41", - "@swc/core-linux-s390x-gnu": "1.15.41", - "@swc/core-linux-x64-gnu": "1.15.41", - "@swc/core-linux-x64-musl": "1.15.41", - "@swc/core-win32-arm64-msvc": "1.15.41", - "@swc/core-win32-ia32-msvc": "1.15.41", - "@swc/core-win32-x64-msvc": "1.15.41" + "@swc/core-darwin-arm64": "1.15.47", + "@swc/core-darwin-x64": "1.15.47", + "@swc/core-linux-arm-gnueabihf": "1.15.47", + "@swc/core-linux-arm64-gnu": "1.15.47", + "@swc/core-linux-arm64-musl": "1.15.47", + "@swc/core-linux-ppc64-gnu": "1.15.47", + "@swc/core-linux-s390x-gnu": "1.15.47", + "@swc/core-linux-x64-gnu": "1.15.47", + "@swc/core-linux-x64-musl": "1.15.47", + "@swc/core-win32-arm64-msvc": "1.15.47", + "@swc/core-win32-ia32-msvc": "1.15.47", + "@swc/core-win32-x64-msvc": "1.15.47" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -857,9 +343,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.41.tgz", - "integrity": "sha512-kREh6J5paQFvP3i7f/4FbqRNOJREutVFVOkder4GVyCBQ39YmER55cW/y1NNjwrchzFqgYswFn0mMDCqbqKzrw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.47.tgz", + "integrity": "sha512-GsoMtan3ojGGMGFbl31mmRu5ctZ56re8grGE8mO/OHJ8O+JRkzod02fe7X6ZQ8JvamA3imkEkx/h3u+vsOgPgA==", "cpu": [ "arm64" ], @@ -874,9 +360,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.41.tgz", - "integrity": "sha512-N8B56ESFazZAWZyIkecADSPCwlLEinW7QLMEeotCpv4J7VXwfH+OLkmRL8o96UZ+1355fwHxDTS6/wK7yucvkA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.47.tgz", + "integrity": "sha512-leTi7Rx3KF4zcC637iqWgk9SoV8VXAD8ppQYXsep63px5A/UftOcxLN1pmr8Z1si/YvX90ompP/rHgpYkgwXWg==", "cpu": [ "x64" ], @@ -891,9 +377,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.41.tgz", - "integrity": "sha512-6XrId2fyle0mS5xxON8rU84mPd2Cq1kDJRj+4BnQKTd7u+2kSA6Ww+JkOP0iTNqOqt9OXhPOEAjBHAuonWcdCg==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.47.tgz", + "integrity": "sha512-hBqHuoWKKIsKmDBn9qVeWqj5GWZhtlcczVaqQmNRXsDfq+voR5CxKRfamA367QjJXtceYuliLFfEL8QsskRM2g==", "cpu": [ "arm" ], @@ -908,9 +394,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.41.tgz", - "integrity": "sha512-ynLIarxlkVnqHn1D0fKOVht6mNU5ks6lrH+MY3kkS+XFaGGgDxFZVjWKJlkYTKm3RCvBTfA8Ng5fLufXheMRKQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.47.tgz", + "integrity": "sha512-TBxvRz+B4K205TWHHZxWVxkC2RFNP/Mz3PNcECBos5PsKwxjg3QSJzdoebr0VCf0Bfh8HOPldKxAP/8XkFe9gA==", "cpu": [ "arm64" ], @@ -928,9 +414,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.41.tgz", - "integrity": "sha512-dXu/5vd4gh8symyhRF+4G7gOPkjmb4pONhh7sl+6GSiW0LOKZlfu5kXmyFbTz9smOT7jgr002qY9b1nujjXt2A==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.47.tgz", + "integrity": "sha512-3Yu3Uq/VgytqsPjTMbkPU1ExADytbdWbruJYhA584E9jrpE2Ki+R6VVPoZCeAVk1Cb7QxcRTgblw6bSa6a/R+w==", "cpu": [ "arm64" ], @@ -948,9 +434,9 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.41.tgz", - "integrity": "sha512-XGO6zVPXoPE0gf/XnI4jBbafNT13AYgoh6ns0JCSdOetI/kqVf0vhpz7NuNgAzZrMVCsmieqjPoTwViDgh4mOQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.47.tgz", + "integrity": "sha512-wfdMi5IaOaNtmh2/6geRoxIdNfqylUZFdtzTKS655y1axWfIWyx7As74vv0wVdjeCIZ3WmCI9odDd4rUttXOSQ==", "cpu": [ "ppc64" ], @@ -968,9 +454,9 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.41.tgz", - "integrity": "sha512-0WUglRwyZtW+iMi7J3iFdrCxreZZIKf4egTwEQfIYRsqFax69A0OrFj+NIoFSE03xBT/IFRrg+S8K6f9Ky+4hA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.47.tgz", + "integrity": "sha512-3hHYBY0yx8Ez7GMRrkhXHQzMdR5IZA6Wq5Ee4svlgwvSECLpnAJ9+0AimEGUFDvuLwE7nV/2+PYe8+Nm4rvNcQ==", "cpu": [ "s390x" ], @@ -988,9 +474,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.41.tgz", - "integrity": "sha512-VxkuQK59c0tHm6uJZCUrS3cyA2JhGGfdU6e41SZz0x/JS+4Sm7C1mIc97In14vkZJopEt7yXA2TouCqZDSygEA==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.47.tgz", + "integrity": "sha512-TjfhjgP/jGCfFHYC3JQPhJA1HwErbIJ9JfREDc1KNkvY6P0LodCgKVIlQ5deeTbkG7ih3bF5PHJLuLpaZjdRyQ==", "cpu": [ "x64" ], @@ -1008,9 +494,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.41.tgz", - "integrity": "sha512-/0qXIu1ZxggLuovLb22vFfKHq2AA4n6Whw5UwmVCHk4pkw7KWnPIQpMCEqUMPsNkFJig7PPp/TSYFu8ZEb2rtQ==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.47.tgz", + "integrity": "sha512-CQpS8Ge/avfjZd0UEwG/sds83Uu32deQXcV1Jo3jD0mmvQQqtYAjpsDZXugmheeAwmt+YIuoVtVHro8LMYHqsQ==", "cpu": [ "x64" ], @@ -1028,9 +514,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.41.tgz", - "integrity": "sha512-Y481sMNZM6rECh9VO4+y26N1lWEDAyxnBZskUf37fl90uHE946VHfmiVQWT0uMFOhyJJFovGTRuF4W82dwewUg==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.47.tgz", + "integrity": "sha512-0W8IKHsUTYiT7G2RqtOoVWk+89yzZikIiDUb/sCK6BmQDBhN91hQSfyUtW12jhEWLzYgcfmisfsZrmZE+84U1A==", "cpu": [ "arm64" ], @@ -1045,9 +531,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.41.tgz", - "integrity": "sha512-BAchBD5qeUzy3hiPSLJtaaoSm4blCLyYffOF1bGE4ETcV+OisqjUAwDQMJj++4bTpvMCDzwC+Bj3PmQyBCtscw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.47.tgz", + "integrity": "sha512-ZIp49d2Z4/ka2jO9otOg4hDvTdPmp86kVOgS2M5FCPI7eKKZ1W0boxWn+8XeZrfERtFGW0AlMRm4JhlJa7l3NA==", "cpu": [ "ia32" ], @@ -1062,9 +548,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.41.tgz", - "integrity": "sha512-WOkA+fJ/ViVBQDsSV9JC52NACTe5PhlurA6viASDZGb7HR3KS01ZG7RZ+Bg6SVQFIoq3gSbTsskQVe6EbHFAYw==", + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.47.tgz", + "integrity": "sha512-2h8Iek95vnixkBRCo+H8p09+Q5ll2NgSMFrWTy0iKt7+/t+8/T5mBpiT6c0ZxSS7wcWjwZ9sGZkK70tTSYHdDw==", "cpu": [ "x64" ], @@ -1086,22 +572,15 @@ "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1121,17 +600,20 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", - "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.3.3.tgz", + "integrity": "sha512-bti8ZAcvz4Lh6/e4Uk2k3aa1TiUXbbMsahuqOHvd3MveFTkKDZOA6wQVkpj7J/+tepX/wGfe+lsGh/t24HTXMQ==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.27", - "@swc/core": "^1.12.11" + "@rolldown/pluginutils": "^1.0.1", + "@swc/core": "^1.15.46" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4 || ^5 || ^6 || ^7" + "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/csstype": { @@ -1141,43 +623,32 @@ "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=12" + "node": ">=12.0.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/fsevents": { @@ -1201,6 +672,279 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1214,9 +958,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -1239,10 +983,23 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -1260,7 +1017,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1293,49 +1050,37 @@ "react": "^18.3.1" } }, - "node_modules/rollup": { - "version": "4.61.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", - "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "node_modules/rolldown": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" + "@oxc-project/types": "=0.143.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.61.1", - "@rollup/rollup-android-arm64": "4.61.1", - "@rollup/rollup-darwin-arm64": "4.61.1", - "@rollup/rollup-darwin-x64": "4.61.1", - "@rollup/rollup-freebsd-arm64": "4.61.1", - "@rollup/rollup-freebsd-x64": "4.61.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", - "@rollup/rollup-linux-arm-musleabihf": "4.61.1", - "@rollup/rollup-linux-arm64-gnu": "4.61.1", - "@rollup/rollup-linux-arm64-musl": "4.61.1", - "@rollup/rollup-linux-loong64-gnu": "4.61.1", - "@rollup/rollup-linux-loong64-musl": "4.61.1", - "@rollup/rollup-linux-ppc64-gnu": "4.61.1", - "@rollup/rollup-linux-ppc64-musl": "4.61.1", - "@rollup/rollup-linux-riscv64-gnu": "4.61.1", - "@rollup/rollup-linux-riscv64-musl": "4.61.1", - "@rollup/rollup-linux-s390x-gnu": "4.61.1", - "@rollup/rollup-linux-x64-gnu": "4.61.1", - "@rollup/rollup-linux-x64-musl": "4.61.1", - "@rollup/rollup-openbsd-x64": "4.61.1", - "@rollup/rollup-openharmony-arm64": "4.61.1", - "@rollup/rollup-win32-arm64-msvc": "4.61.1", - "@rollup/rollup-win32-ia32-msvc": "4.61.1", - "@rollup/rollup-win32-x64-gnu": "4.61.1", - "@rollup/rollup-win32-x64-msvc": "4.61.1", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" } }, "node_modules/scheduler": { @@ -1357,6 +1102,23 @@ "node": ">=0.10.0" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1372,21 +1134,23 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1395,23 +1159,33 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -1428,6 +1202,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } } diff --git a/package.json b/package.json index 44568dc..f38c6de 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "devDependencies": { "@openeverest/plugin-sdk": "^0.1.0", "@types/react": "^18.3.28", - "@vitejs/plugin-react-swc": "^3.5.0", + "@vitejs/plugin-react-swc": "^4.3.3", "typescript": "^5.2.2", - "vite": "^5.4.0" + "vite": "^8.2.0" } } diff --git a/src/catalog.ts b/src/catalog.ts new file mode 100644 index 0000000..c32a403 --- /dev/null +++ b/src/catalog.ts @@ -0,0 +1,44 @@ +// Pure catalog helpers — filtering and chart/version derivation. +import type { CatalogEntry, FilterState } from './types'; + +export function matchesFilter(entry: CatalogEntry, filter: FilterState): boolean { + if (filter.type !== 'all' && entry.type !== filter.type) return false; + if (filter.installedOnly && !entry.installed) return false; + if (filter.hideGated && entry.access === 'gated') return false; + if (filter.query) { + const q = filter.query.toLowerCase(); + const haystack = [ + entry.name, + entry.displayName ?? '', + entry.description ?? '', + (entry.categories ?? []).join(' '), + (entry.keywords ?? []).join(' '), + ] + .join(' ') + .toLowerCase(); + if (!haystack.includes(q)) return false; + } + return true; +} + +export function defaultChannelVersion(entry: CatalogEntry): string | null { + const chart = entry.artifacts?.chart; + if (!chart) return null; + const channel = chart.defaultChannel ?? Object.keys(chart.channels ?? {})[0]; + if (!channel) return null; + return chart.channels?.[channel]?.version ?? null; +} + +export function helmInstallCommand(entry: CatalogEntry): string { + const chart = entry.artifacts?.chart; + const channel = chart?.defaultChannel ?? Object.keys(chart?.channels ?? {})[0] ?? ''; + const ref = chart?.channels?.[channel]?.ref ?? ''; + const version = chart?.channels?.[channel]?.version ?? ''; + const release = entry.install?.helm?.releaseName ?? entry.name; + const namespace = entry.install?.helm?.namespace ?? 'everest-system'; + return [ + `helm install ${release} ${ref} \\`, + ` --version ${version} \\`, + ` -n ${namespace}`, + ].join('\n'); +} diff --git a/src/components/Drawer.ts b/src/components/Drawer.ts new file mode 100644 index 0000000..6bdaecd --- /dev/null +++ b/src/components/Drawer.ts @@ -0,0 +1,320 @@ +import { h, React } from '../runtime'; +import { styles } from '../styles'; +import { defaultChannelVersion, helmInstallCommand } from '../catalog'; +import { IconImg, resolveIconSrc } from '../icons'; +import type { CatalogEntry } from '../types'; + +function humanizeKey(key: string): string { + return key + .replace(/[._-]/g, ' ') + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/\b\w/g, (c) => c.toUpperCase()); +} + +function renderCapabilityValue(key: string, value: unknown): any { + const label = humanizeKey(key); + if (typeof value === 'boolean') { + return value + ? h('span', { key, style: styles.capChipYes }, `\u2713 ${label}`) + : h('span', { key, style: styles.capChipNo }, `\u2717 ${label}`); + } + if (Array.isArray(value)) { + return h( + 'div', + { key, style: styles.capRow }, + h('span', { style: styles.capKey }, label), + h( + 'div', + null, + ...value.map((v, i) => + h('span', { key: i, style: styles.categoryTag }, String(v)), + ), + ), + ); + } + if (value === null || value === undefined) return null; + if (typeof value === 'object') { + return h( + 'div', + { key, style: styles.capRow }, + h('span', { style: styles.capKey }, label), + h( + 'code', + { style: { fontSize: '0.8125rem', color: '#374151' } }, + JSON.stringify(value), + ), + ); + } + return h( + 'div', + { key, style: styles.capRow }, + h('span', { style: styles.capKey }, label), + h('span', { style: { color: '#111827' } }, String(value)), + ); +} + +function renderCapabilities(caps: Record): any { + const entries = Object.entries(caps); + if (!entries.length) return null; + const booleans = entries.filter(([, v]) => typeof v === 'boolean'); + const others = entries.filter(([, v]) => typeof v !== 'boolean'); + return h( + 'div', + null, + booleans.length + ? h( + 'div', + { style: { marginBottom: others.length ? '0.75rem' : 0 } }, + ...booleans.map(([k, v]) => renderCapabilityValue(k, v)), + ) + : null, + others.length ? h('div', null, ...others.map(([k, v]) => renderCapabilityValue(k, v))) : null, + ); +} + +function measureAppBar(): number { + if (typeof document === 'undefined') return 64; + const ab = document.querySelector('header.MuiAppBar-root') as HTMLElement | null; + if (!ab) return 64; + const height = Math.round(ab.getBoundingClientRect().height); + return height > 0 ? height : 64; +} + +function useAppBarOffset(): number { + const [offset, setOffset] = React.useState(measureAppBar); + React.useEffect(() => { + const update = () => setOffset(measureAppBar()); + update(); + window.addEventListener('resize', update); + return () => window.removeEventListener('resize', update); + }, []); + return offset; +} + +export function Drawer(props: { entry: CatalogEntry; pluginName: string; onClose: () => void }): any { + const { entry, pluginName, onClose } = props; + const isGated = entry.access === 'gated'; + const version = isGated ? null : defaultChannelVersion(entry); + const install = isGated ? null : helmInstallCommand(entry); + const extensionPoints = entry.plugin?.extensionPoints ?? []; + const supportedEngines = entry.provider?.supportedEngines ?? []; + const maintainers = entry.maintainers ?? []; + const appbarOffset = useAppBarOffset(); + const backdropStyle = { ...styles.drawerBackdrop, top: appbarOffset }; + + return h( + 'div', + { style: backdropStyle, onClick: onClose }, + h( + 'div', + { + style: styles.drawer, + onClick: (e: any) => e.stopPropagation(), + }, + h( + 'div', + { style: styles.drawerHeader }, + h(IconImg, { + src: resolveIconSrc(entry.icon, pluginName), + style: { width: 40, height: 40 }, + }), + h( + 'div', + null, + h( + 'h2', + { style: { margin: 0, fontSize: '1.25rem', fontWeight: 600 } }, + entry.displayName || entry.name, + ), + h( + 'div', + { style: { color: '#6b7280', fontSize: '0.8125rem' } }, + entry.name, + ' · ', + h('span', { style: styles.typeChip(entry.type) }, entry.type), + isGated + ? h('span', { style: { ...styles.gatedChip, marginLeft: 6 } }, 'Gated') + : null, + ), + ), + h('button', { type: 'button', style: styles.closeBtn, onClick: onClose }, '×'), + ), + + entry.installed + ? h( + 'div', + { style: { marginBottom: '1rem' } }, + h( + 'span', + { style: styles.statusInstalled }, + entry.installedVersion ? `Installed · ${entry.installedVersion}` : 'Installed', + ), + entry.installedPhase + ? h( + 'span', + { style: { marginLeft: 8, color: '#6b7280', fontSize: '0.8125rem' } }, + `Phase: ${entry.installedPhase}`, + ) + : null, + ) + : null, + + entry.description + ? h( + 'p', + { style: { color: '#374151', whiteSpace: 'pre-line' } }, + entry.description, + ) + : null, + + h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Metadata'), + h( + 'div', + { style: { fontSize: '0.875rem', lineHeight: 1.7 } }, + version ? h('div', null, h('b', null, 'Version: '), version) : null, + entry.maturity + ? h( + 'div', + null, + h('b', null, 'Maturity: '), + h( + 'span', + { style: styles.maturityChip(entry.maturity) }, + entry.maturity, + ), + ) + : null, + entry.compatibility?.openeverest + ? h('div', null, h('b', null, 'Requires OpenEverest: '), entry.compatibility.openeverest) + : null, + entry.license ? h('div', null, h('b', null, 'License: '), entry.license) : null, + entry.verified + ? h('div', null, h('b', null, 'Verified: '), 'yes') + : null, + ), + ), + + extensionPoints.length + ? h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Extension points'), + h( + 'div', + null, + ...extensionPoints.map((p) => h('span', { key: p, style: styles.categoryTag }, p)), + ), + ) + : null, + + supportedEngines.length + ? h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Supported engines'), + h( + 'div', + null, + ...supportedEngines.map((e) => h('span', { key: e, style: styles.categoryTag }, e)), + ), + ) + : null, + + entry.capabilities && Object.keys(entry.capabilities).length + ? h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Capabilities'), + renderCapabilities(entry.capabilities), + ) + : null, + + maintainers.length + ? h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Maintainers'), + h( + 'ul', + { style: { margin: 0, paddingLeft: '1.25rem', fontSize: '0.875rem' } }, + ...maintainers.map((m, i) => + h('li', { key: i }, m.name || m.github || m.email || 'unknown'), + ), + ), + ) + : null, + + h( + 'div', + { style: styles.section }, + isGated + ? h( + 'div', + null, + h('h3', { style: styles.sectionTitle }, 'Access required'), + h( + 'p', + { style: { color: '#374151', fontSize: '0.875rem', marginTop: 0 } }, + entry.gated?.instructions || + 'This extension is not publicly available. Contact the vendor to request access.', + ), + entry.gated?.provider + ? h( + 'p', + { style: { color: '#6b7280', fontSize: '0.8125rem', marginTop: '-0.5rem' } }, + `Provided by ${entry.gated.provider}`, + ) + : null, + entry.gated?.contactUrl + ? h( + 'a', + { + href: entry.gated.contactUrl, + target: '_blank', + rel: 'noopener noreferrer', + style: styles.ctaBtn, + }, + 'Contact vendor ↗', + ) + : h( + 'div', + { style: { color: '#6b7280', fontSize: '0.8125rem' } }, + 'No contact URL configured. See the source repository for details.', + ), + ) + : h( + 'div', + null, + h('h3', { style: styles.sectionTitle }, 'Install with Helm'), + h('pre', { style: styles.codeBlock }, install), + ), + ), + + h( + 'div', + { style: styles.section }, + h( + 'div', + { style: { display: 'flex', gap: '0.75rem', flexWrap: 'wrap' } }, + entry.sourceRepo + ? h( + 'a', + { href: entry.sourceRepo, target: '_blank', rel: 'noopener noreferrer' }, + 'Source repository ↗', + ) + : null, + entry.homepage + ? h( + 'a', + { href: entry.homepage, target: '_blank', rel: 'noopener noreferrer' }, + 'Homepage ↗', + ) + : null, + ), + ), + ), + ); +} diff --git a/src/components/Row.ts b/src/components/Row.ts new file mode 100644 index 0000000..94aa6b2 --- /dev/null +++ b/src/components/Row.ts @@ -0,0 +1,72 @@ +import { h } from '../runtime'; +import { styles } from '../styles'; +import { defaultChannelVersion } from '../catalog'; +import { IconImg, resolveIconSrc } from '../icons'; +import type { CatalogEntry } from '../types'; + +export function Row(props: { + entry: CatalogEntry; + pluginName: string; + onSelect: (e: CatalogEntry) => void; +}): any { + const { entry, pluginName, onSelect } = props; + const version = defaultChannelVersion(entry); + return h( + 'tr', + { + key: entry.name, + style: { cursor: 'pointer' }, + onClick: () => onSelect(entry), + }, + h( + 'td', + { style: { ...styles.td, ...styles.iconCell } }, + h(IconImg, { + src: resolveIconSrc(entry.icon, pluginName), + style: styles.iconImg, + }), + ), + h( + 'td', + { style: styles.td }, + h('div', { style: { fontWeight: 600 } }, entry.displayName || entry.name), + h( + 'div', + { style: { color: '#6b7280', fontSize: '0.8125rem', marginTop: 2 } }, + entry.name, + ), + ), + h('td', { style: styles.td }, h('span', { style: styles.typeChip(entry.type) }, entry.type)), + h('td', { style: styles.td }, version ?? '—'), + h( + 'td', + { style: styles.td }, + ...(entry.categories ?? []).map((c) => + h('span', { key: c, style: styles.categoryTag }, c), + ), + ), + h( + 'td', + { style: styles.td }, + h( + 'div', + { style: { display: 'flex', flexDirection: 'column' as const, gap: 4, alignItems: 'flex-start' } }, + h( + 'span', + { style: styles.maturityChip(entry.maturity || 'unknown') }, + entry.maturity || 'unknown', + ), + entry.access === 'gated' + ? h('span', { style: styles.gatedChip }, 'Gated') + : null, + entry.installed + ? h( + 'span', + { style: styles.statusInstalled }, + entry.installedVersion ? `Installed · ${entry.installedVersion}` : 'Installed', + ) + : null, + ), + ), + ); +} diff --git a/src/components/Toolbar.ts b/src/components/Toolbar.ts new file mode 100644 index 0000000..1e8d2b9 --- /dev/null +++ b/src/components/Toolbar.ts @@ -0,0 +1,84 @@ +import { h } from '../runtime'; +import { styles } from '../styles'; +import type { FilterState } from '../types'; + +export function Toolbar(props: { + filter: FilterState; + onChange: (f: FilterState) => void; + onRefresh: () => void; + refreshing: boolean; + lastRefreshed: Date | null; +}): any { + const { filter, onChange, onRefresh, refreshing, lastRefreshed } = props; + const chipDefs: Array<{ key: FilterState['type']; label: string }> = [ + { key: 'all', label: 'All' }, + { key: 'plugin', label: 'Plugins' }, + { key: 'provider', label: 'Providers' }, + ]; + return h( + 'div', + { style: styles.toolbar }, + h('input', { + type: 'search', + placeholder: 'Search by name, description, category…', + value: filter.query, + style: styles.input, + onChange: (e: any) => onChange({ ...filter, query: e.target.value }), + }), + h( + 'div', + { style: styles.chipGroup }, + ...chipDefs.map((c) => + h( + 'button', + { + key: c.key, + type: 'button', + style: styles.chip(filter.type === c.key), + onClick: () => onChange({ ...filter, type: c.key }), + }, + c.label, + ), + ), + ), + h( + 'label', + { style: styles.checkboxRow }, + h('input', { + type: 'checkbox', + checked: filter.installedOnly, + onChange: (e: any) => + onChange({ ...filter, installedOnly: e.target.checked }), + }), + 'Installed only', + ), + h( + 'label', + { style: styles.checkboxRow }, + h('input', { + type: 'checkbox', + checked: !filter.hideGated, + onChange: (e: any) => + onChange({ ...filter, hideGated: !e.target.checked }), + }), + 'Include gated', + ), + h( + 'button', + { + type: 'button', + style: styles.refreshBtn, + onClick: onRefresh, + disabled: refreshing, + }, + refreshing ? 'Refreshing…' : 'Refresh', + ), + lastRefreshed + ? h( + 'span', + { style: { fontSize: '0.75rem', color: '#6b7280' } }, + `Updated ${lastRefreshed.toLocaleTimeString()}`, + ) + : null, + ); +} diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..ac4198e --- /dev/null +++ b/src/data.ts @@ -0,0 +1,34 @@ +// Data loading. Catalog and installed state are fetched separately so a slow +// or failing everest API never blocks the catalog from rendering. +import { pluginFetch } from './runtime'; +import type { ExtensionType, InstalledResponse, SummaryResponse } from './types'; + +// fetchCatalog loads the extension catalog only (no install status). This is +// the cheap, cached call the host can answer immediately, so the UI can paint +// the full list without waiting on the everest API round-trip. +export async function fetchCatalog(): Promise { + const res = await pluginFetch('/api/catalog'); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(text || `HTTP ${res.status}`); + } + const body = (await res.json()) as SummaryResponse; + return { ...body, stale: res.headers.get('X-Hub-Stale') === 'true' }; +} + +// fetchInstalled loads what is currently installed on the host. It is fetched +// separately from the catalog so a slow or failing everest API never blocks +// the catalog from rendering — install labels are applied asynchronously once +// this resolves. +export async function fetchInstalled(): Promise { + const res = await pluginFetch('/api/installed'); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(text || `HTTP ${res.status}`); + } + return res.json(); +} + +export function installedKey(type: ExtensionType, name: string): string { + return `${type}:${name}`; +} diff --git a/src/icons.ts b/src/icons.ts new file mode 100644 index 0000000..f5016b1 --- /dev/null +++ b/src/icons.ts @@ -0,0 +1,50 @@ +// Icon resolution + a self-healing that falls back to an inline +// placeholder when an upstream icon fails to load. +import { h } from './runtime'; + +export const ICON_FALLBACK_DATA_URI = + "data:image/svg+xml;utf8," + + encodeURIComponent( + "", + ); + +// Module-scoped record of icon URLs that already 404'd or otherwise failed. +// Survives React re-renders so the same broken URL is never re-requested. +const failedIconSrcs = new Set(); + +// resolveIconSrc converts whatever the backend put into entry.icon into a +// usable . The backend now emits *relative* paths (e.g. +// 'api/icon/') for proxied icons; we prepend the plugin's runtime +// mount prefix here so the URL is correct regardless of the release name +// the chart was installed under. +export function resolveIconSrc(rawIcon: string | undefined, pluginName: string): string { + if (!rawIcon) return ICON_FALLBACK_DATA_URI; + if ( + rawIcon.startsWith('data:') || + rawIcon.startsWith('http://') || + rawIcon.startsWith('https://') || + rawIcon.startsWith('/') + ) { + return rawIcon; + } + if (!pluginName) return ICON_FALLBACK_DATA_URI; + return `/v1/plugins/${pluginName}/${rawIcon}`; +} + +export function IconImg(props: { src: string; alt?: string; style?: any }): any { + const initial = failedIconSrcs.has(props.src) ? ICON_FALLBACK_DATA_URI : props.src; + return h('img', { + src: initial, + alt: props.alt ?? '', + style: props.style, + onError: (e: any) => { + const el = e.currentTarget as HTMLImageElement & { dataset: DOMStringMap }; + if (el.dataset.failed === '1') return; + el.dataset.failed = '1'; + failedIconSrcs.add(props.src); + if (el.src !== ICON_FALLBACK_DATA_URI) { + el.src = ICON_FALLBACK_DATA_URI; + } + }, + }); +} diff --git a/src/main.tsx b/src/main.tsx index c8f59ac..213de9e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,956 +2,30 @@ // // Registers a sidebar entry and a dedicated route that renders the Hub // browser — a searchable, filterable list of available and installed -// extensions, pulled from the host's /v1/plugins/plugin-hub/api/summary -// endpoint (which proxies to this plugin's Go backend). +// extensions. The catalog is pulled from the plugin's Go backend +// (/api/catalog) and install status is loaded separately (/api/installed) so +// a slow or failing everest API never blocks the catalog from rendering. // // The runtime contract follows the openeverest/generic-plugin-template // pattern: React and the host-authenticated fetch are injected via the -// `register(api)` call, so this module uses React.createElement directly and -// does not import React or any UI framework. The bundle stays small and the -// host stays in charge of dependency versions. +// `register(api)` call (see runtime.ts), so this module uses +// React.createElement (via `h`) directly and does not import React or any UI +// framework. The bundle stays small and the host stays in charge of +// dependency versions. import type { PluginRegisterFn, PluginApi, PluginRouteProps, } from '@openeverest/plugin-sdk'; -let React: PluginApi['React']; -let pluginFetch: PluginApi['fetch']; - -const h = ( - type: any, - props: any, - ...children: any[] -): any => React.createElement(type, props, ...children); - -// --------------------------------------------------------------------------- -// Types matching the backend /api/summary shape. -// --------------------------------------------------------------------------- - -type ExtensionType = 'plugin' | 'provider' | string; - -interface CatalogEntry { - name: string; - type: ExtensionType; - displayName?: string; - description?: string; - icon?: string; - homepage?: string; - sourceRepo?: string; - license?: string; - verified?: boolean; - categories?: string[]; - keywords?: string[]; - maintainers?: Array<{ name?: string; email?: string; github?: string }>; - compatibility?: { openeverest?: string }; - artifacts?: { - chart?: { - defaultChannel?: string; - channels?: Record; - }; - }; - install?: { - helm?: { namespace?: string; releaseName?: string }; - }; - plugin?: { - contributes?: { backend?: boolean; ui?: boolean; cli?: boolean }; - extensionPoints?: string[]; - }; - provider?: { - providerName?: string; - supportedEngines?: string[]; - }; - maturity?: string; - capabilities?: Record; - access?: 'public' | 'gated'; - gated?: { contactUrl?: string; instructions?: string; provider?: string }; - installed?: boolean; - installedVersion?: string; - installedPhase?: string; -} - -interface SummaryResponse { - extensions?: CatalogEntry[]; - metadata?: { catalogId?: string; generatedAt?: string; totalExtensions?: number }; - stale?: boolean; - installedError?: string; -} - -interface FilterState { - query: string; - type: 'all' | 'plugin' | 'provider'; - installedOnly: boolean; - hideGated: boolean; -} - -// --------------------------------------------------------------------------- -// Styling — inline objects, host theme inherited. -// --------------------------------------------------------------------------- - -const styles = { - page: { padding: '1.5rem', maxWidth: 1280, margin: '0 auto' } as const, - headerRow: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - gap: '1rem', - marginBottom: '1rem', - flexWrap: 'wrap' as const, - }, - title: { margin: 0, fontSize: '1.5rem', fontWeight: 600 }, - subtitle: { margin: 0, color: '#6b7280', fontSize: '0.875rem' }, - toolbar: { - display: 'flex', - gap: '0.75rem', - alignItems: 'center', - flexWrap: 'wrap' as const, - padding: '0.75rem 1rem', - background: '#f9fafb', - border: '1px solid #e5e7eb', - borderRadius: 8, - marginBottom: '1rem', - }, - input: { - flex: '1 1 240px', - minWidth: 200, - padding: '0.5rem 0.75rem', - fontSize: '0.875rem', - border: '1px solid #d1d5db', - borderRadius: 6, - background: '#fff', - } as const, - chipGroup: { display: 'flex', gap: '0.25rem' } as const, - chip: (active: boolean) => ({ - padding: '0.4rem 0.75rem', - fontSize: '0.8125rem', - border: '1px solid ' + (active ? '#1f2937' : '#d1d5db'), - background: active ? '#1f2937' : '#fff', - color: active ? '#fff' : '#374151', - borderRadius: 999, - cursor: 'pointer', - }), - checkboxRow: { - display: 'flex', - alignItems: 'center', - gap: '0.4rem', - fontSize: '0.875rem', - color: '#374151', - cursor: 'pointer', - } as const, - table: { - width: '100%', - borderCollapse: 'collapse' as const, - background: '#fff', - border: '1px solid #e5e7eb', - borderRadius: 8, - overflow: 'hidden', - }, - th: { - textAlign: 'left' as const, - padding: '0.75rem 1rem', - fontSize: '0.75rem', - textTransform: 'uppercase' as const, - letterSpacing: '0.05em', - color: '#6b7280', - background: '#f9fafb', - borderBottom: '1px solid #e5e7eb', - }, - td: { - padding: '0.75rem 1rem', - fontSize: '0.875rem', - borderBottom: '1px solid #f3f4f6', - verticalAlign: 'top' as const, - } as const, - iconCell: { width: 40, padding: '0.75rem', textAlign: 'center' as const }, - iconImg: { width: 28, height: 28, objectFit: 'contain' as const }, - statusInstalled: { - display: 'inline-block', - padding: '0.15rem 0.55rem', - background: '#dcfce7', - color: '#166534', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - } as const, - statusAvailable: { - display: 'inline-block', - padding: '0.15rem 0.55rem', - background: '#e5e7eb', - color: '#374151', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - } as const, - maturityChip: (maturity: string) => { - const m = (maturity || 'unknown').toLowerCase(); - const palette: Record = { - alpha: { bg: '#ffedd5', fg: '#9a3412' }, - beta: { bg: '#dbeafe', fg: '#1e3a8a' }, - stable: { bg: '#dcfce7', fg: '#166534' }, - ga: { bg: '#dcfce7', fg: '#166534' }, - deprecated: { bg: '#fee2e2', fg: '#991b1b' }, - unknown: { bg: '#e5e7eb', fg: '#374151' }, - }; - const c = palette[m] ?? palette.unknown; - return { - display: 'inline-block', - padding: '0.15rem 0.55rem', - background: c.bg, - color: c.fg, - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - textTransform: 'capitalize' as const, - }; - }, - gatedChip: { - display: 'inline-block', - padding: '0.15rem 0.55rem', - background: '#ede9fe', - color: '#5b21b6', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - } as const, - capChipYes: { - display: 'inline-flex', - alignItems: 'center', - gap: 4, - padding: '0.15rem 0.55rem', - background: '#dcfce7', - color: '#166534', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - marginRight: 6, - marginBottom: 6, - } as const, - capChipNo: { - display: 'inline-flex', - alignItems: 'center', - gap: 4, - padding: '0.15rem 0.55rem', - background: '#f3f4f6', - color: '#9ca3af', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - marginRight: 6, - marginBottom: 6, - textDecoration: 'line-through' as const, - } as const, - capRow: { - display: 'flex', - gap: '0.5rem', - alignItems: 'baseline', - marginBottom: 4, - flexWrap: 'wrap' as const, - fontSize: '0.875rem', - } as const, - capKey: { color: '#6b7280', fontWeight: 500, minWidth: 120 } as const, - typeChip: (type: string) => ({ - display: 'inline-block', - padding: '0.15rem 0.55rem', - background: type === 'provider' ? '#dbeafe' : '#ede9fe', - color: type === 'provider' ? '#1e3a8a' : '#5b21b6', - borderRadius: 999, - fontSize: '0.75rem', - fontWeight: 600, - textTransform: 'capitalize' as const, - }), - categoryTag: { - display: 'inline-block', - padding: '0.1rem 0.5rem', - fontSize: '0.7rem', - background: '#f3f4f6', - color: '#4b5563', - borderRadius: 4, - marginRight: 4, - } as const, - refreshBtn: { - padding: '0.4rem 0.9rem', - fontSize: '0.8125rem', - border: '1px solid #d1d5db', - background: '#fff', - color: '#374151', - borderRadius: 6, - cursor: 'pointer', - } as const, - ctaBtn: { - padding: '0.5rem 1rem', - fontSize: '0.875rem', - fontWeight: 500, - border: '1px solid #2563eb', - background: '#2563eb', - color: '#fff', - borderRadius: 6, - cursor: 'pointer', - textDecoration: 'none', - display: 'inline-block', - } as const, - ctaLink: { - fontSize: '0.8125rem', - color: '#6b7280', - textDecoration: 'none', - } as const, - headerActions: { - display: 'flex', - flexDirection: 'column' as const, - alignItems: 'flex-end', - gap: '0.375rem', - } as const, - empty: { - padding: '3rem', - textAlign: 'center' as const, - color: '#6b7280', - }, - errorBox: { - padding: '0.75rem 1rem', - background: '#fee2e2', - color: '#991b1b', - border: '1px solid #fecaca', - borderRadius: 6, - marginBottom: '1rem', - fontSize: '0.875rem', - }, - warnBox: { - padding: '0.6rem 1rem', - background: '#fef3c7', - color: '#92400e', - border: '1px solid #fde68a', - borderRadius: 6, - marginBottom: '1rem', - fontSize: '0.8125rem', - }, - drawerBackdrop: { - position: 'fixed' as const, - right: 0, - bottom: 0, - left: 0, - background: 'rgba(15, 23, 42, 0.4)', - // Sit above MUI's permanent drawer (1200) but below the host's AppBar - // (1100 by default, but some themes push it higher) — the `top` offset - // is computed at render time so the AppBar always stays visible. - zIndex: 1200, - display: 'flex', - justifyContent: 'flex-end', - }, - drawer: { - width: 'min(560px, 100%)', - height: '100%', - background: '#fff', - boxShadow: '-4px 0 24px rgba(0, 0, 0, 0.15)', - overflowY: 'auto' as const, - padding: '1.5rem', - boxSizing: 'border-box' as const, - }, - drawerHeader: { - display: 'flex', - alignItems: 'center', - gap: '0.75rem', - marginBottom: '1rem', - }, - closeBtn: { - marginLeft: 'auto', - border: 'none', - background: 'transparent', - fontSize: '1.5rem', - cursor: 'pointer', - color: '#6b7280', - } as const, - section: { marginTop: '1.25rem' } as const, - sectionTitle: { - margin: '0 0 0.5rem', - fontSize: '0.75rem', - textTransform: 'uppercase' as const, - color: '#6b7280', - letterSpacing: '0.05em', - }, - codeBlock: { - background: '#0f172a', - color: '#e2e8f0', - padding: '0.75rem 1rem', - borderRadius: 6, - fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', - fontSize: '0.8125rem', - whiteSpace: 'pre' as const, - overflowX: 'auto' as const, - }, -}; - -const ICON_FALLBACK_DATA_URI = - "data:image/svg+xml;utf8," + - encodeURIComponent( - "", - ); - -// Module-scoped record of icon URLs that already 404'd or otherwise failed. -// Survives React re-renders so the same broken URL is never re-requested. -const failedIconSrcs = new Set(); - -// resolveIconSrc converts whatever the backend put into entry.icon into a -// usable . The backend now emits *relative* paths (e.g. -// 'api/icon/') for proxied icons; we prepend the plugin's runtime -// mount prefix here so the URL is correct regardless of the release name -// the chart was installed under. -function resolveIconSrc(rawIcon: string | undefined, pluginName: string): string { - if (!rawIcon) return ICON_FALLBACK_DATA_URI; - if ( - rawIcon.startsWith('data:') || - rawIcon.startsWith('http://') || - rawIcon.startsWith('https://') || - rawIcon.startsWith('/') - ) { - return rawIcon; - } - if (!pluginName) return ICON_FALLBACK_DATA_URI; - return `/v1/plugins/${pluginName}/${rawIcon}`; -} - -// --------------------------------------------------------------------------- -// Data loading -// --------------------------------------------------------------------------- - -async function fetchSummary(): Promise { - const res = await pluginFetch('/api/summary'); - if (!res.ok) { - const text = await res.text().catch(() => ''); - throw new Error(text || `HTTP ${res.status}`); - } - return res.json(); -} - -// --------------------------------------------------------------------------- -// Catalog table -// --------------------------------------------------------------------------- - -function matchesFilter(entry: CatalogEntry, filter: FilterState): boolean { - if (filter.type !== 'all' && entry.type !== filter.type) return false; - if (filter.installedOnly && !entry.installed) return false; - if (filter.hideGated && entry.access === 'gated') return false; - if (filter.query) { - const q = filter.query.toLowerCase(); - const haystack = [ - entry.name, - entry.displayName ?? '', - entry.description ?? '', - (entry.categories ?? []).join(' '), - (entry.keywords ?? []).join(' '), - ] - .join(' ') - .toLowerCase(); - if (!haystack.includes(q)) return false; - } - return true; -} - -function defaultChannelVersion(entry: CatalogEntry): string | null { - const chart = entry.artifacts?.chart; - if (!chart) return null; - const channel = chart.defaultChannel ?? Object.keys(chart.channels ?? {})[0]; - if (!channel) return null; - return chart.channels?.[channel]?.version ?? null; -} - -function helmInstallCommand(entry: CatalogEntry): string { - const chart = entry.artifacts?.chart; - const channel = chart?.defaultChannel ?? Object.keys(chart?.channels ?? {})[0] ?? ''; - const ref = chart?.channels?.[channel]?.ref ?? ''; - const version = chart?.channels?.[channel]?.version ?? ''; - const release = entry.install?.helm?.releaseName ?? entry.name; - const namespace = entry.install?.helm?.namespace ?? 'everest-system'; - return [ - `helm install ${release} ${ref} \\`, - ` --version ${version} \\`, - ` -n ${namespace}`, - ].join('\n'); -} - -function humanizeKey(key: string): string { - return key - .replace(/[._-]/g, ' ') - .replace(/([a-z])([A-Z])/g, '$1 $2') - .replace(/\b\w/g, (c) => c.toUpperCase()); -} - -function renderCapabilityValue(key: string, value: unknown): any { - const label = humanizeKey(key); - if (typeof value === 'boolean') { - return value - ? h('span', { key, style: styles.capChipYes }, `\u2713 ${label}`) - : h('span', { key, style: styles.capChipNo }, `\u2717 ${label}`); - } - if (Array.isArray(value)) { - return h( - 'div', - { key, style: styles.capRow }, - h('span', { style: styles.capKey }, label), - h( - 'div', - null, - ...value.map((v, i) => - h('span', { key: i, style: styles.categoryTag }, String(v)), - ), - ), - ); - } - if (value === null || value === undefined) return null; - if (typeof value === 'object') { - return h( - 'div', - { key, style: styles.capRow }, - h('span', { style: styles.capKey }, label), - h( - 'code', - { style: { fontSize: '0.8125rem', color: '#374151' } }, - JSON.stringify(value), - ), - ); - } - return h( - 'div', - { key, style: styles.capRow }, - h('span', { style: styles.capKey }, label), - h('span', { style: { color: '#111827' } }, String(value)), - ); -} - -function renderCapabilities(caps: Record): any { - const entries = Object.entries(caps); - if (!entries.length) return null; - const booleans = entries.filter(([, v]) => typeof v === 'boolean'); - const others = entries.filter(([, v]) => typeof v !== 'boolean'); - return h( - 'div', - null, - booleans.length - ? h( - 'div', - { style: { marginBottom: others.length ? '0.75rem' : 0 } }, - ...booleans.map(([k, v]) => renderCapabilityValue(k, v)), - ) - : null, - others.length ? h('div', null, ...others.map(([k, v]) => renderCapabilityValue(k, v))) : null, - ); -} - -// --------------------------------------------------------------------------- -// Components -// --------------------------------------------------------------------------- - -function Toolbar(props: { - filter: FilterState; - onChange: (f: FilterState) => void; - onRefresh: () => void; - refreshing: boolean; - lastRefreshed: Date | null; -}): any { - const { filter, onChange, onRefresh, refreshing, lastRefreshed } = props; - const chipDefs: Array<{ key: FilterState['type']; label: string }> = [ - { key: 'all', label: 'All' }, - { key: 'plugin', label: 'Plugins' }, - { key: 'provider', label: 'Providers' }, - ]; - return h( - 'div', - { style: styles.toolbar }, - h('input', { - type: 'search', - placeholder: 'Search by name, description, category…', - value: filter.query, - style: styles.input, - onChange: (e: any) => onChange({ ...filter, query: e.target.value }), - }), - h( - 'div', - { style: styles.chipGroup }, - ...chipDefs.map((c) => - h( - 'button', - { - key: c.key, - type: 'button', - style: styles.chip(filter.type === c.key), - onClick: () => onChange({ ...filter, type: c.key }), - }, - c.label, - ), - ), - ), - h( - 'label', - { style: styles.checkboxRow }, - h('input', { - type: 'checkbox', - checked: filter.installedOnly, - onChange: (e: any) => - onChange({ ...filter, installedOnly: e.target.checked }), - }), - 'Installed only', - ), - h( - 'label', - { style: styles.checkboxRow }, - h('input', { - type: 'checkbox', - checked: !filter.hideGated, - onChange: (e: any) => - onChange({ ...filter, hideGated: !e.target.checked }), - }), - 'Include gated', - ), - h( - 'button', - { - type: 'button', - style: styles.refreshBtn, - onClick: onRefresh, - disabled: refreshing, - }, - refreshing ? 'Refreshing…' : 'Refresh', - ), - lastRefreshed - ? h( - 'span', - { style: { fontSize: '0.75rem', color: '#6b7280' } }, - `Updated ${lastRefreshed.toLocaleTimeString()}`, - ) - : null, - ); -} - -function IconImg(props: { src: string; alt?: string; style?: any }): any { - const initial = failedIconSrcs.has(props.src) ? ICON_FALLBACK_DATA_URI : props.src; - return h('img', { - src: initial, - alt: props.alt ?? '', - style: props.style, - onError: (e: any) => { - const el = e.currentTarget as HTMLImageElement & { dataset: DOMStringMap }; - if (el.dataset.failed === '1') return; - el.dataset.failed = '1'; - failedIconSrcs.add(props.src); - if (el.src !== ICON_FALLBACK_DATA_URI) { - el.src = ICON_FALLBACK_DATA_URI; - } - }, - }); -} - -function Row(props: { - entry: CatalogEntry; - pluginName: string; - onSelect: (e: CatalogEntry) => void; -}): any { - const { entry, pluginName, onSelect } = props; - const version = defaultChannelVersion(entry); - return h( - 'tr', - { - key: entry.name, - style: { cursor: 'pointer' }, - onClick: () => onSelect(entry), - }, - h( - 'td', - { style: { ...styles.td, ...styles.iconCell } }, - h(IconImg, { - src: resolveIconSrc(entry.icon, pluginName), - style: styles.iconImg, - }), - ), - h( - 'td', - { style: styles.td }, - h('div', { style: { fontWeight: 600 } }, entry.displayName || entry.name), - h( - 'div', - { style: { color: '#6b7280', fontSize: '0.8125rem', marginTop: 2 } }, - entry.name, - ), - ), - h('td', { style: styles.td }, h('span', { style: styles.typeChip(entry.type) }, entry.type)), - h('td', { style: styles.td }, version ?? '—'), - h( - 'td', - { style: styles.td }, - ...(entry.categories ?? []).map((c) => - h('span', { key: c, style: styles.categoryTag }, c), - ), - ), - h( - 'td', - { style: styles.td }, - h( - 'div', - { style: { display: 'flex', flexDirection: 'column' as const, gap: 4, alignItems: 'flex-start' } }, - h( - 'span', - { style: styles.maturityChip(entry.maturity || 'unknown') }, - entry.maturity || 'unknown', - ), - entry.access === 'gated' - ? h('span', { style: styles.gatedChip }, 'Gated') - : null, - entry.installed - ? h( - 'span', - { style: styles.statusInstalled }, - entry.installedVersion ? `Installed · ${entry.installedVersion}` : 'Installed', - ) - : null, - ), - ), - ); -} - -function measureAppBar(): number { - if (typeof document === 'undefined') return 64; - const ab = document.querySelector('header.MuiAppBar-root') as HTMLElement | null; - if (!ab) return 64; - const height = Math.round(ab.getBoundingClientRect().height); - return height > 0 ? height : 64; -} - -function useAppBarOffset(): number { - const [offset, setOffset] = React.useState(measureAppBar); - React.useEffect(() => { - const update = () => setOffset(measureAppBar()); - update(); - window.addEventListener('resize', update); - return () => window.removeEventListener('resize', update); - }, []); - return offset; -} - -function Drawer(props: { entry: CatalogEntry; pluginName: string; onClose: () => void }): any { - const { entry, pluginName, onClose } = props; - const isGated = entry.access === 'gated'; - const version = isGated ? null : defaultChannelVersion(entry); - const install = isGated ? null : helmInstallCommand(entry); - const extensionPoints = entry.plugin?.extensionPoints ?? []; - const supportedEngines = entry.provider?.supportedEngines ?? []; - const maintainers = entry.maintainers ?? []; - const appbarOffset = useAppBarOffset(); - const backdropStyle = { ...styles.drawerBackdrop, top: appbarOffset }; - - return h( - 'div', - { style: backdropStyle, onClick: onClose }, - h( - 'div', - { - style: styles.drawer, - onClick: (e: any) => e.stopPropagation(), - }, - h( - 'div', - { style: styles.drawerHeader }, - h(IconImg, { - src: resolveIconSrc(entry.icon, pluginName), - style: { width: 40, height: 40 }, - }), - h( - 'div', - null, - h( - 'h2', - { style: { margin: 0, fontSize: '1.25rem', fontWeight: 600 } }, - entry.displayName || entry.name, - ), - h( - 'div', - { style: { color: '#6b7280', fontSize: '0.8125rem' } }, - entry.name, - ' · ', - h('span', { style: styles.typeChip(entry.type) }, entry.type), - isGated - ? h('span', { style: { ...styles.gatedChip, marginLeft: 6 } }, 'Gated') - : null, - ), - ), - h('button', { type: 'button', style: styles.closeBtn, onClick: onClose }, '×'), - ), - - entry.installed - ? h( - 'div', - { style: { marginBottom: '1rem' } }, - h( - 'span', - { style: styles.statusInstalled }, - entry.installedVersion ? `Installed · ${entry.installedVersion}` : 'Installed', - ), - entry.installedPhase - ? h( - 'span', - { style: { marginLeft: 8, color: '#6b7280', fontSize: '0.8125rem' } }, - `Phase: ${entry.installedPhase}`, - ) - : null, - ) - : null, - - entry.description - ? h( - 'p', - { style: { color: '#374151', whiteSpace: 'pre-line' } }, - entry.description, - ) - : null, - - h( - 'div', - { style: styles.section }, - h('h3', { style: styles.sectionTitle }, 'Metadata'), - h( - 'div', - { style: { fontSize: '0.875rem', lineHeight: 1.7 } }, - version ? h('div', null, h('b', null, 'Version: '), version) : null, - entry.maturity - ? h( - 'div', - null, - h('b', null, 'Maturity: '), - h( - 'span', - { style: styles.maturityChip(entry.maturity) }, - entry.maturity, - ), - ) - : null, - entry.compatibility?.openeverest - ? h('div', null, h('b', null, 'Requires OpenEverest: '), entry.compatibility.openeverest) - : null, - entry.license ? h('div', null, h('b', null, 'License: '), entry.license) : null, - entry.verified - ? h('div', null, h('b', null, 'Verified: '), 'yes') - : null, - ), - ), - - extensionPoints.length - ? h( - 'div', - { style: styles.section }, - h('h3', { style: styles.sectionTitle }, 'Extension points'), - h( - 'div', - null, - ...extensionPoints.map((p) => h('span', { key: p, style: styles.categoryTag }, p)), - ), - ) - : null, - - supportedEngines.length - ? h( - 'div', - { style: styles.section }, - h('h3', { style: styles.sectionTitle }, 'Supported engines'), - h( - 'div', - null, - ...supportedEngines.map((e) => h('span', { key: e, style: styles.categoryTag }, e)), - ), - ) - : null, - - entry.capabilities && Object.keys(entry.capabilities).length - ? h( - 'div', - { style: styles.section }, - h('h3', { style: styles.sectionTitle }, 'Capabilities'), - renderCapabilities(entry.capabilities), - ) - : null, - - maintainers.length - ? h( - 'div', - { style: styles.section }, - h('h3', { style: styles.sectionTitle }, 'Maintainers'), - h( - 'ul', - { style: { margin: 0, paddingLeft: '1.25rem', fontSize: '0.875rem' } }, - ...maintainers.map((m, i) => - h('li', { key: i }, m.name || m.github || m.email || 'unknown'), - ), - ), - ) - : null, - - h( - 'div', - { style: styles.section }, - isGated - ? h( - 'div', - null, - h('h3', { style: styles.sectionTitle }, 'Access required'), - h( - 'p', - { style: { color: '#374151', fontSize: '0.875rem', marginTop: 0 } }, - entry.gated?.instructions || - 'This extension is not publicly available. Contact the vendor to request access.', - ), - entry.gated?.provider - ? h( - 'p', - { style: { color: '#6b7280', fontSize: '0.8125rem', marginTop: '-0.5rem' } }, - `Provided by ${entry.gated.provider}`, - ) - : null, - entry.gated?.contactUrl - ? h( - 'a', - { - href: entry.gated.contactUrl, - target: '_blank', - rel: 'noopener noreferrer', - style: styles.ctaBtn, - }, - 'Contact vendor ↗', - ) - : h( - 'div', - { style: { color: '#6b7280', fontSize: '0.8125rem' } }, - 'No contact URL configured. See the source repository for details.', - ), - ) - : h( - 'div', - null, - h('h3', { style: styles.sectionTitle }, 'Install with Helm'), - h('pre', { style: styles.codeBlock }, install), - ), - ), - - h( - 'div', - { style: styles.section }, - h( - 'div', - { style: { display: 'flex', gap: '0.75rem', flexWrap: 'wrap' } }, - entry.sourceRepo - ? h( - 'a', - { href: entry.sourceRepo, target: '_blank', rel: 'noopener noreferrer' }, - 'Source repository ↗', - ) - : null, - entry.homepage - ? h( - 'a', - { href: entry.homepage, target: '_blank', rel: 'noopener noreferrer' }, - 'Homepage ↗', - ) - : null, - ), - ), - ), - ); -} +import { React, h, initRuntime } from './runtime'; +import { styles } from './styles'; +import { fetchCatalog, fetchInstalled, installedKey } from './data'; +import { matchesFilter } from './catalog'; +import { Toolbar } from './components/Toolbar'; +import { Row } from './components/Row'; +import { Drawer } from './components/Drawer'; +import type { CatalogEntry, FilterState, SummaryResponse } from './types'; // --------------------------------------------------------------------------- // Page @@ -959,6 +33,8 @@ function Drawer(props: { entry: CatalogEntry; pluginName: string; onClose: () => const HubPage = (props: PluginRouteProps): any => { const [data, setData] = React.useState(null); + const [installedKeys, setInstalledKeys] = React.useState | null>(null); + const [installedError, setInstalledError] = React.useState(null); const [error, setError] = React.useState(null); const [loading, setLoading] = React.useState(true); const [lastRefreshed, setLastRefreshed] = React.useState(null); @@ -973,20 +49,47 @@ const HubPage = (props: PluginRouteProps): any => { const load = React.useCallback(() => { setLoading(true); setError(null); - fetchSummary() + // Catalog is the fast, cached call — it gates the initial paint. + fetchCatalog() .then((res) => { setData(res); setLastRefreshed(new Date()); }) .catch((err: Error) => setError(err.message)) .finally(() => setLoading(false)); + + // Install status is loaded independently so a slow or failing everest API + // never blocks the catalog. Labels fill in once this resolves. + setInstalledKeys(null); + setInstalledError(null); + fetchInstalled() + .then((res) => { + const keys = new Set(); + for (const item of res.items ?? []) { + if (item?.name) keys.add(installedKey(item.type, item.name)); + } + setInstalledKeys(keys); + if (res.error) setInstalledError(res.error); + }) + .catch((err: Error) => { + setInstalledKeys(new Set()); + setInstalledError(err.message); + }); }, []); React.useEffect(() => { load(); }, [load]); - const entries = data?.extensions ?? []; + const installedLoading = installedKeys === null; + const entries = React.useMemo(() => { + const raw = data?.extensions ?? []; + if (!installedKeys) return raw; + return raw.map((e) => ({ + ...e, + installed: installedKeys.has(installedKey(e.type, e.name)), + })); + }, [data, installedKeys]); const filtered = entries.filter((e) => matchesFilter(e, filter)); const counts = { total: entries.length, @@ -1008,7 +111,9 @@ const HubPage = (props: PluginRouteProps): any => { h( 'p', { style: styles.subtitle }, - `Browse OpenEverest plugins and providers. ${counts.total} available · ${counts.installed} installed.`, + `Browse OpenEverest plugins and providers. ${counts.total} available · ${ + installedLoading ? 'checking installed…' : `${counts.installed} installed` + }.`, ), ), h( @@ -1047,11 +152,11 @@ const HubPage = (props: PluginRouteProps): any => { 'Showing cached catalog — upstream hub index is currently unreachable.', ) : null, - data?.installedError + installedError ? h( 'div', { style: styles.warnBox }, - `Could not load installed extensions: ${data.installedError}. Showing catalog without install status.`, + `Could not load installed extensions: ${installedError}. Showing catalog without install status.`, ) : null, @@ -1102,8 +207,7 @@ const HubPage = (props: PluginRouteProps): any => { // --------------------------------------------------------------------------- const register: PluginRegisterFn = (api: PluginApi) => { - React = api.React; - pluginFetch = api.fetch.bind(api); + initRuntime(api); api.registerExtension({ type: 'sidebarItem', diff --git a/src/runtime.ts b/src/runtime.ts new file mode 100644 index 0000000..f978e16 --- /dev/null +++ b/src/runtime.ts @@ -0,0 +1,19 @@ +// Shared runtime wiring for the plugin-hub frontend. +// +// The host injects React and an authenticated fetch via the `register(api)` +// call, so no module imports React directly. `initRuntime` is called once from +// register(); every other module reads these live bindings. +import type { PluginApi } from '@openeverest/plugin-sdk'; + +export let React: PluginApi['React']; +export let pluginFetch: PluginApi['fetch']; + +export function initRuntime(api: PluginApi): void { + React = api.React; + pluginFetch = api.fetch.bind(api); +} + +// h is a thin React.createElement wrapper so components can be authored +// without JSX (the host owns the React version). +export const h = (type: any, props: any, ...children: any[]): any => + React.createElement(type, props, ...children); diff --git a/src/styles.ts b/src/styles.ts new file mode 100644 index 0000000..900e7eb --- /dev/null +++ b/src/styles.ts @@ -0,0 +1,292 @@ +// Styling — inline objects, host theme inherited. + +export const styles = { + page: { padding: '1.5rem', maxWidth: 1280, margin: '0 auto' } as const, + headerRow: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '1rem', + marginBottom: '1rem', + flexWrap: 'wrap' as const, + }, + title: { margin: 0, fontSize: '1.5rem', fontWeight: 600 }, + subtitle: { margin: 0, color: '#6b7280', fontSize: '0.875rem' }, + toolbar: { + display: 'flex', + gap: '0.75rem', + alignItems: 'center', + flexWrap: 'wrap' as const, + padding: '0.75rem 1rem', + background: '#f9fafb', + border: '1px solid #e5e7eb', + borderRadius: 8, + marginBottom: '1rem', + }, + input: { + flex: '1 1 240px', + minWidth: 200, + padding: '0.5rem 0.75rem', + fontSize: '0.875rem', + border: '1px solid #d1d5db', + borderRadius: 6, + background: '#fff', + } as const, + chipGroup: { display: 'flex', gap: '0.25rem' } as const, + chip: (active: boolean) => ({ + padding: '0.4rem 0.75rem', + fontSize: '0.8125rem', + border: '1px solid ' + (active ? '#1f2937' : '#d1d5db'), + background: active ? '#1f2937' : '#fff', + color: active ? '#fff' : '#374151', + borderRadius: 999, + cursor: 'pointer', + }), + checkboxRow: { + display: 'flex', + alignItems: 'center', + gap: '0.4rem', + fontSize: '0.875rem', + color: '#374151', + cursor: 'pointer', + } as const, + table: { + width: '100%', + borderCollapse: 'collapse' as const, + background: '#fff', + border: '1px solid #e5e7eb', + borderRadius: 8, + overflow: 'hidden', + }, + th: { + textAlign: 'left' as const, + padding: '0.75rem 1rem', + fontSize: '0.75rem', + textTransform: 'uppercase' as const, + letterSpacing: '0.05em', + color: '#6b7280', + background: '#f9fafb', + borderBottom: '1px solid #e5e7eb', + }, + td: { + padding: '0.75rem 1rem', + fontSize: '0.875rem', + borderBottom: '1px solid #f3f4f6', + verticalAlign: 'top' as const, + } as const, + iconCell: { width: 40, padding: '0.75rem', textAlign: 'center' as const }, + iconImg: { width: 28, height: 28, objectFit: 'contain' as const }, + statusInstalled: { + display: 'inline-block', + padding: '0.15rem 0.55rem', + background: '#dcfce7', + color: '#166534', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + } as const, + statusAvailable: { + display: 'inline-block', + padding: '0.15rem 0.55rem', + background: '#e5e7eb', + color: '#374151', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + } as const, + maturityChip: (maturity: string) => { + const m = (maturity || 'unknown').toLowerCase(); + const palette: Record = { + alpha: { bg: '#ffedd5', fg: '#9a3412' }, + beta: { bg: '#dbeafe', fg: '#1e3a8a' }, + stable: { bg: '#dcfce7', fg: '#166534' }, + ga: { bg: '#dcfce7', fg: '#166534' }, + deprecated: { bg: '#fee2e2', fg: '#991b1b' }, + unknown: { bg: '#e5e7eb', fg: '#374151' }, + }; + const c = palette[m] ?? palette.unknown; + return { + display: 'inline-block', + padding: '0.15rem 0.55rem', + background: c.bg, + color: c.fg, + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + textTransform: 'capitalize' as const, + }; + }, + gatedChip: { + display: 'inline-block', + padding: '0.15rem 0.55rem', + background: '#ede9fe', + color: '#5b21b6', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + } as const, + capChipYes: { + display: 'inline-flex', + alignItems: 'center', + gap: 4, + padding: '0.15rem 0.55rem', + background: '#dcfce7', + color: '#166534', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + marginRight: 6, + marginBottom: 6, + } as const, + capChipNo: { + display: 'inline-flex', + alignItems: 'center', + gap: 4, + padding: '0.15rem 0.55rem', + background: '#f3f4f6', + color: '#9ca3af', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + marginRight: 6, + marginBottom: 6, + textDecoration: 'line-through' as const, + } as const, + capRow: { + display: 'flex', + gap: '0.5rem', + alignItems: 'baseline', + marginBottom: 4, + flexWrap: 'wrap' as const, + fontSize: '0.875rem', + } as const, + capKey: { color: '#6b7280', fontWeight: 500, minWidth: 120 } as const, + typeChip: (type: string) => ({ + display: 'inline-block', + padding: '0.15rem 0.55rem', + background: type === 'provider' ? '#dbeafe' : '#ede9fe', + color: type === 'provider' ? '#1e3a8a' : '#5b21b6', + borderRadius: 999, + fontSize: '0.75rem', + fontWeight: 600, + textTransform: 'capitalize' as const, + }), + categoryTag: { + display: 'inline-block', + padding: '0.1rem 0.5rem', + fontSize: '0.7rem', + background: '#f3f4f6', + color: '#4b5563', + borderRadius: 4, + marginRight: 4, + } as const, + refreshBtn: { + padding: '0.4rem 0.9rem', + fontSize: '0.8125rem', + border: '1px solid #d1d5db', + background: '#fff', + color: '#374151', + borderRadius: 6, + cursor: 'pointer', + } as const, + ctaBtn: { + padding: '0.5rem 1rem', + fontSize: '0.875rem', + fontWeight: 500, + border: '1px solid #2563eb', + background: '#2563eb', + color: '#fff', + borderRadius: 6, + cursor: 'pointer', + textDecoration: 'none', + display: 'inline-block', + } as const, + ctaLink: { + fontSize: '0.8125rem', + color: '#6b7280', + textDecoration: 'none', + } as const, + headerActions: { + display: 'flex', + flexDirection: 'column' as const, + alignItems: 'flex-end', + gap: '0.375rem', + } as const, + empty: { + padding: '3rem', + textAlign: 'center' as const, + color: '#6b7280', + }, + errorBox: { + padding: '0.75rem 1rem', + background: '#fee2e2', + color: '#991b1b', + border: '1px solid #fecaca', + borderRadius: 6, + marginBottom: '1rem', + fontSize: '0.875rem', + }, + warnBox: { + padding: '0.6rem 1rem', + background: '#fef3c7', + color: '#92400e', + border: '1px solid #fde68a', + borderRadius: 6, + marginBottom: '1rem', + fontSize: '0.8125rem', + }, + drawerBackdrop: { + position: 'fixed' as const, + right: 0, + bottom: 0, + left: 0, + background: 'rgba(15, 23, 42, 0.4)', + // Sit above MUI's permanent drawer (1200) but below the host's AppBar + // (1100 by default, but some themes push it higher) — the `top` offset + // is computed at render time so the AppBar always stays visible. + zIndex: 1200, + display: 'flex', + justifyContent: 'flex-end', + }, + drawer: { + width: 'min(560px, 100%)', + height: '100%', + background: '#fff', + boxShadow: '-4px 0 24px rgba(0, 0, 0, 0.15)', + overflowY: 'auto' as const, + padding: '1.5rem', + boxSizing: 'border-box' as const, + }, + drawerHeader: { + display: 'flex', + alignItems: 'center', + gap: '0.75rem', + marginBottom: '1rem', + }, + closeBtn: { + marginLeft: 'auto', + border: 'none', + background: 'transparent', + fontSize: '1.5rem', + cursor: 'pointer', + color: '#6b7280', + } as const, + section: { marginTop: '1.25rem' } as const, + sectionTitle: { + margin: '0 0 0.5rem', + fontSize: '0.75rem', + textTransform: 'uppercase' as const, + color: '#6b7280', + letterSpacing: '0.05em', + }, + codeBlock: { + background: '#0f172a', + color: '#e2e8f0', + padding: '0.75rem 1rem', + borderRadius: 6, + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', + fontSize: '0.8125rem', + whiteSpace: 'pre' as const, + overflowX: 'auto' as const, + }, +}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5040cc6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,67 @@ +// Types matching the backend catalog/installed API shapes. + +export type ExtensionType = 'plugin' | 'provider' | string; + +export interface CatalogEntry { + name: string; + type: ExtensionType; + displayName?: string; + description?: string; + icon?: string; + homepage?: string; + sourceRepo?: string; + license?: string; + verified?: boolean; + categories?: string[]; + keywords?: string[]; + maintainers?: Array<{ name?: string; email?: string; github?: string }>; + compatibility?: { openeverest?: string }; + artifacts?: { + chart?: { + defaultChannel?: string; + channels?: Record; + }; + }; + install?: { + helm?: { namespace?: string; releaseName?: string }; + }; + plugin?: { + contributes?: { backend?: boolean; ui?: boolean; cli?: boolean }; + extensionPoints?: string[]; + }; + provider?: { + providerName?: string; + supportedEngines?: string[]; + }; + maturity?: string; + capabilities?: Record; + access?: 'public' | 'gated'; + gated?: { contactUrl?: string; instructions?: string; provider?: string }; + installed?: boolean; + installedVersion?: string; + installedPhase?: string; +} + +export interface SummaryResponse { + extensions?: CatalogEntry[]; + metadata?: { catalogId?: string; generatedAt?: string; totalExtensions?: number }; + stale?: boolean; + installedError?: string; +} + +export interface InstalledItem { + name: string; + type: ExtensionType; +} + +export interface InstalledResponse { + items?: InstalledItem[]; + error?: string; +} + +export interface FilterState { + query: string; + type: 'all' | 'plugin' | 'provider'; + installedOnly: boolean; + hideGated: boolean; +}