From a93a76eee980be612a18c52c1efb83877b13375b Mon Sep 17 00:00:00 2001 From: wha7ev9r Date: Fri, 31 Jul 2026 15:13:54 +0800 Subject: [PATCH 1/2] fix: mirror header forwarding, auth, cache accounting, and CI build speed P0 fixes: - ProxyHTTP/ProxyStream now forward client headers (Authorization, Range, Accept) so docker pull, HuggingFace range requests and private registry pulls work through the mirrors - Cap /v2/ registry 401 retry recursion (was unbounded loop on private repos) - Stream cacheable responses to disk instead of buffering with io.ReadAll (avoids OOM on large wheels/tarballs); cache 200 only, never 206 - Rate limiter ignores forged X-Real-IP/X-Forwarded-For unless peer is loopback; fix IPv6 RemoteAddr parsing and IPv6 CIDR defaults - /api/search now requires auth and uses a 10s-timeout client - gitproxy raw/blob URLs no longer carry the extra /raw/ segment (was 404) - 15 mirror handlers read upstream/TTL per request so runtime config changes (hot reload / dashboard edits) take effect P1 fixes: - gitproxy.enabled now actually gates /gh/ and /gl/ routes - Cache usedBytes accounting: scan on startup, decrement on eviction - Constant-time login comparison + login endpoint rate limited - /api/config/public requires auth - Frontend: search pagination (has_more), per_page param, Settings rate save, 401 redirect, router/client circular import, login redirect, catch-all 404 route, full vite dev proxy list - Align compose/README image refs to ghcr.io/wha7ev9r/devbox - Remove dead assets (icons.svg, vue.svg, vite.svg, hero.png, package-lock.json), bump favicon cache version CI: - Dockerfile: build frontend once on BUILDPLATFORM, cross-compile Go for TARGETARCH (no QEMU emulation), BuildKit cache mounts (expected multi-arch build time ~8min -> ~3-4min) --- Dockerfile | 29 +- README.md | 7 +- cmd/devbox/main.go | 2 - docker-compose.yml | 2 +- internal/dashboard/dashboard.go | 12 +- internal/dashboard/json.go | 2 +- internal/dashboard/search.go | 19 +- internal/gitproxy/gitproxy.go | 5 +- internal/gitproxy/gitproxy_test.go | 6 + internal/mirror/alpine.go | 6 +- internal/mirror/apt.go | 6 +- internal/mirror/cache.go | 151 +- internal/mirror/cargo.go | 8 +- internal/mirror/conda.go | 8 +- internal/mirror/cran.go | 8 +- internal/mirror/ghcr.go | 6 +- internal/mirror/github_api.go | 8 +- internal/mirror/golang.go | 8 +- internal/mirror/homebrew.go | 6 +- internal/mirror/huggingface.go | 6 +- internal/mirror/mcr.go | 6 +- internal/mirror/nuget.go | 8 +- internal/mirror/pypi.go | 8 +- internal/mirror/quay.go | 6 +- internal/mirror/rubygems.go | 8 +- internal/ratelimit/ratelimit.go | 51 +- internal/ratelimit/ratelimit_test.go | 46 + internal/server/server.go | 44 +- web/index.html | 2 +- web/package-lock.json | 1945 -------------------------- web/package.json | 4 +- web/pnpm-lock.yaml | 6 +- web/public/icons.svg | 24 - web/src/App.vue | 12 +- web/src/api/client.ts | 11 +- web/src/assets/hero.png | Bin 13057 -> 0 bytes web/src/assets/vite.svg | 1 - web/src/assets/vue.svg | 1 - web/src/router/index.ts | 3 +- web/src/views/Search.vue | 6 +- web/src/views/Settings.vue | 4 +- web/vite.config.ts | 41 +- 42 files changed, 415 insertions(+), 2127 deletions(-) create mode 100644 internal/ratelimit/ratelimit_test.go delete mode 100644 web/package-lock.json delete mode 100644 web/public/icons.svg delete mode 100644 web/src/assets/hero.png delete mode 100644 web/src/assets/vite.svg delete mode 100644 web/src/assets/vue.svg diff --git a/Dockerfile b/Dockerfile index c2cb370..488f323 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,31 @@ -# Stage 1: Build frontend -FROM node:20-alpine AS frontend +# Stage 1: Build frontend (architecture-independent static assets) +# $BUILDPLATFORM ensures this stage only ever runs on the native builder, +# since the same dist/ works for both amd64 and arm64 images. +FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend RUN corepack enable && corepack prepare pnpm@10 --activate WORKDIR /app/web COPY web/pnpm-lock.yaml web/package.json ./ -RUN pnpm install --frozen-lockfile +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile COPY web/ . -RUN pnpm run build +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm run build -# Stage 2: Build Go binary (CGO_ENABLED=0 cross-compile to Linux) -FROM golang:1.25-alpine AS backend +# Stage 2: Cross-compile Go binary. +# CGO_ENABLED=0 lets us compile for the target arch on the native builder, +# avoiding slow QEMU emulation entirely. +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS backend WORKDIR /app +ARG TARGETOS TARGETARCH COPY go.mod go.sum ./ -RUN go mod download +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o devbox ./cmd/devbox/ +RUN --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-s -w" -o devbox ./cmd/devbox/ -# Stage 3: Minimal runtime image -FROM alpine:3.21 +# Stage 3: Minimal runtime image (per-target-architecture base) +FROM --platform=$TARGETPLATFORM alpine:3.21 RUN apk add --no-cache ca-certificates git curl COPY --from=backend /app/devbox /usr/local/bin/devbox COPY --from=frontend /app/web/dist /usr/share/devbox/frontend diff --git a/README.md b/README.md index 29e333b..7799f78 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ docker run -d \ -p 127.0.0.1:8080:8080 \ -v devbox-data:/data \ -e DEVBOX_PUBLIC_URL=https://dev.example.com \ - ghcr.io/ksbbs/devbox:latest + ghcr.io/wha7ev9r/devbox:latest ``` ## 配置 @@ -198,6 +198,9 @@ logging: retention_days: 30 # 流量日志保留天数 ``` +> 限流仅信任来自本机反向代理(`X-Real-IP` / `X-Forwarded-For`)的客户端 IP; +> 若端口直接暴露公网,伪造这两个头不会生效,将按真实连接 IP 限流。 + ### 环境变量覆盖 所有配置项都可通过环境变量覆盖,格式 `DEVBOX_<层级>_<键>`: @@ -280,7 +283,7 @@ Docker Hub 使用 `registry-mirrors` 配置(Docker 原生支持): docker pull dev.example.com/ghcr/owner/image:tag # 例如拉取 DevBox 自身 -docker pull dev.example.com/ghcr/ksbbs/devbox:latest +docker pull dev.example.com/ghcr/wha7ev9r/devbox:latest ``` > 不需要加 `https://`,Docker 客户端会自动走 HTTPS。如果未配置 SSL,需加 `http://` 前缀并设置 Docker insecure registry。 diff --git a/cmd/devbox/main.go b/cmd/devbox/main.go index be86db2..620d8c5 100644 --- a/cmd/devbox/main.go +++ b/cmd/devbox/main.go @@ -13,8 +13,6 @@ import ( "devbox/internal/config" "devbox/internal/mirror" "devbox/internal/server" - - _ "devbox/internal/mirror" ) func main() { diff --git a/docker-compose.yml b/docker-compose.yml index 3a075ee..7c6ff50 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: devbox: - image: ghcr.io/ksbbs/devbox:latest + image: ghcr.io/wha7ev9r/devbox:latest container_name: devbox restart: always ports: diff --git a/internal/dashboard/dashboard.go b/internal/dashboard/dashboard.go index 8702ad4..9496036 100644 --- a/internal/dashboard/dashboard.go +++ b/internal/dashboard/dashboard.go @@ -376,6 +376,10 @@ func (d *Dashboard) MirrorConfigHandler(w http.ResponseWriter, r *http.Request) } func (d *Dashboard) PublicConfigHandler(w http.ResponseWriter, r *http.Request) { + if !d.checkAuth(r) { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } writeJSON(w, map[string]string{"publicUrl": d.publicURL}) } @@ -403,13 +407,19 @@ func (d *Dashboard) LoginHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "invalid request", http.StatusBadRequest) return } - if req.Token != d.authToken { + if subtle.ConstantTimeCompare([]byte(req.Token), []byte(d.authToken)) != 1 { http.Error(w, "invalid token", http.StatusUnauthorized) return } writeJSON(w, map[string]string{"status": "ok", "token": req.Token}) } +// CheckAuth exposes the token check to other packages (e.g. server routes +// that need the same auth policy as dashboard endpoints). +func (d *Dashboard) CheckAuth(r *http.Request) bool { + return d.checkAuth(r) +} + func (d *Dashboard) RateLimitConfigHandler(w http.ResponseWriter, r *http.Request) { if !d.checkAuth(r) { http.Error(w, "unauthorized", http.StatusUnauthorized) diff --git a/internal/dashboard/json.go b/internal/dashboard/json.go index 3cde27a..fb645c9 100644 --- a/internal/dashboard/json.go +++ b/internal/dashboard/json.go @@ -17,4 +17,4 @@ func readJSON(r *http.Request, v interface{}) bool { return false } return json.Unmarshal(body, v) == nil -} \ No newline at end of file +} diff --git a/internal/dashboard/search.go b/internal/dashboard/search.go index c83e282..72574ad 100644 --- a/internal/dashboard/search.go +++ b/internal/dashboard/search.go @@ -6,8 +6,13 @@ import ( "net/http" "net/url" "strconv" + "time" ) +// searchClient bounds upstream registry API calls so a hung upstream cannot +// stall the request (or leak goroutines) forever. +var searchClient = &http.Client{Timeout: 10 * time.Second} + type SearchHandler struct{} func NewSearchHandler() *SearchHandler { @@ -98,7 +103,7 @@ func (sh *SearchHandler) Search(w http.ResponseWriter, r *http.Request) { } func searchNpm(query string, page, perPage int) ([]SearchResult, bool) { - resp, err := http.Get(fmt.Sprintf("https://registry.npmjs.org/-/v1/search?text=%s&size=%d&from=%d", url.QueryEscape(query), perPage, (page-1)*perPage)) + resp, err := searchClient.Get(fmt.Sprintf("https://registry.npmjs.org/-/v1/search?text=%s&size=%d&from=%d", url.QueryEscape(query), perPage, (page-1)*perPage)) if err != nil { return nil, false } @@ -131,7 +136,7 @@ func searchNpm(query string, page, perPage int) ([]SearchResult, bool) { } func searchDocker(query string, page, perPage int) ([]SearchResult, bool) { - resp, err := http.Get(fmt.Sprintf("https://registry.hub.docker.com/v2/search/repositories/?query=%s&page_size=%d&page=%d", url.QueryEscape(query), perPage, page)) + resp, err := searchClient.Get(fmt.Sprintf("https://registry.hub.docker.com/v2/search/repositories/?query=%s&page_size=%d&page=%d", url.QueryEscape(query), perPage, page)) if err != nil { return nil, false } @@ -164,7 +169,7 @@ func searchDocker(query string, page, perPage int) ([]SearchResult, bool) { } func searchPyPI(query string) []SearchResult { - resp, err := http.Get(fmt.Sprintf("https://pypi.org/pypi/%s/json", url.QueryEscape(query))) + resp, err := searchClient.Get(fmt.Sprintf("https://pypi.org/pypi/%s/json", url.QueryEscape(query))) if err != nil { return nil } @@ -191,7 +196,7 @@ func searchPyPI(query string) []SearchResult { } func searchConda(query string) []SearchResult { - resp, err := http.Get(fmt.Sprintf("https://api.anaconda.org/package/%s", url.QueryEscape(query))) + resp, err := searchClient.Get(fmt.Sprintf("https://api.anaconda.org/package/%s", url.QueryEscape(query))) if err != nil { return nil } @@ -221,7 +226,7 @@ func searchConda(query string) []SearchResult { } func searchRubyGems(query string, page, perPage int) ([]SearchResult, bool) { - resp, err := http.Get(fmt.Sprintf("https://rubygems.org/api/v1/search.json?query=%s&page=%d", url.QueryEscape(query), page)) + resp, err := searchClient.Get(fmt.Sprintf("https://rubygems.org/api/v1/search.json?query=%s&page=%d", url.QueryEscape(query), page)) if err != nil { return nil, false } @@ -255,7 +260,7 @@ func searchRubyGems(query string, page, perPage int) ([]SearchResult, bool) { func searchCargo(query string, page, perPage int) ([]SearchResult, bool) { req, _ := http.NewRequest("GET", fmt.Sprintf("https://crates.io/api/v1/crates?q=%s&page=%d&per_page=%d", url.QueryEscape(query), page, perPage), nil) req.Header.Set("User-Agent", "devbox/1.0") - resp, err := http.DefaultClient.Do(req) + resp, err := searchClient.Do(req) if err != nil { return nil, false } @@ -288,7 +293,7 @@ func searchCargo(query string, page, perPage int) ([]SearchResult, bool) { func searchNuGet(query string, page, perPage int) ([]SearchResult, bool) { skip := (page - 1) * perPage - resp, err := http.Get(fmt.Sprintf("https://azuresearch-usnc.nuget.org/query?q=%s&skip=%d&take=%d", url.QueryEscape(query), skip, perPage)) + resp, err := searchClient.Get(fmt.Sprintf("https://azuresearch-usnc.nuget.org/query?q=%s&skip=%d&take=%d", url.QueryEscape(query), skip, perPage)) if err != nil { return nil, false } diff --git a/internal/gitproxy/gitproxy.go b/internal/gitproxy/gitproxy.go index 196466f..22509e7 100644 --- a/internal/gitproxy/gitproxy.go +++ b/internal/gitproxy/gitproxy.go @@ -53,13 +53,16 @@ func (gp *GitProxy) Handler(w http.ResponseWriter, r *http.Request) { func (gp *GitProxy) proxyGitHub(w http.ResponseWriter, r *http.Request, path string) { if isBlobRequest(path) { - path = strings.Replace(path, "/blob/", "/raw/", 1) + // /user/repo/blob/branch/file → raw.githubusercontent.com/user/repo/branch/file + path = strings.Replace(path, "/blob/", "/", 1) gp.proxyRaw(w, r, path) return } if isArchiveRequest(path) { gp.proxyArchive(w, r, gp.githubUpstream, path) } else if isRawRequest(path) { + // /user/repo/raw/branch/file → raw.githubusercontent.com/user/repo/branch/file + path = strings.Replace(path, "/raw/", "/", 1) gp.proxyRaw(w, r, path) } else { gp.proxySmartHTTP(w, r, gp.githubUpstream, path) diff --git a/internal/gitproxy/gitproxy_test.go b/internal/gitproxy/gitproxy_test.go index 87e3eea..5a21fff 100644 --- a/internal/gitproxy/gitproxy_test.go +++ b/internal/gitproxy/gitproxy_test.go @@ -31,6 +31,9 @@ func TestGitProxyGitHubClone(t *testing.T) { func TestGitProxyGitHubRaw(t *testing.T) { raw := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/user/repo/branch/file.go" { + t.Fatalf("expected path /user/repo/branch/file.go, got %s", r.URL.Path) + } w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) w.Write([]byte("raw-file-content")) @@ -53,6 +56,9 @@ func TestGitProxyGitHubRaw(t *testing.T) { func TestGitProxyGitHubBlobRedirect(t *testing.T) { raw := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/user/repo/branch/file.go" { + t.Fatalf("expected path /user/repo/branch/file.go, got %s", r.URL.Path) + } w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) w.Write([]byte("blob-content")) diff --git a/internal/mirror/alpine.go b/internal/mirror/alpine.go index b92116a..bdb336c 100644 --- a/internal/mirror/alpine.go +++ b/internal/mirror/alpine.go @@ -69,10 +69,10 @@ func (a *AlpineMirror) ApplyConfig(cfg config.MirrorConfig) { } func (a *AlpineMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - a.mu.RLock() - upstream := a.upstream - a.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + a.mu.RLock() + upstream := a.upstream + a.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/alpine") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/apt.go b/internal/mirror/apt.go index 112d6f4..e9ecccd 100644 --- a/internal/mirror/apt.go +++ b/internal/mirror/apt.go @@ -69,10 +69,10 @@ func (a *AptMirror) ApplyConfig(cfg config.MirrorConfig) { } func (a *AptMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - a.mu.RLock() - upstream := a.upstream - a.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + a.mu.RLock() + upstream := a.upstream + a.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/apt") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/cache.go b/internal/mirror/cache.go index e7f6891..8b82966 100644 --- a/internal/mirror/cache.go +++ b/internal/mirror/cache.go @@ -75,6 +75,7 @@ func (c *Cache) Get(key string) ([]byte, http.Header, bool) { os.Remove(path) os.Remove(hdrPath) os.Remove(path + ".exp") + c.usedBytes -= info.Size() c.misses.Add(1) return nil, nil, false } @@ -230,23 +231,33 @@ func (c *Cache) ProxyHTTP(w http.ResponseWriter, r *http.Request, upstream strin if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } - resp, err := proxyClient.Get(target) + + newReq, err := http.NewRequest(r.Method, target, r.Body) if err != nil { - http.Error(w, "upstream error: "+err.Error(), http.StatusBadGateway) + http.Error(w, "request error", http.StatusInternalServerError) return } - defer resp.Body.Close() + copyRequestHeaders(newReq, r) - body, err := io.ReadAll(resp.Body) + resp, err := proxyClient.Do(newReq) if err != nil { - http.Error(w, "read error", http.StatusInternalServerError) + http.Error(w, "upstream error: "+err.Error(), http.StatusBadGateway) return } + defer resp.Body.Close() respHdr := resp.Header.Clone() - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - c.Set(key, body, respHdr, ttl) + // Stream to client while spooling cacheable (200 only, so partial + // 206 responses never poison the cache) responses to a temp file. + cacheable := resp.StatusCode == http.StatusOK && ttl >= 0 + path := c.keyPath(key) + var tmp *os.File + if cacheable { + tmp, _ = os.Create(path + ".tmp") + if tmp != nil { + defer os.Remove(path + ".tmp") + } } for k, vv := range respHdr { @@ -255,7 +266,33 @@ func (c *Cache) ProxyHTTP(w http.ResponseWriter, r *http.Request, upstream strin } } w.WriteHeader(resp.StatusCode) - _, _ = w.Write(body) + + var src io.Reader = resp.Body + if tmp != nil { + src = io.TeeReader(resp.Body, tmp) + } + written, copyErr := io.Copy(w, src) + if tmp == nil { + return + } + if err := tmp.Close(); err != nil || copyErr != nil || written <= 0 { + return + } + + c.mu.Lock() + defer c.mu.Unlock() + if info, err := os.Stat(path); err == nil { + c.usedBytes -= info.Size() + } + if err := os.Rename(path+".tmp", path); err != nil { + slog.Warn("cache rename error", "path", path, "error", err) + return + } + c.writeCacheMeta(path, respHdr, ttl) + c.usedBytes += written + if c.maxBytes > 0 && c.usedBytes > c.maxBytes { + c.evictLRU() + } } func (c *Cache) ProxyStream(w http.ResponseWriter, r *http.Request, upstream string) { @@ -263,7 +300,15 @@ func (c *Cache) ProxyStream(w http.ResponseWriter, r *http.Request, upstream str if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } - resp, err := proxyClient.Get(target) + + newReq, err := http.NewRequest(r.Method, target, r.Body) + if err != nil { + http.Error(w, "request error", http.StatusInternalServerError) + return + } + copyRequestHeaders(newReq, r) + + resp, err := proxyClient.Do(newReq) if err != nil { http.Error(w, "upstream error", http.StatusBadGateway) return @@ -279,20 +324,58 @@ func (c *Cache) ProxyStream(w http.ResponseWriter, r *http.Request, upstream str _, _ = io.Copy(w, resp.Body) } +// copyRequestHeaders copies client headers onto the upstream request, +// skipping hop-by-hop headers like Host. +func copyRequestHeaders(dst *http.Request, src *http.Request) { + for k, vv := range src.Header { + if k == "Host" { + continue + } + for _, v := range vv { + dst.Header.Add(k, v) + } + } +} + +// writeCacheMeta persists response headers and (optionally) an expiry file +// for a cached entry. Callers must hold c.mu. +func (c *Cache) writeCacheMeta(path string, hdr http.Header, ttl time.Duration) { + hdrPath := path + ".hdr" + hf, err := os.Create(hdrPath) + if err != nil { + slog.Warn("cache header write error", "path", hdrPath, "error", err) + return + } + for k, vv := range hdr { + for _, v := range vv { + fmt.Fprintf(hf, "%s:%s\n", k, v) + } + } + hf.Close() + + if ttl > 0 { + expPath := path + ".exp" + ef, err := os.Create(expPath) + if err != nil { + slog.Warn("cache expiry write error", "path", expPath, "error", err) + return + } + fmt.Fprintf(ef, "%d", time.Now().Add(ttl).Unix()) + ef.Close() + } +} + func (c *Cache) Hits() int64 { return c.hits.Load() } func (c *Cache) Misses() int64 { return c.misses.Load() } func (c *Cache) CleanExpired() { c.mu.Lock() defer c.mu.Unlock() - cleanDirExpired(c.dir, time.Now().Unix()) -} - -func cleanDirExpired(dir string, now int64) { - files, _ := os.ReadDir(dir) + now := time.Now().Unix() + files, _ := os.ReadDir(c.dir) for _, f := range files { if !f.IsDir() && filepath.Ext(f.Name()) == ".exp" { - data, err := os.ReadFile(filepath.Join(dir, f.Name())) + data, err := os.ReadFile(filepath.Join(c.dir, f.Name())) if err != nil { continue } @@ -300,16 +383,44 @@ func cleanDirExpired(dir string, now int64) { if err != nil { continue } - if now > expiry { - base := filepath.Join(dir, strings.TrimSuffix(f.Name(), ".exp")) - os.Remove(base) - os.Remove(base + ".hdr") - os.Remove(base + ".exp") + if now <= expiry { + continue + } + base := filepath.Join(c.dir, strings.TrimSuffix(f.Name(), ".exp")) + if info, err := os.Stat(base); err == nil { + c.usedBytes -= info.Size() } + os.Remove(base) + os.Remove(base + ".hdr") + os.Remove(base + ".exp") } } } +// ScanSize walks the cache dir and initializes usedBytes from the files on +// disk, so size accounting survives process restarts. +func (c *Cache) ScanSize() { + c.mu.Lock() + defer c.mu.Unlock() + var total int64 + _ = filepath.WalkDir(c.dir, func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return nil + } + name := d.Name() + if filepath.Ext(name) == ".exp" || filepath.Ext(name) == ".hdr" || strings.HasSuffix(name, ".tmp") { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + total += info.Size() + return nil + }) + c.usedBytes = total +} + func (c *Cache) keyPath(key string) string { hash := sha256.Sum256([]byte(key)) return filepath.Join(c.dir, hex.EncodeToString(hash[:])) diff --git a/internal/mirror/cargo.go b/internal/mirror/cargo.go index 49300cb..fb120a4 100644 --- a/internal/mirror/cargo.go +++ b/internal/mirror/cargo.go @@ -69,11 +69,11 @@ func (c *CargoMirror) ApplyConfig(cfg config.MirrorConfig) { } func (c *CargoMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - c.mu.RLock() - upstream := c.upstream - cacheTTL := c.cacheTTL - c.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + c.mu.RLock() + upstream := c.upstream + cacheTTL := c.cacheTTL + c.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/cargo") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/conda.go b/internal/mirror/conda.go index c84ebaf..64d3851 100644 --- a/internal/mirror/conda.go +++ b/internal/mirror/conda.go @@ -69,11 +69,11 @@ func (c *CondaMirror) ApplyConfig(cfg config.MirrorConfig) { } func (c *CondaMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - c.mu.RLock() - upstream := c.upstream - cacheTTL := c.cacheTTL - c.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + c.mu.RLock() + upstream := c.upstream + cacheTTL := c.cacheTTL + c.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/conda") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/cran.go b/internal/mirror/cran.go index 201d34f..6f115e3 100644 --- a/internal/mirror/cran.go +++ b/internal/mirror/cran.go @@ -69,11 +69,11 @@ func (c *CranMirror) ApplyConfig(cfg config.MirrorConfig) { } func (c *CranMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - c.mu.RLock() - upstream := c.upstream - cacheTTL := c.cacheTTL - c.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + c.mu.RLock() + upstream := c.upstream + cacheTTL := c.cacheTTL + c.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/cran") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/ghcr.go b/internal/mirror/ghcr.go index a2c985e..d4af93f 100644 --- a/internal/mirror/ghcr.go +++ b/internal/mirror/ghcr.go @@ -69,10 +69,10 @@ func (g *GhcrMirror) ApplyConfig(cfg config.MirrorConfig) { } func (g *GhcrMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - g.mu.RLock() - upstream := g.upstream - g.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + g.mu.RLock() + upstream := g.upstream + g.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/ghcr") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/github_api.go b/internal/mirror/github_api.go index 3726eb0..2d3eb2a 100644 --- a/internal/mirror/github_api.go +++ b/internal/mirror/github_api.go @@ -69,11 +69,11 @@ func (g *GithubAPIMirror) ApplyConfig(cfg config.MirrorConfig) { } func (g *GithubAPIMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - g.mu.RLock() - upstream := g.upstream - cacheTTL := g.cacheTTL - g.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + g.mu.RLock() + upstream := g.upstream + cacheTTL := g.cacheTTL + g.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/ghapi") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/golang.go b/internal/mirror/golang.go index fef895a..c7d3fdb 100644 --- a/internal/mirror/golang.go +++ b/internal/mirror/golang.go @@ -69,11 +69,11 @@ func (g *GolangMirror) ApplyConfig(cfg config.MirrorConfig) { } func (g *GolangMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - g.mu.RLock() - upstream := g.upstream - cacheTTL := g.cacheTTL - g.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + g.mu.RLock() + upstream := g.upstream + cacheTTL := g.cacheTTL + g.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/golang") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/homebrew.go b/internal/mirror/homebrew.go index 3db5e95..bafa235 100644 --- a/internal/mirror/homebrew.go +++ b/internal/mirror/homebrew.go @@ -69,10 +69,10 @@ func (h *HomebrewMirror) ApplyConfig(cfg config.MirrorConfig) { } func (h *HomebrewMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - h.mu.RLock() - upstream := h.upstream - h.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + h.mu.RLock() + upstream := h.upstream + h.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/homebrew") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/huggingface.go b/internal/mirror/huggingface.go index e82b658..1e03b97 100644 --- a/internal/mirror/huggingface.go +++ b/internal/mirror/huggingface.go @@ -69,10 +69,10 @@ func (h *HfMirror) ApplyConfig(cfg config.MirrorConfig) { } func (h *HfMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - h.mu.RLock() - upstream := h.upstream - h.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + h.mu.RLock() + upstream := h.upstream + h.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/hf") // HuggingFace files can be large (model weights), use streaming proxy cache.ProxyStream(w, r, upstream) diff --git a/internal/mirror/mcr.go b/internal/mirror/mcr.go index 1d23131..d07463f 100644 --- a/internal/mirror/mcr.go +++ b/internal/mirror/mcr.go @@ -69,10 +69,10 @@ func (m *McrMirror) ApplyConfig(cfg config.MirrorConfig) { } func (m *McrMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - m.mu.RLock() - upstream := m.upstream - m.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + m.mu.RLock() + upstream := m.upstream + m.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/mcr") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/nuget.go b/internal/mirror/nuget.go index e6bf8c7..2a0b3ae 100644 --- a/internal/mirror/nuget.go +++ b/internal/mirror/nuget.go @@ -69,11 +69,11 @@ func (n *NuGetMirror) ApplyConfig(cfg config.MirrorConfig) { } func (n *NuGetMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - n.mu.RLock() - upstream := n.upstream - cacheTTL := n.cacheTTL - n.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + n.mu.RLock() + upstream := n.upstream + cacheTTL := n.cacheTTL + n.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/nuget") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/pypi.go b/internal/mirror/pypi.go index 1a021f6..6e9fd30 100644 --- a/internal/mirror/pypi.go +++ b/internal/mirror/pypi.go @@ -69,11 +69,11 @@ func (p *PypiMirror) ApplyConfig(cfg config.MirrorConfig) { } func (p *PypiMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - p.mu.RLock() - upstream := p.upstream - cacheTTL := p.cacheTTL - p.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + p.mu.RLock() + upstream := p.upstream + cacheTTL := p.cacheTTL + p.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/pypi") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/mirror/quay.go b/internal/mirror/quay.go index f2cbde6..1d9dd6d 100644 --- a/internal/mirror/quay.go +++ b/internal/mirror/quay.go @@ -69,10 +69,10 @@ func (q *QuayMirror) ApplyConfig(cfg config.MirrorConfig) { } func (q *QuayMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - q.mu.RLock() - upstream := q.upstream - q.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + q.mu.RLock() + upstream := q.upstream + q.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/quay") cache.ProxyStream(w, r, upstream) } diff --git a/internal/mirror/rubygems.go b/internal/mirror/rubygems.go index 02d03f4..4ffb595 100644 --- a/internal/mirror/rubygems.go +++ b/internal/mirror/rubygems.go @@ -69,11 +69,11 @@ func (rg *RubyGemsMirror) ApplyConfig(cfg config.MirrorConfig) { } func (rg *RubyGemsMirror) ProxyHandler(cache *Cache) http.HandlerFunc { - rg.mu.RLock() - upstream := rg.upstream - cacheTTL := rg.cacheTTL - rg.mu.RUnlock() return func(w http.ResponseWriter, r *http.Request) { + rg.mu.RLock() + upstream := rg.upstream + cacheTTL := rg.cacheTTL + rg.mu.RUnlock() r.URL.Path = strings.TrimPrefix(r.URL.Path, "/rubygems") cache.ProxyHTTP(w, r, upstream, cacheTTL) } diff --git a/internal/ratelimit/ratelimit.go b/internal/ratelimit/ratelimit.go index dae1212..bf7daf7 100644 --- a/internal/ratelimit/ratelimit.go +++ b/internal/ratelimit/ratelimit.go @@ -95,7 +95,11 @@ func parseCIDRList(list []string) []*net.IPNet { var nets []*net.IPNet for _, entry := range list { if !strings.Contains(entry, "/") { - entry += "/32" + if strings.Contains(entry, ":") { + entry += "/128" + } else { + entry += "/32" + } } _, ipNet, err := net.ParseCIDR(entry) if err == nil { @@ -114,24 +118,37 @@ func parseIP(ipStr string) net.IP { } func extractIP(r *http.Request) string { - ip := r.Header.Get("X-Real-IP") - if ip != "" { - return ip - } - ip = r.Header.Get("X-Forwarded-For") - if ip != "" { - for i := 0; i < len(ip); i++ { - if ip[i] == ',' { - return ip[:i] + // Only trust proxy headers (X-Real-IP / X-Forwarded-For) when the direct + // peer is a loopback address, i.e. traffic arrives via a local reverse + // proxy. Directly exposed clients can otherwise forge these headers to + // bypass or deflect rate limiting. + remoteIP := peerIP(r.RemoteAddr) + if remoteIP != nil && remoteIP.IsLoopback() { + if ip := r.Header.Get("X-Real-IP"); ip != "" { + return strings.TrimSpace(ip) + } + if ip := r.Header.Get("X-Forwarded-For"); ip != "" { + if idx := strings.IndexByte(ip, ','); idx > 0 { + ip = ip[:idx] } + return strings.TrimSpace(ip) } - return ip } - host := r.RemoteAddr - for i := len(host) - 1; i >= 0; i-- { - if host[i] == ':' { - return host[:i] - } + if remoteIP != nil { + return remoteIP.String() + } + return r.RemoteAddr +} + +// peerIP extracts the IP from a RemoteAddr string ("1.2.3.4:5678", "[::1]:80"). +func peerIP(remoteAddr string) net.IP { + host, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + host = remoteAddr } - return host + ip := net.ParseIP(host) + if ip == nil { + return nil + } + return ip } diff --git a/internal/ratelimit/ratelimit_test.go b/internal/ratelimit/ratelimit_test.go new file mode 100644 index 0000000..fd02a8e --- /dev/null +++ b/internal/ratelimit/ratelimit_test.go @@ -0,0 +1,46 @@ +package ratelimit + +import ( + "net/http/httptest" + "testing" +) + +func TestExtractIPIgnoresForgedProxyHeaders(t *testing.T) { + // Directly exposed clients must not be able to forge proxy headers. + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "203.0.113.7:5678" + req.Header.Set("X-Real-IP", "10.0.0.1") + req.Header.Set("X-Forwarded-For", "10.0.0.2, 10.0.0.3") + + if got := extractIP(req); got != "203.0.113.7" { + t.Fatalf("expected real peer IP 203.0.113.7, got %s", got) + } +} + +func TestExtractIPTrustsProxyBehindLoopback(t *testing.T) { + // Behind a local reverse proxy the client IP arrives via headers. + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "127.0.0.1:8080" + req.Header.Set("X-Real-IP", "203.0.113.9") + + if got := extractIP(req); got != "203.0.113.9" { + t.Fatalf("expected forwarded IP 203.0.113.9, got %s", got) + } + + req2 := httptest.NewRequest("GET", "/", nil) + req2.RemoteAddr = "[::1]:8080" + req2.Header.Set("X-Forwarded-For", "203.0.113.10, 203.0.113.11") + + if got := extractIP(req2); got != "203.0.113.10" { + t.Fatalf("expected first forwarded IP 203.0.113.10, got %s", got) + } +} + +func TestExtractIPIPv6Peer(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "[2001:db8::1]:443" + + if got := extractIP(req); got != "2001:db8::1" { + t.Fatalf("expected IPv6 peer, got %s", got) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 480dfdd..adb3955 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -83,6 +83,7 @@ func New(cfg *config.Config, configPath string, frontDir string) (*Server, error } cache := mirror.NewCache(cfg.Cache.Dir, cfg.Cache.MaxSizeBytes) + cache.ScanSize() gp := gitproxy.New( cfg.GitProxy.GithubUpstream, @@ -153,8 +154,10 @@ func (s *Server) Start() error { } // Git proxy routes - mux.HandleFunc("/gh/", s.wrapWithStats("gitproxy", s.gitProxyHandler)) - mux.HandleFunc("/gl/", s.wrapWithStats("gitproxy", s.gitProxyHandler)) + if s.cfg.GitProxy.Enabled { + mux.HandleFunc("/gh/", s.wrapWithStats("gitproxy", s.gitProxyHandler)) + mux.HandleFunc("/gl/", s.wrapWithStats("gitproxy", s.gitProxyHandler)) + } // Dashboard API routes mux.HandleFunc("/api/status", s.dash.StatusHandler) @@ -165,7 +168,7 @@ func (s *Server) Start() error { mux.HandleFunc("/api/config/public", s.dash.PublicConfigHandler) mux.HandleFunc("/api/auth/login", s.dash.LoginHandler) mux.HandleFunc("/api/auth/check", s.dash.AuthCheckHandler) - mux.HandleFunc("/api/search", s.search.Search) + mux.HandleFunc("/api/search", s.authWrapped(s.search.Search)) mux.HandleFunc("/api/release-sources", s.dash.ReleaseSourcesHandler) mux.HandleFunc("/api/release-sources/", s.dash.ReleaseSourceHandler) mux.HandleFunc("/api/release-download", s.dash.ReleaseDownloadHandler) @@ -299,12 +302,12 @@ func (s *Server) registryV2Handler(w http.ResponseWriter, r *http.Request) { if err != nil { slog.Error("registry token error", "registry", registryName, "error", err) // Try without token (some repos are public) - s.proxyRegistryRequest(w, r, target, "") + s.proxyRegistryRequest(w, r, target, "", 0) return } slog.Info("registry request", "method", r.Method, "path", path, "target", target, "token_prefix", token[:min(10, len(token))]) - s.proxyRegistryRequest(w, r, target, token) + s.proxyRegistryRequest(w, r, target, token, 0) } func newRegistryClient() *http.Client { @@ -323,7 +326,15 @@ func newRegistryClient() *http.Client { } } -func (s *Server) proxyRegistryRequest(w http.ResponseWriter, r *http.Request, target string, token string) { +func (s *Server) proxyRegistryRequest(w http.ResponseWriter, r *http.Request, target string, token string, depth int) { + // Guard against unbounded 401 retry recursion (e.g. private repos with + // wrong scopes) which would otherwise loop forever against the upstream. + if depth > 2 { + slog.Warn("registry auth retry loop detected, aborting", "path", r.URL.Path) + http.Error(w, "upstream auth loop", http.StatusUnauthorized) + return + } + upstreamReq, err := http.NewRequest(r.Method, target, r.Body) if err != nil { http.Error(w, "request error", http.StatusInternalServerError) @@ -372,7 +383,7 @@ func (s *Server) proxyRegistryRequest(w http.ResponseWriter, r *http.Request, ta resp.Body.Close() // Retry without token — let upstream give fresh 401 slog.Warn("registry token rejected, retrying without token") - s.proxyRegistryRequest(w, r, target, "") + s.proxyRegistryRequest(w, r, target, "", depth+1) return } @@ -392,7 +403,7 @@ func (s *Server) proxyRegistryRequest(w http.ResponseWriter, r *http.Request, ta if err == nil && newToken != "" { resp.Body.Close() slog.Info("registry got new token, retrying", "scope", scope) - s.proxyRegistryRequest(w, r, target, newToken) + s.proxyRegistryRequest(w, r, target, newToken, depth+1) return } } @@ -606,6 +617,17 @@ func (s *Server) mirrorEnabledWrapper(m mirror.Mirror, next http.HandlerFunc) ht } } +// authWrapped applies the same token policy as dashboard endpoints. +func (s *Server) authWrapped(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !s.dash.CheckAuth(r) { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +} + func (s *Server) configWatcher() { if s.configPath == "" { return @@ -893,7 +915,11 @@ func logMiddleware(next http.Handler, accessLog bool) http.Handler { func (s *Server) rateLimitMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path - if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/v2/") || + // Login is exempted from the blanket /api/ pass-through so brute + // force attempts are throttled like everything else. + if path == "/api/auth/login" { + // no-op: fall through to rate limiting + } else if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/v2/") || strings.HasPrefix(path, "/token") || path == "/" || path == "/health" || strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".ico") { diff --git a/web/index.html b/web/index.html index 06b9c09..629863b 100644 --- a/web/index.html +++ b/web/index.html @@ -2,7 +2,7 @@ - + DevBox diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index 4a62242..0000000 --- a/web/package-lock.json +++ /dev/null @@ -1,1945 +0,0 @@ -{ - "name": "web", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "web", - "version": "0.0.0", - "dependencies": { - "vue": "^3.5.32" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.2.4", - "@types/node": "^24.12.2", - "@vitejs/plugin-vue": "^6.0.6", - "@vue/tsconfig": "^0.9.1", - "axios": "^1.15.2", - "tailwindcss": "^4.2.4", - "typescript": "~6.0.2", - "vite": "^8.0.10", - "vue-router": "^4.6.4", - "vue-tsc": "^3.2.7" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", - "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", - "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.4" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", - "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-x64": "4.2.4", - "@tailwindcss/oxide-freebsd-x64": "4.2.4", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-x64-musl": "4.2.4", - "@tailwindcss/oxide-wasm32-wasi": "4.2.4", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", - "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", - "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", - "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", - "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", - "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", - "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", - "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", - "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", - "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", - "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", - "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", - "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz", - "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.2.4", - "@tailwindcss/oxide": "4.2.4", - "tailwindcss": "4.2.4" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.6.tgz", - "integrity": "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.13" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz", - "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.2", - "@vue/shared": "3.5.33", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz", - "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.33", - "@vue/shared": "3.5.33" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz", - "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.2", - "@vue/compiler-core": "3.5.33", - "@vue/compiler-dom": "3.5.33", - "@vue/compiler-ssr": "3.5.33", - "@vue/shared": "3.5.33", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.10", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz", - "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.33", - "@vue/shared": "3.5.33" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/language-core": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.7.tgz", - "integrity": "sha512-Gn4q/tRxbpVGLEuARQ43p3YELlNAFgRUVCgW9U5Cr+5q4vfD2bWDWpl3ABbJMXUt5xlE1dF8dkigg2aUq7JYYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.1.2", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.4" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz", - "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.33" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz", - "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.33", - "@vue/shared": "3.5.33" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz", - "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.33", - "@vue/runtime-core": "3.5.33", - "@vue/shared": "3.5.33", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz", - "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.33", - "@vue/shared": "3.5.33" - }, - "peerDependencies": { - "vue": "3.5.33" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz", - "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==", - "license": "MIT" - }, - "node_modules/@vue/tsconfig": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", - "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">= 5.8", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/alien-signals": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", - "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", - "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "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/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", - "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", - "dev": true, - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", - "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz", - "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.33", - "@vue/compiler-sfc": "3.5.33", - "@vue/runtime-dom": "3.5.33", - "@vue/server-renderer": "3.5.33", - "@vue/shared": "3.5.33" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-router": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", - "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/vue-tsc": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.7.tgz", - "integrity": "sha512-zc1tL3HoQni1zGTGrwBVRQb7rGP5SWdu/m4rGB6JcnAC5MT5LFZIxF7Y+EJEnt4hGF23d60rXH7gRjHGb5KQQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.28", - "@vue/language-core": "3.2.7" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - } - } -} diff --git a/web/package.json b/web/package.json index b1e30f5..6910c2b 100644 --- a/web/package.json +++ b/web/package.json @@ -10,7 +10,8 @@ }, "dependencies": { "axios": "^1.15.2", - "vue": "^3.5.32" + "vue": "^3.5.32", + "vue-router": "^4.6.4" }, "devDependencies": { "@tailwindcss/vite": "^4.2.4", @@ -20,7 +21,6 @@ "tailwindcss": "^4.2.4", "typescript": "~6.0.2", "vite": "^8.0.10", - "vue-router": "^4.6.4", "vue-tsc": "^3.2.7" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 2180f43..3eef6aa 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: vue: specifier: ^3.5.32 version: 3.5.35(typescript@6.0.3) + vue-router: + specifier: ^4.6.4 + version: 4.6.4(vue@3.5.35(typescript@6.0.3)) devDependencies: '@tailwindcss/vite': specifier: ^4.2.4 @@ -36,9 +39,6 @@ importers: vite: specifier: ^8.0.10 version: 8.0.16(@types/node@24.13.1)(jiti@2.7.0) - vue-router: - specifier: ^4.6.4 - version: 4.6.4(vue@3.5.35(typescript@6.0.3)) vue-tsc: specifier: ^3.2.7 version: 3.3.3(typescript@6.0.3) diff --git a/web/public/icons.svg b/web/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/web/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/App.vue b/web/src/App.vue index 12bc265..88a1615 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,6 +1,14 @@