feat: optimize Docker CI/CD builds, refine site-wide UI aesthetic, and remediate secrets
swadhinbiswas committed 2 months ago
3846c8b
1 parent c0437ed
commit 3846c8b
100 files changed
+9765 -4250
Showing 84 changed files+7627-1948
This diff is too large and has been truncated. Displaying first 84 files.
.astro/settings.json.dockerignore.github/workflows/docker-publish.ymlDockerfileDockerfile.runnerDockerfile.workerREADME.mdUIPLAN.mdcli/README.mdcommunity/.env.examplecommunity/.gitignorecommunity/README.mdcommunity/astro.config.mjscommunity/bun.lockcommunity/drizzle.config.tscommunity/package.jsoncommunity/public/logo.svgcommunity/scripts/cron-sync.tscommunity/src/components/InstanceCard.tsxcommunity/src/components/ProfileCard.tsxcommunity/src/components/RepoCard.tsxcommunity/src/components/layout/Footer.astrocommunity/src/components/layout/Header.tsxcommunity/src/components/ui/avatar.tsxcommunity/src/components/ui/badge.tsxcommunity/src/components/ui/button.tsxcommunity/src/components/ui/card.tsxcommunity/src/components/ui/input.tsxcommunity/src/components/ui/label.tsxcommunity/src/components/ui/toaster.tsxcommunity/src/env.d.tscommunity/src/layouts/BaseLayout.astrocommunity/src/lib/auth.tscommunity/src/lib/cache.tscommunity/src/lib/db/index.tscommunity/src/lib/db/schema.tscommunity/src/lib/instance-client.tscommunity/src/lib/utils.tscommunity/src/pages/api/auth/login.tscommunity/src/pages/api/auth/logout.tscommunity/src/pages/api/auth/register.tscommunity/src/pages/api/follows/index.tscommunity/src/pages/api/instances/index.tscommunity/src/pages/api/instances/submit.tscommunity/src/pages/api/instances/sync.tscommunity/src/pages/api/search.tscommunity/src/pages/api/stars/index.tscommunity/src/pages/auth/login.astrocommunity/src/pages/auth/register.astrocommunity/src/pages/dashboard.astrocommunity/src/pages/explore.astrocommunity/src/pages/index.astrocommunity/src/pages/instances/[id].astrocommunity/src/pages/instances/index.astrocommunity/src/pages/instances/submit.astrocommunity/src/pages/r/[instance]/[owner]/[repo].astrocommunity/src/pages/u/[username].astrocommunity/src/styles/globals.csscommunity/tailwind.config.mjscommunity/tsconfig.jsondocker-compose.ymldocs-site/src/content/docs/getting-started/installation.mddocs/features/federation.mddocs/getting-started/installation.mddrizzle.config.tspublic/favicon.pngpublic/favicon.svgpublic/logo.svgpublic/opencodelogo.pngscripts/e2e-proof.shsrc/components/dashboard/StackCard.astrosrc/components/ide/WebIDE.tsxsrc/components/issues/IssueDetail.tsxsrc/components/issues/IssuesList.tsxsrc/components/layout/Header.astrosrc/components/profile/UserProfileView.astrosrc/components/pulls/PullRequestsList.tsxsrc/components/ui/Logo.astrosrc/components/ui/Logo.tsxsrc/pages/[owner]/[repo]/issues/[number].astrosrc/pages/actions.astrosrc/pages/api/repos/[owner]/[repo]/pulls/[number]/comments.tssrc/pages/api/repos/[owner]/[repo]/pulls/[number]/reviews/batch.tssrc/pages/dashboard.astro
▼.astro/settings.json
+1-1
| @@ -1,5 +1,5 @@ | ||
1 | 1 | { |
2 | 2 | "_variables": { |
3 | - "lastUpdateCheck": 1785773647919 | |
3 | + "lastUpdateCheck": 1787148919035 | |
4 | 4 | } |
5 | 5 | } |
6 | 6 | |
▼.dockerignore
+70-4
| @@ -1,9 +1,75 @@ | ||
1 | +# Version control | |
2 | +.git | |
3 | +.gitignore | |
4 | +.gitattributes | |
5 | +.github | |
6 | + | |
7 | +# Dependencies (always install inside container) | |
1 | 8 | node_modules |
9 | +*/node_modules | |
10 | +community/node_modules | |
11 | +docs-site/node_modules | |
12 | +cli/node_modules | |
13 | + | |
14 | +# Subprojects not needed in production app container | |
15 | +community | |
16 | +docs-site | |
17 | +packages | |
18 | +doc | |
19 | +docs | |
20 | + | |
21 | +# Build outputs & caches | |
2 | 22 | dist |
3 | -.git | |
4 | -.env | |
5 | -data | |
6 | -tests | |
23 | +.astro | |
24 | +.cache | |
25 | +.parcel-cache | |
26 | +coverage | |
7 | 27 | playwright-report |
8 | 28 | test-results |
29 | +.nyc_output | |
30 | + | |
31 | +# Local runtime data, state & logs | |
32 | +data | |
33 | +.tmp | |
34 | +*.log | |
35 | +logs | |
36 | +*.local.db | |
37 | +*.sqlite | |
38 | +*.sqlite-journal | |
39 | +*.db | |
40 | +test.db | |
41 | + | |
42 | +# Test suites & benchmarks | |
43 | +tests | |
44 | +__tests__ | |
45 | +*.test.ts | |
46 | +*.spec.ts | |
47 | +playwright.config.ts | |
48 | +vitest.config.ts | |
49 | + | |
50 | +# Documentation & project planning files | |
51 | +*.md | |
52 | +!DOCKERHUB.md | |
53 | +UIPLAN.md | |
54 | +features.md | |
55 | +notimplemented.md | |
56 | +github-roadmap-issues.json | |
57 | +protection.json | |
58 | +out.json | |
59 | +pci | |
60 | +check.log | |
61 | + | |
62 | +# IDE, agent & system metadata | |
63 | +.vscode | |
64 | +.idea | |
9 | 65 | .DS_Store |
66 | +Thumbs.db | |
67 | +*.swp | |
68 | +*.swo | |
69 | +.gemini | |
70 | +scratch | |
71 | + | |
72 | +# Environment secrets | |
73 | +.env | |
74 | +.env.* | |
75 | +!.env.example | |
10 | 76 | |
▼.github/workflows/docker-publish.yml
+63-76
| @@ -5,15 +5,41 @@ on: | ||
5 | 5 | branches: [main] |
6 | 6 | tags: ["v*"] |
7 | 7 | workflow_dispatch: |
8 | + inputs: | |
9 | + platforms: | |
10 | + description: "Platforms to build (e.g. linux/amd64,linux/arm64 or linux/amd64)" | |
11 | + required: false | |
12 | + default: "linux/amd64" | |
13 | + type: string | |
8 | 14 | |
9 | 15 | env: |
10 | 16 | REGISTRY: docker.io |
11 | - IMAGE_NAME: opencodehub/opencodehub | |
12 | - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: "true" | |
17 | + IMAGE_BASE: opencodehub/opencodehub | |
13 | 18 | |
14 | 19 | jobs: |
15 | 20 | build-and-push: |
21 | + name: Build & Push (${{ matrix.target.name }}) | |
16 | 22 | runs-on: ubuntu-latest |
23 | + strategy: | |
24 | + fail-fast: false | |
25 | + matrix: | |
26 | + target: | |
27 | + - name: app | |
28 | + file: ./Dockerfile | |
29 | + suffix: "" | |
30 | + title: "OpenCodeHub Platform" | |
31 | + desc: "A modern, self-hosted Git platform with stacked PRs, merge queue, CI/CD, and AI review." | |
32 | + - name: worker | |
33 | + file: ./Dockerfile.worker | |
34 | + suffix: "-worker" | |
35 | + title: "OpenCodeHub Worker" | |
36 | + desc: "Background task worker for OpenCodeHub (queues, webhooks, notifications)." | |
37 | + - name: runner | |
38 | + file: ./Dockerfile.runner | |
39 | + suffix: "-runner" | |
40 | + title: "OpenCodeHub Runner" | |
41 | + desc: "Docker-in-Docker CI/CD runner for OpenCodeHub pipelines." | |
42 | + | |
17 | 43 | permissions: |
18 | 44 | contents: read |
19 | 45 | packages: write |
| @@ -22,7 +48,19 @@ jobs: | ||
22 | 48 | - name: Checkout |
23 | 49 | uses: actions/checkout@v4 |
24 | 50 | |
25 | - - name: Set up QEMU | |
51 | + - name: Determine platforms | |
52 | + id: platforms | |
53 | + run: | | |
54 | + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.platforms }}" ]; then | |
55 | + echo "platforms=${{ github.event.inputs.platforms }}" >> $GITHUB_OUTPUT | |
56 | + elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
57 | + echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT | |
58 | + else | |
59 | + echo "platforms=linux/amd64" >> $GITHUB_OUTPUT | |
60 | + fi | |
61 | + | |
62 | + - name: Set up QEMU (only when multi-arch requested) | |
63 | + if: contains(steps.platforms.outputs.platforms, 'arm64') | |
26 | 64 | uses: docker/setup-qemu-action@v3 |
27 | 65 | |
28 | 66 | - name: Set up Docker Buildx |
| @@ -34,11 +72,11 @@ jobs: | ||
34 | 72 | username: ${{ secrets.DOCKER_USERNAME }} |
35 | 73 | password: ${{ secrets.DOCKER_TOKEN }} |
36 | 74 | |
37 | - - name: Extract Metadata (App) | |
38 | - id: meta-app | |
75 | + - name: Extract Metadata | |
76 | + id: meta | |
39 | 77 | uses: docker/metadata-action@v5 |
40 | 78 | with: |
41 | - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
79 | + images: ${{ env.REGISTRY }}/${{ env.IMAGE_BASE }}${{ matrix.target.suffix }} | |
42 | 80 | tags: | |
43 | 81 | type=ref,event=branch |
44 | 82 | type=semver,pattern={{version}} |
| @@ -46,89 +84,38 @@ jobs: | ||
46 | 84 | type=sha,prefix= |
47 | 85 | type=raw,value=latest,enable={{is_default_branch}} |
48 | 86 | labels: | |
49 | - org.opencontainers.image.title=OpenCodeHub Platform | |
50 | - org.opencontainers.image.description=A modern, self-hosted Git platform with stacked PRs, merge queue, CI/CD, and AI review. | |
87 | + org.opencontainers.image.title=${{ matrix.target.title }} | |
88 | + org.opencontainers.image.description=${{ matrix.target.desc }} | |
51 | 89 | org.opencontainers.image.url=https://github.com/swadhinbiswas/OpencodeHub |
52 | 90 | org.opencontainers.image.source=https://github.com/swadhinbiswas/OpencodeHub |
53 | 91 | org.opencontainers.image.licenses=MIT |
54 | 92 | |
55 | - - name: Extract Metadata (Worker) | |
56 | - id: meta-worker | |
57 | - uses: docker/metadata-action@v5 | |
58 | - with: | |
59 | - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-worker | |
60 | - tags: | | |
61 | - type=ref,event=branch | |
62 | - type=semver,pattern={{version}} | |
63 | - type=semver,pattern={{major}}.{{minor}} | |
64 | - type=sha,prefix= | |
65 | - type=raw,value=latest,enable={{is_default_branch}} | |
66 | - labels: | | |
67 | - org.opencontainers.image.title=OpenCodeHub Worker | |
68 | - org.opencontainers.image.description=Background task worker for OpenCodeHub (queues, webhooks, notifications). | |
69 | - org.opencontainers.image.url=https://github.com/swadhinbiswas/OpencodeHub | |
70 | - org.opencontainers.image.source=https://github.com/swadhinbiswas/OpencodeHub | |
71 | - org.opencontainers.image.licenses=MIT | |
72 | - | |
73 | - - name: Extract Metadata (Runner) | |
74 | - id: meta-runner | |
75 | - uses: docker/metadata-action@v5 | |
76 | - with: | |
77 | - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-runner | |
78 | - tags: | | |
79 | - type=ref,event=branch | |
80 | - type=semver,pattern={{version}} | |
81 | - type=semver,pattern={{major}}.{{minor}} | |
82 | - type=sha,prefix= | |
83 | - type=raw,value=latest,enable={{is_default_branch}} | |
84 | - labels: | | |
85 | - org.opencontainers.image.title=OpenCodeHub Runner | |
86 | - org.opencontainers.image.description=Docker-in-Docker CI/CD runner for OpenCodeHub pipelines. | |
87 | - org.opencontainers.image.url=https://github.com/swadhinbiswas/OpencodeHub | |
88 | - org.opencontainers.image.source=https://github.com/swadhinbiswas/OpencodeHub | |
89 | - org.opencontainers.image.licenses=MIT | |
90 | - | |
91 | - - name: Build and Push App Image | |
92 | - uses: docker/build-push-action@v5 | |
93 | - with: | |
94 | - context: . | |
95 | - file: ./Dockerfile | |
96 | - push: true | |
97 | - tags: ${{ steps.meta-app.outputs.tags }} | |
98 | - labels: ${{ steps.meta-app.outputs.labels }} | |
99 | - cache-from: type=gha | |
100 | - cache-to: type=gha,mode=max | |
101 | - platforms: linux/amd64,linux/arm64 | |
102 | - | |
103 | - - name: Build and Push Worker Image | |
93 | + - name: Build and Push Image | |
104 | 94 | uses: docker/build-push-action@v5 |
105 | 95 | with: |
106 | 96 | context: . |
107 | - file: ./Dockerfile.worker | |
97 | + file: ${{ matrix.target.file }} | |
108 | 98 | push: true |
109 | - tags: ${{ steps.meta-worker.outputs.tags }} | |
110 | - labels: ${{ steps.meta-worker.outputs.labels }} | |
111 | - cache-from: type=gha | |
112 | - cache-to: type=gha,mode=max | |
113 | - platforms: linux/amd64,linux/arm64 | |
99 | + tags: ${{ steps.meta.outputs.tags }} | |
100 | + labels: ${{ steps.meta.outputs.labels }} | |
101 | + cache-from: type=gha,scope=${{ matrix.target.name }} | |
102 | + cache-to: type=gha,mode=max,scope=${{ matrix.target.name }} | |
103 | + platforms: ${{ steps.platforms.outputs.platforms }} | |
114 | 104 | |
115 | - - name: Build and Push Runner Image | |
116 | - uses: docker/build-push-action@v5 | |
117 | - with: | |
118 | - context: . | |
119 | - file: ./Dockerfile.runner | |
120 | - push: true | |
121 | - tags: ${{ steps.meta-runner.outputs.tags }} | |
122 | - labels: ${{ steps.meta-runner.outputs.labels }} | |
123 | - cache-from: type=gha | |
124 | - cache-to: type=gha,mode=max | |
125 | - platforms: linux/amd64,linux/arm64 | |
105 | + update-dockerhub-desc: | |
106 | + name: Update DockerHub Description | |
107 | + runs-on: ubuntu-latest | |
108 | + needs: build-and-push | |
109 | + if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
110 | + steps: | |
111 | + - name: Checkout | |
112 | + uses: actions/checkout@v4 | |
126 | 113 | |
127 | - - name: Update Docker Hub Repository Description | |
114 | + - name: Update Docker Hub Description | |
128 | 115 | uses: peter-evans/dockerhub-description@v4 |
129 | 116 | with: |
130 | 117 | username: ${{ secrets.DOCKER_USERNAME }} |
131 | 118 | password: ${{ secrets.DOCKER_TOKEN }} |
132 | - repository: ${{ env.IMAGE_NAME }} | |
119 | + repository: ${{ env.IMAGE_BASE }} | |
133 | 120 | readme-filepath: ./DOCKERHUB.md |
134 | 121 | short-description: "Self-hosted Git platform with stacked PRs, merge queue, CI/CD, and AI code review" |
135 | 122 | |
▼Dockerfile
+44-42
| @@ -1,71 +1,78 @@ | ||
1 | -FROM oven/bun:1 AS base | |
1 | +# ── Stage 1: Base Environment ────────────────────────────────── | |
2 | +FROM oven/bun:1-slim AS base | |
2 | 3 | WORKDIR /app |
4 | + | |
3 | 5 | LABEL org.opencontainers.image.title="OpenCodeHub" \ |
4 | 6 | org.opencontainers.image.description="A modern, self-hosted Git platform with stacked PRs, merge queue, CI/CD, and AI review." \ |
5 | 7 | org.opencontainers.image.url="https://github.com/swadhinbiswas/OpencodeHub" \ |
6 | 8 | org.opencontainers.image.source="https://github.com/swadhinbiswas/OpencodeHub" \ |
7 | 9 | org.opencontainers.image.licenses="MIT" |
8 | 10 | |
9 | -# Install ALL dependencies (needed for build including CSS tooling) | |
11 | +# ── Stage 2: Dependencies (All Dev + Prod for build) ─────────── | |
10 | 12 | FROM base AS deps |
11 | -RUN apt-get update && apt-get install -y python3 make g++ gcc libc6-dev && rm -rf /var/lib/apt/lists/* | |
13 | +RUN apt-get update && apt-get install -y --no-install-recommends \ | |
14 | + python3 make g++ gcc libc6-dev \ | |
15 | + && rm -rf /var/lib/apt/lists/* | |
16 | + | |
12 | 17 | COPY package.json bun.lock ./ |
13 | 18 | COPY cli/package.json ./cli/package.json |
14 | -RUN for i in 1 2 3; do \ | |
15 | - bun install --frozen-lockfile && break || \ | |
16 | - (echo "bun install attempt $i failed, retrying in 10s..." && sleep 10); \ | |
17 | - done | |
19 | +RUN bun install --frozen-lockfile | |
18 | 20 | |
19 | -# Install production dependencies only (for runtime image) | |
21 | +# ── Stage 3: Production Dependencies Only ────────────────────── | |
20 | 22 | FROM base AS prod-deps |
21 | -RUN apt-get update && apt-get install -y python3 make g++ gcc libc6-dev && rm -rf /var/lib/apt/lists/* | |
23 | +RUN apt-get update && apt-get install -y --no-install-recommends \ | |
24 | + python3 make g++ gcc libc6-dev \ | |
25 | + && rm -rf /var/lib/apt/lists/* | |
26 | + | |
22 | 27 | COPY package.json bun.lock ./ |
23 | 28 | COPY cli/package.json ./cli/package.json |
24 | -# Clear Bun install cache to avoid stale/corrupt node-gyp entries across layers | |
25 | -RUN rm -rf /root/.bun/install/cache || true | |
26 | -RUN for i in 1 2 3; do \ | |
27 | - bun install --frozen-lockfile --production && break || \ | |
28 | - (echo "prod bun install attempt $i failed, clearing cache and retrying in 10s..." && \ | |
29 | - rm -rf /root/.bun/install/cache && sleep 10); \ | |
30 | - done | |
29 | +RUN bun install --frozen-lockfile --production \ | |
30 | + && find /app/node_modules -type d \( -name "test" -o -name "tests" -o -name "docs" -o -name "example" -o -name "examples" \) -prune -exec rm -rf {} + 2>/dev/null || true \ | |
31 | + && find /app/node_modules -type f \( -name "*.map" -o -name "*.md" -o -name "*.markdown" \) -delete 2>/dev/null || true | |
31 | 32 | |
32 | -# Build the application | |
33 | +# ── Stage 4: Builder ─────────────────────────────────────────── | |
33 | 34 | FROM base AS builder |
34 | 35 | COPY --from=deps /app/node_modules ./node_modules |
35 | -COPY . . | |
36 | +COPY package.json tsconfig.json astro.config.mjs tailwind.config.mjs postcss.config.mjs drizzle.config.ts docker-entrypoint.sh ./ | |
37 | +COPY public ./public | |
38 | +COPY src ./src | |
39 | +COPY cli ./cli | |
40 | +COPY drizzle ./drizzle | |
41 | +COPY scripts ./scripts | |
42 | + | |
43 | +ENV NODE_ENV=production | |
36 | 44 | ENV SKIP_REDIS_CHECK=1 |
37 | 45 | RUN bun run build |
38 | 46 | |
39 | -# Production image | |
47 | +# ── Stage 5: Production Runner (Lean & Minimal) ──────────────── | |
40 | 48 | FROM oven/bun:1-slim AS runner |
41 | 49 | WORKDIR /app |
42 | 50 | |
43 | -# Install git, ssh, bash, and wget (git operations, entrypoint, healthcheck) | |
44 | -RUN apt-get update && apt-get install -y --no-install-recommends git openssh-client bash wget && \ | |
45 | - rm -rf /var/lib/apt/lists/* | |
51 | +# Install minimal runtime dependencies | |
52 | +RUN apt-get update && apt-get install -y --no-install-recommends \ | |
53 | + git \ | |
54 | + openssh-client \ | |
55 | + bash \ | |
56 | + wget \ | |
57 | + ca-certificates \ | |
58 | + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* | |
46 | 59 | |
47 | -# Create data directories | |
48 | -RUN mkdir -p /data/repositories /data/storage /data/cache /data/ssh && \ | |
49 | - chown -R bun:bun /data | |
60 | +# Create persistent storage directories | |
61 | +RUN mkdir -p /data/repos /data/repositories /data/storage /data/cache /data/ssh /data/logs && \ | |
62 | + chown -R bun:bun /data /app | |
50 | 63 | |
51 | -# Copy only what's needed for runtime | |
64 | +# Copy runtime assets | |
52 | 65 | COPY --from=builder --chown=bun:bun /app/dist ./dist |
53 | 66 | COPY --from=prod-deps --chown=bun:bun /app/node_modules ./node_modules |
54 | 67 | COPY --from=builder --chown=bun:bun /app/package.json ./ |
55 | - | |
56 | -# Copy drizzle config, committed migrations, and full source tree. | |
57 | -# The full src/ is required so raw-TS entrypoints (scripts/ssh-server.ts, | |
58 | -# scripts/worker.ts) can resolve the "@/..." path aliases via tsconfig. | |
59 | 68 | COPY --from=builder --chown=bun:bun /app/drizzle.config.ts ./ |
60 | 69 | COPY --from=builder --chown=bun:bun /app/drizzle ./drizzle |
61 | 70 | COPY --from=builder --chown=bun:bun /app/src ./src |
62 | 71 | COPY --from=builder --chown=bun:bun /app/tsconfig.json ./ |
63 | - | |
64 | -# Copy runtime scripts (migrate, ssh-server, worker) and entrypoint | |
65 | 72 | COPY --from=builder --chown=bun:bun /app/scripts ./scripts |
66 | -COPY --chown=bun:bun docker-entrypoint.sh ./ | |
73 | +COPY --from=builder --chown=bun:bun /app/docker-entrypoint.sh ./ | |
67 | 74 | |
68 | -# Set environment variables | |
75 | +# Environment defaults | |
69 | 76 | ENV HOST=0.0.0.0 |
70 | 77 | ENV PORT=4321 |
71 | 78 | ENV DATA_DIR=/data |
| @@ -75,15 +82,10 @@ ENV CACHE_PATH=/data/cache | ||
75 | 82 | ENV SSH_PATH=/data/ssh |
76 | 83 | ENV NODE_ENV=production |
77 | 84 | |
78 | -# Expose ports | |
79 | -EXPOSE 4321 | |
80 | - | |
81 | -# Switch to non-root user | |
85 | +EXPOSE 4321 2222 | |
82 | 86 | USER bun |
83 | 87 | |
84 | -# Health check | |
85 | -HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ | |
86 | - CMD wget --no-verbose --tries=1 --spider http://localhost:4321/api/health || exit 1 | |
88 | +HEALTHCHECK --interval=20s --timeout=5s --start-period=10s --retries=3 \ | |
89 | + CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:4321/api/health || exit 1 | |
87 | 90 | |
88 | -# Start the application with entrypoint | |
89 | 91 | ENTRYPOINT ["bash", "./docker-entrypoint.sh"] |
90 | 92 | |
▼Dockerfile.runner
+18-16
| @@ -1,7 +1,14 @@ | ||
1 | -# CI/CD Runner Dockerfile | |
1 | +# syntax=docker/dockerfile:1.4 | |
2 | +# OpenCodeHub CI/CD Runner (Docker-in-Docker) | |
2 | 3 | FROM docker:24-dind |
3 | 4 | |
4 | -# Install Node.js and other dependencies | |
5 | +LABEL org.opencontainers.image.title="OpenCodeHub Runner" \ | |
6 | + org.opencontainers.image.description="Docker-in-Docker CI/CD runner for OpenCodeHub pipelines." \ | |
7 | + org.opencontainers.image.url="https://github.com/swadhinbiswas/OpencodeHub" \ | |
8 | + org.opencontainers.image.source="https://github.com/swadhinbiswas/OpencodeHub" \ | |
9 | + org.opencontainers.image.licenses="MIT" | |
10 | + | |
11 | +# Install essential build tools & runtimes in a single layer with clean cache | |
5 | 12 | RUN apk add --no-cache \ |
6 | 13 | nodejs \ |
7 | 14 | npm \ |
| @@ -15,28 +22,23 @@ RUN apk add --no-cache \ | ||
15 | 22 | make \ |
16 | 23 | gcc \ |
17 | 24 | g++ \ |
18 | - libc-dev | |
25 | + libc-dev \ | |
26 | + tar \ | |
27 | + gzip \ | |
28 | + xz | |
19 | 29 | |
20 | -# Install common build tools | |
21 | -RUN npm install -g \ | |
22 | - typescript \ | |
23 | - @types/node \ | |
24 | - pnpm \ | |
25 | - yarn | |
30 | +# Install global package managers and clean npm cache | |
31 | +RUN npm install -g pnpm yarn @types/node \ | |
32 | + && npm cache clean --force \ | |
33 | + && rm -rf /root/.npm /tmp/* /var/cache/apk/* | |
26 | 34 | |
27 | 35 | WORKDIR /runner |
28 | 36 | |
29 | -# Copy runner script | |
30 | 37 | COPY scripts/runner.sh /runner/runner.sh |
31 | -RUN chmod +x /runner/runner.sh | |
32 | - | |
33 | -# Create work directories | |
34 | -RUN mkdir -p /work /cache | |
38 | +RUN chmod +x /runner/runner.sh && mkdir -p /work /cache | |
35 | 39 | |
36 | -# Environment variables | |
37 | 40 | ENV RUNNER_WORK_DIR=/work |
38 | 41 | ENV RUNNER_CACHE_DIR=/cache |
39 | 42 | ENV DOCKER_HOST=unix:///var/run/docker.sock |
40 | 43 | |
41 | -# Entry point | |
42 | 44 | ENTRYPOINT ["/runner/runner.sh"] |
43 | 45 | |
▼Dockerfile.worker
+35-35
| @@ -1,49 +1,49 @@ | ||
1 | -FROM oven/bun:1 AS base | |
1 | +FROM oven/bun:1-slim AS base | |
2 | 2 | WORKDIR /app |
3 | 3 | |
4 | -# Install dependencies | |
5 | -FROM base AS deps | |
6 | -RUN apt-get update && apt-get install -y python3 make g++ gcc libc6-dev && rm -rf /var/lib/apt/lists/* | |
4 | +LABEL org.opencontainers.image.title="OpenCodeHub Worker" \ | |
5 | + org.opencontainers.image.description="Background task worker for OpenCodeHub (queues, webhooks, notifications)." \ | |
6 | + org.opencontainers.image.url="https://github.com/swadhinbiswas/OpencodeHub" \ | |
7 | + org.opencontainers.image.source="https://github.com/swadhinbiswas/OpencodeHub" \ | |
8 | + org.opencontainers.image.licenses="MIT" | |
9 | + | |
10 | +# ── Dependencies ─────────────────────────────────────────────── | |
11 | +FROM base AS prod-deps | |
12 | +RUN apt-get update && apt-get install -y --no-install-recommends \ | |
13 | + python3 make g++ gcc libc6-dev \ | |
14 | + && rm -rf /var/lib/apt/lists/* | |
15 | + | |
7 | 16 | COPY package.json bun.lock ./ |
8 | 17 | COPY cli/package.json ./cli/package.json |
9 | -RUN for i in 1 2 3; do \ | |
10 | - bun install --frozen-lockfile && break || \ | |
11 | - (echo "bun install attempt $i failed, retrying in 10s..." && sleep 10); \ | |
12 | - done | |
13 | - | |
14 | -# Copy source into a staging layer so COPY --from has the files | |
15 | -FROM base AS source | |
16 | -COPY --from=deps /app/node_modules ./node_modules | |
17 | -COPY . . | |
18 | - | |
19 | -# Production image | |
20 | -FROM oven/bun:1 AS runner | |
21 | -WORKDIR /app | |
18 | +RUN bun install --frozen-lockfile --production \ | |
19 | + && find /app/node_modules -type d \( -name "test" -o -name "tests" -o -name "docs" -o -name "example" -o -name "examples" \) -prune -exec rm -rf {} + 2>/dev/null || true \ | |
20 | + && find /app/node_modules -type f \( -name "*.map" -o -name "*.md" -o -name "*.markdown" \) -delete 2>/dev/null || true | |
22 | 21 | |
23 | -# Install git (needed for git operations) | |
24 | -RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* | |
22 | +# ── Production Worker Runner ─────────────────────────────────── | |
23 | +FROM oven/bun:1-slim AS runner | |
24 | +WORKDIR /app | |
25 | 25 | |
26 | -# Create data directories | |
27 | -RUN mkdir -p /data/repositories /data/storage /data/cache && \ | |
28 | - chown -R bun:bun /data | |
26 | +RUN apt-get update && apt-get install -y --no-install-recommends \ | |
27 | + git \ | |
28 | + bash \ | |
29 | + ca-certificates \ | |
30 | + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* | |
29 | 31 | |
30 | -# Copy node_modules and source | |
31 | -COPY --from=source --chown=bun:bun /app/node_modules ./node_modules | |
32 | -COPY --from=source --chown=bun:bun /app/package.json ./ | |
33 | -COPY --from=source --chown=bun:bun /app/cli ./cli | |
34 | -COPY --from=source --chown=bun:bun /app/tsconfig.json ./ | |
32 | +RUN mkdir -p /data/repositories /data/storage /data/cache /data/logs && \ | |
33 | + chown -R bun:bun /data /app | |
35 | 34 | |
36 | -# Copy source files needed for worker | |
37 | -COPY --from=source --chown=bun:bun /app/src/lib ./src/lib | |
38 | -COPY --from=source --chown=bun:bun /app/src/db ./src/db | |
39 | -COPY --from=source --chown=bun:bun /app/scripts/worker.ts ./scripts/worker.ts | |
35 | +COPY --from=prod-deps --chown=bun:bun /app/node_modules ./node_modules | |
36 | +COPY --chown=bun:bun package.json tsconfig.json ./ | |
37 | +COPY --chown=bun:bun drizzle.config.ts ./ | |
38 | +COPY --chown=bun:bun drizzle ./drizzle | |
39 | +COPY --chown=bun:bun src ./src | |
40 | +COPY --chown=bun:bun scripts ./scripts | |
41 | +COPY --chown=bun:bun docker-entrypoint.sh ./ | |
40 | 42 | |
41 | -# Set environment variables | |
42 | 43 | ENV NODE_ENV=production |
44 | +ENV PROCESS_TYPE=worker | |
43 | 45 | ENV DATA_DIR=/data |
44 | 46 | |
45 | -# Switch to non-root user | |
46 | 47 | USER bun |
47 | 48 | |
48 | -# Start the worker | |
49 | -CMD ["bun", "scripts/worker.ts"] | |
49 | +ENTRYPOINT ["bash", "./docker-entrypoint.sh"] | |
50 | 50 | |
▼README.md
+139-170
| @@ -1,196 +1,180 @@ | ||
1 | 1 | <p align="center"> |
2 | - <picture> | |
3 | - <source media="(prefers-color-scheme: dark)" srcset="public/logo-dark.png"> | |
4 | - <source media="(prefers-color-scheme: light)" srcset="public/logo-light.png"> | |
5 | - <img src="public/logo-light.png" alt="OpenCodeHub" width="420" /> | |
6 | - </picture> | |
2 | + <a href="https://github.com/swadhinbiswas/OpencodeHub"> | |
3 | + <img src="public/logo.svg" alt="OpenCodeHub Logo" width="128" height="128" /> | |
4 | + </a> | |
7 | 5 | </p> |
8 | 6 | |
9 | -<h3 align="center">The self-hosted Git platform that doesn't compromise.</h3> | |
7 | +<h1 align="center">OpenCodeHub</h1> | |
10 | 8 | |
11 | 9 | <p align="center"> |
12 | - <a href="https://github.com/swadhinbiswas/OpencodeHub/actions"><img src="https://img.shields.io/badge/CI-passing-brightgreen?style=for-the-badge" alt="CI"></a> | |
10 | + <strong>The self-hosted Git collaboration platform that doesn't compromise.</strong><br> | |
11 | + Stacked PRs · Speculative Merge Queue · GitHub Actions CI/CD · AI Code Review · Enterprise Security · High-Performance CLI | |
12 | +</p> | |
13 | + | |
14 | +<p align="center"> | |
15 | + <a href="https://github.com/swadhinbiswas/OpencodeHub/actions"><img src="https://img.shields.io/badge/CI-passing-238636?style=for-the-badge&logo=githubactions&logoColor=white" alt="CI"></a> | |
13 | 16 | <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=for-the-badge" alt="License"></a> |
14 | - <a href="https://www.npmjs.com/package/opencodehub-cli"><img src="https://img.shields.io/npm/v/opencodehub-cli?style=for-the-badge&label=CLI" alt="CLI version"></a> | |
15 | - <a href="https://docs.opencodehub.space"><img src="https://img.shields.io/badge/docs-opencodehub.space-brightgreen?style=for-the-badge" alt="Docs"></a> | |
16 | - <a href="https://hub.docker.com/r/opencodehub/opencodehub"><img src="https://img.shields.io/badge/docker-opencodehub%2Fopencodehub-blue?style=for-the-badge&logo=docker" alt="Docker"></a> | |
17 | + <a href="https://www.npmjs.com/package/opencodehub-cli"><img src="https://img.shields.io/npm/v/opencodehub-cli?style=for-the-badge&label=CLI&color=black" alt="CLI version"></a> | |
18 | + <a href="https://hub.docker.com/r/opencodehub/opencodehub"><img src="https://img.shields.io/badge/docker-251MB-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="Docker"></a> | |
19 | + <a href="https://docs.opencodehub.space"><img src="https://img.shields.io/badge/docs-opencodehub.space-58a6ff?style=for-the-badge" alt="Docs"></a> | |
17 | 20 | </p> |
18 | 21 | |
19 | 22 | --- |
20 | 23 | |
21 | -OpenCodeHub is a self-hosted Git platform with **stacked PRs**, **merge queue**, **CI/CD pipelines**, and **AI code review**. One platform for everything your team needs — no vendor lock-in, no per-seat pricing. | |
24 | +OpenCodeHub is an open-source, self-hosted alternative to GitHub and GitLab engineered as a modular monolith. It provides stack-first developer workflows (Graphite-style stacked diffs), an automated merge queue with speculative builds, Docker-based CI/CD pipelines, and multi-model AI code reviews out of the box — with zero per-seat licensing fees. | |
22 | 25 | |
23 | -**[Documentation](https://docs.opencodehub.space)** · **[Deploy in 5 minutes](#deploy)** · **[CLI Reference](https://docs.opencodehub.space/reference/cli-commands/)** | |
26 | +**[Documentation](https://docs.opencodehub.space)** · **[CLI Quickstart](#-opencodehub-cli-och)** · **[Deploy in 5 Minutes](#-quickstart--deployment)** · **[API Reference](https://docs.opencodehub.space/api/rest-api/)** | |
24 | 27 | |
25 | 28 | --- |
26 | 29 | |
27 | -## Demo | |
28 | - | |
29 | -<!-- Replace VIDEO_ID with your YouTube video ID --> | |
30 | -[](https://youtu.be/VIDEO_ID) | |
30 | +## ✨ Key Differentiators | |
31 | 31 | |
32 | -*Watch the full walkthrough — deployment, stacked PRs, AI review, and merge queue in action.* | |
32 | +| Capability | What It Does | Why It Matters | | |
33 | +|---|---|---| | |
34 | +| **Stacked PRs** | Break complex features into small, dependent branches and PRs (`och stack`) | Faster reviews, zero merge blocking, unblocked teammates | | |
35 | +| **Merge Queue** | Parallel CI validation with speculative builds and priority lanes | Protects `main` from broken builds without serialized slow merges | | |
36 | +| **CI/CD Pipelines** | GitHub Actions YAML compatibility with isolated Docker executors | Native pipeline execution without external CI SaaS dependencies | | |
37 | +| **Multi-Provider AI Review** | Automated code reviews powered by GPT-4, Claude 3.5, Gemini 1.5, Groq, Ollama | Instant feedback on PRs before human peer review | | |
38 | +| **Pluggable Storage** | Local filesystem or S3-compatible object storage (AWS, MinIO, R2, B2, Ceph) | Flexible deployment across homelabs, NAS servers, or hyper-scale clouds | | |
39 | +| **Federation** | Fork, push branches, and submit cross-instance pull requests across servers | Seamless collaboration across autonomous OpenCodeHub instances | | |
33 | 40 | |
34 | 41 | --- |
35 | 42 | |
36 | -## Why OpenCodeHub? | |
43 | +## 💻 OpenCodeHub CLI (`och`) | |
37 | 44 | |
38 | -| Problem | Solution | | |
39 | -|---------|----------| | |
40 | -| PRs too large to review | **Stacked PRs** — break changes into small, dependent branches | | |
41 | -| Merge conflicts on main | **Merge Queue** — stack-aware ordering with speculative CI builds | | |
42 | -| Slow review cycles | **AI Code Review** — catches bugs before humans even look | | |
43 | -| Split across 5+ services | **All-in-one** — Git, PRs, CI/CD, issues, wiki in one place | | |
44 | -| Data leaves your servers | **Self-hosted** — your code stays on your hardware | | |
45 | +The official OpenCodeHub CLI (`opencodehub-cli`) delivers a terminal-first workflow for stacks, code reviews, merge queues, and repository management. | |
45 | 46 | |
47 | +### Installation | |
46 | 48 | |
47 | -## Features | |
48 | - | |
49 | -### Core Platform | |
50 | -- **Git Hosting** — HTTP smart protocol + SSH push/pull, forks, mirroring, LFS | |
51 | -- **Pull Requests** — Inline comments, approvals, suggested changes, draft PRs | |
52 | -- **Issues & Projects** — Labels, milestones, custom fields, kanban boards | |
53 | -- **Wiki** — Repository wiki with revision history | |
54 | -- **Organizations** — Teams, collaborators, role-based access control | |
55 | - | |
56 | -### Delivery Workflows | |
57 | -- **Stacked PRs** — Graphite-style stacked branches with web + CLI support | |
58 | -- **Merge Queue** — Stack-aware queue with speculative builds and priority lanes | |
59 | -- **CI/CD Pipelines** — GitHub Actions-compatible engine with Docker-based runners | |
60 | -- **Webhooks** — Outbound webhooks with event filtering and HMAC signing | |
61 | -- **Automations** — Rule-based workflow automation for PRs and deployments | |
62 | - | |
63 | -### AI & Quality | |
64 | -- **AI Code Review** — 10+ providers: GPT-4, Claude, Gemini, Groq, Ollama, OpenRouter | |
65 | -- **Secret Scanning** — Detect secrets in commits before they reach production | |
66 | -- **Branch Protection** — Required reviews, status checks, push restrictions | |
67 | -- **Developer Metrics** — PR velocity, review efficiency, time-to-merge tracking | |
68 | - | |
69 | -### Security | |
70 | -- **Authentication** — JWT sessions, OAuth (GitHub, Google, GitLab), 2FA/TOTP, SSO/SAML | |
71 | -- **Authorization** — RBAC with roles, team permissions, collaborator levels | |
72 | -- **Rate Limiting** — Redis-backed per-endpoint rate limiting | |
73 | -- **Audit Logging** — Track all administrative actions | |
74 | - | |
75 | -### Extensibility | |
76 | -- **REST API** — 175+ endpoints covering all platform features | |
77 | -- **GraphQL** — Full GraphQL endpoint for flexible queries | |
78 | -- **CLI** — `och` command line tool with 20+ command groups | |
79 | -- **Storage** — Local, S3, MinIO, R2, or any S3-compatible backend | |
49 | +Install globally using your preferred package manager: | |
80 | 50 | |
51 | +```bash | |
52 | +# via npm | |
53 | +npm install -g opencodehub-cli | |
81 | 54 | |
82 | -## Deploy | |
55 | +# via bun | |
56 | +bun add -g opencodehub-cli | |
83 | 57 | |
84 | -### Docker (Recommended) | |
58 | +# via pnpm | |
59 | +pnpm add -g opencodehub-cli | |
85 | 60 | |
86 | -```bash | |
87 | -git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
88 | -cd OpenCodeHub | |
89 | -cp .env.example .env | |
90 | -docker compose up -d | |
91 | -docker compose exec app bun run scripts/seed-admin.ts | |
61 | +# via yarn | |
62 | +yarn global add opencodehub-cli | |
92 | 63 | ``` |
93 | 64 | |
94 | -Open **http://localhost:4321** and create your admin account. | |
95 | - | |
96 | -### Render (Free) | |
97 | - | |
98 | -Deploy to Render with free PostgreSQL + Upstash Redis: | |
65 | +Or run directly without installing: | |
99 | 66 | |
100 | 67 | ```bash |
101 | -# 1. Create free Redis at upstash.com (Singapore region) | |
102 | -# 2. Push to GitHub | |
103 | -# 3. Render → New → Blueprint → Select your repo | |
104 | -# 4. Set REDIS_URL and SITE_URL | |
105 | -# 5. Deploy | |
68 | +npx opencodehub-cli --help | |
106 | 69 | ``` |
107 | 70 | |
108 | -See the [Render Deployment Guide](docs/RENDER-DEPLOYMENT.md) for step-by-step instructions. | |
71 | +### 1. Authenticate | |
109 | 72 | |
110 | -### More Deployment Options | |
73 | +```bash | |
74 | +# Interactive login | |
75 | +och auth login --url https://git.yourcompany.com | |
111 | 76 | |
112 | -| Platform | Guide | Cost | | |
113 | -|----------|-------|------| | |
114 | -| Docker Compose | [Deployment Guide](docs/administration/deployment.md) | Free | | |
115 | -| Render (Asia) | [Render Guide](docs/RENDER-DEPLOYMENT.md) | Free | | |
116 | -| Oracle Cloud | [Free Deployment](docs/FREE-DEPLOYMENT.md) | Free forever | | |
117 | -| NAS (Synology/TrueNAS) | [NAS Guide](docs/administration/deploy-nas.md) | Free | | |
118 | -| Kubernetes | [K8s Guide](docs/administration/kubernetes.md) | Free | | |
119 | -| Cloudflare Tunnel | [Cloudflare Guide](docs/administration/deploy-cloudflare.md) | Free | | |
77 | +# Non-interactive / CI login with Personal Access Token | |
78 | +och auth login --url https://git.yourcompany.com --token och_xxxxxxxxxxxx | |
120 | 79 | |
80 | +# Check health & auth status | |
81 | +och config doctor | |
82 | +``` | |
121 | 83 | |
122 | -## CLI | |
84 | +### 2. Stacked PR Workflow | |
85 | + | |
86 | +Create and submit Graphite-style stacked branches in seconds: | |
123 | 87 | |
124 | 88 | ```bash |
125 | -# Install | |
126 | -npm install -g opencodehub-cli | |
89 | +# Create first stacked branch | |
90 | +och stack create feature/user-auth | |
127 | 91 | |
128 | -# Login | |
129 | -och auth login --url http://localhost:4321 | |
92 | +# Commit changes, then create dependent branch | |
93 | +git commit -am "feat: implement auth middleware" | |
94 | +och stack create feature/user-profile | |
130 | 95 | |
131 | -# Stacked PR workflow | |
132 | -och stack create feature/auth-step-1 | |
96 | +# Push all branches in the stack and generate linked PRs automatically | |
133 | 97 | och stack submit |
98 | + | |
99 | +# Visualize your stack topology in terminal | |
100 | +och stack log | |
101 | + | |
102 | +# Rebase the whole stack when upstream main updates | |
134 | 103 | och stack sync |
104 | +``` | |
135 | 105 | |
136 | -# Merge queue | |
137 | -och queue list | |
138 | -och queue add <pr-number> | |
106 | +### 3. Interactive Focus Cockpit (`och focus`) | |
139 | 107 | |
140 | -# Interactive cockpit | |
108 | +Launch the terminal dashboard for PRs, reviews, and merge queue operations: | |
109 | + | |
110 | +```bash | |
141 | 111 | och focus |
142 | 112 | ``` |
143 | 113 | |
144 | -[Full CLI Reference](https://docs.opencodehub.space/reference/cli-commands/) | |
114 | +- **Interactive Timeline**: Switch branches, view diff stats, and trigger CI runs without leaving the CLI. | |
115 | +- **Review Cockpit**: Approve, request changes, or trigger AI code reviews inline. | |
116 | +- **Queue Controls**: Enqueue PRs and monitor speculative build states in realtime. | |
117 | + | |
118 | +### 4. Merge Queue Management | |
119 | + | |
120 | +```bash | |
121 | +# View active queue status and speculative lanes | |
122 | +och queue list | |
123 | + | |
124 | +# Enqueue a PR for automated CI validation and merge | |
125 | +och queue add 42 | |
126 | + | |
127 | +# Check queue position and build attempts | |
128 | +och queue status 42 | |
129 | +``` | |
145 | 130 | |
146 | 131 | --- |
147 | 132 | |
148 | -## API | |
133 | +## 🚀 Quickstart & Deployment | |
134 | + | |
135 | +### Production Stack (Docker Compose) | |
149 | 136 | |
150 | 137 | ```bash |
151 | -# Create a repository | |
152 | -curl -X POST http://localhost:4321/api/repos \ | |
153 | - -H "Authorization: Bearer YOUR_TOKEN" \ | |
154 | - -H "Content-Type: application/json" \ | |
155 | - -d '{"name":"my-project","visibility":"public"}' | |
156 | - | |
157 | -# List pull requests | |
158 | -curl http://localhost:4321/api/repos/owner/repo/pulls \ | |
159 | - -H "Authorization: Bearer YOUR_TOKEN" | |
160 | - | |
161 | -# GraphQL | |
162 | -curl -X POST http://localhost:4321/api/graphql \ | |
163 | - -H "Authorization: Bearer YOUR_TOKEN" \ | |
164 | - -H "Content-Type: application/json" \ | |
165 | - -d '{"query": "{ repositories { name owner { username } } }"}' | |
138 | +# 1. Clone the repository | |
139 | +git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
140 | +cd OpenCodeHub | |
141 | + | |
142 | +# 2. Configure environment | |
143 | +cp .env.example .env | |
144 | +# Edit .env and set your secrets (JWT_SECRET, SESSION_SECRET, POSTGRES_PASSWORD, REDIS_PASSWORD) | |
145 | + | |
146 | +# 3. Launch the container stack | |
147 | +docker compose up -d | |
148 | + | |
149 | +# 4. Initialize the admin account | |
150 | +docker compose exec app bun scripts/seed-admin.ts | |
166 | 151 | ``` |
167 | 152 | |
168 | -[Full API Reference](https://docs.opencodehub.space/api/rest-api/) | |
153 | +Open **`http://localhost:4321`** in your browser to start collaborating. | |
154 | + | |
155 | +### Container Images | |
156 | + | |
157 | +Official minimal production images (251 MB) are published on Docker Hub: | |
158 | + | |
159 | +| Service | Image Tag | Purpose | | |
160 | +|---|---|---| | |
161 | +| **Platform** | `opencodehub/opencodehub:latest` | Web UI + REST & GraphQL API + Git HTTP / SSH Server | | |
162 | +| **Worker** | `opencodehub/opencodehub-worker:latest` | Background queues, webhooks, and automation jobs | | |
163 | +| **Runner** | `opencodehub/opencodehub-runner:latest` | Docker-in-Docker CI/CD pipeline execution runner | | |
169 | 164 | |
170 | 165 | --- |
171 | 166 | |
172 | -## Tech Stack | |
173 | - | |
174 | -| Layer | Technology | | |
175 | -|-------|-----------| | |
176 | -| Framework | Astro 4.x (SSR) + React 18 | | |
177 | -| UI | Tailwind CSS + Radix UI | | |
178 | -| Database | PostgreSQL / SQLite / Turso (Drizzle ORM) | | |
179 | -| Auth | JWT + OAuth + 2FA/TOTP + SSO/SAML | | |
180 | -| Git | Native git CLI + simple-git + isomorphic-git | | |
181 | -| SSH | ssh2 library | | |
182 | -| CI/CD | Docker-based runners, GitHub Actions syntax | | |
183 | -| Storage | Local filesystem or S3-compatible (AWS, MinIO, R2) | | |
184 | -| AI | OpenAI, Anthropic, Google, Groq, Ollama, OpenRouter | | |
185 | -| Queue | BullMQ + Redis | | |
186 | -| CLI | Commander.js + Inquirer | | |
167 | +## 🎨 Design System & Theme | |
168 | + | |
169 | +OpenCodeHub features a modern developer UI built on **Tailwind CSS**, **Radix UI**, and clean GitHub/Linear design principles: | |
170 | + | |
171 | +- **Adaptive Theming**: Native dark and light theme support with zero layout flicker. | |
172 | +- **High-Contrast Dark Mode**: Pure zinc/slate dark surfaces (`#0d1117` / `#161b22`) paired with crisp emerald status indicators (`#238636`). | |
173 | +- **Responsive Workspace**: Full mobile, tablet, and desktop fidelity with integrated keyboard shortcuts. | |
187 | 174 | |
188 | 175 | --- |
189 | 176 | |
190 | -## Architecture | |
177 | +## 🏛 Architecture | |
191 | 178 | |
192 | 179 | ``` |
193 | 180 | ┌─────────────────────────────────────────────────────────────┐ |
| @@ -200,63 +184,43 @@ curl -X POST http://localhost:4321/api/graphql \ | ||
200 | 184 | │ |
201 | 185 | ┌──────────────────── ─────────────────────────────────────────┐ |
202 | 186 | │ OPENCODEHUB PLATFORM │ |
203 | -│ Web UI (Astro+React) │ REST API (175+ routes) │ GraphQL │ | |
204 | -│ Git Server (HTTP) │ SSH Server (ssh2) │ │ | |
205 | -│ Pipeline Runner (Docker) │ │ | |
187 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ | |
188 | +│ │ Web UI │ │ REST API │ │ GraphQL Endpoint │ │ | |
189 | +│ │ (Astro+React│ │ (175+ routes│ │ (src/pages/api/...) │ │ | |
190 | +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ | |
191 | +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ | |
192 | +│ │ Git Server │ │ SSH Server │ │ Pipeline Runner │ │ | |
193 | +│ │ (HTTP RPC) │ │ (ssh2 daemon│ │ (Docker Executor) │ │ | |
194 | +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ | |
206 | 195 | └─────────────────────────────────────────────────────────────┘ |
207 | 196 | │ |
208 | 197 | ┌─────────────────────────────────────────────────────────────┐ |
209 | -│ PostgreSQL/SQLite/Turso │ Redis │ Pluggable Storage │ | |
198 | +│ PERSISTENCE & INFRASTRUCTURE │ | |
199 | +│ PostgreSQL / SQLite / Turso │ Redis (Queues) │ Pluggable S3 │ | |
210 | 200 | └─────────────────────────────────────────────────────────────┘ |
211 | 201 | ``` |
212 | 202 | |
213 | 203 | --- |
214 | 204 | |
215 | -## Documentation | |
216 | - | |
217 | -| Topic | Link | | |
218 | -|-------|------| | |
219 | -| Installation | [docs.opencodehub.space/getting-started/installation](https://docs.opencodehub.space/getting-started/installation/) | | |
220 | -| Configuration | [docs.opencodehub.space/administration/configuration](https://docs.opencodehub.space/administration/configuration/) | | |
221 | -| Stacked PRs | [docs.opencodehub.space/features/stacked-prs](https://docs.opencodehub.space/features/stacked-prs/) | | |
222 | -| AI Code Review | [docs.opencodehub.space/features/ai-review](https://docs.opencodehub.space/features/ai-review/) | | |
223 | -| CLI Reference | [docs.opencodehub.space/reference/cli-commands](https://docs.opencodehub.space/reference/cli-commands/) | | |
224 | -| API Reference | [docs.opencodehub.space/api/rest-api](https://docs.opencodehub.space/api/rest-api/) | | |
225 | -| Deployment | [docs.opencodehub.space/administration/deployment](https://docs.opencodehub.space/administration/deployment/) | | |
226 | - | |
227 | - | |
228 | -## Contributing | |
205 | +## 🧪 Development & Testing | |
229 | 206 | |
230 | 207 | ```bash |
231 | -# Clone and setup | |
232 | -git clone https://github.com/swadhinbiswas/OpencodeHub.git | |
233 | -cd OpenCodeHub | |
234 | -cp .env.example .env | |
208 | +# Install dependencies | |
235 | 209 | npm install |
236 | -npm run db:push | |
237 | -bun run scripts/seed-admin.ts | |
238 | 210 | |
239 | -# Start development | |
240 | -npm run dev | |
211 | +# Push database schema | |
212 | +npm run db:push | |
241 | 213 | |
242 | -# Run tests | |
214 | +# Run full test suite (679 unit, integration & contract tests) | |
243 | 215 | npm run test |
244 | -``` | |
245 | 216 | |
246 | -See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow and standards. | |
247 | - | |
248 | - | |
249 | -## Community | |
250 | - | |
251 | -- **GitHub**: [github.com/swadhinbiswas/OpencodeHub](https://github.com/swadhinbiswas/OpencodeHub) | |
252 | -- **Documentation**: [docs.opencodehub.space](https://docs.opencodehub.space) | |
253 | -- **Issues**: [GitHub Issues](https://github.com/swadhinbiswas/OpencodeHub/issues) | |
254 | -- **Discussions**: [GitHub Discussions](https://github.com/swadhinbiswas/OpencodeHub/discussions) | |
217 | +# Typecheck & verify build | |
218 | +npm run typecheck | |
219 | +npm run build | |
220 | +``` | |
255 | 221 | |
256 | 222 | --- |
257 | 223 | |
258 | -## License | |
224 | +## 📄 License | |
259 | 225 | |
260 | -[MIT](LICENSE) — Use it however you want. | |
226 | +OpenCodeHub is released under the [MIT License](LICENSE). | |
261 | 227 | |
▼UIPLAN.md
+159-0
| @@ -0,0 +1,159 @@ | ||
1 | +# OpenCodeHub — Self-Hosted GitHub UI Overhaul Plan (`UIPLAN.md`) | |
2 | + | |
3 | +> **Objective**: Completely refactor and elevate OpenCodeHub into an authentic, pixel-perfect self-hosted GitHub platform inspired by the minimalist, high-contrast, black-and-white monochrome aesthetic of [Ryoku](https://ryoku.dev/) and the exact GitHub profile layout provided in the reference screenshot. | |
4 | + | |
5 | +--- | |
6 | + | |
7 | +## 1. System Vision & Landing Page Paradigm | |
8 | + | |
9 | +``` | |
10 | +┌─────────────────────────────────────────────────────────────────────────────┐ | |
11 | +│ GLOBAL HEADER & SERVER NAVIGATION │ | |
12 | +│ [Logo] [User/Org Switcher] [Search: Type / to search] [+] [PRs] [🔔] [Avatar]│ | |
13 | +├─────────────────────────────────────────────────────────────────────────────┤ | |
14 | +│ [Overview] [Repositories 87] [Projects] [Packages] [Stars 50] │ | |
15 | +├───────────────────────────────┬─────────────────────────────────────────────┤ | |
16 | +│ LEFT SIDEBAR (PROFILE) │ RIGHT MAIN CONTENT COLUMN │ | |
17 | +│ │ │ | |
18 | +│ ┌─────────────────────────┐ │ ┌───────────────────────────────────────┐ │ | |
19 | +│ │ Big Avatar (Circle) │ │ │ swadhinbiswas / readme.md │ │ | |
20 | +│ │ + Status Badge (💭) │ │ │ │ │ | |
21 | +│ └─────────────────────────┘ │ │ [Rich Rendered Markdown Profile] │ │ | |
22 | +│ Name: Swadhin Biswas │ │ - Role badges │ │ | |
23 | +│ User: swadhinbiswas · he/him │ │ - Project showcase table │ │ | |
24 | +│ [Follow/Edit] [💖 Sponsor] │ │ - Social contact pills │ │ | |
25 | +│ Bio: MLOps & Data Engineer.. │ └───────────────────────────────────────┘ │ | |
26 | +│ 👥 94 followers · 116 following│ │ | |
27 | +│ │ ┌───────────────────────────────────────┐ │ | |
28 | +│ 🏢 @TheOpenCodeHub │ │ Pinned Repositories (2-Column Grid) │ │ | |
29 | +│ 📍 Dhaka, Bangladesh │ │ ┌─────────────────┐ ┌───────────────┐ │ │ | |
30 | +│ 🕒 16:55 (Local Time) │ │ │ opengrammar │ │ FAANG-Playbook│ │ │ | |
31 | +│ ✉️ [email protected]│ │ │ TypeScript ★115│ │ Astro ★36 │ │ │ | |
32 | +│ 🔗 https://swadhin.cv │ │ └─────────────────┘ └───────────────┘ │ │ | |
33 | +│ Socials: X, LinkedIn, HF... │ └───────────────────────────────────────┘ │ | |
34 | +│ │ │ | |
35 | +│ 🏆 Achievements Badges │ ┌───────────────────────────────────────┐ │ | |
36 | +│ ⭐ Highlights (PRO) │ │ 365-Day Contribution Calendar Heatmap │ │ | |
37 | +│ 🏛️ Organizations Grid │ │ 6,870 contributions in 2026 │ │ | |
38 | +│ │ │ [52 Weeks x 7 Days Grid] [Years Tabs] │ │ | |
39 | +│ [Block or report user] │ └───────────────────────────────────────┘ │ | |
40 | +└───────────────────────────────┴─────────────────────────────────────────────┘ | |
41 | +``` | |
42 | + | |
43 | +### 1.1 Self-Hosted Landing Page Philosophy | |
44 | +On a self-hosted Git platform, visitors and users should not see a generic SaaS marketing page. Instead: | |
45 | +1. **Root URL (`/`)**: Directly renders the **Primary Server User Profile** (or primary organization / logged-in user dashboard), complete with: | |
46 | + - Profile Header & Tabs (`Overview`, `Repositories`, `Projects`, `Packages`, `Stars`) | |
47 | + - Left Profile Dossier (Avatar, bio, followers, metadata, achievements, orgs) | |
48 | + - Profile README (`{username}/README.md` from `{username}/{username}` repo) | |
49 | + - Pinned Repositories grid | |
50 | + - 365-Day Contribution Heatmap & activity timeline | |
51 | + - **Server User & Organization Directory Bar**: Quick discovery and switcher for all users and organizations hosted on this server instance. | |
52 | +2. **User Profile URL (`/[owner]`)**: Renders the exact same GitHub-authentic profile for any user or organization on the server. | |
53 | +3. **Repository Workspace (`/[owner]/[repo]`)**: Clean GitHub-style sub-nav (`Code`, `Issues`, `Pull Requests`, `Actions`, `Merge Queue`, `Wiki`, `Security`, `Settings`) with Ryoku-styled monochrome file browser, commit history, and Markdown view. | |
54 | + | |
55 | +--- | |
56 | + | |
57 | +## 2. Design Tokens & Visual Hierarchy (Ryoku Monochrome) | |
58 | + | |
59 | +### 2.1 CSS Color Tokens (`src/styles/globals.css`) | |
60 | +```css | |
61 | +/* Ryoku High-Contrast Monochrome */ | |
62 | +:root { | |
63 | + --background: 0 0% 100%; | |
64 | + --foreground: 0 0% 9%; | |
65 | + --card: 0 0% 98%; | |
66 | + --card-foreground: 0 0% 9%; | |
67 | + --border: 0 0% 89%; | |
68 | + --input: 0 0% 89%; | |
69 | + --ring: 0 0% 9%; | |
70 | + --radius: 0.375rem; | |
71 | +} | |
72 | + | |
73 | +.dark { | |
74 | + --background: 0 0% 3.5%; /* #09090b - Obsidian void */ | |
75 | + --foreground: 0 0% 98%; /* #fafafa - Crisp white text */ | |
76 | + --card: 0 0% 5.5%; /* #0e0e11 - Elevated container */ | |
77 | + --card-foreground: 0 0% 98%; | |
78 | + --popover: 0 0% 5.5%; | |
79 | + --popover-foreground: 0 0% 98%; | |
80 | + --primary: 0 0% 98%; /* Pure white primary button/accent */ | |
81 | + --primary-foreground: 0 0% 9%; /* Dark text on white */ | |
82 | + --secondary: 0 0% 10%; /* #1a1a1e - Subtle background */ | |
83 | + --secondary-foreground: 0 0% 98%; | |
84 | + --muted: 0 0% 10%; | |
85 | + --muted-foreground: 0 0% 60%; /* #999999 - Subdued metadata */ | |
86 | + --accent: 0 0% 12%; | |
87 | + --accent-foreground: 0 0% 98%; | |
88 | + --border: 0 0% 14%; /* 1px precision hairline border */ | |
89 | + --input: 0 0% 14%; | |
90 | + --ring: 0 0% 98%; | |
91 | + --radius: 0.375rem; | |
92 | +} | |
93 | +``` | |
94 | + | |
95 | +--- | |
96 | + | |
97 | +## 3. Component Architecture & Modular Breakdown | |
98 | + | |
99 | +### 3.1 Global Header (`src/components/layout/Header.astro`) | |
100 | +- Sleek sticky glass navbar (`backdrop-blur-md bg-background/90 border-b border-border`). | |
101 | +- Left: GitHub / OpenCodeHub mark + Server instance badge (`Self-Hosted`) + Quick User/Org switcher dropdown. | |
102 | +- Center/Right: Search bar (`Type / to search` with `⌘K` trigger), `+` create dropdown (New repo, new org), Issues icon, Pull Requests icon, Notifications inbox icon with unread badge, and User Avatar menu. | |
103 | + | |
104 | +### 3.2 User Profile Component (`src/components/profile/UserProfileView.astro`) | |
105 | +A unified, highly reusable component powering both `/` (landing page) and `/[owner]` (profile page): | |
106 | + | |
107 | +1. **Sub-Navigation Tabs**: | |
108 | + - `Overview` (active indicator) | |
109 | + - `Repositories` (with live count badge) | |
110 | + - `Projects` | |
111 | + - `Packages` | |
112 | + - `Stars` (with live star count badge) | |
113 | +2. **Left Profile Sidebar (Dossier)**: | |
114 | + - **Avatar Frame**: `296x296px` round avatar with status indicator badge (`💭`). | |
115 | + - **Identity**: Large display name (`text-2xl font-bold`), username (`text-xl text-muted-foreground font-light`), pronouns (`he/him`). | |
116 | + - **Actions**: `Edit profile` (if logged in owner) or dual buttons `Follow` / `Unfollow` + `💖 Sponsor`. | |
117 | + - **Bio**: Full markdown/text bio with auto-linking. | |
118 | + - **Follower Counter**: `👥 94 followers · 116 following`. | |
119 | + - **Metadata Details**: | |
120 | + - Organization / Company link (`@TheOpenCodeHub`) | |
121 | + - Location pin (`Dhaka, Bangladesh`) | |
122 | + - Local time calculator based on timezone / offset | |
123 | + - Verified email mailto link | |
124 | + - Portfolio / Website URL | |
125 | + - Social links list (X / Twitter, LinkedIn, Hugging Face, ORCID, GitHub) | |
126 | + - **Achievements Shelf**: Interactive badges (`Pull Shark`, `Quickdraw`, `YOLO`, `Galaxy Brain`, `Starstruck`). | |
127 | + - **Highlights Shelf**: `PRO` member badge. | |
128 | + - **Organizations Shelf**: Grid of organization avatar icons with tooltips. | |
129 | + - **Footer Action**: "Block or report user". | |
130 | + | |
131 | +3. **Right Main Column**: | |
132 | + - **Profile README (`{username} / readme.md`)**: | |
133 | + - Header ribbon with file icon and repository source link. | |
134 | + - Rich GitHub-flavored markdown renderer with HTML tables, badge shields, social contact buttons, images, and code blocks. | |
135 | + - **Pinned Repositories (2-Column Grid)**: | |
136 | + - Header: `Pinned` + "Customize your pins" link for owner. | |
137 | + - Pinned cards: Repo icon, Name, `Public` badge pill, Description, Language color indicator, Star count, Fork count. | |
138 | + - **365-Day Contribution Calendar Heatmap**: | |
139 | + - Annual contributions summary banner (`6,870 contributions in 2026`). | |
140 | + - 52-week x 7-day grid with month headers, day labels, high-contrast monochrome/emerald cells, and hover tooltip. | |
141 | + - Multi-year switcher tabs on the right (`2026`, `2025`, `2024`, etc.). | |
142 | + - Contribution activity timeline below the calendar. | |
143 | + | |
144 | +--- | |
145 | + | |
146 | +## 4. Implementation Steps | |
147 | + | |
148 | +| Step | Action Item | Target Files | | |
149 | +| :--- | :--- | :--- | | |
150 | +| **1. Theming & Tokens** | Update global CSS variables for Ryoku monochrome aesthetic | `src/styles/globals.css`, `tailwind.config.mjs` | | |
151 | +| **2. Core Profile Components** | Create dedicated GitHub profile components | `src/components/profile/UserProfileView.astro`<br/>`src/components/profile/ProfileReadme.astro`<br/>`src/components/profile/PinnedRepos.astro`<br/>`src/components/profile/ContributionCalendar.astro` | | |
152 | +| **3. Landing Page Transformation** | Wire root `/` to display the primary server user profile with instance switcher | `src/pages/index.astro` | | |
153 | +| **4. User Profile Page Polish** | Wire `/[owner]/index.astro` to use the unified `UserProfileView` | `src/pages/[owner]/index.astro` | | |
154 | +| **5. Header & Shell Refactor** | Update top navbar with GitHub layout, search, and user dropdown | `src/components/layout/Header.astro`, `src/layouts/BaseLayout.astro` | | |
155 | +| **6. Verification & Test Suite** | Run typecheck, unit tests, and layout responsiveness checks | `npm run typecheck`, `npm test` | | |
156 | + | |
157 | +--- | |
158 | + | |
159 | +*Plan updated and ready to execute.* | |
0 | 160 | |
▼cli/README.md
+78-171
| @@ -1,236 +1,143 @@ | ||
1 | -<p align="center"> | |
2 | - <img src="https://raw.githubusercontent.com/swadhinbiswas/OpencodeHub/main/cli/assets/opencodehub-cli-logo.svg" alt="OpenCodeHub CLI" width="860" /> | |
3 | -</p> | |
4 | - | |
5 | -<h1 align="center">OpenCodeHub CLI (OCH)</h1> | |
1 | +<h1 align="center">OpenCodeHub CLI (och)</h1> | |
6 | 2 | |
7 | 3 | <p align="center"> |
8 | - Production-ready Git workflows and stack-first pull request management from your terminal. | |
4 | + <strong>Stack-first PR workflows, speculative merge queues, and terminal cockpit for OpenCodeHub.</strong> | |
9 | 5 | </p> |
10 | 6 | |
11 | 7 | <p align="center"> |
12 | 8 | <a href="https://www.npmjs.com/package/opencodehub-cli"><img src="https://img.shields.io/npm/v/opencodehub-cli.svg?style=flat-square" alt="npm version" /></a> |
13 | 9 | <a href="https://www.npmjs.com/package/opencodehub-cli"><img src="https://img.shields.io/npm/dm/opencodehub-cli.svg?style=flat-square" alt="npm downloads" /></a> |
14 | - <a href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/cli/LICENSE"><img src="https://img.shields.io/npm/l/opencodehub-cli.svg?style=flat-square" alt="license" /></a> | |
10 | + <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="license" /></a> | |
15 | 11 | </p> |
16 | 12 | |
17 | -## Why OCH | |
13 | +--- | |
18 | 14 | |
19 | -- Fast command-line workflows for repositories, pull requests, issues, and reviews. | |
20 | -- Stack-first branch/PR flow for multi-PR delivery. | |
21 | -- Secure authentication model with OS credential storage support. | |
22 | -- Built-in API tooling for scripting and automation. | |
15 | +## 📦 Installation | |
23 | 16 | |
24 | -## Installation | |
17 | +Install `opencodehub-cli` globally with your favorite package manager: | |
25 | 18 | |
26 | 19 | ```bash |
20 | +# npm | |
27 | 21 | npm install -g opencodehub-cli |
28 | -``` | |
29 | 22 | |
30 | -Verify: | |
23 | +# bun | |
24 | +bun add -g opencodehub-cli | |
31 | 25 | |
32 | -```bash | |
33 | -och --version | |
34 | -och --help | |
35 | -``` | |
36 | - | |
37 | -## Quick Start | |
38 | - | |
39 | -```bash | |
40 | -# 1) Authenticate | |
41 | -och auth login --url https://git.example.com | |
42 | - | |
43 | -# 2) Inspect your setup | |
44 | -och config doctor | |
26 | +# pnpm | |
27 | +pnpm add -g opencodehub-cli | |
45 | 28 | |
46 | -# 3) Push and open PR workflow | |
47 | -cd your-repo | |
48 | -och repo push | |
49 | - | |
50 | -# 4) Create a pull request | |
51 | -och pr create --base main --title "feat: add onboarding" | |
29 | +# yarn | |
30 | +yarn global add opencodehub-cli | |
52 | 31 | ``` |
53 | 32 | |
54 | -## Core Commands | |
55 | - | |
56 | -- `och auth` authentication (`login`, `logout`, `status`) | |
57 | -- `och config` CLI settings and diagnostics (`list`, `set`, `doctor`) | |
58 | -- `och repo` repository operations (`create`, `clone`, `push`, `list`) | |
59 | -- `och pr` pull request lifecycle (`create`, `list`, `view`, `merge`, ...) | |
60 | -- `och stack` stacked branch/PR workflows (`create`, `submit`, `sync`, ...) | |
61 | -- `och review` code review + AI review flows | |
62 | -- `och issue` issue management | |
63 | -- `och ci`, `och queue`, `och metrics`, `och insights`, `och notify`, `och automate` | |
64 | -- `och api` direct API requests (useful for scripts) | |
65 | - | |
66 | -Run `och <command> --help` for full command options. | |
67 | - | |
68 | -## Authentication and Security | |
69 | - | |
70 | -### Login | |
33 | +Or execute commands on-demand without installing: | |
71 | 34 | |
72 | 35 | ```bash |
73 | -och auth login --url https://git.example.com | |
36 | +npx opencodehub-cli focus | |
74 | 37 | ``` |
75 | 38 | |
76 | -For non-interactive environments: | |
39 | +Verify your installation: | |
77 | 40 | |
78 | 41 | ```bash |
79 | -och auth login --url https://git.example.com --token <PERSONAL_ACCESS_TOKEN> | |
42 | +och --version | |
43 | +och --help | |
80 | 44 | ``` |
81 | 45 | |
82 | -### Token storage behavior | |
83 | - | |
84 | -- `OCH_TOKEN` env var always takes precedence. | |
85 | -- macOS: Keychain (`security`) | |
86 | -- Linux: Secret Service (`secret-tool`) | |
87 | -- Windows: DPAPI-encrypted storage via PowerShell | |
88 | -- Fallback: local CLI config storage if secure backend is unavailable | |
89 | - | |
90 | -### Helpful environment variables | |
46 | +--- | |
91 | 47 | |
92 | -- `OCH_TOKEN`: inject token from environment | |
93 | -- `OCH_HTTP_TIMEOUT_MS`: HTTP timeout in ms (default `15000`) | |
94 | -- `OCH_DISABLE_KEYCHAIN=1`: disable credential backend (useful in CI/tests) | |
48 | +## ⚡ Quick Start | |
95 | 49 | |
96 | -## Configuration | |
97 | - | |
98 | -Inspect current configuration: | |
50 | +### 1. Authenticate with your OpenCodeHub Instance | |
99 | 51 | |
100 | 52 | ```bash |
101 | -och config list | |
102 | -och config path | |
103 | -och config doctor | |
104 | -``` | |
53 | +# Interactive login prompt | |
54 | +och auth login --url https://git.yourcompany.com | |
105 | 55 | |
106 | -Set values: | |
107 | - | |
108 | -```bash | |
109 | -och config set serverUrl https://git.example.com | |
110 | -och config set defaultBranch main | |
111 | -och config set insecure false | |
56 | +# Non-interactive / CI login | |
57 | +och auth login --url https://git.yourcompany.com --token och_xxxxxxxxxxxxxxxx | |
112 | 58 | ``` |
113 | 59 | |
114 | -## Common Workflows | |
115 | - | |
116 | -### Repository lifecycle | |
60 | +Check configuration and credential storage health: | |
117 | 61 | |
118 | 62 | ```bash |
119 | -# Create a remote repository | |
120 | -och repo create my-service --description "Internal API service" | |
121 | - | |
122 | -# Clone repository | |
123 | -och repo clone acme/my-service | |
124 | - | |
125 | -# Push local repository | |
126 | -och repo push --branch main | |
63 | +och config doctor | |
127 | 64 | ``` |
128 | 65 | |
129 | -### Pull request lifecycle | |
66 | +### 2. Stacked PR Workflow | |
130 | 67 | |
131 | -```bash | |
132 | -# Create PR from current branch | |
133 | -och pr create --base main --title "feat: add API pagination" | |
134 | - | |
135 | -# List open PRs | |
136 | -och pr list --state open | |
137 | - | |
138 | -# View PR details | |
139 | -och pr view 42 | |
140 | - | |
141 | -# Merge PR | |
142 | -och pr merge 42 | |
143 | -``` | |
144 | - | |
145 | -### Stack workflow | |
68 | +OpenCodeHub supports Graphite-style stacked branches from your terminal: | |
146 | 69 | |
147 | 70 | ```bash |
148 | -# Create first stack branch | |
149 | -och stack create auth-foundation | |
71 | +# 1. Create your first stack branch | |
72 | +och stack create feature/part-1 | |
150 | 73 | |
151 | -# Create subsequent branch | |
152 | -och stack create auth-ui | |
74 | +# Make edits and commit | |
75 | +git commit -am "feat: part 1 implementation" | |
153 | 76 | |
154 | -# Submit stack and create/update PRs | |
155 | -och stack submit | |
156 | -``` | |
77 | +# 2. Create the next dependent branch on top of part-1 | |
78 | +och stack create feature/part-2 | |
157 | 79 | |
158 | -### AI review workflow | |
80 | +git commit -am "feat: part 2 implementation" | |
159 | 81 | |
160 | -```bash | |
161 | -# Trigger AI review | |
162 | -och review ai 42 | |
82 | +# 3. Submit all branches in the stack (pushes refs & creates linked PRs) | |
83 | +och stack submit | |
163 | 84 | |
164 | -# Wait for completion | |
165 | -och review ai 42 --wait | |
85 | +# 4. Visualize the stack hierarchy | |
86 | +och stack log | |
166 | 87 | |
167 | -# Check latest review status | |
168 | -och review status 42 | |
88 | +# 5. Rebase stack when target base branch changes | |
89 | +och stack sync | |
169 | 90 | ``` |
170 | 91 | |
171 | -### API mode for scripting | |
92 | +--- | |
172 | 93 | |
173 | -```bash | |
174 | -# Read current user | |
175 | -och api /user | |
94 | +## 🕹 Interactive Focus Cockpit (`och focus`) | |
176 | 95 | |
177 | -# Create issue via API | |
178 | -och api /repos/acme/platform/issues -X POST -F title="Bug: timeout" -F body="Steps to reproduce" | |
179 | -``` | |
96 | +The `och focus` command opens an interactive terminal dashboard for fast daily development: | |
180 | 97 | |
181 | -## CI Usage Example | |
182 | - | |
183 | -```yaml | |
184 | -name: OCH Automation | |
185 | -on: [push] | |
186 | - | |
187 | -jobs: | |
188 | - sync: | |
189 | - runs-on: ubuntu-latest | |
190 | - steps: | |
191 | - - uses: actions/checkout@v4 | |
192 | - - uses: actions/setup-node@v4 | |
193 | - with: | |
194 | - node-version: "20" | |
195 | - - run: npm install -g opencodehub-cli | |
196 | - - run: | | |
197 | - export OCH_TOKEN="${{ secrets.OCH_TOKEN }}" | |
198 | - och config set serverUrl https://git.example.com | |
199 | - och config doctor | |
200 | - och repo push --branch main | |
98 | +```bash | |
99 | +och focus | |
201 | 100 | ``` |
202 | 101 | |
203 | -## Troubleshooting | |
102 | +- **Stack Cockpit**: Navigate branches, view parent/child dependencies, and submit changes. | |
103 | +- **Review Inbox**: Inspect assigned reviews, view diff snippets, and submit approvals or change requests. | |
104 | +- **Merge Queue Status**: Track speculative build progress and priority lanes in real time. | |
105 | +- **AI Reviews**: Trigger AI review analysis directly from the terminal. | |
204 | 106 | |
205 | -### `Not logged in. Run 'och auth login' first.` | |
107 | +--- | |
206 | 108 | |
207 | -- Run `och auth login --url <server>`. | |
208 | -- Or export `OCH_TOKEN` for automation. | |
109 | +## 📋 Command Reference | |
209 | 110 | |
210 | -### `Server URL not configured` | |
111 | +| Command | Description | Example | | |
112 | +|---|---|---| | |
113 | +| `och auth` | Manage authentication credentials | `och auth login --url http://localhost:4321` | | |
114 | +| `och stack` | Stacked PR creation, sync, log, submit | `och stack submit` | | |
115 | +| `och focus` | Interactive terminal review & stack cockpit | `och focus` | | |
116 | +| `och pr` | Pull request lifecycle management | `och pr create --base main --title "feat: demo"` | | |
117 | +| `och queue` | Merge queue control & speculative runs | `och queue list`, `och queue add 42` | | |
118 | +| `och review` | Code review and AI review trigger | `och review start 42` | | |
119 | +| `och repo` | Repository operations (clone, push, create) | `och repo create my-app` | | |
120 | +| `och issue` | Issue tracking & milestone management | `och issue list`, `och issue create` | | |
121 | +| `och ci` | CI pipeline logs and execution control | `och ci list`, `och ci view 12` | | |
122 | +| `och config` | CLI configuration & doctor check | `och config doctor` | | |
123 | +| `och whoami` | Display active user profile & server URL | `och whoami` | | |
124 | +| `och api` | Make direct authenticated REST API calls | `och api GET /api/user` | | |
211 | 125 | |
212 | -- Run `och config set serverUrl https://git.example.com`. | |
126 | +Run `och <command> --help` for specific flags and subcommands. | |
213 | 127 | |
214 | -### TLS / certificate issues | |
128 | +--- | |
215 | 129 | |
216 | -- Use `och config set caFile /path/to/ca.pem` for custom CA. | |
217 | -- Use `och config set insecure true` only for temporary debugging. | |
130 | +## 🔒 Security & Token Storage | |
218 | 131 | |
219 | -### Validate setup end-to-end | |
132 | +The CLI automatically stores credentials in your operating system's secure credential manager: | |
220 | 133 | |
221 | -```bash | |
222 | -och config doctor | |
223 | -``` | |
134 | +- **macOS**: Apple Keychain (`security`) | |
135 | +- **Linux**: Secret Service API / libsecret (`secret-tool`) | |
136 | +- **Windows**: Windows Credential Manager / DPAPI | |
137 | +- **CI / Headless**: Set the `OCH_TOKEN` and `OCH_URL` environment variables. | |
224 | 138 | |
225 | -## Development | |
226 | - | |
227 | -```bash | |
228 | -cd cli | |
229 | -npm install | |
230 | -npm run build | |
231 | -npm run test | |
232 | -``` | |
139 | +--- | |
233 | 140 | |
234 | -## License | |
141 | +## 📄 License | |
235 | 142 | |
236 | -MIT | |
143 | +MIT © OpenCodeHub Contributors | |
237 | 144 | |
▼community/.env.example
+20-0
| @@ -0,0 +1,20 @@ | ||
1 | +# ====================================================== | |
2 | +# OpenCodeHub Community Hub — Environment Variables | |
3 | +# ====================================================== | |
4 | +# REQUIRED: Turso database URL (create at https://turso.tech/dashboard) | |
5 | +# Format: libsql://your-db-name-your-org.turso.io | |
6 | +TURSO_DATABASE_URL= | |
7 | + | |
8 | +# REQUIRED: Turso auth token (from https://turso.tech/dashboard → your DB → Tokens) | |
9 | +TURSO_AUTH_TOKEN= | |
10 | + | |
11 | +# JWT secret for community auth (change in production) | |
12 | +JWT_SECRET=change-me-in-production | |
13 | + | |
14 | +# Upstash Redis (optional — for caching probe/repos data) | |
15 | +# Get these from https://console.upstash.com → Redis → REST API | |
16 | +UPSTASH_REDIS_REST_URL= | |
17 | +UPSTASH_REDIS_REST_TOKEN= | |
18 | + | |
19 | +# Site URL (where the community hub is hosted) | |
20 | +SITE_URL=http://localhost:4322 | |
0 | 21 | |
▼community/.gitignore
+6-0
| @@ -0,0 +1,6 @@ | ||
1 | +node_modules | |
2 | +dist | |
3 | +.astro | |
4 | +data | |
5 | +.env | |
6 | + | |
0 | 7 | |
▼community/README.md
+70-0
| @@ -0,0 +1,70 @@ | ||
1 | +# OpenCodeHub Community Hub | |
2 | + | |
3 | +Federated discovery hub for self-hosted **OpenCodeHub** instances. Anyone who hosts OpenCodeHub can submit their URL — the hub probes `GET /api/instance`, fetches `GET /api/repos?visibility=public`, caches public repos & profiles in **Turso** (LibSQL), and displays them with a beautiful, responsive **Tailwind + shadcn/ui** interface. Community users can star and follow; cloning/contributing requires your own node via federation. | |
4 | + | |
5 | +## Stack | |
6 | +- **Framework:** Astro 4 + React 18 (SSR, `@astrojs/node` standalone) | |
7 | +- **UI:** Tailwind CSS 3 + shadcn/ui (Radix) + lucide-react, dark mode, fully responsive | |
8 | +- **DB:** Turso (LibSQL) via Drizzle ORM — `community_users`, `instances`, `cached_repos`, `cached_users`, `stars`, `follows` | |
9 | +- **Cache:** Upstash Redis (`@upstash/redis`) with in-memory fallback (30s–1h TTLs) | |
10 | +- **Auth:** Community JWT (jose + bcryptjs), cookie `community_session` (7d) | |
11 | + | |
12 | +## Setup | |
13 | + | |
14 | +### 1. Create a Turso database | |
15 | +1. Sign up at [turso.tech](https://turso.tech/dashboard) | |
16 | +2. Create a new database (e.g. `community-hub`) | |
17 | +3. Copy the database URL (looks like `libsql://community-hub-your-org.turso.io`) | |
18 | +4. Create an auth token (Settings → Auth Tokens → Create) | |
19 | + | |
20 | +### 2. Configure | |
21 | +```bash | |
22 | +cd community | |
23 | +cp .env.example .env | |
24 | +# Edit .env and set: | |
25 | +# TURSO_DATABASE_URL=libsql://your-db-name-your-org.turso.io | |
26 | +# TURSO_AUTH_TOKEN=your-token-here | |
27 | +# JWT_SECRET=any-random-secret | |
28 | +# UPSTASH_REDIS_REST_URL=https://... (optional) | |
29 | +# UPSTASH_REDIS_REST_TOKEN=... (optional) | |
30 | +``` | |
31 | + | |
32 | +### 3. Push schema & run | |
33 | +```bash | |
34 | +npm install | |
35 | +npm run db:push # Push Drizzle schema to Turso | |
36 | +npm run dev # http://localhost:4322 | |
37 | +``` | |
38 | + | |
39 | +### 4. Production | |
40 | +```bash | |
41 | +npm run build | |
42 | +npm run start | |
43 | +``` | |
44 | + | |
45 | +## How it works | |
46 | +1. **Submit URL** at `/instances/submit` — we probe `GET /api/instance` and verify `data.product === "opencodehub"`. | |
47 | +2. **Aggregate** — paginated `GET /api/repos?visibility=public&sort=updated` (100/page), plus owner metadata for profiles. Only public data is fetched. | |
48 | +3. **Display** — Explore shows trending repos, languages, profiles, instance cards. Each repo card shows `fullName`, `description`, `language`, `starCount`, `httpCloneUrl`, and the source instance badge. | |
49 | +4. **Engage** — Logged-in Community users can **star** repos and **follow** profiles (rows in Turso); counts are community-local and do not mutate the remote instance. | |
50 | +5. **Contribute** — "Clone via `httpCloneUrl`" requires your own OpenCodeHub node. Push to your instance and open a cross-instance PR (federation). | |
51 | + | |
52 | +## API | |
53 | +- `POST /api/instances/submit` — `{url}` → probe + upsert instance | |
54 | +- `GET /api/instances` — list instances | |
55 | +- `POST /api/instances/sync?id=` — fetch public repos from instance, cache in Turso | |
56 | +- `POST /api/stars` — toggle star on a cached repo | |
57 | +- `POST /api/follows` — toggle follow on a profile | |
58 | + | |
59 | +## Pages | |
60 | +- `/` — hero + how it works + CTA | |
61 | +- `/explore` — trending repos, languages, profiles (searchable) | |
62 | +- `/instances` — submitted nodes (InstanceCard grid) | |
63 | +- `/instances/submit` — verify & submit flow | |
64 | +- `/instances/[id]` — instance detail with live sync | |
65 | +- `/u/[username]` — profile + contribution matrix | |
66 | +- `/r/[instance]/[owner]/[repo]` — repo detail, clone URLs, contribute flow | |
67 | +- `/auth/login` & `/auth/register` — Community auth | |
68 | + | |
69 | +## Deploy | |
70 | +Any Node host + Turso + Upstash (optional). For Turso edge, swap adapter to `@astrojs/vercel` if needed. | |
0 | 71 | |
▼community/astro.config.mjs
+14-0
| @@ -0,0 +1,14 @@ | ||
1 | +import node from "@astrojs/node"; | |
2 | +import react from "@astrojs/react"; | |
3 | +import tailwind from "@astrojs/tailwind"; | |
4 | +import { defineConfig } from "astro/config"; | |
5 | + | |
6 | +export default defineConfig({ | |
7 | + integrations: [react(), tailwind({ applyBaseStyles: false })], | |
8 | + output: "server", | |
9 | + adapter: node({ mode: "standalone" }), | |
10 | + server: { port: 4322, host: true }, | |
11 | + vite: { | |
12 | + ssr: { noExternal: ["@radix-ui/*"] }, | |
13 | + }, | |
14 | +}); | |
0 | 15 | |
▼community/bun.lock
+1604-0
| @@ -0,0 +1,1604 @@ | ||
1 | +{ | |
2 | + "lockfileVersion": 1, | |
3 | + "configVersion": 0, | |
4 | + "workspaces": { | |
5 | + "": { | |
6 | + "name": "opencodehub-community", | |
7 | + "dependencies": { | |
8 | + "@astrojs/check": "^0.9.6", | |
9 | + "@astrojs/node": "^8.3.0", | |
10 | + "@astrojs/react": "^3.6.0", | |
11 | + "@astrojs/tailwind": "^5.1.2", | |
12 | + "@libsql/client": "^0.15.15", | |
13 | + "@radix-ui/react-avatar": "^1.1.0", | |
14 | + "@radix-ui/react-dialog": "^1.1.1", | |
15 | + "@radix-ui/react-dropdown-menu": "^2.1.1", | |
16 | + "@radix-ui/react-label": "^2.1.0", | |
17 | + "@radix-ui/react-slot": "^1.1.0", | |
18 | + "@radix-ui/react-tabs": "^1.1.0", | |
19 | + "@radix-ui/react-toast": "^1.2.1", | |
20 | + "@upstash/redis": "^1.36.0", | |
21 | + "astro": "^4.11.0", | |
22 | + "astro-icon": "^1.1.5", | |
23 | + "bcryptjs": "^2.4.3", | |
24 | + "class-variance-authority": "^0.7.0", | |
25 | + "clsx": "^2.1.1", | |
26 | + "dotenv": "^16.4.5", | |
27 | + "drizzle-orm": "^0.45.2", | |
28 | + "jose": "^5.6.3", | |
29 | + "lucide-react": "^0.400.0", | |
30 | + "nanoid": "^5.0.7", | |
31 | + "react": "^18.3.1", | |
32 | + "react-dom": "^18.3.1", | |
33 | + "sonner": "^2.0.7", | |
34 | + "tailwind-merge": "^2.6.0", | |
35 | + "tailwindcss-animate": "^1.0.7", | |
36 | + "zod": "^3.23.8", | |
37 | + }, | |
38 | + "devDependencies": { | |
39 | + "@types/bcryptjs": "^2.4.6", | |
40 | + "@types/node": "^20.14.9", | |
41 | + "@types/react": "^18.3.3", | |
42 | + "@types/react-dom": "^18.3.0", | |
43 | + "autoprefixer": "^10.4.19", | |
44 | + "drizzle-kit": "^0.31.8", | |
45 | + "postcss": "^8.5.2", | |
46 | + "tailwindcss": "^3.4.4", | |
47 | + "typescript": "^5.9.3", | |
48 | + }, | |
49 | + }, | |
50 | + }, | |
51 | + "packages": { | |
52 | + "@alloc/quick-lru": ["@alloc/[email protected]", "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], | |
53 | + | |
54 | + "@antfu/install-pkg": ["@antfu/[email protected]", "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], | |
55 | + | |
56 | + "@astrojs/check": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/check/-/check-0.9.10.tgz", { "dependencies": { "@astrojs/language-server": "^2.16.7", "chokidar": "^4.0.3", "kleur": "^4.1.5", "yargs": "^18.0.0" }, "peerDependencies": { "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ=="], | |
57 | + | |
58 | + "@astrojs/compiler": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/compiler/-/compiler-2.13.1.tgz", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], | |
59 | + | |
60 | + "@astrojs/internal-helpers": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.4.1.tgz", {}, "sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g=="], | |
61 | + | |
62 | + "@astrojs/language-server": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/language-server/-/language-server-2.16.14.tgz", { "dependencies": { "@astrojs/compiler": "^2.13.1", "@astrojs/yaml2ts": "^0.2.4", "@jridgewell/sourcemap-codec": "^1.5.5", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.16", "volar-service-css": "0.0.71", "volar-service-emmet": "0.0.71", "volar-service-html": "0.0.71", "volar-service-prettier": "0.0.71", "volar-service-typescript": "0.0.71", "volar-service-typescript-twoslash-queries": "0.0.71", "volar-service-yaml": "0.0.71", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.1.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier-plugin-astro"], "bin": { "astro-ls": "bin/nodeServer.js" } }, "sha512-YPXkBu6N4d1sT09pvBmIDGZay+1MemV551FSgdEM3aZRDzbkxd2H7Cvf8MsVJLVB1mIEyTf1XbMN/30gp7s46w=="], | |
63 | + | |
64 | + "@astrojs/markdown-remark": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz", { "dependencies": { "@astrojs/prism": "3.1.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^1.22.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg=="], | |
65 | + | |
66 | + "@astrojs/node": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/node/-/node-8.3.4.tgz", { "dependencies": { "send": "^0.19.0", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^4.2.0" } }, "sha512-xzQs39goN7xh9np9rypGmbgZj3AmmjNxEMj9ZWz5aBERlqqFF3n8A/w/uaJeZ/bkHS60l1BXVS0tgsQt9MFqBA=="], | |
67 | + | |
68 | + "@astrojs/prism": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/prism/-/prism-3.1.0.tgz", { "dependencies": { "prismjs": "^1.29.0" } }, "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw=="], | |
69 | + | |
70 | + "@astrojs/react": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/react/-/react-3.6.3.tgz", { "dependencies": { "@vitejs/plugin-react": "^4.3.3", "ultrahtml": "^1.5.3", "vite": "^5.4.10" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21", "@types/react-dom": "^17.0.17 || ^18.0.6", "react": "^17.0.2 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0-beta" } }, "sha512-5ihLQDH5Runddug5AZYlnp/Q5T81QxhwnWJXA9rchBAdh11c6UhBbv9Kdk7b2PkXoEU70CGWBP9hSh0VCR58eA=="], | |
71 | + | |
72 | + "@astrojs/tailwind": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/tailwind/-/tailwind-5.1.5.tgz", { "dependencies": { "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "postcss-load-config": "^4.0.2" }, "peerDependencies": { "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", "tailwindcss": "^3.0.24" } }, "sha512-1diguZEau7FZ9vIjzE4BwavGdhD3+JkdS8zmibl1ene+EHgIU5hI0NMgRYG3yea+Niaf7cyMwjeWeLvzq/maxg=="], | |
73 | + | |
74 | + "@astrojs/telemetry": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/telemetry/-/telemetry-3.1.0.tgz", { "dependencies": { "ci-info": "^4.0.0", "debug": "^4.3.4", "dlv": "^1.1.3", "dset": "^3.1.3", "is-docker": "^3.0.0", "is-wsl": "^3.0.0", "which-pm-runs": "^1.1.0" } }, "sha512-/ca/+D8MIKEC8/A9cSaPUqQNZm+Es/ZinRv0ZAzvu2ios7POQSsVD+VOj7/hypWNsNM3T7RpfgNq7H2TU1KEHA=="], | |
75 | + | |
76 | + "@astrojs/yaml2ts": ["@astrojs/[email protected]", "https://registry.npmmirror.com/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="], | |
77 | + | |
78 | + "@babel/code-frame": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], | |
79 | + | |
80 | + "@babel/compat-data": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], | |
81 | + | |
82 | + "@babel/core": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], | |
83 | + | |
84 | + "@babel/generator": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], | |
85 | + | |
86 | + "@babel/helper-annotate-as-pure": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], | |
87 | + | |
88 | + "@babel/helper-compilation-targets": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], | |
89 | + | |
90 | + "@babel/helper-globals": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], | |
91 | + | |
92 | + "@babel/helper-module-imports": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], | |
93 | + | |
94 | + "@babel/helper-module-transforms": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], | |
95 | + | |
96 | + "@babel/helper-plugin-utils": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], | |
97 | + | |
98 | + "@babel/helper-string-parser": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], | |
99 | + | |
100 | + "@babel/helper-validator-identifier": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], | |
101 | + | |
102 | + "@babel/helper-validator-option": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], | |
103 | + | |
104 | + "@babel/helpers": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], | |
105 | + | |
106 | + "@babel/parser": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], | |
107 | + | |
108 | + "@babel/plugin-syntax-jsx": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], | |
109 | + | |
110 | + "@babel/plugin-transform-react-jsx": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A=="], | |
111 | + | |
112 | + "@babel/plugin-transform-react-jsx-self": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], | |
113 | + | |
114 | + "@babel/plugin-transform-react-jsx-source": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], | |
115 | + | |
116 | + "@babel/template": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], | |
117 | + | |
118 | + "@babel/traverse": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], | |
119 | + | |
120 | + "@babel/types": ["@babel/[email protected]", "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], | |
121 | + | |
122 | + "@cyberalien/svg-utils": ["@cyberalien/[email protected]", "https://registry.npmmirror.com/@cyberalien/svg-utils/-/svg-utils-1.2.19.tgz", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="], | |
123 | + | |
124 | + "@drizzle-team/brocli": ["@drizzle-team/[email protected]", "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], | |
125 | + | |
126 | + "@emmetio/abbreviation": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], | |
127 | + | |
128 | + "@emmetio/css-abbreviation": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], | |
129 | + | |
130 | + "@emmetio/css-parser": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/css-parser/-/css-parser-0.4.1.tgz", { "dependencies": { "@emmetio/stream-reader": "^2.2.0", "@emmetio/stream-reader-utils": "^0.1.0" } }, "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ=="], | |
131 | + | |
132 | + "@emmetio/html-matcher": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", { "dependencies": { "@emmetio/scanner": "^1.0.0" } }, "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ=="], | |
133 | + | |
134 | + "@emmetio/scanner": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/scanner/-/scanner-1.0.4.tgz", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="], | |
135 | + | |
136 | + "@emmetio/stream-reader": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", {}, "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw=="], | |
137 | + | |
138 | + "@emmetio/stream-reader-utils": ["@emmetio/[email protected]", "https://registry.npmmirror.com/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], | |
139 | + | |
140 | + "@emnapi/runtime": ["@emnapi/[email protected]", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.3.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], | |
141 | + | |
142 | + "@esbuild-kit/core-utils": ["@esbuild-kit/[email protected]", "https://registry.npmmirror.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], | |
143 | + | |
144 | + "@esbuild-kit/esm-loader": ["@esbuild-kit/[email protected]", "https://registry.npmmirror.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], | |
145 | + | |
146 | + "@esbuild/aix-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], | |
147 | + | |
148 | + "@esbuild/android-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], | |
149 | + | |
150 | + "@esbuild/android-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], | |
151 | + | |
152 | + "@esbuild/android-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], | |
153 | + | |
154 | + "@esbuild/darwin-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], | |
155 | + | |
156 | + "@esbuild/darwin-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], | |
157 | + | |
158 | + "@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], | |
159 | + | |
160 | + "@esbuild/freebsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], | |
161 | + | |
162 | + "@esbuild/linux-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], | |
163 | + | |
164 | + "@esbuild/linux-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], | |
165 | + | |
166 | + "@esbuild/linux-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], | |
167 | + | |
168 | + "@esbuild/linux-loong64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], | |
169 | + | |
170 | + "@esbuild/linux-mips64el": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], | |
171 | + | |
172 | + "@esbuild/linux-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], | |
173 | + | |
174 | + "@esbuild/linux-riscv64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], | |
175 | + | |
176 | + "@esbuild/linux-s390x": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], | |
177 | + | |
178 | + "@esbuild/linux-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], | |
179 | + | |
180 | + "@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], | |
181 | + | |
182 | + "@esbuild/netbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], | |
183 | + | |
184 | + "@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], | |
185 | + | |
186 | + "@esbuild/openbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], | |
187 | + | |
188 | + "@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], | |
189 | + | |
190 | + "@esbuild/sunos-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], | |
191 | + | |
192 | + "@esbuild/win32-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], | |
193 | + | |
194 | + "@esbuild/win32-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], | |
195 | + | |
196 | + "@esbuild/win32-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], | |
197 | + | |
198 | + "@floating-ui/core": ["@floating-ui/[email protected]", "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], | |
199 | + | |
200 | + "@floating-ui/dom": ["@floating-ui/[email protected]", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], | |
201 | + | |
202 | + "@floating-ui/react-dom": ["@floating-ui/[email protected]", "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", { "dependencies": { "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg=="], | |
203 | + | |
204 | + "@floating-ui/utils": ["@floating-ui/[email protected]", "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.12.tgz", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], | |
205 | + | |
206 | + "@iconify/tools": ["@iconify/[email protected]", "https://registry.npmmirror.com/@iconify/tools/-/tools-5.0.12.tgz", { "dependencies": { "@cyberalien/svg-utils": "^1.2.15", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.3", "fflate": "^0.8.3", "modern-tar": "^0.7.6", "pathe": "^2.0.3", "svgo": "^4.0.1" } }, "sha512-aFPwSFmFphUPVjNLUkgxgUPSPVgTEEjv0mE7NgvfoQBuE5TtsMrKa4HTY65cM5oXZ2a1xKQcW/RKhiOKEiAarw=="], | |
207 | + | |
208 | + "@iconify/types": ["@iconify/[email protected]", "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], | |
209 | + | |
210 | + "@iconify/utils": ["@iconify/[email protected]", "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.4.tgz", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], | |
211 | + | |
212 | + "@img/sharp-darwin-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], | |
213 | + | |
214 | + "@img/sharp-darwin-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], | |
215 | + | |
216 | + "@img/sharp-libvips-darwin-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], | |
217 | + | |
218 | + "@img/sharp-libvips-darwin-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], | |
219 | + | |
220 | + "@img/sharp-libvips-linux-arm": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], | |
221 | + | |
222 | + "@img/sharp-libvips-linux-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], | |
223 | + | |
224 | + "@img/sharp-libvips-linux-s390x": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], | |
225 | + | |
226 | + "@img/sharp-libvips-linux-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], | |
227 | + | |
228 | + "@img/sharp-libvips-linuxmusl-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], | |
229 | + | |
230 | + "@img/sharp-libvips-linuxmusl-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], | |
231 | + | |
232 | + "@img/sharp-linux-arm": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], | |
233 | + | |
234 | + "@img/sharp-linux-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], | |
235 | + | |
236 | + "@img/sharp-linux-s390x": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], | |
237 | + | |
238 | + "@img/sharp-linux-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], | |
239 | + | |
240 | + "@img/sharp-linuxmusl-arm64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], | |
241 | + | |
242 | + "@img/sharp-linuxmusl-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], | |
243 | + | |
244 | + "@img/sharp-wasm32": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], | |
245 | + | |
246 | + "@img/sharp-win32-ia32": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], | |
247 | + | |
248 | + "@img/sharp-win32-x64": ["@img/[email protected]", "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], | |
249 | + | |
250 | + "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], | |
251 | + | |
252 | + "@jridgewell/remapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], | |
253 | + | |
254 | + "@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], | |
255 | + | |
256 | + "@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], | |
257 | + | |
258 | + "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], | |
259 | + | |
260 | + "@libsql/client": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/client/-/client-0.15.15.tgz", { "dependencies": { "@libsql/core": "^0.15.14", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.5.22", "promise-limit": "^2.7.0" } }, "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w=="], | |
261 | + | |
262 | + "@libsql/core": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/core/-/core-0.15.15.tgz", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA=="], | |
263 | + | |
264 | + "@libsql/darwin-arm64": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A=="], | |
265 | + | |
266 | + "@libsql/darwin-x64": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ=="], | |
267 | + | |
268 | + "@libsql/hrana-client": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", { "dependencies": { "@libsql/isomorphic-fetch": "^0.3.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw=="], | |
269 | + | |
270 | + "@libsql/isomorphic-fetch": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", {}, "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw=="], | |
271 | + | |
272 | + "@libsql/isomorphic-ws": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], | |
273 | + | |
274 | + "@libsql/linux-arm-gnueabihf": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", { "os": "linux", "cpu": "arm" }, "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ=="], | |
275 | + | |
276 | + "@libsql/linux-arm-musleabihf": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", { "os": "linux", "cpu": "arm" }, "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg=="], | |
277 | + | |
278 | + "@libsql/linux-arm64-gnu": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w=="], | |
279 | + | |
280 | + "@libsql/linux-arm64-musl": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg=="], | |
281 | + | |
282 | + "@libsql/linux-x64-gnu": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", { "os": "linux", "cpu": "x64" }, "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg=="], | |
283 | + | |
284 | + "@libsql/linux-x64-musl": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", { "os": "linux", "cpu": "x64" }, "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w=="], | |
285 | + | |
286 | + "@libsql/win32-x64-msvc": ["@libsql/[email protected]", "https://registry.npmmirror.com/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", { "os": "win32", "cpu": "x64" }, "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg=="], | |
287 | + | |
288 | + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/[email protected]", "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], | |
289 | + | |
290 | + "@neon-rs/load": ["@neon-rs/[email protected]", "https://registry.npmmirror.com/@neon-rs/load/-/load-0.0.4.tgz", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], | |
291 | + | |
292 | + "@nodelib/fs.scandir": ["@nodelib/[email protected]", "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], | |
293 | + | |
294 | + "@nodelib/fs.stat": ["@nodelib/[email protected]", "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], | |
295 | + | |
296 | + "@nodelib/fs.walk": ["@nodelib/[email protected]", "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], | |
297 | + | |
298 | + "@oslojs/encoding": ["@oslojs/[email protected]", "https://registry.npmmirror.com/@oslojs/encoding/-/encoding-1.1.0.tgz", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], | |
299 | + | |
300 | + "@radix-ui/primitive": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.7.tgz", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], | |
301 | + | |
302 | + "@radix-ui/react-arrow": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA=="], | |
303 | + | |
304 | + "@radix-ui/react-avatar": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA=="], | |
305 | + | |
306 | + "@radix-ui/react-collection": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA=="], | |
307 | + | |
308 | + "@radix-ui/react-compose-refs": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA=="], | |
309 | + | |
310 | + "@radix-ui/react-context": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.2.2.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA=="], | |
311 | + | |
312 | + "@radix-ui/react-dialog": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA=="], | |
313 | + | |
314 | + "@radix-ui/react-direction": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg=="], | |
315 | + | |
316 | + "@radix-ui/react-dismissable-layer": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w=="], | |
317 | + | |
318 | + "@radix-ui/react-dropdown-menu": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g=="], | |
319 | + | |
320 | + "@radix-ui/react-focus-guards": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ=="], | |
321 | + | |
322 | + "@radix-ui/react-focus-scope": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ=="], | |
323 | + | |
324 | + "@radix-ui/react-id": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.4.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA=="], | |
325 | + | |
326 | + "@radix-ui/react-label": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.15.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g=="], | |
327 | + | |
328 | + "@radix-ui/react-menu": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA=="], | |
329 | + | |
330 | + "@radix-ui/react-popper": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-rect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg=="], | |
331 | + | |
332 | + "@radix-ui/react-portal": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ=="], | |
333 | + | |
334 | + "@radix-ui/react-presence": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw=="], | |
335 | + | |
336 | + "@radix-ui/react-primitive": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", { "dependencies": { "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg=="], | |
337 | + | |
338 | + "@radix-ui/react-roving-focus": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ=="], | |
339 | + | |
340 | + "@radix-ui/react-slot": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q=="], | |
341 | + | |
342 | + "@radix-ui/react-tabs": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog=="], | |
343 | + | |
344 | + "@radix-ui/react-toast": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg=="], | |
345 | + | |
346 | + "@radix-ui/react-use-callback-ref": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ=="], | |
347 | + | |
348 | + "@radix-ui/react-use-controllable-state": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ=="], | |
349 | + | |
350 | + "@radix-ui/react-use-effect-event": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg=="], | |
351 | + | |
352 | + "@radix-ui/react-use-is-hydrated": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw=="], | |
353 | + | |
354 | + "@radix-ui/react-use-layout-effect": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw=="], | |
355 | + | |
356 | + "@radix-ui/react-use-rect": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", { "dependencies": { "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ=="], | |
357 | + | |
358 | + "@radix-ui/react-use-size": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw=="], | |
359 | + | |
360 | + "@radix-ui/react-visually-hidden": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ=="], | |
361 | + | |
362 | + "@radix-ui/rect": ["@radix-ui/[email protected]", "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.3.tgz", {}, "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw=="], | |
363 | + | |
364 | + "@rolldown/pluginutils": ["@rolldown/[email protected]", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], | |
365 | + | |
366 | + "@rollup/pluginutils": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], | |
367 | + | |
368 | + "@rollup/rollup-android-arm-eabi": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", { "os": "android", "cpu": "arm" }, "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg=="], | |
369 | + | |
370 | + "@rollup/rollup-android-arm64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", { "os": "android", "cpu": "arm64" }, "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A=="], | |
371 | + | |
372 | + "@rollup/rollup-darwin-arm64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw=="], | |
373 | + | |
374 | + "@rollup/rollup-darwin-x64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A=="], | |
375 | + | |
376 | + "@rollup/rollup-freebsd-arm64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ=="], | |
377 | + | |
378 | + "@rollup/rollup-freebsd-x64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg=="], | |
379 | + | |
380 | + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", { "os": "linux", "cpu": "arm" }, "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA=="], | |
381 | + | |
382 | + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", { "os": "linux", "cpu": "arm" }, "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ=="], | |
383 | + | |
384 | + "@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ=="], | |
385 | + | |
386 | + "@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g=="], | |
387 | + | |
388 | + "@rollup/rollup-linux-loong64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", { "os": "linux", "cpu": "none" }, "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA=="], | |
389 | + | |
390 | + "@rollup/rollup-linux-loong64-musl": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", { "os": "linux", "cpu": "none" }, "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ=="], | |
391 | + | |
392 | + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w=="], | |
393 | + | |
394 | + "@rollup/rollup-linux-ppc64-musl": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w=="], | |
395 | + | |
396 | + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", { "os": "linux", "cpu": "none" }, "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ=="], | |
397 | + | |
398 | + "@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", { "os": "linux", "cpu": "none" }, "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA=="], | |
399 | + | |
400 | + "@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA=="], | |
401 | + | |
402 | + "@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw=="], | |
403 | + | |
404 | + "@rollup/rollup-linux-x64-musl": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA=="], | |
405 | + | |
406 | + "@rollup/rollup-openbsd-x64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ=="], | |
407 | + | |
408 | + "@rollup/rollup-openharmony-arm64": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", { "os": "none", "cpu": "arm64" }, "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ=="], | |
409 | + | |
410 | + "@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw=="], | |
411 | + | |
412 | + "@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ=="], | |
413 | + | |
414 | + "@rollup/rollup-win32-x64-gnu": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", { "os": "win32", "cpu": "x64" }, "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw=="], | |
415 | + | |
416 | + "@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected]", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", { "os": "win32", "cpu": "x64" }, "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q=="], | |
417 | + | |
418 | + "@shikijs/core": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/core/-/core-1.29.2.tgz", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], | |
419 | + | |
420 | + "@shikijs/engine-javascript": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], | |
421 | + | |
422 | + "@shikijs/engine-oniguruma": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="], | |
423 | + | |
424 | + "@shikijs/langs": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/langs/-/langs-1.29.2.tgz", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="], | |
425 | + | |
426 | + "@shikijs/themes": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/themes/-/themes-1.29.2.tgz", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="], | |
427 | + | |
428 | + "@shikijs/types": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/types/-/types-1.29.2.tgz", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="], | |
429 | + | |
430 | + "@shikijs/vscode-textmate": ["@shikijs/[email protected]", "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], | |
431 | + | |
432 | + "@types/babel__core": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], | |
433 | + | |
434 | + "@types/babel__generator": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], | |
435 | + | |
436 | + "@types/babel__template": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], | |
437 | + | |
438 | + "@types/babel__traverse": ["@types/[email protected]", "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], | |
439 | + | |
440 | + "@types/bcryptjs": ["@types/[email protected]", "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", {}, "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ=="], | |
441 | + | |
442 | + "@types/cookie": ["@types/[email protected]", "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], | |
443 | + | |
444 | + "@types/debug": ["@types/[email protected]", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], | |
445 | + | |
446 | + "@types/estree": ["@types/[email protected]", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], | |
447 | + | |
448 | + "@types/hast": ["@types/[email protected]", "https://registry.npmmirror.com/@types/hast/-/hast-3.0.5.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], | |
449 | + | |
450 | + "@types/mdast": ["@types/[email protected]", "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], | |
451 | + | |
452 | + "@types/ms": ["@types/[email protected]", "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], | |
453 | + | |
454 | + "@types/nlcst": ["@types/[email protected]", "https://registry.npmmirror.com/@types/nlcst/-/nlcst-2.0.3.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], | |
455 | + | |
456 | + "@types/node": ["@types/[email protected]", "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="], | |
457 | + | |
458 | + "@types/prop-types": ["@types/[email protected]", "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], | |
459 | + | |
460 | + "@types/react": ["@types/[email protected]", "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], | |
461 | + | |
462 | + "@types/react-dom": ["@types/[email protected]", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], | |
463 | + | |
464 | + "@types/unist": ["@types/[email protected]", "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], | |
465 | + | |
466 | + "@types/ws": ["@types/[email protected]", "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], | |
467 | + | |
468 | + "@ungap/structured-clone": ["@ungap/[email protected]", "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], | |
469 | + | |
470 | + "@upstash/redis": ["@upstash/[email protected]", "https://registry.npmmirror.com/@upstash/redis/-/redis-1.38.2.tgz", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-RZ+JaRCVIS+ZTGnjOnDMr+/0aqDxsBb5rV6aW+Pys4SGELwr5lwE3UbLHRCHqMQ5Ulxj0b/CFBHfbmE175Otog=="], | |
471 | + | |
472 | + "@vitejs/plugin-react": ["@vitejs/[email protected]", "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], | |
473 | + | |
474 | + "@volar/kit": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/kit/-/kit-2.4.28.tgz", { "dependencies": { "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg=="], | |
475 | + | |
476 | + "@volar/language-core": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], | |
477 | + | |
478 | + "@volar/language-server": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/language-server/-/language-server-2.4.28.tgz", { "dependencies": { "@volar/language-core": "2.4.28", "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw=="], | |
479 | + | |
480 | + "@volar/language-service": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/language-service/-/language-service-2.4.28.tgz", { "dependencies": { "@volar/language-core": "2.4.28", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw=="], | |
481 | + | |
482 | + "@volar/source-map": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], | |
483 | + | |
484 | + "@volar/typescript": ["@volar/[email protected]", "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], | |
485 | + | |
486 | + "@vscode/emmet-helper": ["@vscode/[email protected]", "https://registry.npmmirror.com/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", { "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", "vscode-uri": "^3.0.8" } }, "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw=="], | |
487 | + | |
488 | + "@vscode/l10n": ["@vscode/[email protected]", "https://registry.npmmirror.com/@vscode/l10n/-/l10n-0.0.18.tgz", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="], | |
489 | + | |
490 | + "acorn": ["[email protected]", "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz", { "bin": "bin/acorn" }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], | |
491 | + | |
492 | + "ajv": ["[email protected]", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], | |
493 | + | |
494 | + "ajv-draft-04": ["[email protected]", "https://registry.npmmirror.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", { "peerDependencies": { "ajv": "^8.5.0" } }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], | |
495 | + | |
496 | + "ajv-i18n": ["[email protected]", "https://registry.npmmirror.com/ajv-i18n/-/ajv-i18n-4.2.0.tgz", { "peerDependencies": { "ajv": "^8.0.0-beta.0" } }, "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg=="], | |
497 | + | |
498 | + "ansi-align": ["[email protected]", "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], | |
499 | + | |
500 | + "ansi-regex": ["[email protected]", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.3.0.tgz", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], | |
501 | + | |
502 | + "ansi-styles": ["[email protected]", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], | |
503 | + | |
504 | + "any-promise": ["[email protected]", "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], | |
505 | + | |
506 | + "anymatch": ["[email protected]", "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], | |
507 | + | |
508 | + "arg": ["[email protected]", "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], | |
509 | + | |
510 | + "argparse": ["[email protected]", "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], | |
511 | + | |
512 | + "aria-hidden": ["[email protected]", "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], | |
513 | + | |
514 | + "aria-query": ["[email protected]", "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.2.tgz", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], | |
515 | + | |
516 | + "array-iterate": ["[email protected]", "https://registry.npmmirror.com/array-iterate/-/array-iterate-2.0.1.tgz", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], | |
517 | + | |
518 | + "astro": ["[email protected]", "https://registry.npmmirror.com/astro/-/astro-4.16.19.tgz", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.4.1", "@astrojs/markdown-remark": "5.3.0", "@astrojs/telemetry": "3.1.0", "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/types": "^7.26.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.3", "@types/babel__core": "^7.20.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.3.7", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.5.4", "esbuild": "^0.21.5", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", "html-escaper": "^3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.14", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "ora": "^8.1.1", "p-limit": "^6.1.0", "p-queue": "^8.0.1", "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.6.3", "shiki": "^1.23.1", "tinyexec": "^0.3.1", "tsconfck": "^3.1.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", "vite": "^5.4.11", "vitefu": "^1.0.4", "which-pm": "^3.0.0", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": "astro.js" }, "sha512-baeSswPC5ZYvhGDoj25L2FuzKRWMgx105FetOPQVJFMCAp0o08OonYC7AhwsFdhvp7GapqjnC1Fe3lKb2lupYw=="], | |
519 | + | |
520 | + "astro-icon": ["[email protected]", "https://registry.npmmirror.com/astro-icon/-/astro-icon-1.2.0.tgz", { "dependencies": { "@iconify/tools": "^5.0.12", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.4" } }, "sha512-0848V2WZuTPhqGSqLeZimLJZ5wmamMtWYzvA2kvg8s2DrUPZ3/nrTcHJWN0eJHt62jy7uvNxgTNiUJGIr7JNxg=="], | |
521 | + | |
522 | + "autoprefixer": ["[email protected]", "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.4.tgz", { "dependencies": { "browserslist": "^4.28.6", "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA=="], | |
523 | + | |
524 | + "axobject-query": ["[email protected]", "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], | |
525 | + | |
526 | + "bail": ["[email protected]", "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], | |
527 | + | |
528 | + "base-64": ["[email protected]", "https://registry.npmmirror.com/base-64/-/base-64-1.0.0.tgz", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], | |
529 | + | |
530 | + "baseline-browser-mapping": ["[email protected]", "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", { "bin": "dist/cli.cjs" }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], | |
531 | + | |
532 | + "bcryptjs": ["[email protected]", "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz", {}, "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="], | |
533 | + | |
534 | + "binary-extensions": ["[email protected]", "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], | |
535 | + | |
536 | + "boolbase": ["[email protected]", "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], | |
537 | + | |
538 | + "boxen": ["[email protected]", "https://registry.npmmirror.com/boxen/-/boxen-8.0.1.tgz", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], | |
539 | + | |
540 | + "braces": ["[email protected]", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], | |
541 | + | |
542 | + "browserslist": ["[email protected]", "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.8.tgz", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": "cli.js" }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], | |
543 | + | |
544 | + "buffer-from": ["[email protected]", "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], | |
545 | + | |
546 | + "camelcase": ["[email protected]", "https://registry.npmmirror.com/camelcase/-/camelcase-8.0.0.tgz", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], | |
547 | + | |
548 | + "camelcase-css": ["[email protected]", "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], | |
549 | + | |
550 | + "caniuse-lite": ["[email protected]", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], | |
551 | + | |
552 | + "ccount": ["[email protected]", "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], | |
553 | + | |
554 | + "chalk": ["[email protected]", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], | |
555 | + | |
556 | + "character-entities": ["[email protected]", "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], | |
557 | + | |
558 | + "character-entities-html4": ["[email protected]", "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], | |
559 | + | |
560 | + "character-entities-legacy": ["[email protected]", "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], | |
561 | + | |
562 | + "chokidar": ["[email protected]", "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], | |
563 | + | |
564 | + "ci-info": ["[email protected]", "https://registry.npmmirror.com/ci-info/-/ci-info-4.4.0.tgz", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], | |
565 | + | |
566 | + "class-variance-authority": ["[email protected]", "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], | |
567 | + | |
568 | + "cli-boxes": ["[email protected]", "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-3.0.0.tgz", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], | |
569 | + | |
570 | + "cli-cursor": ["[email protected]", "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], | |
571 | + | |
572 | + "cli-spinners": ["[email protected]", "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], | |
573 | + | |
574 | + "cliui": ["[email protected]", "https://registry.npmmirror.com/cliui/-/cliui-9.0.1.tgz", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], | |
575 | + | |
576 | + "clsx": ["[email protected]", "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], | |
577 | + | |
578 | + "color": ["[email protected]", "https://registry.npmmirror.com/color/-/color-4.2.3.tgz", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], | |
579 | + | |
580 | + "color-convert": ["[email protected]", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], | |
581 | + | |
582 | + "color-name": ["[email protected]", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], | |
583 | + | |
584 | + "color-string": ["[email protected]", "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], | |
585 | + | |
586 | + "comma-separated-tokens": ["[email protected]", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], | |
587 | + | |
588 | + "commander": ["[email protected]", "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], | |
589 | + | |
590 | + "common-ancestor-path": ["[email protected]", "https://registry.npmmirror.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], | |
591 | + | |
592 | + "convert-source-map": ["[email protected]", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], | |
593 | + | |
594 | + "cookie": ["[email protected]", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], | |
595 | + | |
596 | + "css-select": ["[email protected]", "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], | |
597 | + | |
598 | + "css-tree": ["[email protected]", "https://registry.npmmirror.com/css-tree/-/css-tree-3.2.1.tgz", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], | |
599 | + | |
600 | + "css-what": ["[email protected]", "https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], | |
601 | + | |
602 | + "cssesc": ["[email protected]", "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], | |
603 | + | |
604 | + "csso": ["[email protected]", "https://registry.npmmirror.com/csso/-/csso-5.0.5.tgz", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], | |
605 | + | |
606 | + "csstype": ["[email protected]", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], | |
607 | + | |
608 | + "data-uri-to-buffer": ["[email protected]", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], | |
609 | + | |
610 | + "debug": ["[email protected]", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], | |
611 | + | |
612 | + "decode-named-character-reference": ["[email protected]", "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], | |
613 | + | |
614 | + "depd": ["[email protected]", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], | |
615 | + | |
616 | + "dequal": ["[email protected]", "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], | |
617 | + | |
618 | + "destroy": ["[email protected]", "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], | |
619 | + | |
620 | + "detect-libc": ["[email protected]", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.2.tgz", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], | |
621 | + | |
622 | + "detect-node-es": ["[email protected]", "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], | |
623 | + | |
624 | + "deterministic-object-hash": ["[email protected]", "https://registry.npmmirror.com/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], | |
625 | + | |
626 | + "devalue": ["[email protected]", "https://registry.npmmirror.com/devalue/-/devalue-5.9.0.tgz", {}, "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A=="], | |
627 | + | |
628 | + "devlop": ["[email protected]", "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], | |
629 | + | |
630 | + "didyoumean": ["[email protected]", "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], | |
631 | + | |
632 | + "diff": ["[email protected]", "https://registry.npmmirror.com/diff/-/diff-5.2.2.tgz", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], | |
633 | + | |
634 | + "dlv": ["[email protected]", "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], | |
635 | + | |
636 | + "dom-serializer": ["[email protected]", "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], | |
637 | + | |
638 | + "domelementtype": ["[email protected]", "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], | |
639 | + | |
640 | + "domhandler": ["[email protected]", "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], | |
641 | + | |
642 | + "domutils": ["[email protected]", "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], | |
643 | + | |
644 | + "dotenv": ["[email protected]", "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], | |
645 | + | |
646 | + "drizzle-kit": ["[email protected]", "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": "bin.cjs" }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], | |
647 | + | |
648 | + "drizzle-orm": ["[email protected]", "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.45.2.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], | |
649 | + | |
650 | + "dset": ["[email protected]", "https://registry.npmmirror.com/dset/-/dset-3.1.4.tgz", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], | |
651 | + | |
652 | + "ee-first": ["[email protected]", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], | |
653 | + | |
654 | + "electron-to-chromium": ["[email protected]", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], | |
655 | + | |
656 | + "emmet": ["[email protected]", "https://registry.npmmirror.com/emmet/-/emmet-2.4.11.tgz", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], | |
657 | + | |
658 | + "emoji-regex": ["[email protected]", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], | |
659 | + | |
660 | + "emoji-regex-xs": ["[email protected]", "https://registry.npmmirror.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], | |
661 | + | |
662 | + "encodeurl": ["[email protected]", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], | |
663 | + | |
664 | + "entities": ["[email protected]", "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], | |
665 | + | |
666 | + "es-errors": ["[email protected]", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], | |
667 | + | |
668 | + "es-module-lexer": ["[email protected]", "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], | |
669 | + | |
670 | + "esbuild": ["[email protected]", "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], | |
671 | + | |
672 | + "escalade": ["[email protected]", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], | |
673 | + | |
674 | + "escape-html": ["[email protected]", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], | |
675 | + | |
676 | + "escape-string-regexp": ["[email protected]", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], | |
677 | + | |
678 | + "esprima": ["[email protected]", "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], | |
679 | + | |
680 | + "estree-walker": ["[email protected]", "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], | |
681 | + | |
682 | + "etag": ["[email protected]", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], | |
683 | + | |
684 | + "eventemitter3": ["[email protected]", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], | |
685 | + | |
686 | + "extend": ["[email protected]", "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], | |
687 | + | |
688 | + "extend-shallow": ["[email protected]", "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], | |
689 | + | |
690 | + "fast-deep-equal": ["[email protected]", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], | |
691 | + | |
692 | + "fast-glob": ["[email protected]", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], | |
693 | + | |
694 | + "fast-uri": ["[email protected]", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.5.tgz", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], | |
695 | + | |
696 | + "fastq": ["[email protected]", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], | |
697 | + | |
698 | + "fdir": ["[email protected]", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], | |
699 | + | |
700 | + "fetch-blob": ["[email protected]", "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], | |
701 | + | |
702 | + "fflate": ["[email protected]", "https://registry.npmmirror.com/fflate/-/fflate-0.8.3.tgz", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], | |
703 | + | |
704 | + "fill-range": ["[email protected]", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], | |
705 | + | |
706 | + "find-up": ["[email protected]", "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], | |
707 | + | |
708 | + "find-up-simple": ["[email protected]", "https://registry.npmmirror.com/find-up-simple/-/find-up-simple-1.0.1.tgz", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], | |
709 | + | |
710 | + "find-yarn-workspace-root2": ["[email protected]", "https://registry.npmmirror.com/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", { "dependencies": { "micromatch": "^4.0.2", "pkg-dir": "^4.2.0" } }, "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA=="], | |
711 | + | |
712 | + "flattie": ["[email protected]", "https://registry.npmmirror.com/flattie/-/flattie-1.1.1.tgz", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], | |
713 | + | |
714 | + "formdata-polyfill": ["[email protected]", "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], | |
715 | + | |
716 | + "fraction.js": ["[email protected]", "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], | |
717 | + | |
718 | + "fresh": ["[email protected]", "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], | |
719 | + | |
720 | + "fsevents": ["[email protected]", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], | |
721 | + | |
722 | + "function-bind": ["[email protected]", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], | |
723 | + | |
724 | + "gensync": ["[email protected]", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], | |
725 | + | |
726 | + "get-caller-file": ["[email protected]", "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], | |
727 | + | |
728 | + "get-east-asian-width": ["[email protected]", "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], | |
729 | + | |
730 | + "get-nonce": ["[email protected]", "https://registry.npmmirror.com/get-nonce/-/get-nonce-1.0.1.tgz", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], | |
731 | + | |
732 | + "get-tsconfig": ["[email protected]", "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.14.3.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], | |
733 | + | |
734 | + "github-slugger": ["[email protected]", "https://registry.npmmirror.com/github-slugger/-/github-slugger-2.0.0.tgz", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], | |
735 | + | |
736 | + "glob-parent": ["[email protected]", "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], | |
737 | + | |
738 | + "graceful-fs": ["[email protected]", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], | |
739 | + | |
740 | + "gray-matter": ["[email protected]", "https://registry.npmmirror.com/gray-matter/-/gray-matter-4.0.3.tgz", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], | |
741 | + | |
742 | + "hasown": ["[email protected]", "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], | |
743 | + | |
744 | + "hast-util-from-html": ["[email protected]", "https://registry.npmmirror.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], | |
745 | + | |
746 | + "hast-util-from-parse5": ["[email protected]", "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], | |
747 | + | |
748 | + "hast-util-is-element": ["[email protected]", "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], | |
749 | + | |
750 | + "hast-util-parse-selector": ["[email protected]", "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], | |
751 | + | |
752 | + "hast-util-raw": ["[email protected]", "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], | |
753 | + | |
754 | + "hast-util-to-html": ["[email protected]", "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], | |
755 | + | |
756 | + "hast-util-to-parse5": ["[email protected]", "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], | |
757 | + | |
758 | + "hast-util-to-text": ["[email protected]", "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], | |
759 | + | |
760 | + "hast-util-whitespace": ["[email protected]", "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], | |
761 | + | |
762 | + "hastscript": ["[email protected]", "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], | |
763 | + | |
764 | + "html-escaper": ["[email protected]", "https://registry.npmmirror.com/html-escaper/-/html-escaper-3.0.3.tgz", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], | |
765 | + | |
766 | + "html-void-elements": ["[email protected]", "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], | |
767 | + | |
768 | + "http-cache-semantics": ["[email protected]", "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], | |
769 | + | |
770 | + "http-errors": ["[email protected]", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], | |
771 | + | |
772 | + "import-meta-resolve": ["[email protected]", "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], | |
773 | + | |
774 | + "inherits": ["[email protected]", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], | |
775 | + | |
776 | + "is-arrayish": ["[email protected]", "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], | |
777 | + | |
778 | + "is-binary-path": ["[email protected]", "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], | |
779 | + | |
780 | + "is-core-module": ["[email protected]", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], | |
781 | + | |
782 | + "is-docker": ["[email protected]", "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", { "bin": "cli.js" }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], | |
783 | + | |
784 | + "is-extendable": ["[email protected]", "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], | |
785 | + | |
786 | + "is-extglob": ["[email protected]", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], | |
787 | + | |
788 | + "is-fullwidth-code-point": ["[email protected]", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], | |
789 | + | |
790 | + "is-glob": ["[email protected]", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], | |
791 | + | |
792 | + "is-inside-container": ["[email protected]", "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", { "dependencies": { "is-docker": "^3.0.0" }, "bin": "cli.js" }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], | |
793 | + | |
794 | + "is-interactive": ["[email protected]", "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], | |
795 | + | |
796 | + "is-number": ["[email protected]", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], | |
797 | + | |
798 | + "is-plain-obj": ["[email protected]", "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], | |
799 | + | |
800 | + "is-unicode-supported": ["[email protected]", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], | |
801 | + | |
802 | + "is-wsl": ["[email protected]", "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], | |
803 | + | |
804 | + "jiti": ["[email protected]", "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", { "bin": "bin/jiti.js" }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], | |
805 | + | |
806 | + "jose": ["[email protected]", "https://registry.npmmirror.com/jose/-/jose-5.10.0.tgz", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], | |
807 | + | |
808 | + "js-base64": ["[email protected]", "https://registry.npmmirror.com/js-base64/-/js-base64-3.9.3.tgz", {}, "sha512-uwYQp+VJ38FVvtim6qNbit6e9uT6dwWQ4Y1+H9TxhW5hcHjpHwoxlR0nMpqUmIFOmu4VqMxwdJA88gIVuZJQ/g=="], | |
809 | + | |
810 | + "js-tokens": ["[email protected]", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], | |
811 | + | |
812 | + "js-yaml": ["[email protected]", "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.1.tgz", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], | |
813 | + | |
814 | + "jsesc": ["[email protected]", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], | |
815 | + | |
816 | + "json-schema-traverse": ["[email protected]", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], | |
817 | + | |
818 | + "json5": ["[email protected]", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], | |
819 | + | |
820 | + "jsonc-parser": ["[email protected]", "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], | |
821 | + | |
822 | + "kind-of": ["[email protected]", "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], | |
823 | + | |
824 | + "kleur": ["[email protected]", "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], | |
825 | + | |
826 | + "libsql": ["[email protected]", "https://registry.npmmirror.com/libsql/-/libsql-0.5.29.tgz", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="], | |
827 | + | |
828 | + "lilconfig": ["[email protected]", "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], | |
829 | + | |
830 | + "lines-and-columns": ["[email protected]", "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], | |
831 | + | |
832 | + "load-yaml-file": ["[email protected]", "https://registry.npmmirror.com/load-yaml-file/-/load-yaml-file-0.2.0.tgz", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.13.0", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw=="], | |
833 | + | |
834 | + "locate-path": ["[email protected]", "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], | |
835 | + | |
836 | + "log-symbols": ["[email protected]", "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], | |
837 | + | |
838 | + "longest-streak": ["[email protected]", "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], | |
839 | + | |
840 | + "loose-envify": ["[email protected]", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], | |
841 | + | |
842 | + "lru-cache": ["[email protected]", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], | |
843 | + | |
844 | + "lucide-react": ["[email protected]", "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.400.0.tgz", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-rpp7pFHh3Xd93KHixNgB0SqThMHpYNzsGUu69UaQbSZ75Q/J3m5t6EhKyMT3m4w2WOxmJ2mY0tD3vebnXqQryQ=="], | |
845 | + | |
846 | + "magic-string": ["[email protected]", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], | |
847 | + | |
848 | + "magicast": ["[email protected]", "https://registry.npmmirror.com/magicast/-/magicast-0.3.5.tgz", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], | |
849 | + | |
850 | + "markdown-table": ["[email protected]", "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], | |
851 | + | |
852 | + "mdast-util-definitions": ["[email protected]", "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], | |
853 | + | |
854 | + "mdast-util-find-and-replace": ["[email protected]", "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], | |
855 | + | |
856 | + "mdast-util-from-markdown": ["[email protected]", "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], | |
857 | + | |
858 | + "mdast-util-gfm": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], | |
859 | + | |
860 | + "mdast-util-gfm-autolink-literal": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], | |
861 | + | |
862 | + "mdast-util-gfm-footnote": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], | |
863 | + | |
864 | + "mdast-util-gfm-strikethrough": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], | |
865 | + | |
866 | + "mdast-util-gfm-table": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], | |
867 | + | |
868 | + "mdast-util-gfm-task-list-item": ["[email protected]", "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], | |
869 | + | |
870 | + "mdast-util-phrasing": ["[email protected]", "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], | |
871 | + | |
872 | + "mdast-util-to-hast": ["[email protected]", "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], | |
873 | + | |
874 | + "mdast-util-to-markdown": ["[email protected]", "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], | |
875 | + | |
876 | + "mdast-util-to-string": ["[email protected]", "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], | |
877 | + | |
878 | + "mdn-data": ["[email protected]", "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.27.1.tgz", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], | |
879 | + | |
880 | + "merge2": ["[email protected]", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], | |
881 | + | |
882 | + "micromark": ["[email protected]", "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], | |
883 | + | |
884 | + "micromark-core-commonmark": ["[email protected]", "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], | |
885 | + | |
886 | + "micromark-extension-gfm": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], | |
887 | + | |
888 | + "micromark-extension-gfm-autolink-literal": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], | |
889 | + | |
890 | + "micromark-extension-gfm-footnote": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], | |
891 | + | |
892 | + "micromark-extension-gfm-strikethrough": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], | |
893 | + | |
894 | + "micromark-extension-gfm-table": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], | |
895 | + | |
896 | + "micromark-extension-gfm-tagfilter": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], | |
897 | + | |
898 | + "micromark-extension-gfm-task-list-item": ["[email protected]", "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], | |
899 | + | |
900 | + "micromark-factory-destination": ["[email protected]", "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], | |
901 | + | |
902 | + "micromark-factory-label": ["[email protected]", "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], | |
903 | + | |
904 | + "micromark-factory-space": ["[email protected]", "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], | |
905 | + | |
906 | + "micromark-factory-title": ["[email protected]", "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], | |
907 | + | |
908 | + "micromark-factory-whitespace": ["[email protected]", "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], | |
909 | + | |
910 | + "micromark-util-character": ["[email protected]", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], | |
911 | + | |
912 | + "micromark-util-chunked": ["[email protected]", "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], | |
913 | + | |
914 | + "micromark-util-classify-character": ["[email protected]", "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], | |
915 | + | |
916 | + "micromark-util-combine-extensions": ["[email protected]", "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], | |
917 | + | |
918 | + "micromark-util-decode-numeric-character-reference": ["[email protected]", "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], | |
919 | + | |
920 | + "micromark-util-decode-string": ["[email protected]", "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], | |
921 | + | |
922 | + "micromark-util-encode": ["[email protected]", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], | |
923 | + | |
924 | + "micromark-util-html-tag-name": ["[email protected]", "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], | |
925 | + | |
926 | + "micromark-util-normalize-identifier": ["[email protected]", "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], | |
927 | + | |
928 | + "micromark-util-resolve-all": ["[email protected]", "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], | |
929 | + | |
930 | + "micromark-util-sanitize-uri": ["[email protected]", "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], | |
931 | + | |
932 | + "micromark-util-subtokenize": ["[email protected]", "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], | |
933 | + | |
934 | + "micromark-util-symbol": ["[email protected]", "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], | |
935 | + | |
936 | + "micromark-util-types": ["[email protected]", "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], | |
937 | + | |
938 | + "micromatch": ["[email protected]", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], | |
939 | + | |
940 | + "mime": ["[email protected]", "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", { "bin": "cli.js" }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], | |
941 | + | |
942 | + "mimic-function": ["[email protected]", "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], | |
943 | + | |
944 | + "modern-tar": ["[email protected]", "https://registry.npmmirror.com/modern-tar/-/modern-tar-0.7.7.tgz", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="], | |
945 | + | |
946 | + "mrmime": ["[email protected]", "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], | |
947 | + | |
948 | + "ms": ["[email protected]", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], | |
949 | + | |
950 | + "muggle-string": ["[email protected]", "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], | |
951 | + | |
952 | + "mz": ["[email protected]", "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], | |
953 | + | |
954 | + "nanoid": ["[email protected]", "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.16.tgz", { "bin": "bin/nanoid.js" }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], | |
955 | + | |
956 | + "neotraverse": ["[email protected]", "https://registry.npmmirror.com/neotraverse/-/neotraverse-0.6.18.tgz", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], | |
957 | + | |
958 | + "nlcst-to-string": ["[email protected]", "https://registry.npmmirror.com/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], | |
959 | + | |
960 | + "node-domexception": ["[email protected]", "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], | |
961 | + | |
962 | + "node-fetch": ["[email protected]", "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], | |
963 | + | |
964 | + "node-releases": ["[email protected]", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.53.tgz", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], | |
965 | + | |
966 | + "normalize-path": ["[email protected]", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], | |
967 | + | |
968 | + "nth-check": ["[email protected]", "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], | |
969 | + | |
970 | + "object-assign": ["[email protected]", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], | |
971 | + | |
972 | + "object-hash": ["[email protected]", "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], | |
973 | + | |
974 | + "on-finished": ["[email protected]", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], | |
975 | + | |
976 | + "onetime": ["[email protected]", "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], | |
977 | + | |
978 | + "oniguruma-to-es": ["[email protected]", "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], | |
979 | + | |
980 | + "ora": ["[email protected]", "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], | |
981 | + | |
982 | + "p-limit": ["[email protected]", "https://registry.npmmirror.com/p-limit/-/p-limit-6.2.0.tgz", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], | |
983 | + | |
984 | + "p-locate": ["[email protected]", "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], | |
985 | + | |
986 | + "p-queue": ["[email protected]", "https://registry.npmmirror.com/p-queue/-/p-queue-8.1.1.tgz", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], | |
987 | + | |
988 | + "p-timeout": ["[email protected]", "https://registry.npmmirror.com/p-timeout/-/p-timeout-6.1.4.tgz", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], | |
989 | + | |
990 | + "p-try": ["[email protected]", "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], | |
991 | + | |
992 | + "package-manager-detector": ["[email protected]", "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.8.0.tgz", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], | |
993 | + | |
994 | + "parse-latin": ["[email protected]", "https://registry.npmmirror.com/parse-latin/-/parse-latin-7.0.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], | |
995 | + | |
996 | + "parse5": ["[email protected]", "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], | |
997 | + | |
998 | + "path-browserify": ["[email protected]", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], | |
999 | + | |
1000 | + "path-exists": ["[email protected]", "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], | |
1001 | + | |
1002 | + "path-parse": ["[email protected]", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], | |
1003 | + | |
1004 | + "pathe": ["[email protected]", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], | |
1005 | + | |
1006 | + "picocolors": ["[email protected]", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], | |
1007 | + | |
1008 | + "picomatch": ["[email protected]", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], | |
1009 | + | |
1010 | + "pify": ["[email protected]", "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], | |
1011 | + | |
1012 | + "pirates": ["[email protected]", "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], | |
1013 | + | |
1014 | + "pkg-dir": ["[email protected]", "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], | |
1015 | + | |
1016 | + "postcss": ["[email protected]", "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], | |
1017 | + | |
1018 | + "postcss-import": ["[email protected]", "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], | |
1019 | + | |
1020 | + "postcss-js": ["[email protected]", "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], | |
1021 | + | |
1022 | + "postcss-load-config": ["[email protected]", "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz", { "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["ts-node"] }, "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ=="], | |
1023 | + | |
1024 | + "postcss-nested": ["[email protected]", "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], | |
1025 | + | |
1026 | + "postcss-selector-parser": ["[email protected]", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="], | |
1027 | + | |
1028 | + "postcss-value-parser": ["[email protected]", "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], | |
1029 | + | |
1030 | + "preferred-pm": ["[email protected]", "https://registry.npmmirror.com/preferred-pm/-/preferred-pm-4.1.1.tgz", { "dependencies": { "find-up-simple": "^1.0.0", "find-yarn-workspace-root2": "1.2.16", "which-pm": "^3.0.1" } }, "sha512-rU+ZAv1Ur9jAUZtGPebQVQPzdGhNzaEiQ7VL9+cjsAWPHFYOccNXPNiev1CCDSOg/2j7UujM7ojNhpkuILEVNQ=="], | |
1031 | + | |
1032 | + "prettier": ["[email protected]", "https://registry.npmmirror.com/prettier/-/prettier-3.9.6.tgz", { "bin": "bin/prettier.cjs" }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], | |
1033 | + | |
1034 | + "prismjs": ["[email protected]", "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], | |
1035 | + | |
1036 | + "promise-limit": ["[email protected]", "https://registry.npmmirror.com/promise-limit/-/promise-limit-2.7.0.tgz", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], | |
1037 | + | |
1038 | + "prompts": ["[email protected]", "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], | |
1039 | + | |
1040 | + "property-information": ["[email protected]", "https://registry.npmmirror.com/property-information/-/property-information-7.2.0.tgz", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], | |
1041 | + | |
1042 | + "queue-microtask": ["[email protected]", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], | |
1043 | + | |
1044 | + "range-parser": ["[email protected]", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], | |
1045 | + | |
1046 | + "react": ["[email protected]", "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], | |
1047 | + | |
1048 | + "react-dom": ["[email protected]", "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], | |
1049 | + | |
1050 | + "react-refresh": ["[email protected]", "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], | |
1051 | + | |
1052 | + "react-remove-scroll": ["[email protected]", "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], | |
1053 | + | |
1054 | + "react-remove-scroll-bar": ["[email protected]", "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], | |
1055 | + | |
1056 | + "react-style-singleton": ["[email protected]", "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], | |
1057 | + | |
1058 | + "read-cache": ["[email protected]", "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], | |
1059 | + | |
1060 | + "readdirp": ["[email protected]", "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], | |
1061 | + | |
1062 | + "regex": ["[email protected]", "https://registry.npmmirror.com/regex/-/regex-5.1.1.tgz", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], | |
1063 | + | |
1064 | + "regex-recursion": ["[email protected]", "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-5.1.1.tgz", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], | |
1065 | + | |
1066 | + "regex-utilities": ["[email protected]", "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], | |
1067 | + | |
1068 | + "rehype": ["[email protected]", "https://registry.npmmirror.com/rehype/-/rehype-13.0.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], | |
1069 | + | |
1070 | + "rehype-parse": ["[email protected]", "https://registry.npmmirror.com/rehype-parse/-/rehype-parse-9.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], | |
1071 | + | |
1072 | + "rehype-raw": ["[email protected]", "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], | |
1073 | + | |
1074 | + "rehype-stringify": ["[email protected]", "https://registry.npmmirror.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], | |
1075 | + | |
1076 | + "remark-gfm": ["[email protected]", "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], | |
1077 | + | |
1078 | + "remark-parse": ["[email protected]", "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], | |
1079 | + | |
1080 | + "remark-rehype": ["[email protected]", "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], | |
1081 | + | |
1082 | + "remark-smartypants": ["[email protected]", "https://registry.npmmirror.com/remark-smartypants/-/remark-smartypants-3.0.3.tgz", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA=="], | |
1083 | + | |
1084 | + "remark-stringify": ["[email protected]", "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], | |
1085 | + | |
1086 | + "request-light": ["[email protected]", "https://registry.npmmirror.com/request-light/-/request-light-0.7.0.tgz", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="], | |
1087 | + | |
1088 | + "require-from-string": ["[email protected]", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], | |
1089 | + | |
1090 | + "resolve": ["[email protected]", "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], | |
1091 | + | |
1092 | + "resolve-pkg-maps": ["[email protected]", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], | |
1093 | + | |
1094 | + "restore-cursor": ["[email protected]", "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], | |
1095 | + | |
1096 | + "retext": ["[email protected]", "https://registry.npmmirror.com/retext/-/retext-9.0.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], | |
1097 | + | |
1098 | + "retext-latin": ["[email protected]", "https://registry.npmmirror.com/retext-latin/-/retext-latin-4.0.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], | |
1099 | + | |
1100 | + "retext-smartypants": ["[email protected]", "https://registry.npmmirror.com/retext-smartypants/-/retext-smartypants-6.2.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="], | |
1101 | + | |
1102 | + "retext-stringify": ["[email protected]", "https://registry.npmmirror.com/retext-stringify/-/retext-stringify-4.0.0.tgz", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], | |
1103 | + | |
1104 | + "reusify": ["[email protected]", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], | |
1105 | + | |
1106 | + "rollup": ["[email protected]", "https://registry.npmmirror.com/rollup/-/rollup-4.62.4.tgz", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.4", "@rollup/rollup-android-arm64": "4.62.4", "@rollup/rollup-darwin-arm64": "4.62.4", "@rollup/rollup-darwin-x64": "4.62.4", "@rollup/rollup-freebsd-arm64": "4.62.4", "@rollup/rollup-freebsd-x64": "4.62.4", "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", "@rollup/rollup-linux-arm-musleabihf": "4.62.4", "@rollup/rollup-linux-arm64-gnu": "4.62.4", "@rollup/rollup-linux-arm64-musl": "4.62.4", "@rollup/rollup-linux-loong64-gnu": "4.62.4", "@rollup/rollup-linux-loong64-musl": "4.62.4", "@rollup/rollup-linux-ppc64-gnu": "4.62.4", "@rollup/rollup-linux-ppc64-musl": "4.62.4", "@rollup/rollup-linux-riscv64-gnu": "4.62.4", "@rollup/rollup-linux-riscv64-musl": "4.62.4", "@rollup/rollup-linux-s390x-gnu": "4.62.4", "@rollup/rollup-linux-x64-gnu": "4.62.4", "@rollup/rollup-linux-x64-musl": "4.62.4", "@rollup/rollup-openbsd-x64": "4.62.4", "@rollup/rollup-openharmony-arm64": "4.62.4", "@rollup/rollup-win32-arm64-msvc": "4.62.4", "@rollup/rollup-win32-ia32-msvc": "4.62.4", "@rollup/rollup-win32-x64-gnu": "4.62.4", "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg=="], | |
1107 | + | |
1108 | + "run-parallel": ["[email protected]", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], | |
1109 | + | |
1110 | + "sax": ["[email protected]", "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], | |
1111 | + | |
1112 | + "scheduler": ["[email protected]", "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], | |
1113 | + | |
1114 | + "section-matter": ["[email protected]", "https://registry.npmmirror.com/section-matter/-/section-matter-1.0.0.tgz", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], | |
1115 | + | |
1116 | + "semver": ["[email protected]", "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", { "bin": "bin/semver.js" }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], | |
1117 | + | |
1118 | + "send": ["[email protected]", "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], | |
1119 | + | |
1120 | + "server-destroy": ["[email protected]", "https://registry.npmmirror.com/server-destroy/-/server-destroy-1.0.1.tgz", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="], | |
1121 | + | |
1122 | + "setprototypeof": ["[email protected]", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], | |
1123 | + | |
1124 | + "sharp": ["[email protected]", "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], | |
1125 | + | |
1126 | + "shiki": ["[email protected]", "https://registry.npmmirror.com/shiki/-/shiki-1.29.2.tgz", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], | |
1127 | + | |
1128 | + "signal-exit": ["[email protected]", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], | |
1129 | + | |
1130 | + "simple-swizzle": ["[email protected]", "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], | |
1131 | + | |
1132 | + "sisteransi": ["[email protected]", "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], | |
1133 | + | |
1134 | + "sonner": ["[email protected]", "https://registry.npmmirror.com/sonner/-/sonner-2.0.8.tgz", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg=="], | |
1135 | + | |
1136 | + "source-map": ["[email protected]", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], | |
1137 | + | |
1138 | + "source-map-js": ["[email protected]", "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], | |
1139 | + | |
1140 | + "source-map-support": ["[email protected]", "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], | |
1141 | + | |
1142 | + "space-separated-tokens": ["[email protected]", "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], | |
1143 | + | |
1144 | + "sprintf-js": ["[email protected]", "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], | |
1145 | + | |
1146 | + "statuses": ["[email protected]", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], | |
1147 | + | |
1148 | + "stdin-discarder": ["[email protected]", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], | |
1149 | + | |
1150 | + "string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-8.2.2.tgz", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], | |
1151 | + | |
1152 | + "stringify-entities": ["[email protected]", "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], | |
1153 | + | |
1154 | + "strip-ansi": ["[email protected]", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], | |
1155 | + | |
1156 | + "strip-bom": ["[email protected]", "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], | |
1157 | + | |
1158 | + "strip-bom-string": ["[email protected]", "https://registry.npmmirror.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], | |
1159 | + | |
1160 | + "sucrase": ["[email protected]", "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], | |
1161 | + | |
1162 | + "supports-preserve-symlinks-flag": ["[email protected]", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], | |
1163 | + | |
1164 | + "svgo": ["[email protected]", "https://registry.npmmirror.com/svgo/-/svgo-4.0.2.tgz", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="], | |
1165 | + | |
1166 | + "tailwind-merge": ["[email protected]", "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-2.6.1.tgz", {}, "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ=="], | |
1167 | + | |
1168 | + "tailwindcss": ["[email protected]", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], | |
1169 | + | |
1170 | + "tailwindcss-animate": ["[email protected]", "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], | |
1171 | + | |
1172 | + "thenify": ["[email protected]", "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], | |
1173 | + | |
1174 | + "thenify-all": ["[email protected]", "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], | |
1175 | + | |
1176 | + "tinyexec": ["[email protected]", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], | |
1177 | + | |
1178 | + "tinyglobby": ["[email protected]", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], | |
1179 | + | |
1180 | + "to-regex-range": ["[email protected]", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], | |
1181 | + | |
1182 | + "toidentifier": ["[email protected]", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], | |
1183 | + | |
1184 | + "trim-lines": ["[email protected]", "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], | |
1185 | + | |
1186 | + "trough": ["[email protected]", "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], | |
1187 | + | |
1188 | + "ts-interface-checker": ["[email protected]", "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], | |
1189 | + | |
1190 | + "tsconfck": ["[email protected]", "https://registry.npmmirror.com/tsconfck/-/tsconfck-3.1.6.tgz", { "peerDependencies": { "typescript": "^5.0.0" }, "bin": "bin/tsconfck.js" }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], | |
1191 | + | |
1192 | + "tslib": ["[email protected]", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | |
1193 | + | |
1194 | + "tsx": ["[email protected]", "https://registry.npmmirror.com/tsx/-/tsx-4.23.12.tgz", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], | |
1195 | + | |
1196 | + "type-fest": ["[email protected]", "https://registry.npmmirror.com/type-fest/-/type-fest-4.41.0.tgz", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], | |
1197 | + | |
1198 | + "typesafe-path": ["[email protected]", "https://registry.npmmirror.com/typesafe-path/-/typesafe-path-0.2.2.tgz", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="], | |
1199 | + | |
1200 | + "typescript": ["[email protected]", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | |
1201 | + | |
1202 | + "typescript-auto-import-cache": ["[email protected]", "https://registry.npmmirror.com/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz", { "dependencies": { "semver": "^7.3.8" } }, "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ=="], | |
1203 | + | |
1204 | + "ultrahtml": ["[email protected]", "https://registry.npmmirror.com/ultrahtml/-/ultrahtml-1.7.0.tgz", {}, "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g=="], | |
1205 | + | |
1206 | + "uncrypto": ["[email protected]", "https://registry.npmmirror.com/uncrypto/-/uncrypto-0.1.3.tgz", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], | |
1207 | + | |
1208 | + "undici-types": ["[email protected]", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], | |
1209 | + | |
1210 | + "unified": ["[email protected]", "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], | |
1211 | + | |
1212 | + "unist-util-find-after": ["[email protected]", "https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], | |
1213 | + | |
1214 | + "unist-util-is": ["[email protected]", "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], | |
1215 | + | |
1216 | + "unist-util-modify-children": ["[email protected]", "https://registry.npmmirror.com/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], | |
1217 | + | |
1218 | + "unist-util-position": ["[email protected]", "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], | |
1219 | + | |
1220 | + "unist-util-remove-position": ["[email protected]", "https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], | |
1221 | + | |
1222 | + "unist-util-stringify-position": ["[email protected]", "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], | |
1223 | + | |
1224 | + "unist-util-visit": ["[email protected]", "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], | |
1225 | + | |
1226 | + "unist-util-visit-children": ["[email protected]", "https://registry.npmmirror.com/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], | |
1227 | + | |
1228 | + "unist-util-visit-parents": ["[email protected]", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], | |
1229 | + | |
1230 | + "update-browserslist-db": ["[email protected]", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], | |
1231 | + | |
1232 | + "use-callback-ref": ["[email protected]", "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], | |
1233 | + | |
1234 | + "use-sidecar": ["[email protected]", "https://registry.npmmirror.com/use-sidecar/-/use-sidecar-1.1.3.tgz", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], | |
1235 | + | |
1236 | + "util-deprecate": ["[email protected]", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], | |
1237 | + | |
1238 | + "vfile": ["[email protected]", "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], | |
1239 | + | |
1240 | + "vfile-location": ["[email protected]", "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], | |
1241 | + | |
1242 | + "vfile-message": ["[email protected]", "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], | |
1243 | + | |
1244 | + "vite": ["[email protected]", "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": "bin/vite.js" }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], | |
1245 | + | |
1246 | + "vitefu": ["[email protected]", "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.3.tgz", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], | |
1247 | + | |
1248 | + "volar-service-css": ["[email protected]", "https://registry.npmmirror.com/volar-service-css/-/volar-service-css-0.0.71.tgz", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw=="], | |
1249 | + | |
1250 | + "volar-service-emmet": ["[email protected]", "https://registry.npmmirror.com/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz", { "dependencies": { "@emmetio/css-parser": "^0.4.1", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA=="], | |
1251 | + | |
1252 | + "volar-service-html": ["[email protected]", "https://registry.npmmirror.com/volar-service-html/-/volar-service-html-0.0.71.tgz", { "dependencies": { "vscode-html-languageservice": "^5.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw=="], | |
1253 | + | |
1254 | + "volar-service-prettier": ["[email protected]", "https://registry.npmmirror.com/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0", "prettier": "^2.2 || ^3.0" } }, "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg=="], | |
1255 | + | |
1256 | + "volar-service-typescript": ["[email protected]", "https://registry.npmmirror.com/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz", { "dependencies": { "path-browserify": "^1.0.1", "semver": "^7.6.2", "typescript-auto-import-cache": "^0.3.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-nls": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw=="], | |
1257 | + | |
1258 | + "volar-service-typescript-twoslash-queries": ["[email protected]", "https://registry.npmmirror.com/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.71.tgz", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A=="], | |
1259 | + | |
1260 | + "volar-service-yaml": ["[email protected]", "https://registry.npmmirror.com/volar-service-yaml/-/volar-service-yaml-0.0.71.tgz", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.23.0" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q=="], | |
1261 | + | |
1262 | + "vscode-css-languageservice": ["[email protected]", "https://registry.npmmirror.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA=="], | |
1263 | + | |
1264 | + "vscode-html-languageservice": ["[email protected]", "https://registry.npmmirror.com/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg=="], | |
1265 | + | |
1266 | + "vscode-json-languageservice": ["[email protected]", "https://registry.npmmirror.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="], | |
1267 | + | |
1268 | + "vscode-jsonrpc": ["[email protected]", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz", {}, "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw=="], | |
1269 | + | |
1270 | + "vscode-languageserver": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], | |
1271 | + | |
1272 | + "vscode-languageserver-protocol": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz", { "dependencies": { "vscode-jsonrpc": "9.0.1", "vscode-languageserver-types": "3.18.0" } }, "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg=="], | |
1273 | + | |
1274 | + "vscode-languageserver-textdocument": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], | |
1275 | + | |
1276 | + "vscode-languageserver-types": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz", {}, "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g=="], | |
1277 | + | |
1278 | + "vscode-nls": ["[email protected]", "https://registry.npmmirror.com/vscode-nls/-/vscode-nls-5.2.0.tgz", {}, "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng=="], | |
1279 | + | |
1280 | + "vscode-uri": ["[email protected]", "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], | |
1281 | + | |
1282 | + "web-namespaces": ["[email protected]", "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], | |
1283 | + | |
1284 | + "web-streams-polyfill": ["[email protected]", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], | |
1285 | + | |
1286 | + "which-pm": ["[email protected]", "https://registry.npmmirror.com/which-pm/-/which-pm-3.0.1.tgz", { "dependencies": { "load-yaml-file": "^0.2.0" } }, "sha512-v2JrMq0waAI4ju1xU5x3blsxBBMgdgZve580iYMN5frDaLGjbA24fok7wKCsya8KLVO19Ju4XDc5+zTZCJkQfg=="], | |
1287 | + | |
1288 | + "which-pm-runs": ["[email protected]", "https://registry.npmmirror.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], | |
1289 | + | |
1290 | + "widest-line": ["[email protected]", "https://registry.npmmirror.com/widest-line/-/widest-line-5.0.0.tgz", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], | |
1291 | + | |
1292 | + "wrap-ansi": ["[email protected]", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], | |
1293 | + | |
1294 | + "ws": ["[email protected]", "https://registry.npmmirror.com/ws/-/ws-8.21.3.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], | |
1295 | + | |
1296 | + "xxhash-wasm": ["[email protected]", "https://registry.npmmirror.com/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], | |
1297 | + | |
1298 | + "y18n": ["[email protected]", "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], | |
1299 | + | |
1300 | + "yallist": ["[email protected]", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], | |
1301 | + | |
1302 | + "yaml": ["[email protected]", "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz", { "bin": "bin.mjs" }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], | |
1303 | + | |
1304 | + "yaml-language-server": ["[email protected]", "https://registry.npmmirror.com/yaml-language-server/-/yaml-language-server-1.23.0.tgz", { "dependencies": { "@vscode/l10n": "^0.0.18", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-i18n": "^4.2.0", "prettier": "^3.8.1", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-uri": "^3.0.2", "yaml": "2.8.3" }, "bin": "bin/yaml-language-server" }, "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A=="], | |
1305 | + | |
1306 | + "yargs": ["[email protected]", "https://registry.npmmirror.com/yargs/-/yargs-18.1.0.tgz", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="], | |
1307 | + | |
1308 | + "yargs-parser": ["[email protected]", "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], | |
1309 | + | |
1310 | + "yocto-queue": ["[email protected]", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], | |
1311 | + | |
1312 | + "zod": ["[email protected]", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], | |
1313 | + | |
1314 | + "zod-to-json-schema": ["[email protected]", "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], | |
1315 | + | |
1316 | + "zod-to-ts": ["[email protected]", "https://registry.npmmirror.com/zod-to-ts/-/zod-to-ts-1.2.0.tgz", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], | |
1317 | + | |
1318 | + "zwitch": ["[email protected]", "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], | |
1319 | + | |
1320 | + "@antfu/install-pkg/tinyexec": ["[email protected]", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.3.0.tgz", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], | |
1321 | + | |
1322 | + "@astrojs/check/chokidar": ["[email protected]", "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], | |
1323 | + | |
1324 | + "@babel/core/semver": ["[email protected]", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], | |
1325 | + | |
1326 | + "@babel/helper-compilation-targets/semver": ["[email protected]", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], | |
1327 | + | |
1328 | + "@esbuild-kit/core-utils/esbuild": ["[email protected]", "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": "bin/esbuild" }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], | |
1329 | + | |
1330 | + "@rollup/pluginutils/estree-walker": ["[email protected]", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], | |
1331 | + | |
1332 | + "@rollup/pluginutils/picomatch": ["[email protected]", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], | |
1333 | + | |
1334 | + "ansi-align/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | |
1335 | + | |
1336 | + "astro/esbuild": ["[email protected]", "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": "bin/esbuild" }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], | |
1337 | + | |
1338 | + "boxen/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], | |
1339 | + | |
1340 | + "chokidar/glob-parent": ["[email protected]", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], | |
1341 | + | |
1342 | + "cliui/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], | |
1343 | + | |
1344 | + "csso/css-tree": ["[email protected]", "https://registry.npmmirror.com/css-tree/-/css-tree-2.2.1.tgz", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], | |
1345 | + | |
1346 | + "dom-serializer/entities": ["[email protected]", "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], | |
1347 | + | |
1348 | + "fast-glob/glob-parent": ["[email protected]", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], | |
1349 | + | |
1350 | + "fdir/picomatch": ["[email protected]", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], | |
1351 | + | |
1352 | + "gray-matter/js-yaml": ["[email protected]", "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.15.1.tgz", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], | |
1353 | + | |
1354 | + "load-yaml-file/js-yaml": ["[email protected]", "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.15.1.tgz", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], | |
1355 | + | |
1356 | + "load-yaml-file/pify": ["[email protected]", "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], | |
1357 | + | |
1358 | + "log-symbols/is-unicode-supported": ["[email protected]", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], | |
1359 | + | |
1360 | + "ora/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], | |
1361 | + | |
1362 | + "p-locate/p-limit": ["[email protected]", "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], | |
1363 | + | |
1364 | + "postcss/nanoid": ["[email protected]", "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz", { "bin": "bin/nanoid.cjs" }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], | |
1365 | + | |
1366 | + "prompts/kleur": ["[email protected]", "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], | |
1367 | + | |
1368 | + "send/debug": ["[email protected]", "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], | |
1369 | + | |
1370 | + "sharp/detect-libc": ["[email protected]", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
1371 | + | |
1372 | + "svgo/commander": ["[email protected]", "https://registry.npmmirror.com/commander/-/commander-11.1.0.tgz", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], | |
1373 | + | |
1374 | + "tinyglobby/picomatch": ["[email protected]", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], | |
1375 | + | |
1376 | + "tsx/esbuild": ["[email protected]", "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.2.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": "bin/esbuild" }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], | |
1377 | + | |
1378 | + "vite/esbuild": ["[email protected]", "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": "bin/esbuild" }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], | |
1379 | + | |
1380 | + "vscode-css-languageservice/vscode-languageserver-types": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], | |
1381 | + | |
1382 | + "vscode-json-languageservice/jsonc-parser": ["[email protected]", "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], | |
1383 | + | |
1384 | + "vscode-languageserver/vscode-languageserver-protocol": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], | |
1385 | + | |
1386 | + "widest-line/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], | |
1387 | + | |
1388 | + "wrap-ansi/string-width": ["[email protected]", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], | |
1389 | + | |
1390 | + "yaml-language-server/request-light": ["[email protected]", "https://registry.npmmirror.com/request-light/-/request-light-0.5.8.tgz", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], | |
1391 | + | |
1392 | + "yaml-language-server/yaml": ["[email protected]", "https://registry.npmmirror.com/yaml/-/yaml-2.8.3.tgz", { "bin": "bin.mjs" }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], | |
1393 | + | |
1394 | + "yargs/yargs-parser": ["[email protected]", "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-22.0.0.tgz", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], | |
1395 | + | |
1396 | + "@astrojs/check/chokidar/readdirp": ["[email protected]", "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], | |
1397 | + | |
1398 | + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], | |
1399 | + | |
1400 | + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], | |
1401 | + | |
1402 | + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], | |
1403 | + | |
1404 | + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], | |
1405 | + | |
1406 | + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], | |
1407 | + | |
1408 | + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], | |
1409 | + | |
1410 | + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], | |
1411 | + | |
1412 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], | |
1413 | + | |
1414 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], | |
1415 | + | |
1416 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], | |
1417 | + | |
1418 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], | |
1419 | + | |
1420 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], | |
1421 | + | |
1422 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], | |
1423 | + | |
1424 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], | |
1425 | + | |
1426 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], | |
1427 | + | |
1428 | + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], | |
1429 | + | |
1430 | + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], | |
1431 | + | |
1432 | + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], | |
1433 | + | |
1434 | + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], | |
1435 | + | |
1436 | + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], | |
1437 | + | |
1438 | + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], | |
1439 | + | |
1440 | + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], | |
1441 | + | |
1442 | + "ansi-align/string-width/emoji-regex": ["[email protected]", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], | |
1443 | + | |
1444 | + "ansi-align/string-width/strip-ansi": ["[email protected]", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | |
1445 | + | |
1446 | + "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], | |
1447 | + | |
1448 | + "astro/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], | |
1449 | + | |
1450 | + "astro/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], | |
1451 | + | |
1452 | + "astro/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], | |
1453 | + | |
1454 | + "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], | |
1455 | + | |
1456 | + "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], | |
1457 | + | |
1458 | + "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], | |
1459 | + | |
1460 | + "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], | |
1461 | + | |
1462 | + "astro/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], | |
1463 | + | |
1464 | + "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], | |
1465 | + | |
1466 | + "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], | |
1467 | + | |
1468 | + "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], | |
1469 | + | |
1470 | + "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], | |
1471 | + | |
1472 | + "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], | |
1473 | + | |
1474 | + "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], | |
1475 | + | |
1476 | + "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], | |
1477 | + | |
1478 | + "astro/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], | |
1479 | + | |
1480 | + "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], | |
1481 | + | |
1482 | + "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], | |
1483 | + | |
1484 | + "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], | |
1485 | + | |
1486 | + "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], | |
1487 | + | |
1488 | + "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], | |
1489 | + | |
1490 | + "astro/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], | |
1491 | + | |
1492 | + "csso/css-tree/mdn-data": ["[email protected]", "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.28.tgz", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], | |
1493 | + | |
1494 | + "gray-matter/js-yaml/argparse": ["[email protected]", "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], | |
1495 | + | |
1496 | + "load-yaml-file/js-yaml/argparse": ["[email protected]", "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], | |
1497 | + | |
1498 | + "send/debug/ms": ["[email protected]", "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], | |
1499 | + | |
1500 | + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], | |
1501 | + | |
1502 | + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], | |
1503 | + | |
1504 | + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], | |
1505 | + | |
1506 | + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], | |
1507 | + | |
1508 | + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], | |
1509 | + | |
1510 | + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], | |
1511 | + | |
1512 | + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], | |
1513 | + | |
1514 | + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], | |
1515 | + | |
1516 | + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], | |
1517 | + | |
1518 | + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], | |
1519 | + | |
1520 | + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], | |
1521 | + | |
1522 | + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], | |
1523 | + | |
1524 | + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], | |
1525 | + | |
1526 | + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], | |
1527 | + | |
1528 | + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], | |
1529 | + | |
1530 | + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], | |
1531 | + | |
1532 | + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], | |
1533 | + | |
1534 | + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], | |
1535 | + | |
1536 | + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], | |
1537 | + | |
1538 | + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], | |
1539 | + | |
1540 | + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], | |
1541 | + | |
1542 | + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], | |
1543 | + | |
1544 | + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], | |
1545 | + | |
1546 | + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], | |
1547 | + | |
1548 | + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], | |
1549 | + | |
1550 | + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], | |
1551 | + | |
1552 | + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], | |
1553 | + | |
1554 | + "vite/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], | |
1555 | + | |
1556 | + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], | |
1557 | + | |
1558 | + "vite/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], | |
1559 | + | |
1560 | + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], | |
1561 | + | |
1562 | + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], | |
1563 | + | |
1564 | + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], | |
1565 | + | |
1566 | + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], | |
1567 | + | |
1568 | + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], | |
1569 | + | |
1570 | + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], | |
1571 | + | |
1572 | + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], | |
1573 | + | |
1574 | + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], | |
1575 | + | |
1576 | + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], | |
1577 | + | |
1578 | + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], | |
1579 | + | |
1580 | + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], | |
1581 | + | |
1582 | + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], | |
1583 | + | |
1584 | + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], | |
1585 | + | |
1586 | + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], | |
1587 | + | |
1588 | + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], | |
1589 | + | |
1590 | + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], | |
1591 | + | |
1592 | + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], | |
1593 | + | |
1594 | + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], | |
1595 | + | |
1596 | + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], | |
1597 | + | |
1598 | + "vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], | |
1599 | + | |
1600 | + "vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], | |
1601 | + | |
1602 | + "ansi-align/string-width/strip-ansi/ansi-regex": ["[email protected]", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], | |
1603 | + } | |
1604 | +} | |
0 | 1605 | |
▼community/drizzle.config.ts
+21-0
| @@ -0,0 +1,21 @@ | ||
1 | +import { defineConfig } from "drizzle-kit"; | |
2 | +import "dotenv/config"; | |
3 | + | |
4 | +const url = (process.env.TURSO_DATABASE_URL || "").trim(); | |
5 | +if (!url) { | |
6 | + throw new Error( | |
7 | + "TURSO_DATABASE_URL is required for drizzle-kit. Set it in .env or as an env var.\n" + | |
8 | + "Create a database at https://turso.tech/dashboard and get the URL from there.\n" + | |
9 | + "Example: TURSO_DATABASE_URL=libsql://your-db-name-your-org.turso.io" | |
10 | + ); | |
11 | +} | |
12 | + | |
13 | +export default defineConfig({ | |
14 | + schema: "./src/lib/db/schema.ts", | |
15 | + out: "./drizzle", | |
16 | + dialect: "turso", | |
17 | + dbCredentials: { | |
18 | + url, | |
19 | + authToken: (process.env.TURSO_AUTH_TOKEN || "").trim() || undefined, | |
20 | + }, | |
21 | +}); | |
0 | 22 | |
▼community/package.json
+64-0
| @@ -0,0 +1,64 @@ | ||
1 | +{ | |
2 | + "name": "opencodehub-community", | |
3 | + "version": "1.0.0", | |
4 | + "description": "OpenCodeHub Community Hub — aggregate public repos & profiles from self-hosted instances", | |
5 | + "type": "module", | |
6 | + "scripts": { | |
7 | + "dev": "astro dev", | |
8 | + "build": "astro build", | |
9 | + "preview": "astro preview", | |
10 | + "start": "node --env-file=.env ./dist/server/entry.mjs", | |
11 | + "db:generate": "drizzle-kit generate", | |
12 | + "db:migrate": "drizzle-kit migrate", | |
13 | + "db:push": "drizzle-kit push", | |
14 | + "db:studio": "drizzle-kit studio", | |
15 | + "sync": "bun scripts/cron-sync.ts", | |
16 | + "lint": "astro check", | |
17 | + "typecheck": "tsc --noEmit" | |
18 | + }, | |
19 | + "dependencies": { | |
20 | + "@astrojs/check": "^0.9.6", | |
21 | + "@astrojs/node": "^8.3.0", | |
22 | + "@astrojs/react": "^3.6.0", | |
23 | + "@astrojs/tailwind": "^5.1.2", | |
24 | + "@libsql/client": "^0.15.15", | |
25 | + "@radix-ui/react-avatar": "^1.1.0", | |
26 | + "@radix-ui/react-dialog": "^1.1.1", | |
27 | + "@radix-ui/react-dropdown-menu": "^2.1.1", | |
28 | + "@radix-ui/react-label": "^2.1.0", | |
29 | + "@radix-ui/react-slot": "^1.1.0", | |
30 | + "@radix-ui/react-toast": "^1.2.1", | |
31 | + "@radix-ui/react-tabs": "^1.1.0", | |
32 | + "@upstash/redis": "^1.36.0", | |
33 | + "astro": "^4.11.0", | |
34 | + "astro-icon": "^1.1.5", | |
35 | + "bcryptjs": "^2.4.3", | |
36 | + "class-variance-authority": "^0.7.0", | |
37 | + "clsx": "^2.1.1", | |
38 | + "dotenv": "^16.4.5", | |
39 | + "drizzle-orm": "^0.45.2", | |
40 | + "jose": "^5.6.3", | |
41 | + "lucide-react": "^0.400.0", | |
42 | + "nanoid": "^5.0.7", | |
43 | + "react": "^18.3.1", | |
44 | + "react-dom": "^18.3.1", | |
45 | + "sonner": "^2.0.7", | |
46 | + "tailwind-merge": "^2.6.0", | |
47 | + "tailwindcss-animate": "^1.0.7", | |
48 | + "zod": "^3.23.8" | |
49 | + }, | |
50 | + "devDependencies": { | |
51 | + "@types/bcryptjs": "^2.4.6", | |
52 | + "@types/node": "^20.14.9", | |
53 | + "@types/react": "^18.3.3", | |
54 | + "@types/react-dom": "^18.3.0", | |
55 | + "autoprefixer": "^10.4.19", | |
56 | + "drizzle-kit": "^0.31.8", | |
57 | + "postcss": "^8.5.2", | |
58 | + "tailwindcss": "^3.4.4", | |
59 | + "typescript": "^5.9.3" | |
60 | + }, | |
61 | + "engines": { | |
62 | + "node": ">=18.0.0" | |
63 | + } | |
64 | +} | |
0 | 65 | |
▼community/public/logo.svg
+9-0
| @@ -0,0 +1,9 @@ | ||
1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="45 45 210 210" fill="currentColor"> | |
2 | + <style> | |
3 | + :root { color: #1f2328; } | |
4 | + @media (prefers-color-scheme: dark) { | |
5 | + :root { color: #f0f6fc; } | |
6 | + } | |
7 | + </style> | |
8 | + <path fill-rule="evenodd" clip-rule="evenodd" d="M184.27588,89.68521c2.96924,0.22368 3.29297,2.88721 3.75439,5.34082c1.19385,6.34468 0.82178,12.62754 0.00293,18.98174c0.34131,0.62461 0.66797,1.25728 0.97998,1.89756c5.40088,11.37407 2.61914,24.01758 -7.58203,31.66011c-2.17236,1.62891 -4.00049,2.51074 -6.4248,3.66357c0.82178,0.76904 1.60254,1.5293 2.35693,2.37012c10.20264,11.34814 10.51318,31.02539 1.01953,42.86865c-0.7749,0.9668 -1.70801,1.875 -2.36572,2.91064c-0.18896,1.81934 -0.08496,5.79053 -0.08496,7.70508l0.00732,14.01855c0.01172,4.06201 0.68115,7.9541 -2.32178,11.17236c-1.55713,1.69189 -3.74414,2.66602 -6.04395,2.69238c-2.2251,0.0542 -4.37256,-0.81006 -5.93994,-2.3877c-2.77588,-2.78174 -2.42139,-5.74805 -2.42285,-9.37646c-0.00293,-2.15186 0.00293,-4.32129 0.00293,-6.47314l0.00146,-24.48047c6.01758,-2.28369 9.25928,-8.7041 7.61719,-14.91797c-0.83789,-3.1875 -2.90332,-5.91357 -5.74365,-7.5835c-2.87109,-1.6626 -6.28418,-2.12109 -9.49365,-1.27441c-3.25635,0.86279 -6.023,3.00879 -7.66934,5.94727c-1.6771,2.98242 -2.0748,6.51709 -1.10244,9.79834c1.07827,3.66211 3.30176,6.06738 6.59795,7.8457v31.3418l-0.00146,8.86523c-0.00293,1.69775 -0.05127,3.50098 0.06885,5.18408c0.07031,0.98438 0.24023,1.74902 0.93457,2.45801c1.06494,1.08838 2.63818,1.26123 4.08838,1.25244c5.39648,-0.03516 11.21777,-0.63721 16.4458,-2.0083c11.50488,-3.01465 25.31982,-11.12842 33.73096,-19.5498c-1.04004,-3.65479 -1.04297,-6.17139 0.27979,-9.73682c0.11426,-0.28418 0.23584,-0.56396 0.36768,-0.83936c1.53955,-3.31641 4.34473,-5.87695 7.78711,-7.10889c3.49512,-1.24365 7.1792,-1.10596 10.5249,0.52588c3.38965,1.6582 5.97949,4.59814 7.19385,8.16943c1.18945,3.55518 0.89648,7.43848 -0.81152,10.77539c-1.72119,3.35449 -4.70801,5.88574 -8.2998,7.03418c-3.99609,1.25098 -7.6333,0.60791 -11.26465,-1.28613c-11.9165,11.17676 -26.95312,19.90283 -43.24658,22.32861c-6.1377,0.91406 -18.28271,1.79883 -24.2209,0.28711c-1.4543,-0.36035 -2.82349,-1.00342 -4.0292,-1.89258c-1.92026,-1.43994 -3.30073,-3.4834 -3.91978,-5.80225c-0.80962,-2.91797 -0.59106,-7.26416 -0.58989,-10.39453l0.0126,-20.7627c0.00176,-2.729 0.26045,-7.91602 -0.20142,-10.47656c-0.9293,-1.44434 -2.89556,-3.36035 -4.03506,-4.95996c-7.36787,-10.34326 -8.05313,-24.92871 -1.97593,-36.03662c1.89521,-3.46436 3.96914,-5.61768 6.89136,-8.27051c-1.10273,-0.60498 -2.32017,-1.15137 -3.42407,-1.75488c-10.07725,-5.50151 -15.41455,-16.65469 -12.49043,-27.89648c0.75337,-2.89644 1.79604,-5.09502 3.26177,-7.72251c-0.23101,-1.61455 -0.4084,-3.23628 -0.53218,-4.86255c-0.26484,-3.68013 -0.14707,-15.76128 2.28369,-18.38569c0.49512,-0.53452 1.12632,-0.72598 1.84219,-0.73696c4.777,-0.07251 14.12007,8.55615 17.44307,11.97817c6.83555,-0.78853 16.69834,-0.79482 23.51572,0.04937c3.84082,-4.00254 11.6543,-11.28779 17.2251,-12.14487z M216.82617,208.70947c3.49512,-0.50391 6.74268,1.9043 7.27588,5.39355c0.5332,3.49072 -1.8457,6.75879 -5.33057,7.32275c-3.52881,0.57129 -6.84521,-1.84424 -7.38574,-5.37744c-0.54053,-3.5332 1.90283,-6.8291 5.44043,-7.33887z M150.01025,49.42808c8.76855,-0.53481 18.6665,0.87524 27.06592,3.29912c20.78613,5.99868 38.0083,20.7375 48.39844,39.57656c11.02441,20.03057 13.20557,42.40825 6.84668,64.47847c-4.68457,16.26416 -15.27686,33.76758 -29.49902,43.79297c-0.8291,0.58447 -2.42578,0.64014 -3.39551,0.33105c-1.20264,-0.38965 -2.1958,-1.24951 -2.75391,-2.38477c-0.78223,-1.54248 -0.68701,-3.38379 0.24756,-4.83838c0.77344,-1.19238 2.30859,-1.99658 3.36621,-2.96631c1.39453,-1.27734 2.7832,-2.58984 4.08691,-3.96094c8.34229,-8.7583 14.54004,-19.33154 18.10547,-30.89062c6.12744,-19.20322 4.20703,-40.06919 -5.31885,-57.83232c-13.10742,-24.82339 -38.29687,-39.67676 -66.35449,-38.24443c-11.62852,0.5937 -20.97378,3.44312 -31.22754,8.85864c-0.09214,0.21592 0.06855,2.36089 0.08599,2.76357c0.10093,2.32456 -0.25884,4.14609 -1.20132,6.28271c-1.61675,3.70122 -4.65234,6.5981 -8.4252,8.03994c-4.34063,1.67285 -7.91528,1.0749 -12.02065,-0.72729c-1.39482,1.60225 -2.82202,3.25049 -4.10435,4.94326c-9.65625,12.99712 -14.91196,28.73716 -15.0019,44.92852c0.00176,4.17891 0.44326,7.88203 1.01514,11.99033c2.62295,0.16113 4.83779,0.89355 7.03564,2.35986c3.29355,2.22656 5.54854,5.68799 6.254,9.60059c0.80186,4.28027 -0.46406,7.95996 -2.85498,11.45801c6.1188,10.2832 12.25576,17.34961 22.19165,24.18604c2.72197,1.86182 5.57842,3.51855 8.54546,4.95703c1.91646,0.92285 5.19565,1.96143 6.36152,3.69434c0.70986,1.0708 0.95127,2.38623 0.66841,3.64014c-0.63237,2.74805 -3.42524,4.40332 -6.0498,3.42188c-16.91338,-6.33398 -30.71909,-19.39453 -39.48354,-34.77979c-1.91895,0.38965 -3.55415,0.55664 -5.51953,0.2959c-3.75059,-0.50244 -7.1439,-2.48584 -9.42217,-5.50635c-2.37363,-3.13184 -3.39595,-7.08252 -2.83887,-10.97168c0.58975,-4.17334 2.75815,-7.26123 6.04922,-9.76172c-0.84521,-4.86138 -1.40742,-9.20464 -1.51685,-14.15186c-0.4582,-20.73208 7.9248,-42.52515 22.04824,-57.59678c-0.52134,-1.40479 -0.85063,-2.36895 -1.07036,-3.8603c-0.57583,-3.9999 0.44736,-8.06558 2.8478,-11.31636c2.4063,-3.16348 5.97246,-5.23945 9.91157,-5.76943c4.11064,-0.54932 7.88833,0.62358 11.15728,3.0999c12.08159,-6.54038 21.99536,-9.5105 35.76973,-10.4395z M104.33159,64.69878c3.83057,-0.32915 7.21245,2.48599 7.58335,6.31274c0.37104,3.82676 -2.40718,7.23911 -6.22969,7.65176c-2.51001,0.271 -4.97197,-0.82749 -6.44678,-2.87651c-1.4748,-2.04902 -1.73496,-4.73247 -0.68115,-7.02656c1.05381,-2.29424 3.25898,-3.84521 5.77427,-4.06143z M77.86846,154.6333c3.67031,-0.67822 7.19575,1.74463 7.87866,5.41406c0.68291,3.66943 -1.73511,7.19824 -5.40337,7.88672c-3.67588,0.68994 -7.21377,-1.73437 -7.898,-5.41113c-0.68437,-3.67676 1.74478,-7.21143 5.42271,-7.88965z M153.91992,174.95801c3.0835,-0.47461 5.96777,1.64209 6.43945,4.72559c0.47021,3.0835 -1.64941,5.96484 -4.73437,6.43359c-3.0791,0.46582 -5.95459,-1.64941 -6.4248,-4.72852c-0.47021,-3.07764 1.64209,-5.95605 4.71973,-6.43066z" /> | |
9 | +</svg> | |
0 | 10 | |
▼community/scripts/cron-sync.ts
+71-0
| @@ -0,0 +1,71 @@ | ||
1 | +import "dotenv/config"; | |
2 | +import { getDb } from "../src/lib/db/index.js"; | |
3 | +import { instances, cachedRepos, cachedUsers } from "../src/lib/db/schema.js"; | |
4 | +import { fetchAllPublicRepos } from "../src/lib/instance-client.js"; | |
5 | +import { eq } from "drizzle-orm"; | |
6 | +import { nanoid } from "nanoid"; | |
7 | + | |
8 | +async function runSync() { | |
9 | + console.log("[Sync] Starting global instance synchronization..."); | |
10 | + const db = getDb() as any; | |
11 | + | |
12 | + const allInstances = await db.query.instances.findMany(); | |
13 | + console.log(`[Sync] Found ${allInstances.length} instances to sync.`); | |
14 | + | |
15 | + for (const instance of allInstances) { | |
16 | + console.log(`[Sync] Syncing instance: ${instance.name} (${instance.url})`); | |
17 | + try { | |
18 | + const repos = await fetchAllPublicRepos(instance.url); | |
19 | + const ownersMap = new Map(); | |
20 | + | |
21 | + for (const r of repos) { | |
22 | + if (r.owner && !ownersMap.has(r.owner.username)) { | |
23 | + ownersMap.set(r.owner.username, r.owner); | |
24 | + } | |
25 | + | |
26 | + const existing = await db.query.cachedRepos.findFirst({ where: eq(cachedRepos.remoteId, r.id) }).catch(()=>null); | |
27 | + if (existing) { | |
28 | + await db.update(cachedRepos).set({ | |
29 | + fullName: r.fullName, name: r.name, description: r.description, language: r.language, | |
30 | + topics: JSON.stringify(r.topics), starCount: r.starCount, forkCount: r.forkCount, | |
31 | + httpCloneUrl: r.httpCloneUrl, updatedAt: r.updatedAt | |
32 | + }).where(eq(cachedRepos.id, existing.id)); | |
33 | + } else { | |
34 | + await db.insert(cachedRepos).values({ | |
35 | + id: nanoid(), instanceId: instance.id, remoteId: r.id, fullName: r.fullName, name: r.name, | |
36 | + ownerUsername: r.owner.username, ownerDisplayName: r.owner.displayName, ownerAvatarUrl: r.owner.avatarUrl, | |
37 | + description: r.description, visibility: r.visibility, language: r.language, topics: JSON.stringify(r.topics), | |
38 | + starCount: r.starCount, forkCount: r.forkCount, httpCloneUrl: r.httpCloneUrl, updatedAt: r.updatedAt | |
39 | + }); | |
40 | + } | |
41 | + } | |
42 | + | |
43 | + for (const owner of ownersMap.values()) { | |
44 | + const existingUser = await db.query.cachedUsers.findFirst({ where: eq(cachedUsers.username, owner.username) }).catch(() => null); | |
45 | + if (existingUser) { | |
46 | + await db.update(cachedUsers).set({ | |
47 | + displayName: owner.displayName, avatarUrl: owner.avatarUrl, | |
48 | + }).where(eq(cachedUsers.id, existingUser.id)); | |
49 | + } else { | |
50 | + await db.insert(cachedUsers).values({ | |
51 | + id: nanoid(), instanceId: instance.id, remoteId: owner.id || owner.username, | |
52 | + username: owner.username, displayName: owner.displayName, avatarUrl: owner.avatarUrl, | |
53 | + }); | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + await db.update(instances).set({ | |
58 | + repoCount: repos.length, status: "online", lastSyncAt: new Date().toISOString() | |
59 | + }).where(eq(instances.id, instance.id)); | |
60 | + | |
61 | + console.log(`[Sync] ✓ Success: ${instance.name} (${repos.length} repos updated)`); | |
62 | + } catch (e: any) { | |
63 | + console.error(`[Sync] ✗ Failed: ${instance.name} - ${e.message}`); | |
64 | + await db.update(instances).set({ status: "error" }).where(eq(instances.id, instance.id)).catch(()=>{}); | |
65 | + } | |
66 | + } | |
67 | + console.log("[Sync] Global synchronization complete."); | |
68 | + process.exit(0); | |
69 | +} | |
70 | + | |
71 | +runSync(); | |
0 | 72 | |
▼community/src/components/InstanceCard.tsx
+31-0
| @@ -0,0 +1,31 @@ | ||
1 | +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; | |
2 | +import { Badge } from "@/components/ui/badge"; | |
3 | +import { Button } from "@/components/ui/button"; | |
4 | +import { Globe, Box, Users, ExternalLink } from "lucide-react"; | |
5 | +export function InstanceCard({ instance }: { instance: any }) { | |
6 | + return ( | |
7 | + <Card className="hover:shadow-lg transition-all hover:-translate-y-0.5"> | |
8 | + <CardHeader className="pb-3"> | |
9 | + <div className="flex items-center gap-3"> | |
10 | + <div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center"><Globe className="h-5 w-5 text-primary" /></div> | |
11 | + <div className="flex-1 min-w-0"> | |
12 | + <CardTitle className="text-base truncate">{instance.name}</CardTitle> | |
13 | + <CardDescription className="truncate text-xs">{instance.siteUrl}</CardDescription> | |
14 | + </div> | |
15 | + <Badge variant={instance.status === "online" ? "default" : "secondary"} className="capitalize">{instance.status || "pending"}</Badge> | |
16 | + </div> | |
17 | + </CardHeader> | |
18 | + <CardContent className="space-y-3"> | |
19 | + <div className="flex gap-4 text-sm"> | |
20 | + <span className="flex items-center gap-1 text-muted-foreground"><Box className="h-4 w-4" />{instance.repoCount ?? 0} repos</span> | |
21 | + <span className="flex items-center gap-1 text-muted-foreground"><Users className="h-4 w-4" />{instance.userCount ?? 0} users</span> | |
22 | + </div> | |
23 | + {instance.version && <div className="text-xs text-muted-foreground">v{instance.version} · {instance.capabilities?.join(", ")}</div>} | |
24 | + <div className="flex gap-2"> | |
25 | + <Button size="sm" variant="outline" className="flex-1" onClick={()=>window.open(instance.siteUrl, "_blank")}><ExternalLink className="h-4 w-4 mr-1" />Visit</Button> | |
26 | + <Button size="sm" className="flex-1" onClick={()=>window.location.href=`/instances/${instance.id}`}>Explore</Button> | |
27 | + </div> | |
28 | + </CardContent> | |
29 | + </Card> | |
30 | + ); | |
31 | +} | |
0 | 32 | |
▼community/src/components/ProfileCard.tsx
+26-0
| @@ -0,0 +1,26 @@ | ||
1 | +import { Card, CardContent } from "@/components/ui/card"; | |
2 | +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; | |
3 | +import { Button } from "@/components/ui/button"; | |
4 | +import { MapPin, Link as LinkIcon, Building } from "lucide-react"; | |
5 | +export function ProfileCard({ profile }: { profile: any }) { | |
6 | + return ( | |
7 | + <Card className="hover:shadow-md transition-shadow"> | |
8 | + <CardContent className="p-5 flex gap-4"> | |
9 | + <Avatar className="h-14 w-14"> | |
10 | + <AvatarImage src={profile.avatarUrl} /> | |
11 | + <AvatarFallback>{profile.username?.[0]?.toUpperCase()}</AvatarFallback> | |
12 | + </Avatar> | |
13 | + <div className="flex-1 min-w-0"> | |
14 | + <div className="font-semibold truncate">{profile.displayName || profile.username} <span className="text-muted-foreground font-normal">@{profile.username}</span></div> | |
15 | + <p className="text-sm text-muted-foreground line-clamp-2 mt-1">{profile.bio || "No bio"}</p> | |
16 | + <div className="flex flex-wrap gap-3 mt-2 text-xs text-muted-foreground"> | |
17 | + {profile.location && <span className="flex items-center gap-1"><MapPin className="h-3 w-3" />{profile.location}</span>} | |
18 | + {profile.company && <span className="flex items-center gap-1"><Building className="h-3 w-3" />{profile.company}</span>} | |
19 | + {profile.website && <a href={profile.website} target="_blank" className="flex items-center gap-1 hover:text-primary"><LinkIcon className="h-3 w-3" />Website</a>} | |
20 | + </div> | |
21 | + </div> | |
22 | + <Button size="sm" variant="outline">Follow</Button> | |
23 | + </CardContent> | |
24 | + </Card> | |
25 | + ); | |
26 | +} | |
0 | 27 | |
▼community/src/components/RepoCard.tsx
+29-0
| @@ -0,0 +1,29 @@ | ||
1 | +import { Card, CardContent } from "@/components/ui/card"; | |
2 | +import { Badge } from "@/components/ui/badge"; | |
3 | +import { Button } from "@/components/ui/button"; | |
4 | +import { Star, GitFork, Eye, BookOpen } from "lucide-react"; | |
5 | +export function RepoCard({ repo, onStar }: { repo: any; onStar?: () => void }) { | |
6 | + return ( | |
7 | + <Card className="hover:shadow-lg transition-shadow group"> | |
8 | + <CardContent className="p-5"> | |
9 | + <div className="flex items-start justify-between gap-3"> | |
10 | + <div className="flex-1 min-w-0"> | |
11 | + <div className="flex items-center gap-2"> | |
12 | + <BookOpen className="h-4 w-4 text-muted-foreground flex-shrink-0" /> | |
13 | + <a href={`/explore?repo=${encodeURIComponent(repo.fullName)}`} className="font-semibold text-primary hover:underline truncate">{repo.fullName}</a> | |
14 | + <Badge variant="outline" className="text-[10px] capitalize">{repo.visibility}</Badge> | |
15 | + </div> | |
16 | + <p className="text-sm text-muted-foreground mt-1 line-clamp-2">{repo.description || "No description"}</p> | |
17 | + <div className="flex items-center gap-3 mt-3 text-xs text-muted-foreground"> | |
18 | + {repo.language && <span className="flex items-center gap-1"><span className="h-2.5 w-2.5 rounded-full bg-primary" />{repo.language}</span>} | |
19 | + <span className="flex items-center gap-1"><Star className="h-3.5 w-3.5" />{repo.starCount}</span> | |
20 | + <span className="flex items-center gap-1"><GitFork className="h-3.5 w-3.5" />{repo.forkCount}</span> | |
21 | + {repo.instanceName && <Badge variant="secondary" className="text-[10px]">{repo.instanceName}</Badge>} | |
22 | + </div> | |
23 | + </div> | |
24 | + <Button size="sm" variant="outline" onClick={onStar} className="flex-shrink-0"><Star className="h-4 w-4 mr-1" />Star</Button> | |
25 | + </div> | |
26 | + </CardContent> | |
27 | + </Card> | |
28 | + ); | |
29 | +} | |
0 | 30 | |
▼community/src/components/layout/Footer.astro
+65-0
| @@ -0,0 +1,65 @@ | ||
1 | +--- | |
2 | +import { Github, Globe, Server, Sparkles, Heart } from "lucide-react"; | |
3 | +--- | |
4 | +<footer class="border-t border-border/40 bg-card/40 backdrop-blur-sm mt-auto"> | |
5 | + <div class="container px-4 py-12"> | |
6 | + <div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-10"> | |
7 | + <!-- Brand & Mission --> | |
8 | + <div class="md:col-span-2 space-y-4"> | |
9 | + <div class="flex items-center gap-2"> | |
10 | + <div class="h-8 w-8 rounded-lg bg-gradient-to-br from-primary via-indigo-500 to-purple-600 flex items-center justify-center text-white shadow-md shadow-primary/20"> | |
11 | + <Github className="h-4 w-4" /> | |
12 | + </div> | |
13 | + <span class="font-bold text-lg tracking-tight bg-gradient-to-r from-white via-zinc-200 to-zinc-400 bg-clip-text text-transparent"> | |
14 | + OpenCodeHub Community | |
15 | + </span> | |
16 | + </div> | |
17 | + <p class="text-sm text-muted-foreground max-w-md leading-relaxed"> | |
18 | + The decentralized aggregator for self-hosted OpenCodeHub instances. Discover public repositories, connect with developers, and build across federated nodes without central gatekeepers. | |
19 | + </p> | |
20 | + <div class="flex items-center gap-2 text-xs text-muted-foreground pt-1"> | |
21 | + <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-medium"> | |
22 | + <span class="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse"></span> | |
23 | + Federation Active | |
24 | + </span> | |
25 | + <span>·</span> | |
26 | + <span>Powered by Turso & Astro</span> | |
27 | + </div> | |
28 | + </div> | |
29 | + | |
30 | + <!-- Navigation Links --> | |
31 | + <div class="space-y-3"> | |
32 | + <h4 class="text-sm font-semibold text-foreground tracking-wider uppercase">Platform</h4> | |
33 | + <ul class="space-y-2 text-sm text-muted-foreground"> | |
34 | + <li><a href="/explore" class="hover:text-primary transition-colors flex items-center gap-1.5"><Globe className="h-3.5 w-3.5" /> Explore Repos</a></li> | |
35 | + <li><a href="/instances" class="hover:text-primary transition-colors flex items-center gap-1.5"><Server className="h-3.5 w-3.5" /> Federated Instances</a></li> | |
36 | + <li><a href="/instances/submit" class="hover:text-primary transition-colors flex items-center gap-1.5"><Sparkles className="h-3.5 w-3.5" /> Submit Instance</a></li> | |
37 | + <li><a href="/dashboard" class="hover:text-primary transition-colors">User Dashboard</a></li> | |
38 | + </ul> | |
39 | + </div> | |
40 | + | |
41 | + <!-- Resources & Docs --> | |
42 | + <div class="space-y-3"> | |
43 | + <h4 class="text-sm font-semibold text-foreground tracking-wider uppercase">Open Source</h4> | |
44 | + <ul class="space-y-2 text-sm text-muted-foreground"> | |
45 | + <li><a href="https://github.com/swadhinbiswas/OpencodeHub" target="_blank" rel="noreferrer" class="hover:text-primary transition-colors flex items-center gap-1.5"><Github className="h-3.5 w-3.5" /> GitHub Repo</a></li> | |
46 | + <li><a href="/instances/submit" class="hover:text-primary transition-colors">Self-Host Docs</a></li> | |
47 | + <li><a href="/api/instances" class="hover:text-primary transition-colors">Public JSON API</a></li> | |
48 | + <li><a href="/auth/login" class="hover:text-primary transition-colors">Sign In / Register</a></li> | |
49 | + </ul> | |
50 | + </div> | |
51 | + </div> | |
52 | + | |
53 | + <!-- Bottom Bar --> | |
54 | + <div class="border-t border-border/40 pt-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-muted-foreground"> | |
55 | + <div> | |
56 | + © {new Date().getFullYear()} OpenCodeHub Community. Built for the federated future. | |
57 | + </div> | |
58 | + <div class="flex items-center gap-1"> | |
59 | + <span>Crafted with</span> | |
60 | + <Heart className="h-3.5 w-3.5 text-rose-500 fill-rose-500" /> | |
61 | + <span>for open source self-hosters</span> | |
62 | + </div> | |
63 | + </div> | |
64 | + </div> | |
65 | +</footer> | |
0 | 66 | |
▼community/src/components/layout/Header.tsx
+80-0
| @@ -0,0 +1,80 @@ | ||
1 | +import { useState, useEffect } from "react"; | |
2 | +import { Button } from "@/components/ui/button"; | |
3 | +import { Compass, Plus, LogIn, LogOut, Menu, X, Server } from "lucide-react"; | |
4 | + | |
5 | +export function Header({ user }: { user?: any }) { | |
6 | + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); | |
7 | + const [currentPath, setCurrentPath] = useState(""); | |
8 | + | |
9 | + useEffect(() => { | |
10 | + if (typeof window !== "undefined") { | |
11 | + setCurrentPath(window.location.pathname); | |
12 | + } | |
13 | + }, []); | |
14 | + | |
15 | + const handleLogout = async () => { | |
16 | + try { | |
17 | + await fetch("/api/auth/logout", { method: "POST" }); | |
18 | + window.location.href = "/"; | |
19 | + } catch (e) { | |
20 | + console.error("Logout failed:", e); | |
21 | + } | |
22 | + }; | |
23 | + | |
24 | + const navLinks = [ | |
25 | + { href: "/explore", label: "Explore", icon: Compass }, | |
26 | + { href: "/instances", label: "Instances", icon: Server }, | |
27 | + ]; | |
28 | + | |
29 | + return ( | |
30 | + <header className="sticky top-0 z-50 w-full border-b border-border/50 bg-background/80 backdrop-blur-xl supports-[backdrop-filter]:bg-background/60"> | |
31 | + <div className="container flex h-16 items-center justify-between px-4 sm:px-8"> | |
32 | + <div className="flex items-center gap-8"> | |
33 | + <a href="/" className="flex items-center gap-2.5 group"> | |
34 | + <img src="/logo.svg" alt="OpenCodeHub" className="h-9 w-9" /> | |
35 | + <div className="flex flex-col"> | |
36 | + <span className="font-bold text-base tracking-tight leading-none text-foreground flex items-center gap-1.5"> | |
37 | + OpenCodeHub | |
38 | + <span className="text-[10px] uppercase font-semibold tracking-wider px-1.5 py-0.5 rounded bg-primary/10 text-primary border border-primary/20">Community</span> | |
39 | + </span> | |
40 | + <span className="text-[11px] text-muted-foreground leading-tight">Federated Hub</span> | |
41 | + </div> | |
42 | + </a> | |
43 | + <nav className="hidden md:flex items-center gap-1.5"> | |
44 | + {navLinks.map(({ href, label, icon: Icon }) => { | |
45 | + const isActive = currentPath === href || (href !== "/" && currentPath.startsWith(href)); | |
46 | + return ( | |
47 | + <a key={href} href={href} className={`px-3.5 py-2 text-sm font-medium rounded-lg flex items-center gap-2 transition-all ${isActive ? "bg-primary/10 text-primary font-semibold" : "text-muted-foreground hover:text-foreground hover:bg-accent/60"}`}> | |
48 | + <Icon className={`h-4 w-4 ${isActive ? "text-primary" : "text-muted-foreground"}`} /> | |
49 | + {label} | |
50 | + </a> | |
51 | + ); | |
52 | + })} | |
53 | + </nav> | |
54 | + </div> | |
55 | + <div className="flex items-center gap-2"> | |
56 | + <a href="/instances/submit"><Button size="sm" variant="outline"><Plus className="h-4 w-4 mr-1" />Submit URL</Button></a> | |
57 | + {user ? ( | |
58 | + <div className="flex items-center gap-2 ml-2"> | |
59 | + <span className="text-sm font-medium hidden sm:inline-block">@{user.username}</span> | |
60 | + <Button size="icon" variant="ghost" onClick={handleLogout} title="Sign out"><LogOut className="h-4 w-4 text-muted-foreground" /></Button> | |
61 | + </div> | |
62 | + ) : ( | |
63 | + <a href="/auth/login"><Button size="sm"><LogIn className="h-4 w-4 mr-1" />Sign in</Button></a> | |
64 | + )} | |
65 | + <Button size="icon" variant="ghost" className="md:hidden" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}> | |
66 | + {mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />} | |
67 | + </Button> | |
68 | + </div> | |
69 | + </div> | |
70 | + {mobileMenuOpen && ( | |
71 | + <div className="md:hidden border-t bg-background/95 backdrop-blur-xl px-4 py-3 space-y-1"> | |
72 | + {navLinks.map(({ href, label, icon: Icon }) => ( | |
73 | + <a key={href} href={href} className="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md hover:bg-accent">{label}</a> | |
74 | + ))} | |
75 | + <a href="/instances/submit" className="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md hover:bg-accent">Submit URL</a> | |
76 | + </div> | |
77 | + )} | |
78 | + </header> | |
79 | + ); | |
80 | +} | |
0 | 81 | |
▼community/src/components/ui/avatar.tsx
+10-0
| @@ -0,0 +1,10 @@ | ||
1 | +import * as React from "react"; | |
2 | +import * as AvatarPrimitive from "@radix-ui/react-avatar"; | |
3 | +import { cn } from "@/lib/utils"; | |
4 | +const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>>(({ className, ...props }, ref) => <AvatarPrimitive.Root ref={ref} className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)} {...props} />); | |
5 | +Avatar.displayName = AvatarPrimitive.Root.displayName; | |
6 | +const AvatarImage = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Image>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>>(({ className, ...props }, ref) => <AvatarPrimitive.Image ref={ref} className={cn("aspect-square h-full w-full", className)} {...props} />); | |
7 | +AvatarImage.displayName = AvatarPrimitive.Image.displayName; | |
8 | +const AvatarFallback = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>>(({ className, ...props }, ref) => <AvatarPrimitive.Fallback ref={ref} className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)} {...props} />); | |
9 | +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; | |
10 | +export { Avatar, AvatarImage, AvatarFallback }; | |
0 | 11 | |
▼community/src/components/ui/badge.tsx
+10-0
| @@ -0,0 +1,10 @@ | ||
1 | +import * as React from "react"; | |
2 | +import { cva, type VariantProps } from "class-variance-authority"; | |
3 | +import { cn } from "@/lib/utils"; | |
4 | +const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { | |
5 | + variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground" } }, | |
6 | + defaultVariants: { variant: "default" }, | |
7 | +}); | |
8 | +export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {} | |
9 | +function Badge({ className, variant, ...props }: BadgeProps) { return <div className={cn(badgeVariants({ variant }), className)} {...props} />; } | |
10 | +export { Badge, badgeVariants }; | |
0 | 11 | |
▼community/src/components/ui/button.tsx
+18-0
| @@ -0,0 +1,18 @@ | ||
1 | +import * as React from "react"; | |
2 | +import { Slot } from "@radix-ui/react-slot"; | |
3 | +import { cva, type VariantProps } from "class-variance-authority"; | |
4 | +import { cn } from "@/lib/utils"; | |
5 | +const buttonVariants = cva("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { | |
6 | + variants: { | |
7 | + variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, | |
8 | + size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" }, | |
9 | + }, | |
10 | + defaultVariants: { variant: "default", size: "default" }, | |
11 | +}); | |
12 | +export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean; } | |
13 | +const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => { | |
14 | + const Comp = asChild ? Slot : "button"; | |
15 | + return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />; | |
16 | +}); | |
17 | +Button.displayName = "Button"; | |
18 | +export { Button, buttonVariants }; | |
0 | 19 | |
▼community/src/components/ui/card.tsx
+15-0
| @@ -0,0 +1,15 @@ | ||
1 | +import * as React from "react"; | |
2 | +import { cn } from "@/lib/utils"; | |
3 | +const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => <div ref={ref} className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props} />); | |
4 | +Card.displayName = "Card"; | |
5 | +const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />); | |
6 | +CardHeader.displayName = "CardHeader"; | |
7 | +const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => <h3 ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />); | |
8 | +CardTitle.displayName = "CardTitle"; | |
9 | +const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, ...props }, ref) => <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />); | |
10 | +CardDescription.displayName = "CardDescription"; | |
11 | +const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />); | |
12 | +CardContent.displayName = "CardContent"; | |
13 | +const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />); | |
14 | +CardFooter.displayName = "CardFooter"; | |
15 | +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; | |
0 | 16 | |
▼community/src/components/ui/input.tsx
+8-0
| @@ -0,0 +1,8 @@ | ||
1 | +import * as React from "react"; | |
2 | +import { cn } from "@/lib/utils"; | |
3 | +export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {} | |
4 | +const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => { | |
5 | + return <input type={type} className={cn("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className)} ref={ref} {...props} />; | |
6 | +}); | |
7 | +Input.displayName = "Input"; | |
8 | +export { Input }; | |
0 | 9 | |
▼community/src/components/ui/label.tsx
+10-0
| @@ -0,0 +1,10 @@ | ||
1 | +import * as React from "react"; | |
2 | +import * as LabelPrimitive from "@radix-ui/react-label"; | |
3 | +import { cva, type VariantProps } from "class-variance-authority"; | |
4 | +import { cn } from "@/lib/utils"; | |
5 | +const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"); | |
6 | +const Label = React.forwardRef<React.ElementRef<typeof LabelPrimitive.Root>, React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>>(({ className, ...props }, ref) => ( | |
7 | + <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} /> | |
8 | +)); | |
9 | +Label.displayName = LabelPrimitive.Root.displayName; | |
10 | +export { Label }; | |
0 | 11 | |
▼community/src/components/ui/toaster.tsx
+7-0
| @@ -0,0 +1,7 @@ | ||
1 | +import { Toaster as Sonner } from "sonner"; | |
2 | + | |
3 | +export function Toaster() { | |
4 | + return ( | |
5 | + <Sonner theme="dark" className="toaster group" /> | |
6 | + ); | |
7 | +} | |
0 | 8 | |
▼community/src/env.d.ts
+1-0
| @@ -0,0 +1 @@ | ||
1 | +/// <reference path="../.astro/types.d.ts" /> | |
0 | 2 | |
▼community/src/layouts/BaseLayout.astro
+36-0
| @@ -0,0 +1,36 @@ | ||
1 | +--- | |
2 | +import "../styles/globals.css"; | |
3 | +import { Toaster } from "../components/ui/toaster"; | |
4 | +import Footer from "../components/layout/Footer.astro"; | |
5 | + | |
6 | +interface Props { | |
7 | + title?: string; | |
8 | + description?: string; | |
9 | +} | |
10 | + | |
11 | +const { | |
12 | + title = "OpenCodeHub Community — Discover & Connect", | |
13 | + description = "Aggregating public repositories, developers, and nodes from self-hosted OpenCodeHub instances worldwide." | |
14 | +} = Astro.props; | |
15 | +--- | |
16 | +<!doctype html> | |
17 | +<html lang="en" class="dark"> | |
18 | + <head> | |
19 | + <meta charset="UTF-8" /> | |
20 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
21 | + <title>{title}</title> | |
22 | + <meta name="description" content={description} /> | |
23 | + <meta name="theme-color" content="#09090b" /> | |
24 | + <link rel="icon" type="image/svg+xml" href="/logo.svg" /> | |
25 | + <link rel="preconnect" href="https://fonts.googleapis.com"> | |
26 | + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
27 | + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> | |
28 | + </head> | |
29 | + <body class="min-h-screen bg-background font-sans antialiased text-foreground selection:bg-primary/20 selection:text-primary flex flex-col"> | |
30 | + <div class="flex-1 flex flex-col"> | |
31 | + <slot /> | |
32 | + </div> | |
33 | + <Footer /> | |
34 | + <Toaster client:load /> | |
35 | + </body> | |
36 | +</html> | |
0 | 37 | |
▼community/src/lib/auth.ts
+24-0
| @@ -0,0 +1,24 @@ | ||
1 | +import { SignJWT, jwtVerify } from "jose"; | |
2 | +import bcrypt from "bcryptjs"; | |
3 | +function getSecret(): Uint8Array { | |
4 | + const secretStr = ( | |
5 | + process.env.JWT_SECRET || | |
6 | + (typeof import.meta !== "undefined" && (import.meta as any).env?.JWT_SECRET) || | |
7 | + "community-dev-secret-change-me" | |
8 | + ).trim(); | |
9 | + return new TextEncoder().encode(secretStr); | |
10 | +} | |
11 | + | |
12 | +export async function hashPassword(pw: string) { return bcrypt.hash(pw, 10); } | |
13 | +export async function verifyPassword(pw: string, hash: string) { return bcrypt.compare(pw, hash); } | |
14 | +export async function signToken(payload: Record<string, any>) { | |
15 | + return await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("7d").sign(getSecret()); | |
16 | +} | |
17 | +export async function verifyToken(token: string) { | |
18 | + try { const { payload } = await jwtVerify(token, getSecret()); return payload as any; } catch { return null; } | |
19 | +} | |
20 | +export function getUserFromCookie(cookieHeader: string | null) { | |
21 | + if (!cookieHeader) return null; | |
22 | + const m = cookieHeader.match(/community_session=([^;]+)/); | |
23 | + return m ? m[1] : null; | |
24 | +} | |
0 | 25 | |
▼community/src/lib/cache.ts
+38-0
| @@ -0,0 +1,38 @@ | ||
1 | +import { Redis } from "@upstash/redis"; | |
2 | +let redis: Redis | null = null; | |
3 | +function getRedis(): Redis | null { | |
4 | + if (redis) return redis; | |
5 | + const url = ( | |
6 | + process.env.UPSTASH_REDIS_REST_URL || | |
7 | + (typeof import.meta !== "undefined" && (import.meta as any).env?.UPSTASH_REDIS_REST_URL) || | |
8 | + "" | |
9 | + ).trim(); | |
10 | + const token = ( | |
11 | + process.env.UPSTASH_REDIS_REST_TOKEN || | |
12 | + (typeof import.meta !== "undefined" && (import.meta as any).env?.UPSTASH_REDIS_REST_TOKEN) || | |
13 | + "" | |
14 | + ).trim(); | |
15 | + if (!url || !token) return null; | |
16 | + redis = new Redis({ url, token }); | |
17 | + return redis; | |
18 | +} | |
19 | +const mem = new Map<string, {v:any; exp:number}>(); | |
20 | +export async function cacheGet<T>(key: string): Promise<T | null> { | |
21 | + const r = getRedis(); | |
22 | + if (r) { try { const v = await r.get<T>(key); if (v) return v as T; } catch {} } | |
23 | + const m = mem.get(key); | |
24 | + if (m && m.exp > Date.now()) return m.v as T; | |
25 | + if (m) mem.delete(key); | |
26 | + return null; | |
27 | +} | |
28 | +export async function cacheSet(key: string, value: any, ttlSec = 300) { | |
29 | + const r = getRedis(); | |
30 | + if (r) { try { await r.set(key, value, { ex: ttlSec }); } catch {} } | |
31 | + mem.set(key, { v: value, exp: Date.now() + ttlSec*1000 }); | |
32 | + if (mem.size > 500) { const k = mem.keys().next().value; if(k) mem.delete(k); } | |
33 | +} | |
34 | +export async function cacheInvalidate(pattern: string) { | |
35 | + const r = getRedis(); | |
36 | + if (r) { try { const keys = await r.keys(pattern); if(keys.length) await r.del(...keys); } catch {} } | |
37 | + for (const k of [...mem.keys()]) if (k.includes(pattern.replace("*",""))) mem.delete(k); | |
38 | +} | |
0 | 39 | |
▼community/src/lib/db/index.ts
+39-0
| @@ -0,0 +1,39 @@ | ||
1 | +import { createClient } from "@libsql/client"; | |
2 | +import { drizzle } from "drizzle-orm/libsql"; | |
3 | +import * as schema from "./schema"; | |
4 | + | |
5 | +let _db: ReturnType<typeof drizzle> | null = null; | |
6 | + | |
7 | +export function getDb() { | |
8 | + if (_db) return _db; | |
9 | + | |
10 | + const url = ( | |
11 | + process.env.TURSO_DATABASE_URL || | |
12 | + (typeof import.meta !== "undefined" && (import.meta as any).env?.TURSO_DATABASE_URL) || | |
13 | + "" | |
14 | + ).trim(); | |
15 | + | |
16 | + const authToken = ( | |
17 | + process.env.TURSO_AUTH_TOKEN || | |
18 | + (typeof import.meta !== "undefined" && (import.meta as any).env?.TURSO_AUTH_TOKEN) || | |
19 | + "" | |
20 | + ).trim(); | |
21 | + | |
22 | + if (!url) { | |
23 | + throw new Error( | |
24 | + "TURSO_DATABASE_URL is required. Set it in your .env to a Turso database URL.\n" + | |
25 | + "Example: TURSO_DATABASE_URL=libsql://your-db-name-your-org.turso.io" | |
26 | + ); | |
27 | + } | |
28 | + | |
29 | + const client = createClient({ | |
30 | + url, | |
31 | + authToken: authToken || undefined, | |
32 | + }); | |
33 | + | |
34 | + _db = drizzle(client, { schema }); | |
35 | + return _db; | |
36 | +} | |
37 | + | |
38 | +export * from "./schema"; | |
39 | + | |
0 | 40 | |
▼community/src/lib/db/schema.ts
+100-0
| @@ -0,0 +1,100 @@ | ||
1 | +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; | |
2 | +import { sql } from "drizzle-orm"; | |
3 | + | |
4 | +export const communityUsers = sqliteTable("community_users", { | |
5 | + id: text("id").primaryKey(), | |
6 | + username: text("username").notNull().unique(), | |
7 | + email: text("email").notNull().unique(), | |
8 | + displayName: text("display_name"), | |
9 | + bio: text("bio"), | |
10 | + avatarUrl: text("avatar_url"), | |
11 | + passwordHash: text("password_hash").notNull(), | |
12 | + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), | |
13 | + updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), | |
14 | +}); | |
15 | + | |
16 | +export const instances = sqliteTable("instances", { | |
17 | + id: text("id").primaryKey(), | |
18 | + url: text("url").notNull().unique(), | |
19 | + siteUrl: text("site_url").notNull(), | |
20 | + name: text("name").notNull(), | |
21 | + version: text("version"), | |
22 | + capabilities: text("capabilities"), | |
23 | + status: text("status").default("pending"), | |
24 | + lastSyncAt: text("last_sync_at"), | |
25 | + repoCount: integer("repo_count").default(0), | |
26 | + userCount: integer("user_count").default(0), | |
27 | + submittedBy: text("submitted_by"), | |
28 | + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), | |
29 | +}, (t) => [index("instances_url_idx").on(t.url)]); | |
30 | + | |
31 | +export const cachedRepos = sqliteTable("cached_repos", { | |
32 | + id: text("id").primaryKey(), | |
33 | + instanceId: text("instance_id").notNull().references(() => instances.id, { onDelete: "cascade" }), | |
34 | + remoteId: text("remote_id").notNull(), | |
35 | + fullName: text("full_name").notNull(), | |
36 | + name: text("name").notNull(), | |
37 | + ownerUsername: text("owner_username").notNull(), | |
38 | + ownerDisplayName: text("owner_display_name"), | |
39 | + ownerAvatarUrl: text("owner_avatar_url"), | |
40 | + description: text("description"), | |
41 | + visibility: text("visibility").default("public"), | |
42 | + language: text("language"), | |
43 | + topics: text("topics"), | |
44 | + starCount: integer("star_count").default(0), | |
45 | + forkCount: integer("fork_count").default(0), | |
46 | + watchCount: integer("watch_count").default(0), | |
47 | + httpCloneUrl: text("http_clone_url"), | |
48 | + updatedAt: text("updated_at"), | |
49 | + cachedAt: text("cached_at").default(sql`CURRENT_TIMESTAMP`), | |
50 | +}, (t) => [ | |
51 | + index("cached_repos_instance_idx").on(t.instanceId), | |
52 | + index("cached_repos_fullname_idx").on(t.fullName), | |
53 | +]); | |
54 | + | |
55 | +export const cachedUsers = sqliteTable("cached_users", { | |
56 | + id: text("id").primaryKey(), | |
57 | + instanceId: text("instance_id").notNull().references(() => instances.id, { onDelete: "cascade" }), | |
58 | + remoteId: text("remote_id").notNull(), | |
59 | + username: text("username").notNull(), | |
60 | + displayName: text("display_name"), | |
61 | + bio: text("bio"), | |
62 | + avatarUrl: text("avatar_url"), | |
63 | + location: text("location"), | |
64 | + website: text("website"), | |
65 | + company: text("company"), | |
66 | + repoCount: integer("repo_count").default(0), | |
67 | + followerCount: integer("follower_count").default(0), | |
68 | + cachedAt: text("cached_at").default(sql`CURRENT_TIMESTAMP`), | |
69 | +}, (t) => [index("cached_users_instance_idx").on(t.instanceId)]); | |
70 | + | |
71 | +export const stars = sqliteTable("stars", { | |
72 | + id: text("id").primaryKey(), | |
73 | + userId: text("user_id").notNull().references(() => communityUsers.id, { onDelete: "cascade" }), | |
74 | + repoId: text("repo_id").notNull().references(() => cachedRepos.id, { onDelete: "cascade" }), | |
75 | + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), | |
76 | +}, (t) => [index("stars_user_repo_idx").on(t.userId, t.repoId)]); | |
77 | + | |
78 | +export const follows = sqliteTable("follows", { | |
79 | + id: text("id").primaryKey(), | |
80 | + followerId: text("follower_id").notNull().references(() => communityUsers.id, { onDelete: "cascade" }), | |
81 | + followeeType: text("followee_type").notNull(), | |
82 | + followeeId: text("followee_id").notNull(), | |
83 | + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), | |
84 | +}); | |
85 | + | |
86 | +import { relations } from "drizzle-orm"; | |
87 | + | |
88 | +export const cachedReposRelations = relations(cachedRepos, ({ one }) => ({ | |
89 | + instance: one(instances, { | |
90 | + fields: [cachedRepos.instanceId], | |
91 | + references: [instances.id], | |
92 | + }), | |
93 | +})); | |
94 | + | |
95 | +export const cachedUsersRelations = relations(cachedUsers, ({ one }) => ({ | |
96 | + instance: one(instances, { | |
97 | + fields: [cachedUsers.instanceId], | |
98 | + references: [instances.id], | |
99 | + }), | |
100 | +})); | |
0 | 101 | |
▼community/src/lib/instance-client.ts
+44-0
| @@ -0,0 +1,44 @@ | ||
1 | +export interface InstanceInfo { | |
2 | + product: string; name: string; siteUrl: string; version: string; capabilities: string[]; | |
3 | +} | |
4 | +export interface RemoteRepo { | |
5 | + id: string; name: string; fullName: string; description: string | null; | |
6 | + visibility: string; defaultBranch: string; starCount: number; forkCount: number; watchCount: number; | |
7 | + language: string | null; topics: string[]; httpCloneUrl: string; updatedAt: string; createdAt: string; | |
8 | + owner: { id: string; username: string; displayName: string | null; avatarUrl: string | null; }; | |
9 | +} | |
10 | + | |
11 | +export async function probeInstance(baseUrl: string): Promise<{ ok: true; info: InstanceInfo } | { ok: false; error: string }> { | |
12 | + try { | |
13 | + const origin = new URL(baseUrl).origin; | |
14 | + const ctrl = new AbortController(); const t = setTimeout(()=>ctrl.abort(), 8000); | |
15 | + const res = await fetch(`${origin}/api/instance`, { signal: ctrl.signal, headers: { Accept: "application/json" } }); | |
16 | + clearTimeout(t); | |
17 | + if (!res.ok) return { ok: false, error: `Instance probe failed: ${res.status}` }; | |
18 | + const json = await res.json(); | |
19 | + if (json?.data?.product !== "opencodehub") return { ok: false, error: "Not an OpenCodeHub instance" }; | |
20 | + return { ok: true, info: json.data as InstanceInfo }; | |
21 | + } catch (e: any) { | |
22 | + return { ok: false, error: e?.message || "Network error" }; | |
23 | + } | |
24 | +} | |
25 | + | |
26 | +export async function fetchPublicRepos(origin: string, page = 1, perPage = 50): Promise<{ repos: RemoteRepo[]; total: number; totalPages: number }> { | |
27 | + const url = `${new URL(origin).origin}/api/repos?visibility=public&sort=updated&page=${page}&per_page=${perPage}`; | |
28 | + const res = await fetch(url, { headers: { Accept: "application/json", "User-Agent": "CommunityHub/1.0" } }); | |
29 | + if (!res.ok) throw new Error(`Failed to fetch repos: ${res.status}`); | |
30 | + const json = await res.json(); | |
31 | + if (!json.success) throw new Error(json.error?.message || "Failed"); | |
32 | + return { repos: json.data as RemoteRepo[], total: json.meta?.total || json.data.length, totalPages: json.meta?.totalPages || 1 }; | |
33 | +} | |
34 | + | |
35 | +export async function fetchAllPublicRepos(origin: string): Promise<RemoteRepo[]> { | |
36 | + let page = 1; const all: RemoteRepo[] = []; | |
37 | + while (true) { | |
38 | + const { repos, totalPages } = await fetchPublicRepos(origin, page, 100); | |
39 | + all.push(...repos); | |
40 | + if (page >= totalPages || repos.length === 0) break; | |
41 | + page++; await new Promise(r=>setTimeout(r,150)); | |
42 | + } | |
43 | + return all; | |
44 | +} | |
0 | 45 | |
▼community/src/lib/utils.ts
+11-0
| @@ -0,0 +1,11 @@ | ||
1 | +import { clsx, type ClassValue } from "clsx"; | |
2 | +import { twMerge } from "tailwind-merge"; | |
3 | +export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } | |
4 | +export function timeAgo(date: string | Date) { | |
5 | + const s = Math.floor((Date.now() - new Date(date).getTime())/1000); | |
6 | + if (s < 60) return "just now"; | |
7 | + if (s < 3600) return `${Math.floor(s/60)}m ago`; | |
8 | + if (s < 86400) return `${Math.floor(s/3600)}h ago`; | |
9 | + if (s < 2592000) return `${Math.floor(s/86400)}d ago`; | |
10 | + return new Date(date).toLocaleDateString(); | |
11 | +} | |
0 | 12 | |
▼community/src/pages/api/auth/login.ts
+46-0
| @@ -0,0 +1,46 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { communityUsers } from "@/lib/db/schema"; | |
4 | +import { verifyPassword, signToken } from "@/lib/auth"; | |
5 | +import { eq } from "drizzle-orm"; | |
6 | + | |
7 | +export const prerender = false; | |
8 | + | |
9 | +export const POST: APIRoute = async ({ request, cookies }) => { | |
10 | + try { | |
11 | + const body = await request.json(); | |
12 | + const { email, password } = body; | |
13 | + | |
14 | + if (!email || !password) { | |
15 | + return new Response(JSON.stringify({ success: false, error: "Missing email or password" }), { status: 400 }); | |
16 | + } | |
17 | + | |
18 | + const db = getDb() as any; | |
19 | + | |
20 | + const user = await db.query.communityUsers.findFirst({ | |
21 | + where: eq(communityUsers.email, email) | |
22 | + }); | |
23 | + | |
24 | + if (!user) { | |
25 | + return new Response(JSON.stringify({ success: false, error: "Invalid credentials" }), { status: 401 }); | |
26 | + } | |
27 | + | |
28 | + const isValid = await verifyPassword(password, user.passwordHash); | |
29 | + if (!isValid) { | |
30 | + return new Response(JSON.stringify({ success: false, error: "Invalid credentials" }), { status: 401 }); | |
31 | + } | |
32 | + | |
33 | + const token = await signToken({ sub: user.id, username: user.username }); | |
34 | + | |
35 | + cookies.set("community_session", token, { | |
36 | + path: "/", | |
37 | + httpOnly: true, | |
38 | + secure: process.env.NODE_ENV === "production", | |
39 | + maxAge: 60 * 60 * 24 * 7, | |
40 | + }); | |
41 | + | |
42 | + return new Response(JSON.stringify({ success: true, data: { id: user.id, username: user.username } }), { status: 200 }); | |
43 | + } catch (error: any) { | |
44 | + return new Response(JSON.stringify({ success: false, error: error.message || "Server error" }), { status: 500 }); | |
45 | + } | |
46 | +}; | |
0 | 47 | |
▼community/src/pages/api/auth/logout.ts
+8-0
| @@ -0,0 +1,8 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | + | |
3 | +export const prerender = false; | |
4 | + | |
5 | +export const POST: APIRoute = async ({ cookies }) => { | |
6 | + cookies.delete("community_session", { path: "/" }); | |
7 | + return new Response(JSON.stringify({ success: true }), { status: 200 }); | |
8 | +}; | |
0 | 9 | |
▼community/src/pages/api/auth/register.ts
+52-0
| @@ -0,0 +1,52 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { communityUsers } from "@/lib/db/schema"; | |
4 | +import { hashPassword, signToken } from "@/lib/auth"; | |
5 | +import { nanoid } from "nanoid"; | |
6 | +import { eq, or } from "drizzle-orm"; | |
7 | + | |
8 | +export const prerender = false; | |
9 | + | |
10 | +export const POST: APIRoute = async ({ request, cookies }) => { | |
11 | + try { | |
12 | + const body = await request.json(); | |
13 | + const { username, email, password } = body; | |
14 | + | |
15 | + if (!username || !email || !password) { | |
16 | + return new Response(JSON.stringify({ success: false, error: "Missing required fields" }), { status: 400 }); | |
17 | + } | |
18 | + | |
19 | + const db = getDb() as any; | |
20 | + | |
21 | + const existingUser = await db.query.communityUsers.findFirst({ | |
22 | + where: or(eq(communityUsers.username, username), eq(communityUsers.email, email)) | |
23 | + }); | |
24 | + | |
25 | + if (existingUser) { | |
26 | + return new Response(JSON.stringify({ success: false, error: "Username or email already exists" }), { status: 400 }); | |
27 | + } | |
28 | + | |
29 | + const passwordHash = await hashPassword(password); | |
30 | + const id = nanoid(); | |
31 | + | |
32 | + await db.insert(communityUsers).values({ | |
33 | + id, | |
34 | + username, | |
35 | + email, | |
36 | + passwordHash, | |
37 | + }); | |
38 | + | |
39 | + const token = await signToken({ sub: id, username }); | |
40 | + | |
41 | + cookies.set("community_session", token, { | |
42 | + path: "/", | |
43 | + httpOnly: true, | |
44 | + secure: process.env.NODE_ENV === "production", | |
45 | + maxAge: 60 * 60 * 24 * 7, | |
46 | + }); | |
47 | + | |
48 | + return new Response(JSON.stringify({ success: true, data: { id, username } }), { status: 200 }); | |
49 | + } catch (error: any) { | |
50 | + return new Response(JSON.stringify({ success: false, error: error.message || "Server error" }), { status: 500 }); | |
51 | + } | |
52 | +}; | |
0 | 53 | |
▼community/src/pages/api/follows/index.ts
+40-0
| @@ -0,0 +1,40 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { follows } from "@/lib/db/schema"; | |
4 | +import { verifyToken } from "@/lib/auth"; | |
5 | +import { nanoid } from "nanoid"; | |
6 | +import { eq, and } from "drizzle-orm"; | |
7 | + | |
8 | +export const prerender = false; | |
9 | + | |
10 | +export const POST: APIRoute = async ({ request, cookies }) => { | |
11 | + try { | |
12 | + const { followeeType, followeeId } = await request.json(); | |
13 | + if (!followeeType || !followeeId) { | |
14 | + return new Response(JSON.stringify({ success: false, error: "followeeType and followeeId required" }), { status: 400 }); | |
15 | + } | |
16 | + | |
17 | + const token = cookies.get("community_session")?.value; | |
18 | + if (!token) return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401 }); | |
19 | + | |
20 | + const user = await verifyToken(token); | |
21 | + if (!user) return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401 }); | |
22 | + | |
23 | + const followerId = user.sub; | |
24 | + const db = getDb() as any; | |
25 | + | |
26 | + const existing = await db.query.follows.findFirst({ | |
27 | + where: and(eq(follows.followerId, followerId), eq(follows.followeeId, followeeId)) | |
28 | + }).catch(() => null); | |
29 | + | |
30 | + if (existing) { | |
31 | + await db.delete(follows).where(eq(follows.id, existing.id)); | |
32 | + return new Response(JSON.stringify({ success: true, data: { following: false } }), { headers: { "Content-Type": "application/json" } }); | |
33 | + } | |
34 | + | |
35 | + await db.insert(follows).values({ id: nanoid(), followerId, followeeType, followeeId }); | |
36 | + return new Response(JSON.stringify({ success: true, data: { following: true } }), { headers: { "Content-Type": "application/json" } }); | |
37 | + } catch (e: any) { | |
38 | + return new Response(JSON.stringify({ success: false, error: e.message }), { status: 500 }); | |
39 | + } | |
40 | +}; | |
0 | 41 | |
▼community/src/pages/api/instances/index.ts
+12-0
| @@ -0,0 +1,12 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +export const prerender = false; | |
4 | +export const GET: APIRoute = async () => { | |
5 | + try { | |
6 | + const db = getDb() as any; | |
7 | + const rows = await db.query.instances.findMany?.().catch(()=>[]) || []; | |
8 | + return new Response(JSON.stringify({ success: true, data: rows }), { headers: { "Content-Type": "application/json" } }); | |
9 | + } catch (e: any) { | |
10 | + return new Response(JSON.stringify({ success: true, data: [] }), { headers: { "Content-Type": "application/json" } }); | |
11 | + } | |
12 | +}; | |
0 | 13 | |
▼community/src/pages/api/instances/submit.ts
+48-0
| @@ -0,0 +1,48 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { probeInstance, fetchPublicRepos } from "@/lib/instance-client"; | |
3 | +import { getDb } from "@/lib/db"; | |
4 | +import { instances } from "@/lib/db/schema"; | |
5 | +import { nanoid } from "nanoid"; | |
6 | +import { cacheSet } from "@/lib/cache"; | |
7 | +import { eq } from "drizzle-orm"; | |
8 | + | |
9 | +export const prerender = false; | |
10 | + | |
11 | +export const POST: APIRoute = async ({ request }) => { | |
12 | + try { | |
13 | + const body = await request.json(); | |
14 | + const rawUrl = (body.url || "").trim(); | |
15 | + if (!rawUrl) return new Response(JSON.stringify({ success: false, error: { message: "URL is required" } }), { status: 400, headers: { "Content-Type": "application/json" } }); | |
16 | + let origin: string; | |
17 | + try { origin = new URL(rawUrl).origin; } catch { return new Response(JSON.stringify({ success: false, error: { message: "Invalid URL" } }), { status: 400, headers: { "Content-Type": "application/json" } }); } | |
18 | + | |
19 | + const probe = await probeInstance(origin); | |
20 | + if (!probe.ok) return new Response(JSON.stringify({ success: false, error: { message: probe.error } }), { status: 400, headers: { "Content-Type": "application/json" } }); | |
21 | + | |
22 | + const info = probe.info; | |
23 | + const db = getDb() as any; | |
24 | + | |
25 | + let id: string; | |
26 | + // try find existing | |
27 | + let existing: any = null; | |
28 | + try { existing = await db.query.instances.findFirst({ where: eq(instances.url, origin) }); } catch {} | |
29 | + if (existing) { | |
30 | + id = existing.id; | |
31 | + await db.update(instances).set({ siteUrl: info.siteUrl, name: info.name, version: info.version, capabilities: JSON.stringify(info.capabilities), status: "online", lastSyncAt: new Date().toISOString() }).where(eq(instances.id, id)); | |
32 | + } else { | |
33 | + id = nanoid(); | |
34 | + await db.insert(instances).values({ id, url: origin, siteUrl: info.siteUrl, name: info.name, version: info.version, capabilities: JSON.stringify(info.capabilities), status: "online", lastSyncAt: new Date().toISOString() }); | |
35 | + } | |
36 | + | |
37 | + let repoCount = 0; | |
38 | + try { | |
39 | + const { repos } = await fetchPublicRepos(origin, 1, 5); | |
40 | + repoCount = repos.length; | |
41 | + await cacheSet(`instance:${id}:probe`, info, 3600); | |
42 | + } catch {} | |
43 | + | |
44 | + return new Response(JSON.stringify({ success: true, data: { id, ...info, repoCount } }), { status: 200, headers: { "Content-Type": "application/json" } }); | |
45 | + } catch (e: any) { | |
46 | + return new Response(JSON.stringify({ success: false, error: { message: e.message || "Server error" } }), { status: 500, headers: { "Content-Type": "application/json" } }); | |
47 | + } | |
48 | +}; | |
0 | 49 | |
▼community/src/pages/api/instances/sync.ts
+73-0
| @@ -0,0 +1,73 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { instances, cachedRepos } from "@/lib/db/schema"; | |
4 | +import { fetchAllPublicRepos } from "@/lib/instance-client"; | |
5 | +import { cacheGet, cacheSet } from "@/lib/cache"; | |
6 | +import { nanoid } from "nanoid"; | |
7 | +import { eq } from "drizzle-orm"; | |
8 | +export const prerender = false; | |
9 | +export const POST: APIRoute = async ({ url }) => { | |
10 | + const id = url.searchParams.get("id"); | |
11 | + if (!id) return new Response(JSON.stringify({ success: false, error: { message: "id required" } }), { status: 400 }); | |
12 | + const db = getDb() as any; | |
13 | + const instance = await db.query.instances.findFirst({ where: eq(instances.id, id) }).catch(()=>null); | |
14 | + if (!instance) return new Response(JSON.stringify({ success: false, error: { message: "Instance not found" } }), { status: 404 }); | |
15 | + const cached = await cacheGet<any>(`sync:${id}`); | |
16 | + if (cached) return new Response(JSON.stringify({ success: true, data: cached, cached: true }), { headers: { "Content-Type": "application/json" } }); | |
17 | + try { | |
18 | + const repos = await fetchAllPublicRepos(instance.url); | |
19 | + // Upsert cached repos and owners | |
20 | + const ownersMap = new Map(); | |
21 | + | |
22 | + for (const r of repos) { | |
23 | + // Collect owners | |
24 | + if (r.owner && !ownersMap.has(r.owner.username)) { | |
25 | + ownersMap.set(r.owner.username, r.owner); | |
26 | + } | |
27 | + | |
28 | + const existing = await db.query.cachedRepos.findFirst({ where: eq(cachedRepos.remoteId, r.id) }).catch(()=>null); | |
29 | + if (existing) { | |
30 | + await db.update(cachedRepos).set({ fullName: r.fullName, name: r.name, description: r.description, language: r.language, topics: JSON.stringify(r.topics), starCount: r.starCount, forkCount: r.forkCount, httpCloneUrl: r.httpCloneUrl, updatedAt: r.updatedAt }).where(eq(cachedRepos.id, existing.id)); | |
31 | + } else { | |
32 | + await db.insert(cachedRepos).values({ id: nanoid(), instanceId: id, remoteId: r.id, fullName: r.fullName, name: r.name, ownerUsername: r.owner.username, ownerDisplayName: r.owner.displayName, ownerAvatarUrl: r.owner.avatarUrl, description: r.description, visibility: r.visibility, language: r.language, topics: JSON.stringify(r.topics), starCount: r.starCount, forkCount: r.forkCount, httpCloneUrl: r.httpCloneUrl, updatedAt: r.updatedAt }); | |
33 | + } | |
34 | + } | |
35 | + | |
36 | + const { cachedUsers } = await import("@/lib/db/schema"); | |
37 | + for (const owner of ownersMap.values()) { | |
38 | + const existingUser = await db.query.cachedUsers.findFirst({ where: eq(cachedUsers.username, owner.username) }).catch(() => null); | |
39 | + if (existingUser) { | |
40 | + await db.update(cachedUsers).set({ | |
41 | + displayName: owner.displayName, | |
42 | + avatarUrl: owner.avatarUrl, | |
43 | + }).where(eq(cachedUsers.id, existingUser.id)); | |
44 | + } else { | |
45 | + await db.insert(cachedUsers).values({ | |
46 | + id: nanoid(), | |
47 | + instanceId: id, | |
48 | + remoteId: owner.id || owner.username, | |
49 | + username: owner.username, | |
50 | + displayName: owner.displayName, | |
51 | + avatarUrl: owner.avatarUrl, | |
52 | + }); | |
53 | + } | |
54 | + } | |
55 | + await db.update(instances).set({ repoCount: repos.length, status: "online", lastSyncAt: new Date().toISOString() }).where(eq(instances.id, id)); | |
56 | + const result = { repos, count: repos.length }; | |
57 | + await cacheSet(`sync:${id}`, result, 300); | |
58 | + return new Response(JSON.stringify({ success: true, data: result }), { headers: { "Content-Type": "application/json" } }); | |
59 | + } catch (e: any) { | |
60 | + await db.update(instances).set({ status: "error" }).where(eq(instances.id, id)).catch(()=>{}); | |
61 | + return new Response(JSON.stringify({ success: false, error: { message: e.message } }), { status: 500 }); | |
62 | + } | |
63 | +}; | |
64 | +export const GET: APIRoute = async ({ url }) => { | |
65 | + const id = url.searchParams.get("id"); | |
66 | + const db = getDb() as any; | |
67 | + if (id) { | |
68 | + const rows = await db.query.cachedRepos.findMany({ where: eq(cachedRepos.instanceId, id) }).catch(()=>[]); | |
69 | + return new Response(JSON.stringify({ success: true, data: rows }), { headers: { "Content-Type": "application/json" } }); | |
70 | + } | |
71 | + const all = await db.query.cachedRepos.findMany().catch(()=>[]); | |
72 | + return new Response(JSON.stringify({ success: true, data: all }), { headers: { "Content-Type": "application/json" } }); | |
73 | +}; | |
0 | 74 | |
▼community/src/pages/api/search.ts
+40-0
| @@ -0,0 +1,40 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { cachedRepos, cachedUsers } from "@/lib/db/schema"; | |
4 | +import { like, or } from "drizzle-orm"; | |
5 | + | |
6 | +export const prerender = false; | |
7 | + | |
8 | +export const GET: APIRoute = async ({ url }) => { | |
9 | + try { | |
10 | + const q = url.searchParams.get("q") || ""; | |
11 | + if (!q || q.length < 2) { | |
12 | + return new Response(JSON.stringify({ success: true, data: { repos: [], users: [] } }), { status: 200 }); | |
13 | + } | |
14 | + | |
15 | + const db = getDb() as any; | |
16 | + const searchPattern = `%${q}%`; | |
17 | + | |
18 | + const repos = await db.query.cachedRepos.findMany({ | |
19 | + where: or( | |
20 | + like(cachedRepos.fullName, searchPattern), | |
21 | + like(cachedRepos.description, searchPattern) | |
22 | + ), | |
23 | + limit: 10, | |
24 | + with: { instance: true } | |
25 | + }); | |
26 | + | |
27 | + const users = await db.query.cachedUsers.findMany({ | |
28 | + where: or( | |
29 | + like(cachedUsers.username, searchPattern), | |
30 | + like(cachedUsers.displayName, searchPattern) | |
31 | + ), | |
32 | + limit: 5, | |
33 | + with: { instance: true } | |
34 | + }); | |
35 | + | |
36 | + return new Response(JSON.stringify({ success: true, data: { repos, users } }), { status: 200 }); | |
37 | + } catch (error: any) { | |
38 | + return new Response(JSON.stringify({ success: false, error: error.message }), { status: 500 }); | |
39 | + } | |
40 | +}; | |
0 | 41 | |
▼community/src/pages/api/stars/index.ts
+52-0
| @@ -0,0 +1,52 @@ | ||
1 | +import type { APIRoute } from "astro"; | |
2 | +import { getDb } from "@/lib/db"; | |
3 | +import { stars, cachedRepos } from "@/lib/db/schema"; | |
4 | +import { verifyToken } from "@/lib/auth"; | |
5 | +import { nanoid } from "nanoid"; | |
6 | +import { eq, and } from "drizzle-orm"; | |
7 | + | |
8 | +export const prerender = false; | |
9 | + | |
10 | +export const POST: APIRoute = async ({ request, cookies }) => { | |
11 | + try { | |
12 | + const body = await request.json(); | |
13 | + const { repoId, fullName } = body; | |
14 | + if (!repoId && !fullName) { | |
15 | + return new Response(JSON.stringify({ success: false, error: "repoId or fullName required" }), { status: 400 }); | |
16 | + } | |
17 | + | |
18 | + const token = cookies.get("community_session")?.value; | |
19 | + if (!token) return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401 }); | |
20 | + | |
21 | + const user = await verifyToken(token); | |
22 | + if (!user) return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401 }); | |
23 | + | |
24 | + const userId = user.sub; | |
25 | + const db = getDb() as any; | |
26 | + | |
27 | + let targetId = repoId; | |
28 | + if (!targetId && fullName) { | |
29 | + const repo = await db.query.cachedRepos.findFirst({ where: eq(cachedRepos.fullName, fullName) }).catch(() => null); | |
30 | + targetId = repo?.id; | |
31 | + if (!targetId) return new Response(JSON.stringify({ success: false, error: "Repo not cached yet — sync the instance first" }), { status: 404 }); | |
32 | + } | |
33 | + | |
34 | + const existing = await db.query.stars.findFirst({ where: and(eq(stars.userId, userId), eq(stars.repoId, targetId)) }).catch(() => null); | |
35 | + if (existing) { | |
36 | + await db.delete(stars).where(and(eq(stars.userId, userId), eq(stars.repoId, targetId))); | |
37 | + return new Response(JSON.stringify({ success: true, data: { starred: false } }), { headers: { "Content-Type": "application/json" } }); | |
38 | + } | |
39 | + | |
40 | + await db.insert(stars).values({ id: nanoid(), userId, repoId: targetId }); | |
41 | + return new Response(JSON.stringify({ success: true, data: { starred: true } }), { headers: { "Content-Type": "application/json" } }); | |
42 | + } catch (e: any) { | |
43 | + return new Response(JSON.stringify({ success: false, error: e.message }), { status: 500 }); | |
44 | + } | |
45 | +}; | |
46 | + | |
47 | +export const GET: APIRoute = async ({ url }) => { | |
48 | + const repoId = url.searchParams.get("repoId"); | |
49 | + const db = getDb() as any; | |
50 | + const count = await db.query.stars.findMany({ where: repoId ? eq(stars.repoId, repoId) : undefined }).catch(()=>[]); | |
51 | + return new Response(JSON.stringify({ success: true, data: { count: count.length } }), { headers: { "Content-Type": "application/json" } }); | |
52 | +}; | |
0 | 53 | |
▼community/src/pages/auth/login.astro
+118-0
| @@ -0,0 +1,118 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../components/ui/card"; | |
5 | +import { Button } from "../../components/ui/button"; | |
6 | +import { Input } from "../../components/ui/input"; | |
7 | +import { Label } from "../../components/ui/label"; | |
8 | +import { Github, LogIn, Sparkles, Lock, Mail } from "lucide-react"; | |
9 | +import { verifyToken } from "../../lib/auth"; | |
10 | + | |
11 | +const token = Astro.cookies.get("community_session")?.value; | |
12 | +const user = token ? await verifyToken(token) : null; | |
13 | +if (user) { | |
14 | + return Astro.redirect("/dashboard"); | |
15 | +} | |
16 | +--- | |
17 | +<BaseLayout title="Sign In — OpenCodeHub Community"> | |
18 | + <Header client:load user={user} /> | |
19 | + | |
20 | + <main class="container px-4 py-16 max-w-md mx-auto my-auto space-y-6"> | |
21 | + <div class="text-center space-y-2"> | |
22 | + <div class="h-12 w-12 rounded-2xl bg-gradient-to-br from-primary via-indigo-500 to-purple-600 flex items-center justify-center text-white mx-auto shadow-lg shadow-primary/25"> | |
23 | + <Github className="h-6 w-6" /> | |
24 | + </div> | |
25 | + <h1 class="text-2xl font-extrabold tracking-tight text-foreground"> | |
26 | + Welcome Back | |
27 | + </h1> | |
28 | + <p class="text-xs text-muted-foreground"> | |
29 | + Sign in to star repos, follow developers, and manage your nodes. | |
30 | + </p> | |
31 | + </div> | |
32 | + | |
33 | + <Card className="border-border/60 bg-card/60 backdrop-blur-xl shadow-xl"> | |
34 | + <CardContent className="pt-6"> | |
35 | + <form id="login-form" class="space-y-4"> | |
36 | + <div class="space-y-2"> | |
37 | + <Label htmlFor="email" className="text-xs font-semibold">Email Address</Label> | |
38 | + <div class="relative"> | |
39 | + <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
40 | + <Input | |
41 | + id="email" | |
42 | + type="email" | |
43 | + placeholder="[email protected]" | |
44 | + required | |
45 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary h-10 text-sm" | |
46 | + /> | |
47 | + </div> | |
48 | + </div> | |
49 | + | |
50 | + <div class="space-y-2"> | |
51 | + <div class="flex items-center justify-between"> | |
52 | + <Label htmlFor="password" className="text-xs font-semibold">Password</Label> | |
53 | + </div> | |
54 | + <div class="relative"> | |
55 | + <Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
56 | + <Input | |
57 | + id="password" | |
58 | + type="password" | |
59 | + placeholder="••••••••" | |
60 | + required | |
61 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary h-10 text-sm" | |
62 | + /> | |
63 | + </div> | |
64 | + </div> | |
65 | + | |
66 | + <Button type="submit" className="w-full h-10 bg-primary hover:bg-primary/90 text-sm font-semibold shadow-md shadow-primary/20" id="submit-btn"> | |
67 | + <LogIn className="h-4 w-4 mr-2" /> | |
68 | + Sign In | |
69 | + </Button> | |
70 | + | |
71 | + <p class="text-xs text-center text-muted-foreground pt-2"> | |
72 | + Don't have an account?{" "} | |
73 | + <a href="/auth/register" class="text-primary font-semibold hover:underline"> | |
74 | + Create account | |
75 | + </a> | |
76 | + </p> | |
77 | + </form> | |
78 | + </CardContent> | |
79 | + </Card> | |
80 | + </main> | |
81 | + | |
82 | + <script> | |
83 | + import { toast } from "sonner"; | |
84 | + | |
85 | + const form = document.getElementById("login-form") as HTMLFormElement; | |
86 | + const submitBtn = document.getElementById("submit-btn") as HTMLButtonElement; | |
87 | + | |
88 | + form?.addEventListener("submit", async (e) => { | |
89 | + e.preventDefault(); | |
90 | + const email = (document.getElementById("email") as HTMLInputElement).value.trim(); | |
91 | + const password = (document.getElementById("password") as HTMLInputElement).value; | |
92 | + | |
93 | + submitBtn.disabled = true; | |
94 | + submitBtn.innerHTML = `<span class="inline-flex items-center gap-2"><svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Signing in...</span>`; | |
95 | + | |
96 | + try { | |
97 | + const res = await fetch("/api/auth/login", { | |
98 | + method: "POST", | |
99 | + headers: { "Content-Type": "application/json" }, | |
100 | + body: JSON.stringify({ email, password }), | |
101 | + }); | |
102 | + const data = await res.json(); | |
103 | + | |
104 | + if (data.success) { | |
105 | + toast.success("Successfully signed in!"); | |
106 | + setTimeout(() => window.location.href = "/dashboard", 600); | |
107 | + } else { | |
108 | + toast.error(data.error || "Invalid credentials"); | |
109 | + } | |
110 | + } catch (error: any) { | |
111 | + toast.error(error.message || "An error occurred during sign in"); | |
112 | + } finally { | |
113 | + submitBtn.disabled = false; | |
114 | + submitBtn.innerHTML = `<svg class="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" x2="3" y1="12" y2="12"/></svg> Sign In`; | |
115 | + } | |
116 | + }); | |
117 | + </script> | |
118 | +</BaseLayout> | |
0 | 119 | |
▼community/src/pages/auth/register.astro
+130-0
| @@ -0,0 +1,130 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../components/ui/card"; | |
5 | +import { Button } from "../../components/ui/button"; | |
6 | +import { Input } from "../../components/ui/input"; | |
7 | +import { Label } from "../../components/ui/label"; | |
8 | +import { Github, Sparkles, Lock, Mail, User } from "lucide-react"; | |
9 | +import { verifyToken } from "../../lib/auth"; | |
10 | + | |
11 | +const token = Astro.cookies.get("community_session")?.value; | |
12 | +const user = token ? await verifyToken(token) : null; | |
13 | +if (user) { | |
14 | + return Astro.redirect("/dashboard"); | |
15 | +} | |
16 | +--- | |
17 | +<BaseLayout title="Create Account — OpenCodeHub Community"> | |
18 | + <Header client:load user={user} /> | |
19 | + | |
20 | + <main class="container px-4 py-16 max-w-md mx-auto my-auto space-y-6"> | |
21 | + <div class="text-center space-y-2"> | |
22 | + <div class="h-12 w-12 rounded-2xl bg-gradient-to-br from-primary via-indigo-500 to-purple-600 flex items-center justify-center text-white mx-auto shadow-lg shadow-primary/25"> | |
23 | + <Sparkles className="h-6 w-6" /> | |
24 | + </div> | |
25 | + <h1 class="text-2xl font-extrabold tracking-tight text-foreground"> | |
26 | + Join the Network | |
27 | + </h1> | |
28 | + <p class="text-xs text-muted-foreground"> | |
29 | + Create your Community account to star, follow, and connect nodes. | |
30 | + </p> | |
31 | + </div> | |
32 | + | |
33 | + <Card className="border-border/60 bg-card/60 backdrop-blur-xl shadow-xl"> | |
34 | + <CardContent className="pt-6"> | |
35 | + <form id="register-form" class="space-y-4"> | |
36 | + <div class="space-y-2"> | |
37 | + <Label htmlFor="username" className="text-xs font-semibold">Username</Label> | |
38 | + <div class="relative"> | |
39 | + <User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
40 | + <Input | |
41 | + id="username" | |
42 | + placeholder="octocat" | |
43 | + required | |
44 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary h-10 text-sm" | |
45 | + /> | |
46 | + </div> | |
47 | + </div> | |
48 | + | |
49 | + <div class="space-y-2"> | |
50 | + <Label htmlFor="email" className="text-xs font-semibold">Email Address</Label> | |
51 | + <div class="relative"> | |
52 | + <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
53 | + <Input | |
54 | + id="email" | |
55 | + type="email" | |
56 | + placeholder="[email protected]" | |
57 | + required | |
58 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary h-10 text-sm" | |
59 | + /> | |
60 | + </div> | |
61 | + </div> | |
62 | + | |
63 | + <div class="space-y-2"> | |
64 | + <Label htmlFor="password" className="text-xs font-semibold">Password</Label> | |
65 | + <div class="relative"> | |
66 | + <Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
67 | + <Input | |
68 | + id="password" | |
69 | + type="password" | |
70 | + placeholder="••••••••" | |
71 | + required | |
72 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary h-10 text-sm" | |
73 | + /> | |
74 | + </div> | |
75 | + </div> | |
76 | + | |
77 | + <Button type="submit" className="w-full h-10 bg-primary hover:bg-primary/90 text-sm font-semibold shadow-md shadow-primary/20" id="submit-btn"> | |
78 | + <Sparkles className="h-4 w-4 mr-2" /> | |
79 | + Create Account | |
80 | + </Button> | |
81 | + | |
82 | + <p class="text-xs text-center text-muted-foreground pt-2"> | |
83 | + Already have an account?{" "} | |
84 | + <a href="/auth/login" class="text-primary font-semibold hover:underline"> | |
85 | + Sign in | |
86 | + </a> | |
87 | + </p> | |
88 | + </form> | |
89 | + </CardContent> | |
90 | + </Card> | |
91 | + </main> | |
92 | + | |
93 | + <script> | |
94 | + import { toast } from "sonner"; | |
95 | + | |
96 | + const form = document.getElementById("register-form") as HTMLFormElement; | |
97 | + const submitBtn = document.getElementById("submit-btn") as HTMLButtonElement; | |
98 | + | |
99 | + form?.addEventListener("submit", async (e) => { | |
100 | + e.preventDefault(); | |
101 | + const username = (document.getElementById("username") as HTMLInputElement).value.trim(); | |
102 | + const email = (document.getElementById("email") as HTMLInputElement).value.trim(); | |
103 | + const password = (document.getElementById("password") as HTMLInputElement).value; | |
104 | + | |
105 | + submitBtn.disabled = true; | |
106 | + submitBtn.innerHTML = `<span class="inline-flex items-center gap-2"><svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Creating account...</span>`; | |
107 | + | |
108 | + try { | |
109 | + const res = await fetch("/api/auth/register", { | |
110 | + method: "POST", | |
111 | + headers: { "Content-Type": "application/json" }, | |
112 | + body: JSON.stringify({ username, email, password }), | |
113 | + }); | |
114 | + const data = await res.json(); | |
115 | + | |
116 | + if (data.success) { | |
117 | + toast.success("Account created successfully!"); | |
118 | + setTimeout(() => window.location.href = "/dashboard", 600); | |
119 | + } else { | |
120 | + toast.error(data.error || "Failed to create account"); | |
121 | + } | |
122 | + } catch (error: any) { | |
123 | + toast.error(error.message || "An error occurred during registration"); | |
124 | + } finally { | |
125 | + submitBtn.disabled = false; | |
126 | + submitBtn.innerHTML = `<svg class="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/></svg> Create Account`; | |
127 | + } | |
128 | + }); | |
129 | + </script> | |
130 | +</BaseLayout> | |
0 | 131 | |
▼community/src/pages/dashboard.astro
+204-0
| @@ -0,0 +1,204 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; | |
5 | +import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar"; | |
6 | +import { Button } from "../components/ui/button"; | |
7 | +import { Badge } from "../components/ui/badge"; | |
8 | +import { verifyToken } from "../lib/auth"; | |
9 | +import { getDb } from "../lib/db"; | |
10 | +import { eq, inArray } from "drizzle-orm"; | |
11 | +import { stars, follows, cachedRepos, cachedUsers } from "../lib/db/schema"; | |
12 | +import { | |
13 | + Star, | |
14 | + Users, | |
15 | + GitFork, | |
16 | + Server, | |
17 | + ExternalLink, | |
18 | + Compass, | |
19 | + Sparkles, | |
20 | + LogOut, | |
21 | + BookOpen, | |
22 | + UserCheck | |
23 | +} from "lucide-react"; | |
24 | + | |
25 | +const token = Astro.cookies.get("community_session")?.value; | |
26 | +if (!token) { | |
27 | + return Astro.redirect("/auth/login"); | |
28 | +} | |
29 | + | |
30 | +const user = await verifyToken(token); | |
31 | +if (!user) { | |
32 | + return Astro.redirect("/auth/login"); | |
33 | +} | |
34 | + | |
35 | +const db = getDb() as any; | |
36 | + | |
37 | +// Fetch Starred Repos | |
38 | +const starredRows = await db.query.stars.findMany({ | |
39 | + where: eq(stars.userId, user.sub) | |
40 | +}).catch(() => []); | |
41 | + | |
42 | +const starredRepoIds = starredRows.map((r: any) => r.repoId); | |
43 | +let starredRepos: any[] = []; | |
44 | +if (starredRepoIds.length > 0) { | |
45 | + starredRepos = await db.query.cachedRepos.findMany({ | |
46 | + where: inArray(cachedRepos.id, starredRepoIds), | |
47 | + with: { instance: true } | |
48 | + }).catch(() => []); | |
49 | +} | |
50 | + | |
51 | +// Fetch Followed Users | |
52 | +const followedRows = await db.query.follows.findMany({ | |
53 | + where: eq(follows.followerId, user.sub) | |
54 | +}).catch(() => []); | |
55 | +const followedUsernames = followedRows.map((f: any) => f.followeeId); | |
56 | + | |
57 | +let followedProfiles: any[] = []; | |
58 | +if (followedUsernames.length > 0) { | |
59 | + followedProfiles = await db.query.cachedUsers.findMany({ | |
60 | + where: inArray(cachedUsers.username, followedUsernames), | |
61 | + with: { instance: true } | |
62 | + }).catch(() => []); | |
63 | +} | |
64 | +--- | |
65 | +<BaseLayout title="My Dashboard — OpenCodeHub Community"> | |
66 | + <Header client:load user={user} /> | |
67 | + | |
68 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8 max-w-6xl mx-auto"> | |
69 | + | |
70 | + <!-- User Welcome Banner --> | |
71 | + <div class="rounded-3xl border border-border/60 bg-gradient-to-r from-primary/10 via-card to-card p-6 sm:p-8 flex flex-col sm:flex-row items-center justify-between gap-6 backdrop-blur-xl"> | |
72 | + <div class="flex items-center gap-4 text-center sm:text-left"> | |
73 | + <div class="h-16 w-16 rounded-2xl bg-primary/20 text-primary border border-primary/30 flex items-center justify-center text-2xl font-bold uppercase shadow-inner"> | |
74 | + {user.username?.[0] || "U"} | |
75 | + </div> | |
76 | + <div class="space-y-1"> | |
77 | + <h1 class="text-2xl sm:text-3xl font-extrabold tracking-tight text-foreground"> | |
78 | + Welcome back, @{user.username} | |
79 | + </h1> | |
80 | + <p class="text-xs sm:text-sm text-muted-foreground"> | |
81 | + Your personalized dashboard across the federated OpenCodeHub network. | |
82 | + </p> | |
83 | + </div> | |
84 | + </div> | |
85 | + | |
86 | + <div class="flex items-center gap-3"> | |
87 | + <a href="/explore"> | |
88 | + <Button size="sm" variant="outline" className="gap-1.5 text-xs"> | |
89 | + <Compass className="h-3.5 w-3.5" /> | |
90 | + Explore Repos | |
91 | + </Button> | |
92 | + </a> | |
93 | + <a href="/instances/submit"> | |
94 | + <Button size="sm" className="gap-1.5 text-xs bg-primary hover:bg-primary/90"> | |
95 | + <Sparkles className="h-3.5 w-3.5" /> | |
96 | + Submit Node | |
97 | + </Button> | |
98 | + </a> | |
99 | + </div> | |
100 | + </div> | |
101 | + | |
102 | + <!-- 2-Column Grid: Starred & Following --> | |
103 | + <div class="grid lg:grid-cols-2 gap-8"> | |
104 | + | |
105 | + <!-- Starred Repositories Section --> | |
106 | + <div class="space-y-4"> | |
107 | + <div class="flex items-center justify-between"> | |
108 | + <h2 class="text-lg font-bold text-foreground flex items-center gap-2"> | |
109 | + <Star className="h-5 w-5 text-amber-400 fill-amber-400" /> | |
110 | + Starred Repositories ({starredRepos.length}) | |
111 | + </h2> | |
112 | + </div> | |
113 | + | |
114 | + <div class="space-y-3"> | |
115 | + {starredRepos.length === 0 ? ( | |
116 | + <div class="rounded-2xl border border-dashed border-border/80 bg-card/30 p-8 text-center space-y-2"> | |
117 | + <p class="text-sm font-semibold text-foreground">No starred repositories yet</p> | |
118 | + <p class="text-xs text-muted-foreground">Browse the community network to star projects you like.</p> | |
119 | + <div class="pt-2"> | |
120 | + <a href="/explore"><Button size="sm" variant="outline" className="text-xs">Explore Repos</Button></a> | |
121 | + </div> | |
122 | + </div> | |
123 | + ) : ( | |
124 | + starredRepos.map((repo: any) => ( | |
125 | + <div class="rounded-2xl border border-border/60 bg-card/50 p-4 hover:border-primary/40 transition-all flex items-center justify-between gap-3 shadow-sm"> | |
126 | + <div class="min-w-0 space-y-1"> | |
127 | + <div class="flex items-center gap-2"> | |
128 | + <a href={`/r/${repo.instance?.name || 'node'}/${repo.fullName}`} class="font-bold text-sm text-primary hover:underline truncate"> | |
129 | + {repo.fullName} | |
130 | + </a> | |
131 | + {repo.instance?.name && ( | |
132 | + <span class="text-[10px] px-2 py-0.5 rounded bg-muted text-muted-foreground font-medium"> | |
133 | + {repo.instance.name} | |
134 | + </span> | |
135 | + )} | |
136 | + </div> | |
137 | + <p class="text-xs text-muted-foreground line-clamp-1"> | |
138 | + {repo.description || "No description"} | |
139 | + </p> | |
140 | + </div> | |
141 | + | |
142 | + <div class="flex items-center gap-3 text-xs text-muted-foreground shrink-0"> | |
143 | + <span class="flex items-center gap-1 font-medium"><Star className="h-3 w-3 text-amber-400 fill-amber-400" /> {repo.starCount || 0}</span> | |
144 | + <a href={`/r/${repo.instance?.name || 'node'}/${repo.fullName}`}> | |
145 | + <Button size="sm" variant="ghost" className="h-8 px-2 text-xs">View</Button> | |
146 | + </a> | |
147 | + </div> | |
148 | + </div> | |
149 | + )) | |
150 | + )} | |
151 | + </div> | |
152 | + </div> | |
153 | + | |
154 | + <!-- Followed Profiles Section --> | |
155 | + <div class="space-y-4"> | |
156 | + <div class="flex items-center justify-between"> | |
157 | + <h2 class="text-lg font-bold text-foreground flex items-center gap-2"> | |
158 | + <Users className="h-5 w-5 text-purple-400" /> | |
159 | + Following ({followedProfiles.length}) | |
160 | + </h2> | |
161 | + </div> | |
162 | + | |
163 | + <div class="space-y-3"> | |
164 | + {followedProfiles.length === 0 ? ( | |
165 | + <div class="rounded-2xl border border-dashed border-border/80 bg-card/30 p-8 text-center space-y-2"> | |
166 | + <p class="text-sm font-semibold text-foreground">Not following anyone yet</p> | |
167 | + <p class="text-xs text-muted-foreground">Follow developers across federated nodes to keep up with their public projects.</p> | |
168 | + <div class="pt-2"> | |
169 | + <a href="/explore"><Button size="sm" variant="outline" className="text-xs">Find Developers</Button></a> | |
170 | + </div> | |
171 | + </div> | |
172 | + ) : ( | |
173 | + followedProfiles.map((profile: any) => ( | |
174 | + <div class="rounded-2xl border border-border/60 bg-card/50 p-4 hover:border-primary/40 transition-all flex items-center justify-between gap-3 shadow-sm"> | |
175 | + <div class="flex items-center gap-3 min-w-0"> | |
176 | + <img | |
177 | + src={profile.avatarUrl || `https://api.dicebear.com/7.x/initials/svg?seed=${profile.username}`} | |
178 | + alt={profile.username} | |
179 | + class="h-9 w-9 rounded-full bg-muted border border-border/40 object-cover shrink-0" | |
180 | + /> | |
181 | + <div class="min-w-0"> | |
182 | + <a href={`/u/${profile.username}`} class="font-bold text-sm text-foreground hover:text-primary truncate block"> | |
183 | + @{profile.username} | |
184 | + </a> | |
185 | + <span class="text-xs text-muted-foreground truncate block"> | |
186 | + {profile.instance?.name || "Federated Node"} | |
187 | + </span> | |
188 | + </div> | |
189 | + </div> | |
190 | + | |
191 | + <a href={`/u/${profile.username}`} class="shrink-0"> | |
192 | + <Button size="sm" variant="outline" className="text-xs h-8"> | |
193 | + Profile | |
194 | + </Button> | |
195 | + </a> | |
196 | + </div> | |
197 | + )) | |
198 | + )} | |
199 | + </div> | |
200 | + </div> | |
201 | + | |
202 | + </div> | |
203 | + </main> | |
204 | +</BaseLayout> | |
0 | 205 | |
▼community/src/pages/explore.astro
+397-0
| @@ -0,0 +1,397 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../components/layout/Header"; | |
4 | +import { Input } from "../components/ui/input"; | |
5 | +import { Button } from "../components/ui/button"; | |
6 | +import { Card, CardContent } from "../components/ui/card"; | |
7 | +import { Badge } from "../components/ui/badge"; | |
8 | +import { | |
9 | + Search, Filter, Star, GitFork, BookOpen, Copy, Globe, ExternalLink, | |
10 | + Users, Sparkles, Code2, X, TrendingUp, Clock, Hash, ArrowUpDown | |
11 | +} from "lucide-react"; | |
12 | +import { verifyToken } from "../lib/auth"; | |
13 | +import { getDb } from "../lib/db"; | |
14 | +import { desc, like, or, eq, sql, and, inArray } from "drizzle-orm"; | |
15 | +import { cachedRepos, cachedUsers, instances } from "../lib/db/schema"; | |
16 | + | |
17 | +const token = Astro.cookies.get("community_session")?.value; | |
18 | +const user = token ? await verifyToken(token) : null; | |
19 | + | |
20 | +const q = Astro.url.searchParams.get("q") || ""; | |
21 | +const selectedLang = Astro.url.searchParams.get("lang") || ""; | |
22 | +const selectedTopic = Astro.url.searchParams.get("topic") || ""; | |
23 | +const sortBy = Astro.url.searchParams.get("sort") || "stars"; | |
24 | +const page = Math.max(1, parseInt(Astro.url.searchParams.get("page") || "1")); | |
25 | +const perPage = 20; | |
26 | +const offset = (page - 1) * perPage; | |
27 | + | |
28 | +const db = getDb() as any; | |
29 | + | |
30 | +// Dynamic language list from actual data | |
31 | +const allLangs = await db | |
32 | + .select({ language: cachedRepos.language, count: sql<number>`count(*)` }) | |
33 | + .from(cachedRepos) | |
34 | + .where(sql`${cachedRepos.language} IS NOT NULL AND ${cachedRepos.language} != ''`) | |
35 | + .groupBy(cachedRepos.language) | |
36 | + .orderBy(desc(sql`count(*)`)) | |
37 | + .limit(15) | |
38 | + .catch(() => []); | |
39 | +const popularLanguages = allLangs.filter((l: any) => l.language).map((l: any) => l.language); | |
40 | + | |
41 | +// Dynamic topic list from actual data | |
42 | +const allTopicsRaw = await db.select({ topics: cachedRepos.topics }).from(cachedRepos).where(sql`${cachedRepos.topics} IS NOT NULL AND ${cachedRepos.topics} != '[]'`).catch(() => []); | |
43 | +const topicCounts = new Map<string, number>(); | |
44 | +for (const row of allTopicsRaw) { | |
45 | + try { | |
46 | + const parsed = typeof row.topics === "string" ? JSON.parse(row.topics) : row.topics; | |
47 | + if (Array.isArray(parsed)) { | |
48 | + for (const t of parsed) { | |
49 | + const topic = String(t).trim().toLowerCase(); | |
50 | + if (topic) topicCounts.set(topic, (topicCounts.get(topic) || 0) + 1); | |
51 | + } | |
52 | + } | |
53 | + } catch {} | |
54 | +} | |
55 | +const popularTopics = [...topicCounts.entries()] | |
56 | + .sort((a, b) => b[1] - a[1]) | |
57 | + .slice(0, 12) | |
58 | + .map(([topic, count]) => ({ topic, count })); | |
59 | + | |
60 | +const langColors: Record<string, string> = { | |
61 | + TypeScript: "#3178c6", JavaScript: "#f1e05a", Python: "#3572A5", Go: "#00ADD8", | |
62 | + Rust: "#dea584", Java: "#b07219", "C++": "#f34b7d", C: "#555555", Ruby: "#701516", | |
63 | + PHP: "#4F5D95", Swift: "#F05138", Kotlin: "#A97BFF", Dart: "#00B4AB", | |
64 | + Shell: "#89e051", HTML: "#e34c26", CSS: "#563d7c", Vue: "#41b883", Astro: "#ff5a03", | |
65 | + Scala: "#c22d40", Haskell: "#5e5086", Elixir: "#6e4a7e", Lua: "#000080", | |
66 | + R: "#198CE7", MATLAB: "#e16737", Julia: "#a270ba", Zig: "#ec915c", | |
67 | +}; | |
68 | + | |
69 | +// Build where clause — fix: combine lang+text properly | |
70 | +const searchPattern = `%${q}%`; | |
71 | +const conditions = []; | |
72 | +if (q) { | |
73 | + conditions.push(or(like(cachedRepos.fullName, searchPattern), like(cachedRepos.description, searchPattern))); | |
74 | +} | |
75 | +if (selectedLang) { | |
76 | + conditions.push(eq(cachedRepos.language, selectedLang)); | |
77 | +} | |
78 | +if (selectedTopic) { | |
79 | + conditions.push(sql`${cachedRepos.topics} LIKE ${`%${selectedTopic.toLowerCase()}%`}`); | |
80 | +} | |
81 | + | |
82 | +const repoWhere = conditions.length > 0 | |
83 | + ? conditions.length === 1 ? conditions[0] : and(...conditions) | |
84 | + : undefined; | |
85 | + | |
86 | +// Sorting | |
87 | +let orderBy; | |
88 | +switch (sortBy) { | |
89 | + case "newest": orderBy = [desc(cachedRepos.cachedAt)]; break; | |
90 | + case "updated": orderBy = [desc(cachedRepos.updatedAt)]; break; | |
91 | + case "forks": orderBy = [desc(cachedRepos.forkCount), desc(cachedRepos.starCount)]; break; | |
92 | + case "stars": default: orderBy = [desc(cachedRepos.starCount), desc(cachedRepos.updatedAt)]; break; | |
93 | +} | |
94 | + | |
95 | +// Fetch repos with count | |
96 | +const [repos, countResult] = await Promise.all([ | |
97 | + db.query.cachedRepos.findMany({ | |
98 | + where: repoWhere, | |
99 | + orderBy, | |
100 | + limit: perPage, | |
101 | + offset, | |
102 | + with: { instance: true }, | |
103 | + }).catch(() => []), | |
104 | + db.select({ count: sql<number>`count(*)` }).from(cachedRepos).where(repoWhere).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
105 | +]); | |
106 | +const totalPages = Math.ceil(countResult / perPage); | |
107 | + | |
108 | +// Fetch profiles | |
109 | +const profiles = await db.query.cachedUsers.findMany({ | |
110 | + where: q ? or(like(cachedUsers.username, searchPattern), like(cachedUsers.displayName, searchPattern)) : undefined, | |
111 | + orderBy: [desc(cachedUsers.cachedAt)], | |
112 | + limit: 6, | |
113 | + with: { instance: true }, | |
114 | +}).catch(() => []); | |
115 | + | |
116 | +// Network stats | |
117 | +const [totalRepos, totalUsers, totalInstances] = await Promise.all([ | |
118 | + db.select({ count: sql<number>`count(*)` }).from(cachedRepos).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
119 | + db.select({ count: sql<number>`count(*)` }).from(cachedUsers).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
120 | + db.select({ count: sql<number>`count(*)` }).from(instances).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
121 | +]); | |
122 | + | |
123 | +function buildUrl(params: Record<string, string>) { | |
124 | + const sp = new URLSearchParams(); | |
125 | + if (q) sp.set("q", q); | |
126 | + if (selectedLang) sp.set("lang", selectedLang); | |
127 | + if (selectedTopic) sp.set("topic", selectedTopic); | |
128 | + if (sortBy !== "stars") sp.set("sort", sortBy); | |
129 | + for (const [k, v] of Object.entries(params)) { | |
130 | + if (v === "") sp.delete(k); else sp.set(k, v); | |
131 | + } | |
132 | + const qs = sp.toString(); | |
133 | + return `/explore${qs ? `?${qs}` : ""}`; | |
134 | +} | |
135 | +--- | |
136 | +<BaseLayout title="Explore — OpenCodeHub Community"> | |
137 | + <Header client:load user={user} /> | |
138 | + | |
139 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8"> | |
140 | + | |
141 | + <!-- Header & Search --> | |
142 | + <div class="flex flex-col md:flex-row md:items-center justify-between gap-6 pb-6 border-b border-border/50"> | |
143 | + <div class="space-y-1"> | |
144 | + <h1 class="text-3xl font-extrabold tracking-tight text-foreground flex items-center gap-2.5"> | |
145 | + <BookOpen className="h-7 w-7 text-primary" /> | |
146 | + Explore the Network | |
147 | + </h1> | |
148 | + <p class="text-sm text-muted-foreground"> | |
149 | + Discover public projects and developers from {totalInstances} federated {totalInstances === 1 ? "node" : "nodes"} · {totalRepos} repos · {totalUsers} users | |
150 | + </p> | |
151 | + </div> | |
152 | + <form class="flex items-center gap-2 w-full md:w-auto" action="/explore" method="get"> | |
153 | + {selectedLang && <input type="hidden" name="lang" value={selectedLang} />} | |
154 | + {selectedTopic && <input type="hidden" name="topic" value={selectedTopic} />} | |
155 | + {sortBy !== "stars" && <input type="hidden" name="sort" value={sortBy} />} | |
156 | + <div class="relative flex-1 md:w-80"> | |
157 | + <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
158 | + <Input name="q" defaultValue={q} placeholder="Search repos, descriptions..." className="pl-9 pr-8 bg-card/60 border-border/60" id="search" /> | |
159 | + {q && ( | |
160 | + <a href={buildUrl({ q: "" })} class="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"> | |
161 | + <X className="h-3.5 w-3.5" /> | |
162 | + </a> | |
163 | + )} | |
164 | + </div> | |
165 | + <Button type="submit" size="sm" className="bg-primary hover:bg-primary/90 text-xs font-semibold px-4 h-9">Search</Button> | |
166 | + </form> | |
167 | + </div> | |
168 | + | |
169 | + <!-- Language Filters --> | |
170 | + {popularLanguages.length > 0 && ( | |
171 | + <div class="flex items-center gap-2 overflow-x-auto pb-2 text-xs"> | |
172 | + <span class="text-muted-foreground font-medium flex items-center gap-1 shrink-0 mr-1"> | |
173 | + <Code2 className="h-3.5 w-3.5" /> Languages: | |
174 | + </span> | |
175 | + <a href={buildUrl({ lang: "" })} class={`px-3 py-1.5 rounded-full border transition-colors shrink-0 font-medium ${!selectedLang ? "bg-primary text-primary-foreground border-primary" : "bg-card/60 text-muted-foreground hover:text-foreground border-border/60 hover:bg-accent"}`}>All</a> | |
176 | + {popularLanguages.slice(0, 10).map((lang: string) => { | |
177 | + const isActive = selectedLang.toLowerCase() === lang.toLowerCase(); | |
178 | + return ( | |
179 | + <a href={buildUrl({ lang: isActive ? "" : lang })} class={`px-3 py-1.5 rounded-full border transition-colors shrink-0 flex items-center gap-1.5 font-medium ${isActive ? "bg-primary text-primary-foreground border-primary" : "bg-card/60 text-muted-foreground hover:text-foreground border-border/60 hover:bg-accent"}`}> | |
180 | + <span class="h-2 w-2 rounded-full" style={`background-color: ${langColors[lang] || "#888"}`}></span> | |
181 | + {lang} | |
182 | + </a> | |
183 | + ); | |
184 | + })} | |
185 | + </div> | |
186 | + )} | |
187 | + | |
188 | + <!-- Topic Filters --> | |
189 | + {popularTopics.length > 0 && ( | |
190 | + <div class="flex items-center gap-2 overflow-x-auto pb-2 text-xs"> | |
191 | + <span class="text-muted-foreground font-medium flex items-center gap-1 shrink-0 mr-1"> | |
192 | + <Hash className="h-3.5 w-3.5" /> Topics: | |
193 | + </span> | |
194 | + {popularTopics.map(({ topic, count }) => { | |
195 | + const isActive = selectedTopic.toLowerCase() === topic.toLowerCase(); | |
196 | + return ( | |
197 | + <a href={buildUrl({ topic: isActive ? "" : topic })} class={`px-3 py-1.5 rounded-full border transition-colors shrink-0 font-medium ${isActive ? "bg-primary text-primary-foreground border-primary" : "bg-card/60 text-muted-foreground hover:text-foreground border-border/60 hover:bg-accent"}`}> | |
198 | + #{topic} <span class="text-muted-foreground/60 ml-0.5">{count}</span> | |
199 | + </a> | |
200 | + ); | |
201 | + })} | |
202 | + </div> | |
203 | + )} | |
204 | + | |
205 | + <!-- Active Filters --> | |
206 | + {(q || selectedLang || selectedTopic) && ( | |
207 | + <div class="flex items-center gap-2 flex-wrap text-xs"> | |
208 | + <span class="text-muted-foreground">Active filters:</span> | |
209 | + {q && <Badge variant="secondary" class="gap-1">"{q}" <a href={buildUrl({ q: "" })}><X className="h-3 w-3" /></a></Badge>} | |
210 | + {selectedLang && <Badge variant="secondary" class="gap-1">{selectedLang} <a href={buildUrl({ lang: "" })}><X className="h-3 w-3" /></a></Badge>} | |
211 | + {selectedTopic && <Badge variant="secondary" class="gap-1">#{selectedTopic} <a href={buildUrl({ topic: "" })}><X className="h-3 w-3" /></a></Badge>} | |
212 | + <a href="/explore" class="text-muted-foreground hover:text-foreground ml-2">Clear all</a> | |
213 | + </div> | |
214 | + )} | |
215 | + | |
216 | + <!-- Sort Bar --> | |
217 | + <div class="flex items-center justify-between"> | |
218 | + <h2 class="text-lg font-bold text-foreground flex items-center gap-2"> | |
219 | + {q ? `Results for "${q}"` : selectedLang ? `${selectedLang} Repos` : selectedTopic ? `#${selectedTopic}` : sortBy === "newest" ? "Recently Added" : sortBy === "updated" ? "Recently Updated" : "Trending Repositories"} | |
220 | + <Badge variant="secondary" className="text-[11px]">{countResult} {countResult === 1 ? "repo" : "repos"}</Badge> | |
221 | + </h2> | |
222 | + <div class="flex items-center gap-1 text-xs"> | |
223 | + {[ | |
224 | + { key: "stars", label: "Stars", icon: Star }, | |
225 | + { key: "updated", label: "Updated", icon: Clock }, | |
226 | + { key: "newest", label: "Newest", icon: TrendingUp }, | |
227 | + { key: "forks", label: "Forks", icon: GitFork }, | |
228 | + ].map(({ key, label, icon: Icon }) => ( | |
229 | + <a href={buildUrl({ sort: key })} class={`px-2.5 py-1.5 rounded-md flex items-center gap-1 font-medium transition-colors ${sortBy === key ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground hover:bg-accent"}`}> | |
230 | + <Icon className="h-3 w-3" /> {label} | |
231 | + </a> | |
232 | + ))} | |
233 | + </div> | |
234 | + </div> | |
235 | + | |
236 | + <!-- Main Content --> | |
237 | + <div class="grid lg:grid-cols-3 gap-8"> | |
238 | + <div class="lg:col-span-2 space-y-3.5"> | |
239 | + {repos.length === 0 ? ( | |
240 | + <div class="rounded-2xl border border-dashed border-border/80 bg-card/30 p-10 text-center space-y-3"> | |
241 | + <div class="h-12 w-12 rounded-2xl bg-primary/10 text-primary flex items-center justify-center mx-auto"><BookOpen className="h-6 w-6" /></div> | |
242 | + <h3 class="font-semibold text-base">No repositories found</h3> | |
243 | + <p class="text-xs text-muted-foreground max-w-sm mx-auto"> | |
244 | + {q || selectedLang || selectedTopic ? "Try different search terms or remove filters." : "Submit your OpenCodeHub instance to start aggregating repos."} | |
245 | + </p> | |
246 | + <a href="/instances/submit"><Button size="sm" className="gap-1.5 text-xs"><Sparkles className="h-3.5 w-3.5" /> Submit Node</Button></a> | |
247 | + </div> | |
248 | + ) : ( | |
249 | + repos.map((repo: any) => { | |
250 | + const langColor = langColors[repo.language] || "#888"; | |
251 | + let topics: string[] = []; | |
252 | + try { topics = typeof repo.topics === "string" ? JSON.parse(repo.topics) : repo.topics || []; } catch {} | |
253 | + return ( | |
254 | + <div class="rounded-2xl border border-border/60 bg-card/50 p-5 hover:border-primary/40 hover:bg-card/80 transition-all shadow-sm"> | |
255 | + <div class="flex items-start justify-between gap-3"> | |
256 | + <div class="flex-1 min-w-0 space-y-1.5"> | |
257 | + <div class="flex items-center gap-2 flex-wrap"> | |
258 | + <a href={`/r/${repo.instance?.name || "node"}/${repo.fullName}`} class="font-bold text-base text-primary hover:underline truncate">{repo.fullName}</a> | |
259 | + <Badge variant="outline" className="text-[10px] uppercase font-semibold tracking-wider">{repo.visibility || "public"}</Badge> | |
260 | + {repo.instance?.name && <a href={`/instances/${repo.instanceId}`} class="text-[11px] font-medium text-muted-foreground hover:text-foreground inline-flex items-center gap-1 px-2 py-0.5 rounded bg-muted/60"><Globe className="h-2.5 w-2.5 text-primary" />{repo.instance.name}</a>} | |
261 | + </div> | |
262 | + <p class="text-xs sm:text-sm text-muted-foreground line-clamp-2 leading-relaxed">{repo.description || "No description provided."}</p> | |
263 | + {topics.length > 0 && ( | |
264 | + <div class="flex flex-wrap gap-1.5 pt-1"> | |
265 | + {topics.slice(0, 5).map((t: string) => ( | |
266 | + <a href={buildUrl({ topic: t })} class="text-[10px] px-2 py-0.5 rounded-full bg-primary/5 text-primary border border-primary/10 hover:bg-primary/10 transition-colors">#{t}</a> | |
267 | + ))} | |
268 | + </div> | |
269 | + )} | |
270 | + <div class="flex flex-wrap items-center gap-4 pt-2 text-xs text-muted-foreground"> | |
271 | + {repo.language && <span class="flex items-center gap-1.5 font-medium text-foreground"><span class="h-2.5 w-2.5 rounded-full" style={`background-color: ${langColor}`}></span>{repo.language}</span>} | |
272 | + <span class="flex items-center gap-1"><Star className="h-3.5 w-3.5 text-amber-400 fill-amber-400" /><span class="font-medium">{repo.starCount || 0}</span></span> | |
273 | + <span class="flex items-center gap-1"><GitFork className="h-3.5 w-3.5" /><span class="font-medium">{repo.forkCount || 0}</span></span> | |
274 | + {repo.updatedAt && <span class="text-muted-foreground/60">Updated {new Date(repo.updatedAt).toLocaleDateString()}</span>} | |
275 | + </div> | |
276 | + </div> | |
277 | + <div class="flex items-center gap-1.5 shrink-0"> | |
278 | + <button class="star-btn inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border/60 bg-muted/40 hover:bg-primary/10 hover:border-primary/40 hover:text-primary transition-colors text-xs font-medium" data-repo-id={repo.id} data-full-name={repo.fullName}> | |
279 | + <Star className="h-3.5 w-3.5 text-amber-400" /><span>Star</span> | |
280 | + </button> | |
281 | + <button class="clone-btn inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border/60 bg-muted/40 hover:bg-accent hover:text-foreground transition-colors text-xs font-medium" data-clone-url={repo.httpCloneUrl}> | |
282 | + <Copy className="h-3.5 w-3.5" /><span class="hidden sm:inline">Clone</span> | |
283 | + </button> | |
284 | + </div> | |
285 | + </div> | |
286 | + </div> | |
287 | + ); | |
288 | + }) | |
289 | + )} | |
290 | + | |
291 | + <!-- Pagination --> | |
292 | + {totalPages > 1 && ( | |
293 | + <div class="flex items-center justify-center gap-2 pt-4"> | |
294 | + {page > 1 && <a href={buildUrl({ page: String(page - 1) })} class="px-3 py-1.5 rounded-md border text-xs font-medium hover:bg-accent">Prev</a>} | |
295 | + {Array.from({ length: Math.min(totalPages, 7) }, (_, i) => { | |
296 | + const p = i + 1; | |
297 | + return <a href={buildUrl({ page: String(p) })} class={`px-3 py-1.5 rounded-md text-xs font-medium ${p === page ? "bg-primary text-primary-foreground" : "border hover:bg-accent"}`}>{p}</a>; | |
298 | + })} | |
299 | + {page < totalPages && <a href={buildUrl({ page: String(page + 1) })} class="px-3 py-1.5 rounded-md border text-xs font-medium hover:bg-accent">Next</a>} | |
300 | + </div> | |
301 | + )} | |
302 | + </div> | |
303 | + | |
304 | + <!-- Sidebar --> | |
305 | + <div class="space-y-6"> | |
306 | + <!-- Network Stats --> | |
307 | + <Card className="border-border/60 bg-card/50"> | |
308 | + <CardContent className="p-5 space-y-3"> | |
309 | + <h3 class="text-sm font-bold flex items-center gap-2"><Globe className="h-4 w-4 text-primary" /> Network Stats</h3> | |
310 | + <div class="grid grid-cols-3 gap-3 text-center"> | |
311 | + <div class="p-2 rounded-lg bg-muted/40"><div class="text-lg font-bold text-foreground">{totalInstances}</div><div class="text-[10px] text-muted-foreground">Nodes</div></div> | |
312 | + <div class="p-2 rounded-lg bg-muted/40"><div class="text-lg font-bold text-foreground">{totalRepos}</div><div class="text-[10px] text-muted-foreground">Repos</div></div> | |
313 | + <div class="p-2 rounded-lg bg-muted/40"><div class="text-lg font-bold text-foreground">{totalUsers}</div><div class="text-[10px] text-muted-foreground">Users</div></div> | |
314 | + </div> | |
315 | + </CardContent> | |
316 | + </Card> | |
317 | + | |
318 | + <!-- Profiles --> | |
319 | + <Card className="border-border/60 bg-card/50"> | |
320 | + <CardContent className="p-5 space-y-4"> | |
321 | + <h3 class="text-sm font-bold flex items-center gap-2"><Users className="h-4 w-4 text-purple-400" /> {q ? "Matching Profiles" : "Community Profiles"}</h3> | |
322 | + {profiles.length === 0 ? ( | |
323 | + <p class="text-xs text-muted-foreground">Profiles appear as nodes synchronize.</p> | |
324 | + ) : ( | |
325 | + <div class="space-y-3"> | |
326 | + {profiles.map((profile: any) => ( | |
327 | + <div class="flex items-center justify-between gap-3 p-2 rounded-xl hover:bg-muted/40 transition-colors"> | |
328 | + <div class="flex items-center gap-2.5 min-w-0"> | |
329 | + <img src={profile.avatarUrl || `https://api.dicebear.com/7.x/initials/svg?seed=${profile.username}`} alt="" class="h-8 w-8 rounded-full bg-muted border border-border/40 shrink-0" /> | |
330 | + <div class="min-w-0"> | |
331 | + <a href={`/u/${profile.username}`} class="text-xs font-semibold text-foreground hover:text-primary truncate block">@{profile.username}</a> | |
332 | + <span class="text-[11px] text-muted-foreground truncate block">{profile.instance?.name || "Federated User"}</span> | |
333 | + </div> | |
334 | + </div> | |
335 | + <button class="follow-btn shrink-0 text-[11px] font-medium px-2.5 py-1 rounded-md border border-border/60 hover:bg-primary hover:text-primary-foreground hover:border-primary transition-colors" data-username={profile.username}>Follow</button> | |
336 | + </div> | |
337 | + ))} | |
338 | + </div> | |
339 | + )} | |
340 | + </CardContent> | |
341 | + </Card> | |
342 | + | |
343 | + <!-- How to Contribute --> | |
344 | + <Card className="border-primary/20 bg-gradient-to-br from-primary/5 via-card to-card"> | |
345 | + <CardContent className="p-5 space-y-3"> | |
346 | + <h4 class="text-sm font-bold flex items-center gap-2"><Code2 className="h-4 w-4 text-primary" /> How to Contribute</h4> | |
347 | + <ol class="text-xs text-muted-foreground space-y-2 list-decimal list-inside leading-relaxed"> | |
348 | + <li>Copy the project's <code class="bg-muted px-1 py-0.5 rounded">httpCloneUrl</code></li> | |
349 | + <li>Clone locally to your own workstation</li> | |
350 | + <li>Push to your personal node and open a cross-node PR!</li> | |
351 | + </ol> | |
352 | + <a href="/instances" class="text-xs text-primary font-medium hover:underline inline-flex items-center gap-1 pt-1">Browse all nodes <ExternalLink className="h-3 w-3" /></a> | |
353 | + </CardContent> | |
354 | + </Card> | |
355 | + </div> | |
356 | + </div> | |
357 | + </main> | |
358 | + | |
359 | + <script> | |
360 | + import { toast } from "sonner"; | |
361 | + document.querySelectorAll('.clone-btn').forEach(btn => { | |
362 | + btn.addEventListener('click', (e) => { | |
363 | + const url = (e.currentTarget as HTMLButtonElement).dataset.cloneUrl; | |
364 | + if (url) { navigator.clipboard.writeText(url); toast.success("Clone URL copied!"); } | |
365 | + }); | |
366 | + }); | |
367 | + document.querySelectorAll('.star-btn').forEach(btn => { | |
368 | + btn.addEventListener('click', async (e) => { | |
369 | + const t = e.currentTarget as HTMLButtonElement; | |
370 | + try { | |
371 | + const res = await fetch('/api/stars', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repoId: t.dataset.repoId, fullName: t.dataset.fullName }) }); | |
372 | + const data = await res.json(); | |
373 | + if (data.success) { | |
374 | + t.classList.toggle("bg-primary/20", data.data?.starred); | |
375 | + t.classList.toggle("text-primary", data.data?.starred); | |
376 | + toast.success(data.data?.starred ? `Starred ${t.dataset.fullName}` : `Unstarred ${t.dataset.fullName}`); | |
377 | + } else { toast.error(data.error?.message || "Failed"); } | |
378 | + } catch { toast.error("Network error"); } | |
379 | + }); | |
380 | + }); | |
381 | + document.querySelectorAll('.follow-btn').forEach(btn => { | |
382 | + btn.addEventListener('click', async (e) => { | |
383 | + const t = e.currentTarget as HTMLButtonElement; | |
384 | + try { | |
385 | + const res = await fetch('/api/follows', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ followeeType: 'user', followeeId: t.dataset.username }) }); | |
386 | + const data = await res.json(); | |
387 | + if (data.success) { | |
388 | + const f = data.data?.following; | |
389 | + t.textContent = f ? "Following" : "Follow"; | |
390 | + t.classList.toggle("bg-primary", f); | |
391 | + t.classList.toggle("text-primary-foreground", f); | |
392 | + } else { toast.error(data.error?.message || "Failed"); } | |
393 | + } catch { toast.error("Network error"); } | |
394 | + }); | |
395 | + }); | |
396 | + </script> | |
397 | +</BaseLayout> | |
0 | 398 | |
▼community/src/pages/index.astro
+248-0
| @@ -0,0 +1,248 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../components/ui/card"; | |
5 | +import { Button } from "../components/ui/button"; | |
6 | +import { Badge } from "../components/ui/badge"; | |
7 | +import { | |
8 | + Globe, | |
9 | + Star, | |
10 | + Users, | |
11 | + TrendingUp, | |
12 | + Zap, | |
13 | + Server, | |
14 | + GitFork, | |
15 | + ShieldCheck, | |
16 | + Network, | |
17 | + ArrowRight, | |
18 | + Sparkles, | |
19 | + Layers, | |
20 | + Code2 | |
21 | +} from "lucide-react"; | |
22 | +import { verifyToken } from "../lib/auth"; | |
23 | +import { getDb } from "../lib/db"; | |
24 | +import { instances, cachedRepos, communityUsers } from "../lib/db/schema"; | |
25 | +import { sql, desc } from "drizzle-orm"; | |
26 | + | |
27 | +const token = Astro.cookies.get("community_session")?.value; | |
28 | +const user = token ? await verifyToken(token) : null; | |
29 | + | |
30 | +const db = getDb() as any; | |
31 | +const instancesCount = await db.select({ count: sql<number>`count(*)` }).from(instances).then((res: any) => res[0].count).catch(() => 0); | |
32 | +const reposCount = await db.select({ count: sql<number>`count(*)` }).from(cachedRepos).then((res: any) => res[0].count).catch(() => 0); | |
33 | +const usersCount = await db.select({ count: sql<number>`count(*)` }).from(communityUsers).then((res: any) => res[0].count).catch(() => 0); | |
34 | + | |
35 | +// Fetch a few featured repos for the hero showcase | |
36 | +const featuredRepos = await db.query.cachedRepos.findMany({ | |
37 | + orderBy: [desc(cachedRepos.starCount), desc(cachedRepos.updatedAt)], | |
38 | + limit: 3, | |
39 | + with: { instance: true } | |
40 | +}).catch(() => []); | |
41 | +--- | |
42 | +<BaseLayout title="OpenCodeHub Community — Decentralized Code Aggregator"> | |
43 | + <Header client:load user={user} /> | |
44 | + <main class="space-y-16 sm:space-y-24"> | |
45 | + | |
46 | + <!-- Hero Section --> | |
47 | + <section class="relative overflow-hidden pt-12 pb-20 sm:pt-20 sm:pb-28"> | |
48 | + {/* Ambient background glow */} | |
49 | + <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-gradient-to-tr from-primary/20 via-indigo-500/15 to-purple-600/10 blur-[120px] pointer-events-none -z-10"></div> | |
50 | + | |
51 | + <div class="container px-4 sm:px-8 text-center space-y-8 max-w-4xl mx-auto"> | |
52 | + <div class="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-medium backdrop-blur-sm shadow-sm animate-in fade-in zoom-in duration-500"> | |
53 | + <Zap className="h-3.5 w-3.5 fill-primary" /> | |
54 | + <span>Decentralized & Self-Hosted Hub</span> | |
55 | + </div> | |
56 | + | |
57 | + <h1 class="text-4xl sm:text-6xl font-extrabold tracking-tight leading-[1.15] text-foreground"> | |
58 | + Discover Open Source Beyond <br /> | |
59 | + <span class="bg-gradient-to-r from-blue-400 via-indigo-300 to-purple-400 bg-clip-text text-transparent"> | |
60 | + Centralized Platforms | |
61 | + </span> | |
62 | + </h1> | |
63 | + | |
64 | + <p class="text-base sm:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed"> | |
65 | + Aggregating public repositories, developers, and code from self-hosted OpenCodeHub nodes worldwide. Explore, star, follow, and federate without lock-in. | |
66 | + </p> | |
67 | + | |
68 | + <!-- CTA Action Buttons --> | |
69 | + <div class="flex flex-wrap justify-center items-center gap-4 pt-2"> | |
70 | + <a href="/explore"> | |
71 | + <Button size="lg" className="gap-2 shadow-lg shadow-primary/25 bg-primary hover:bg-primary/90 text-sm font-semibold h-11 px-6"> | |
72 | + <Globe className="h-4 w-4" /> | |
73 | + Explore Repositories | |
74 | + </Button> | |
75 | + </a> | |
76 | + <a href="/instances/submit"> | |
77 | + <Button size="lg" variant="outline" className="gap-2 border-border/80 hover:bg-card text-sm font-medium h-11 px-6"> | |
78 | + <Server className="h-4 w-4 text-primary" /> | |
79 | + Submit Your Node | |
80 | + </Button> | |
81 | + </a> | |
82 | + </div> | |
83 | + | |
84 | + <!-- Live Global Metrics --> | |
85 | + <div class="grid grid-cols-3 gap-3 sm:gap-6 pt-10 max-w-2xl mx-auto"> | |
86 | + <div class="rounded-2xl border border-border/50 bg-card/50 backdrop-blur-md p-4 sm:p-6 text-center hover:border-primary/40 transition-colors shadow-sm"> | |
87 | + <div class="text-2xl sm:text-4xl font-extrabold text-foreground tracking-tight flex items-center justify-center gap-1"> | |
88 | + <span class="bg-gradient-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent"> | |
89 | + {instancesCount || 0} | |
90 | + </span> | |
91 | + </div> | |
92 | + <div class="text-xs sm:text-sm font-medium text-muted-foreground mt-1 flex items-center justify-center gap-1"> | |
93 | + <Server className="h-3 w-3 text-primary" /> | |
94 | + Active Nodes | |
95 | + </div> | |
96 | + </div> | |
97 | + | |
98 | + <div class="rounded-2xl border border-border/50 bg-card/50 backdrop-blur-md p-4 sm:p-6 text-center hover:border-primary/40 transition-colors shadow-sm"> | |
99 | + <div class="text-2xl sm:text-4xl font-extrabold text-foreground tracking-tight"> | |
100 | + <span class="bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent"> | |
101 | + {reposCount || 0} | |
102 | + </span> | |
103 | + </div> | |
104 | + <div class="text-xs sm:text-sm font-medium text-muted-foreground mt-1 flex items-center justify-center gap-1"> | |
105 | + <Code2 className="h-3 w-3 text-indigo-400" /> | |
106 | + Public Repos | |
107 | + </div> | |
108 | + </div> | |
109 | + | |
110 | + <div class="rounded-2xl border border-border/50 bg-card/50 backdrop-blur-md p-4 sm:p-6 text-center hover:border-primary/40 transition-colors shadow-sm"> | |
111 | + <div class="text-2xl sm:text-4xl font-extrabold text-foreground tracking-tight"> | |
112 | + <span class="bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent"> | |
113 | + {usersCount || 0} | |
114 | + </span> | |
115 | + </div> | |
116 | + <div class="text-xs sm:text-sm font-medium text-muted-foreground mt-1 flex items-center justify-center gap-1"> | |
117 | + <Users className="h-3 w-3 text-purple-400" /> | |
118 | + Community Users | |
119 | + </div> | |
120 | + </div> | |
121 | + </div> | |
122 | + </div> | |
123 | + </section> | |
124 | + | |
125 | + <!-- Why Federated / Features Grid --> | |
126 | + <section class="container px-4 sm:px-8"> | |
127 | + <div class="text-center space-y-3 mb-12 max-w-2xl mx-auto"> | |
128 | + <h2 class="text-2xl sm:text-3xl font-bold tracking-tight">Why the OpenCodeHub Network?</h2> | |
129 | + <p class="text-sm sm:text-base text-muted-foreground"> | |
130 | + A truly open collaboration hub designed for self-hosters, sovereign developers, and private organizations. | |
131 | + </p> | |
132 | + </div> | |
133 | + | |
134 | + <div class="grid md:grid-cols-3 gap-6"> | |
135 | + <div class="rounded-2xl border border-border/50 bg-card/40 p-6 space-y-4 hover:border-primary/30 transition-all hover:-translate-y-1 shadow-sm"> | |
136 | + <div class="h-12 w-12 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary"> | |
137 | + <Network className="h-6 w-6" /> | |
138 | + </div> | |
139 | + <h3 class="text-lg font-bold">Federated Discovery</h3> | |
140 | + <p class="text-sm text-muted-foreground leading-relaxed"> | |
141 | + Run your own instance on bare-metal or cloud. Submit your URL once, and your public projects become discoverable globally while remaining entirely on your server. | |
142 | + </p> | |
143 | + </div> | |
144 | + | |
145 | + <div class="rounded-2xl border border-border/50 bg-card/40 p-6 space-y-4 hover:border-primary/30 transition-all hover:-translate-y-1 shadow-sm"> | |
146 | + <div class="h-12 w-12 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-500"> | |
147 | + <Star className="h-6 w-6" /> | |
148 | + </div> | |
149 | + <h3 class="text-lg font-bold">Global Social Graph</h3> | |
150 | + <p class="text-sm text-muted-foreground leading-relaxed"> | |
151 | + Star repositories, follow profiles, and track contributions across different nodes. Your activity seamlessly aggregates into your unified Community dashboard. | |
152 | + </p> | |
153 | + </div> | |
154 | + | |
155 | + <div class="rounded-2xl border border-border/50 bg-card/40 p-6 space-y-4 hover:border-primary/30 transition-all hover:-translate-y-1 shadow-sm"> | |
156 | + <div class="h-12 w-12 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-500"> | |
157 | + <ShieldCheck className="h-6 w-6" /> | |
158 | + </div> | |
159 | + <h3 class="text-lg font-bold">Privacy & Sovereignty</h3> | |
160 | + <p class="text-sm text-muted-foreground leading-relaxed"> | |
161 | + Only explicit <code>visibility=public</code> projects are indexed. Private repositories and credentials never leave your local infrastructure. | |
162 | + </p> | |
163 | + </div> | |
164 | + </div> | |
165 | + </section> | |
166 | + | |
167 | + <!-- How Federation Works (4-step visual flow) --> | |
168 | + <section class="container px-4 sm:px-8"> | |
169 | + <div class="rounded-3xl border border-border/60 bg-card/60 backdrop-blur-xl p-8 sm:p-12 relative overflow-hidden"> | |
170 | + <div class="absolute -right-20 -bottom-20 w-80 h-80 bg-primary/10 blur-[100px] pointer-events-none"></div> | |
171 | + | |
172 | + <div class="space-y-3 mb-10 text-center sm:text-left"> | |
173 | + <Badge variant="outline" className="text-xs uppercase tracking-wider">Simple Architecture</Badge> | |
174 | + <h2 class="text-2xl sm:text-3xl font-bold tracking-tight">How It Works</h2> | |
175 | + <p class="text-sm text-muted-foreground max-w-xl"> | |
176 | + From submitting your URL to global synchronization, the process is zero-configuration and automated. | |
177 | + </p> | |
178 | + </div> | |
179 | + | |
180 | + <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-6"> | |
181 | + <div class="space-y-3 relative"> | |
182 | + <div class="h-10 w-10 rounded-xl bg-primary text-primary-foreground font-bold flex items-center justify-center text-sm shadow-md shadow-primary/20"> | |
183 | + 1 | |
184 | + </div> | |
185 | + <h4 class="font-semibold text-base">Submit Node</h4> | |
186 | + <p class="text-xs text-muted-foreground leading-relaxed"> | |
187 | + Paste your instance URL (e.g. <code>https://code.example.com</code>) at the submission portal. | |
188 | + </p> | |
189 | + </div> | |
190 | + | |
191 | + <div class="space-y-3 relative"> | |
192 | + <div class="h-10 w-10 rounded-xl bg-indigo-500 text-white font-bold flex items-center justify-center text-sm shadow-md shadow-indigo-500/20"> | |
193 | + 2 | |
194 | + </div> | |
195 | + <h4 class="font-semibold text-base">Verification</h4> | |
196 | + <p class="text-xs text-muted-foreground leading-relaxed"> | |
197 | + Our edge worker probes <code>/api/instance</code> to verify health and discover supported features. | |
198 | + </p> | |
199 | + </div> | |
200 | + | |
201 | + <div class="space-y-3 relative"> | |
202 | + <div class="h-10 w-10 rounded-xl bg-purple-500 text-white font-bold flex items-center justify-center text-sm shadow-md shadow-purple-500/20"> | |
203 | + 3 | |
204 | + </div> | |
205 | + <h4 class="font-semibold text-base">Continuous Sync</h4> | |
206 | + <p class="text-xs text-muted-foreground leading-relaxed"> | |
207 | + Public repos, owner profiles, stars, and tags are cached in Turso and refreshed periodically. | |
208 | + </p> | |
209 | + </div> | |
210 | + | |
211 | + <div class="space-y-3 relative"> | |
212 | + <div class="h-10 w-10 rounded-xl bg-pink-500 text-white font-bold flex items-center justify-center text-sm shadow-md shadow-pink-500/20"> | |
213 | + 4 | |
214 | + </div> | |
215 | + <h4 class="font-semibold text-base">Collaborate</h4> | |
216 | + <p class="text-xs text-muted-foreground leading-relaxed"> | |
217 | + Clone directly via <code>httpCloneUrl</code>, star locally, and open cross-node pull requests. | |
218 | + </p> | |
219 | + </div> | |
220 | + </div> | |
221 | + </div> | |
222 | + </section> | |
223 | + | |
224 | + <!-- Bottom CTA Banner --> | |
225 | + <section class="container px-4 sm:px-8 pb-12"> | |
226 | + <div class="rounded-3xl border border-primary/20 bg-gradient-to-r from-primary/10 via-indigo-500/10 to-purple-600/10 p-8 sm:p-12 text-center space-y-6 max-w-4xl mx-auto"> | |
227 | + <h2 class="text-2xl sm:text-3xl font-extrabold tracking-tight">Ready to Connect Your Instance?</h2> | |
228 | + <p class="text-sm sm:text-base text-muted-foreground max-w-lg mx-auto"> | |
229 | + Join the federated network today. It only takes 5 seconds to submit your URL and get listed. | |
230 | + </p> | |
231 | + <div class="flex flex-wrap justify-center gap-3"> | |
232 | + <a href="/instances/submit"> | |
233 | + <Button size="lg" className="gap-2 bg-primary hover:bg-primary/90 shadow-md shadow-primary/20"> | |
234 | + <Sparkles className="h-4 w-4" /> | |
235 | + Submit Instance Now | |
236 | + </Button> | |
237 | + </a> | |
238 | + <a href="/explore"> | |
239 | + <Button size="lg" variant="outline"> | |
240 | + Explore Network | |
241 | + </Button> | |
242 | + </a> | |
243 | + </div> | |
244 | + </div> | |
245 | + </section> | |
246 | + | |
247 | + </main> | |
248 | +</BaseLayout> | |
0 | 249 | |
▼community/src/pages/instances/[id].astro
+227-0
| @@ -0,0 +1,227 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"; | |
5 | +import { Badge } from "../../components/ui/badge"; | |
6 | +import { Button } from "../../components/ui/button"; | |
7 | +import { | |
8 | + Globe, | |
9 | + Star, | |
10 | + GitFork, | |
11 | + ExternalLink, | |
12 | + ArrowLeft, | |
13 | + RefreshCw, | |
14 | + Server, | |
15 | + Copy, | |
16 | + BookOpen, | |
17 | + Activity, | |
18 | + Code2 | |
19 | +} from "lucide-react"; | |
20 | +import { verifyToken } from "../../lib/auth"; | |
21 | +import { getDb } from "../../lib/db"; | |
22 | +import { eq, desc } from "drizzle-orm"; | |
23 | +import { instances, cachedRepos } from "../../lib/db/schema"; | |
24 | + | |
25 | +const token = Astro.cookies.get("community_session")?.value; | |
26 | +const user = token ? await verifyToken(token) : null; | |
27 | +const { id } = Astro.params; | |
28 | + | |
29 | +const db = getDb() as any; | |
30 | + | |
31 | +// Fetch Instance Details | |
32 | +const instance = await db.query.instances.findFirst({ | |
33 | + where: eq(instances.id, id as string) | |
34 | +}).catch(() => null); | |
35 | + | |
36 | +// Fetch Repositories for this instance | |
37 | +const repos = await db.query.cachedRepos.findMany({ | |
38 | + where: eq(cachedRepos.instanceId, id as string), | |
39 | + orderBy: [desc(cachedRepos.starCount), desc(cachedRepos.updatedAt)], | |
40 | + limit: 50 | |
41 | +}).catch(() => []); | |
42 | + | |
43 | +let capabilitiesList: string[] = []; | |
44 | +try { | |
45 | + if (instance?.capabilities) { | |
46 | + capabilitiesList = typeof instance.capabilities === "string" ? JSON.parse(instance.capabilities) : instance.capabilities; | |
47 | + } | |
48 | +} catch {} | |
49 | +--- | |
50 | +<BaseLayout title={`${instance?.name || 'Instance'} — OpenCodeHub Community`}> | |
51 | + <Header client:load user={user} /> | |
52 | + | |
53 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8"> | |
54 | + | |
55 | + <a href="/instances" class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"> | |
56 | + <ArrowLeft className="h-3.5 w-3.5" /> Back to all instances | |
57 | + </a> | |
58 | + | |
59 | + {!instance ? ( | |
60 | + <div class="rounded-3xl border border-dashed border-border/80 bg-card/40 p-12 text-center space-y-3"> | |
61 | + <h2 class="text-xl font-bold">Instance Not Found</h2> | |
62 | + <p class="text-xs text-muted-foreground">The requested node ID does not exist in the community database.</p> | |
63 | + <div class="pt-2"> | |
64 | + <a href="/instances"><Button size="sm">Browse Nodes</Button></a> | |
65 | + </div> | |
66 | + </div> | |
67 | + ) : ( | |
68 | + <> | |
69 | + <!-- Instance Hero Header --> | |
70 | + <div class="rounded-3xl border border-border/60 bg-card/60 backdrop-blur-xl p-6 sm:p-8 space-y-6"> | |
71 | + <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> | |
72 | + <div class="flex items-center gap-4"> | |
73 | + <div class="h-14 w-14 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary shrink-0"> | |
74 | + <Server className="h-7 w-7" /> | |
75 | + </div> | |
76 | + <div> | |
77 | + <div class="flex items-center gap-2 flex-wrap"> | |
78 | + <h1 class="text-2xl sm:text-3xl font-extrabold tracking-tight text-foreground"> | |
79 | + {instance.name} | |
80 | + </h1> | |
81 | + <span class={`inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold border ${ | |
82 | + instance.status === "online" | |
83 | + ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" | |
84 | + : "bg-destructive/10 text-destructive border-destructive/20" | |
85 | + }`}> | |
86 | + <span class={`h-1.5 w-1.5 rounded-full ${instance.status === "online" ? "bg-emerald-400 animate-pulse" : "bg-destructive"}`}></span> | |
87 | + <span class="capitalize">{instance.status || "pending"}</span> | |
88 | + </span> | |
89 | + </div> | |
90 | + <a href={instance.siteUrl} target="_blank" class="text-xs sm:text-sm text-muted-foreground hover:underline inline-flex items-center gap-1 mt-1"> | |
91 | + {instance.siteUrl} <ExternalLink className="h-3 w-3" /> | |
92 | + </a> | |
93 | + </div> | |
94 | + </div> | |
95 | + | |
96 | + <!-- Actions --> | |
97 | + <div class="flex items-center gap-2 shrink-0"> | |
98 | + <Button id="sync-btn" variant="outline" size="sm" className="gap-1.5 text-xs font-medium"> | |
99 | + <RefreshCw className="h-3.5 w-3.5" id="sync-icon" /> | |
100 | + Sync Node | |
101 | + </Button> | |
102 | + <a href={instance.siteUrl} target="_blank" rel="noreferrer"> | |
103 | + <Button size="sm" className="gap-1.5 text-xs font-semibold bg-primary hover:bg-primary/90"> | |
104 | + <ExternalLink className="h-3.5 w-3.5" /> | |
105 | + Visit Instance | |
106 | + </Button> | |
107 | + </a> | |
108 | + </div> | |
109 | + </div> | |
110 | + | |
111 | + <!-- Metadata Badges --> | |
112 | + <div class="flex flex-wrap items-center gap-2 pt-2 border-t border-border/50 text-xs"> | |
113 | + {instance.version && ( | |
114 | + <Badge variant="outline" className="font-mono text-[11px]"> | |
115 | + v{instance.version} | |
116 | + </Badge> | |
117 | + )} | |
118 | + {capabilitiesList.map((cap: string) => ( | |
119 | + <Badge variant="secondary" className="text-[11px] uppercase font-semibold"> | |
120 | + {cap} | |
121 | + </Badge> | |
122 | + ))} | |
123 | + <span class="text-muted-foreground ml-auto text-xs flex items-center gap-1.5"> | |
124 | + <Activity className="h-3.5 w-3.5 text-indigo-400" /> | |
125 | + Last Synced: {instance.lastSyncAt ? new Date(instance.lastSyncAt).toLocaleString() : 'Never'} | |
126 | + </span> | |
127 | + </div> | |
128 | + </div> | |
129 | + | |
130 | + <!-- Repositories Grid --> | |
131 | + <div class="space-y-4"> | |
132 | + <div class="flex items-center justify-between"> | |
133 | + <h2 class="text-xl font-bold flex items-center gap-2"> | |
134 | + <BookOpen className="h-5 w-5 text-primary" /> | |
135 | + Public Repositories ({repos.length}) | |
136 | + </h2> | |
137 | + </div> | |
138 | + | |
139 | + {repos.length === 0 ? ( | |
140 | + <div class="rounded-2xl border border-dashed border-border/80 bg-card/30 p-10 text-center space-y-2"> | |
141 | + <p class="text-sm font-semibold text-foreground">No repositories indexed yet</p> | |
142 | + <p class="text-xs text-muted-foreground">Click "Sync Node" above to fetch the latest public repositories from this server.</p> | |
143 | + </div> | |
144 | + ) : ( | |
145 | + <div class="grid md:grid-cols-2 gap-4"> | |
146 | + {repos.map((r: any) => ( | |
147 | + <div class="rounded-2xl border border-border/60 bg-card/50 p-5 hover:border-primary/40 transition-all shadow-sm space-y-3"> | |
148 | + <div class="flex items-start justify-between gap-2"> | |
149 | + <div class="min-w-0"> | |
150 | + <a href={`/r/${instance.name}/${r.fullName}`} class="font-bold text-base text-primary hover:underline truncate block"> | |
151 | + {r.fullName} | |
152 | + </a> | |
153 | + <p class="text-xs text-muted-foreground mt-1 line-clamp-2"> | |
154 | + {r.description || "No description provided."} | |
155 | + </p> | |
156 | + </div> | |
157 | + <Badge variant="outline" className="text-[10px] uppercase font-semibold shrink-0"> | |
158 | + {r.visibility || "public"} | |
159 | + </Badge> | |
160 | + </div> | |
161 | + | |
162 | + <div class="flex items-center justify-between pt-2 border-t border-border/40 text-xs text-muted-foreground"> | |
163 | + <div class="flex items-center gap-3"> | |
164 | + {r.language && <span class="font-medium text-foreground">{r.language}</span>} | |
165 | + <span class="flex items-center gap-1"><Star className="h-3 w-3 text-amber-400 fill-amber-400" /> {r.starCount || 0}</span> | |
166 | + <span class="flex items-center gap-1"><GitFork className="h-3 w-3" /> {r.forkCount || 0}</span> | |
167 | + </div> | |
168 | + | |
169 | + <div class="flex items-center gap-1.5"> | |
170 | + <button | |
171 | + class="clone-btn text-[11px] font-medium px-2 py-1 rounded border border-border/60 hover:bg-accent transition-colors" | |
172 | + data-clone-url={r.httpCloneUrl} | |
173 | + > | |
174 | + Copy Clone URL | |
175 | + </button> | |
176 | + </div> | |
177 | + </div> | |
178 | + </div> | |
179 | + ))} | |
180 | + </div> | |
181 | + )} | |
182 | + </div> | |
183 | + </> | |
184 | + )} | |
185 | + </main> | |
186 | + | |
187 | + <script define:vars={{ id }}> | |
188 | + import { toast } from "sonner"; | |
189 | + | |
190 | + // Clone URL | |
191 | + document.querySelectorAll('.clone-btn').forEach(btn => { | |
192 | + btn.addEventListener('click', (e) => { | |
193 | + const url = (e.currentTarget as HTMLButtonElement).dataset.cloneUrl; | |
194 | + if (url) { | |
195 | + navigator.clipboard.writeText(url); | |
196 | + toast.success("Clone URL copied to clipboard!"); | |
197 | + } | |
198 | + }); | |
199 | + }); | |
200 | + | |
201 | + // In-place Sync Button | |
202 | + const syncBtn = document.getElementById('sync-btn'); | |
203 | + const syncIcon = document.getElementById('sync-icon'); | |
204 | + | |
205 | + syncBtn?.addEventListener('click', async () => { | |
206 | + syncBtn.setAttribute('disabled', 'true'); | |
207 | + syncIcon?.classList.add('animate-spin'); | |
208 | + toast.info("Triggering background sync..."); | |
209 | + | |
210 | + try { | |
211 | + const res = await fetch(`/api/instances/sync?id=${id}`, { method: 'POST' }); | |
212 | + const data = await res.json(); | |
213 | + if (data.success) { | |
214 | + toast.success(`Synced successfully! ${data.data?.count || 0} repos updated.`); | |
215 | + setTimeout(() => window.location.reload(), 800); | |
216 | + } else { | |
217 | + toast.error(data.error?.message || "Sync failed"); | |
218 | + } | |
219 | + } catch (err) { | |
220 | + toast.error("Network error triggering sync"); | |
221 | + } finally { | |
222 | + syncBtn.removeAttribute('disabled'); | |
223 | + syncIcon?.classList.remove('animate-spin'); | |
224 | + } | |
225 | + }); | |
226 | + </script> | |
227 | +</BaseLayout> | |
0 | 228 | |
▼community/src/pages/instances/index.astro
+179-0
| @@ -0,0 +1,179 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent } from "../../components/ui/card"; | |
5 | +import { Button } from "../../components/ui/button"; | |
6 | +import { Badge } from "../../components/ui/badge"; | |
7 | +import { | |
8 | + Globe, | |
9 | + Plus, | |
10 | + Server, | |
11 | + Activity, | |
12 | + Users, | |
13 | + BookOpen, | |
14 | + ExternalLink, | |
15 | + ArrowRight, | |
16 | + ShieldCheck, | |
17 | + CheckCircle2, | |
18 | + AlertCircle | |
19 | +} from "lucide-react"; | |
20 | +import { verifyToken } from "../../lib/auth"; | |
21 | +import { getDb } from "../../lib/db"; | |
22 | +import { desc } from "drizzle-orm"; | |
23 | +import { instances } from "../../lib/db/schema"; | |
24 | + | |
25 | +const token = Astro.cookies.get("community_session")?.value; | |
26 | +const user = token ? await verifyToken(token) : null; | |
27 | + | |
28 | +const db = getDb() as any; | |
29 | +const allInstances = await db.query.instances.findMany({ | |
30 | + orderBy: [desc(instances.repoCount)] | |
31 | +}).catch(() => []); | |
32 | +--- | |
33 | +<BaseLayout title="Federated Instances — OpenCodeHub Community"> | |
34 | + <Header client:load user={user} /> | |
35 | + | |
36 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8"> | |
37 | + | |
38 | + <!-- Top Header --> | |
39 | + <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-border/50"> | |
40 | + <div class="space-y-1"> | |
41 | + <h1 class="text-3xl font-extrabold tracking-tight text-foreground flex items-center gap-2.5"> | |
42 | + <Server className="h-7 w-7 text-primary" /> | |
43 | + Federated Nodes | |
44 | + </h1> | |
45 | + <p class="text-sm text-muted-foreground"> | |
46 | + Every registered OpenCodeHub instance across the decentralized ecosystem. | |
47 | + </p> | |
48 | + </div> | |
49 | + | |
50 | + <a href="/instances/submit"> | |
51 | + <Button className="gap-2 bg-primary hover:bg-primary/90 shadow-md shadow-primary/20 text-xs font-semibold"> | |
52 | + <Plus className="h-4 w-4" /> | |
53 | + Submit Node | |
54 | + </Button> | |
55 | + </a> | |
56 | + </div> | |
57 | + | |
58 | + <!-- Instances Grid --> | |
59 | + {allInstances.length === 0 ? ( | |
60 | + <div class="rounded-3xl border border-dashed border-border/80 bg-card/40 p-12 text-center space-y-4 max-w-xl mx-auto"> | |
61 | + <div class="h-14 w-14 rounded-2xl bg-primary/10 text-primary flex items-center justify-center mx-auto shadow-inner"> | |
62 | + <Globe className="h-7 w-7" /> | |
63 | + </div> | |
64 | + <div class="space-y-1"> | |
65 | + <h3 class="font-bold text-lg text-foreground">No instances registered yet</h3> | |
66 | + <p class="text-xs text-muted-foreground max-w-md mx-auto leading-relaxed"> | |
67 | + Be the first pioneer! Submit your self-hosted OpenCodeHub URL. We will probe <code>/api/instance</code> and start aggregating your public repos. | |
68 | + </p> | |
69 | + </div> | |
70 | + <div class="pt-2"> | |
71 | + <a href="/instances/submit"> | |
72 | + <Button size="sm" className="gap-1.5 text-xs"> | |
73 | + <Plus className="h-3.5 w-3.5" /> | |
74 | + Submit Your Instance | |
75 | + </Button> | |
76 | + </a> | |
77 | + </div> | |
78 | + </div> | |
79 | + ) : ( | |
80 | + <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
81 | + {allInstances.map((instance: any) => { | |
82 | + const isOnline = instance.status === "online"; | |
83 | + let capabilitiesList: string[] = []; | |
84 | + try { | |
85 | + if (instance.capabilities) { | |
86 | + capabilitiesList = typeof instance.capabilities === "string" ? JSON.parse(instance.capabilities) : instance.capabilities; | |
87 | + } | |
88 | + } catch {} | |
89 | + | |
90 | + return ( | |
91 | + <div class="rounded-2xl border border-border/60 bg-card/60 backdrop-blur-sm p-6 hover:border-primary/40 hover:bg-card/90 transition-all shadow-sm flex flex-col justify-between gap-6 group"> | |
92 | + <div class="space-y-4"> | |
93 | + <div class="flex items-start justify-between gap-3"> | |
94 | + <div class="flex items-center gap-3"> | |
95 | + <div class="h-10 w-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary shrink-0"> | |
96 | + <Server className="h-5 w-5" /> | |
97 | + </div> | |
98 | + <div class="min-w-0"> | |
99 | + <a href={`/instances/${instance.id}`} class="font-bold text-base text-foreground hover:text-primary transition-colors truncate block"> | |
100 | + {instance.name} | |
101 | + </a> | |
102 | + <a href={instance.siteUrl} target="_blank" class="text-xs text-muted-foreground hover:underline truncate block"> | |
103 | + {instance.siteUrl} | |
104 | + </a> | |
105 | + </div> | |
106 | + </div> | |
107 | + | |
108 | + <!-- Status Pulse Pill --> | |
109 | + <div class={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-semibold border ${ | |
110 | + isOnline | |
111 | + ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" | |
112 | + : "bg-destructive/10 text-destructive border-destructive/20" | |
113 | + }`}> | |
114 | + <span class={`h-1.5 w-1.5 rounded-full ${isOnline ? "bg-emerald-400 animate-pulse" : "bg-destructive"}`}></span> | |
115 | + <span class="capitalize">{instance.status || "pending"}</span> | |
116 | + </div> | |
117 | + </div> | |
118 | + | |
119 | + <!-- Capabilities Badges --> | |
120 | + <div class="flex flex-wrap gap-1.5 pt-1"> | |
121 | + {instance.version && ( | |
122 | + <Badge variant="outline" className="text-[10px] font-mono"> | |
123 | + v{instance.version} | |
124 | + </Badge> | |
125 | + )} | |
126 | + {capabilitiesList.slice(0, 3).map((cap: string) => ( | |
127 | + <Badge variant="secondary" className="text-[10px] uppercase font-semibold tracking-wider"> | |
128 | + {cap} | |
129 | + </Badge> | |
130 | + ))} | |
131 | + </div> | |
132 | + </div> | |
133 | + | |
134 | + <!-- Bottom Stats & Action Bar --> | |
135 | + <div class="pt-4 border-t border-border/50 flex flex-col gap-3"> | |
136 | + <div class="grid grid-cols-2 gap-2 text-xs text-muted-foreground"> | |
137 | + <div class="flex items-center gap-1.5"> | |
138 | + <BookOpen className="h-3.5 w-3.5 text-primary" /> | |
139 | + <span class="font-medium text-foreground">{instance.repoCount || 0}</span> repos | |
140 | + </div> | |
141 | + <div class="flex items-center gap-1.5"> | |
142 | + <Activity className="h-3.5 w-3.5 text-indigo-400" /> | |
143 | + <span>{instance.lastSyncAt ? new Date(instance.lastSyncAt).toLocaleDateString() : 'Pending'}</span> | |
144 | + </div> | |
145 | + </div> | |
146 | + | |
147 | + <div class="grid grid-cols-2 gap-2 pt-1"> | |
148 | + <a href={`/instances/${instance.id}`} class="w-full"> | |
149 | + <Button size="sm" variant="outline" className="w-full text-xs gap-1 hover:bg-primary hover:text-primary-foreground hover:border-primary"> | |
150 | + Explore Node | |
151 | + <ArrowRight className="h-3 w-3" /> | |
152 | + </Button> | |
153 | + </a> | |
154 | + <a href={instance.siteUrl} target="_blank" rel="noreferrer" class="w-full"> | |
155 | + <Button size="sm" variant="ghost" className="w-full text-xs gap-1 text-muted-foreground hover:text-foreground"> | |
156 | + <ExternalLink className="h-3 w-3" /> | |
157 | + Visit Site | |
158 | + </Button> | |
159 | + </a> | |
160 | + </div> | |
161 | + </div> | |
162 | + </div> | |
163 | + ); | |
164 | + })} | |
165 | + </div> | |
166 | + )} | |
167 | + | |
168 | + <!-- Privacy Note Box --> | |
169 | + <div class="rounded-2xl border border-border/60 bg-muted/20 p-5 text-xs text-muted-foreground flex items-start gap-3"> | |
170 | + <ShieldCheck className="h-5 w-5 text-primary shrink-0 mt-0.5" /> | |
171 | + <div class="space-y-1"> | |
172 | + <span class="font-semibold text-foreground">Zero-Access Privacy Guarantee:</span> | |
173 | + <p class="leading-relaxed"> | |
174 | + Only public repositories (<code>visibility=public</code>) and public author identities are aggregated. Internal codebases, private keys, and user tokens are completely untouched. | |
175 | + </p> | |
176 | + </div> | |
177 | + </div> | |
178 | + </main> | |
179 | +</BaseLayout> | |
0 | 180 | |
▼community/src/pages/instances/submit.astro
+162-0
| @@ -0,0 +1,162 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../components/ui/card"; | |
5 | +import { Button } from "../../components/ui/button"; | |
6 | +import { Input } from "../../components/ui/input"; | |
7 | +import { Label } from "../../components/ui/label"; | |
8 | +import { | |
9 | + Server, | |
10 | + Sparkles, | |
11 | + ShieldCheck, | |
12 | + CheckCircle2, | |
13 | + ArrowLeft, | |
14 | + Globe, | |
15 | + Code2, | |
16 | + HelpCircle, | |
17 | + Loader2 | |
18 | +} from "lucide-react"; | |
19 | +import { verifyToken } from "../../lib/auth"; | |
20 | + | |
21 | +const token = Astro.cookies.get("community_session")?.value; | |
22 | +const user = token ? await verifyToken(token) : null; | |
23 | +--- | |
24 | +<BaseLayout title="Submit Federated Node — OpenCodeHub Community"> | |
25 | + <Header client:load user={user} /> | |
26 | + | |
27 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 max-w-2xl mx-auto space-y-8"> | |
28 | + | |
29 | + <a href="/instances" class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"> | |
30 | + <ArrowLeft className="h-3.5 w-3.5" /> Back to Instances | |
31 | + </a> | |
32 | + | |
33 | + <div class="space-y-2"> | |
34 | + <h1 class="text-3xl font-extrabold tracking-tight text-foreground flex items-center gap-2.5"> | |
35 | + <Server className="h-7 w-7 text-primary" /> | |
36 | + Connect Your Node | |
37 | + </h1> | |
38 | + <p class="text-sm text-muted-foreground"> | |
39 | + Submit your self-hosted OpenCodeHub URL to join the global federated mesh. | |
40 | + </p> | |
41 | + </div> | |
42 | + | |
43 | + <!-- Main Submission Card --> | |
44 | + <Card className="border-border/60 bg-card/60 backdrop-blur-xl shadow-lg"> | |
45 | + <CardHeader> | |
46 | + <CardTitle className="text-lg">Instance Endpoint</CardTitle> | |
47 | + <CardDescription className="text-xs"> | |
48 | + Must be a publicly reachable OpenCodeHub instance. We verify by querying <code>GET /api/instance</code>. | |
49 | + </CardDescription> | |
50 | + </CardHeader> | |
51 | + | |
52 | + <CardContent> | |
53 | + <form id="submit-form" class="space-y-5"> | |
54 | + <div class="space-y-2"> | |
55 | + <Label htmlFor="url" className="text-xs font-semibold">Instance URL</Label> | |
56 | + <div class="relative"> | |
57 | + <Globe className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
58 | + <Input | |
59 | + id="url" | |
60 | + name="url" | |
61 | + placeholder="https://code.example.com" | |
62 | + required | |
63 | + type="url" | |
64 | + className="pl-9 bg-card/50 border-border/60 focus:border-primary text-sm h-11" | |
65 | + /> | |
66 | + </div> | |
67 | + <p class="text-[11px] text-muted-foreground"> | |
68 | + Example: <code>https://code.mysite.com</code> or <code>https://opencodehub.org</code> | |
69 | + </p> | |
70 | + </div> | |
71 | + | |
72 | + <Button type="submit" className="w-full h-11 bg-primary hover:bg-primary/90 text-sm font-semibold shadow-md shadow-primary/20" id="submit-btn"> | |
73 | + <Sparkles className="h-4 w-4 mr-2" /> | |
74 | + Verify & Register Node | |
75 | + </Button> | |
76 | + | |
77 | + <!-- Result Alert Box --> | |
78 | + <div id="result" class="text-xs hidden rounded-xl border p-4 space-y-2 animate-in fade-in"></div> | |
79 | + </form> | |
80 | + </CardContent> | |
81 | + </Card> | |
82 | + | |
83 | + <!-- Information Accordion / Card --> | |
84 | + <div class="rounded-2xl border border-border/60 bg-card/30 p-6 space-y-4 text-xs text-muted-foreground"> | |
85 | + <h4 class="font-bold text-foreground text-sm flex items-center gap-2"> | |
86 | + <ShieldCheck className="h-4 w-4 text-emerald-400" /> | |
87 | + Federation Architecture & Privacy | |
88 | + </h4> | |
89 | + | |
90 | + <div class="space-y-3 leading-relaxed"> | |
91 | + <div class="flex gap-2.5"> | |
92 | + <CheckCircle2 className="h-4 w-4 text-primary shrink-0 mt-0.5" /> | |
93 | + <span> | |
94 | + <b>Public Scope:</b> We only fetch repositories where <code>visibility === "public"</code> and public creator profiles. Private codebases are never indexed or logged. | |
95 | + </span> | |
96 | + </div> | |
97 | + <div class="flex gap-2.5"> | |
98 | + <CheckCircle2 className="h-4 w-4 text-primary shrink-0 mt-0.5" /> | |
99 | + <span> | |
100 | + <b>Cross-Instance Contribution:</b> Developers can clone your repositories via <code>httpCloneUrl</code> directly from your server without requiring an account on this hub. | |
101 | + </span> | |
102 | + </div> | |
103 | + </div> | |
104 | + </div> | |
105 | + </main> | |
106 | + | |
107 | + <script> | |
108 | + import { toast } from "sonner"; | |
109 | + | |
110 | + const form = document.getElementById('submit-form') as HTMLFormElement; | |
111 | + const btn = document.getElementById('submit-btn') as HTMLButtonElement; | |
112 | + const result = document.getElementById('result') as HTMLDivElement; | |
113 | + | |
114 | + form?.addEventListener('submit', async (e) => { | |
115 | + e.preventDefault(); | |
116 | + let url = (document.getElementById('url') as HTMLInputElement).value.trim(); | |
117 | + if (!url) return; | |
118 | + | |
119 | + if (!url.startsWith('http://') && !url.startsWith('https://')) { | |
120 | + url = 'https://' + url; | |
121 | + } | |
122 | + | |
123 | + btn.disabled = true; | |
124 | + btn.innerHTML = `<span class="inline-flex items-center gap-2"><svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Probing /api/instance...</span>`; | |
125 | + result.classList.add('hidden'); | |
126 | + | |
127 | + try { | |
128 | + const res = await fetch('/api/instances/submit', { | |
129 | + method: 'POST', | |
130 | + headers: { 'Content-Type': 'application/json' }, | |
131 | + body: JSON.stringify({ url }) | |
132 | + }); | |
133 | + const data = await res.json(); | |
134 | + result.classList.remove('hidden'); | |
135 | + | |
136 | + if (res.ok && data.success) { | |
137 | + result.className = 'text-xs rounded-xl border p-4 bg-emerald-500/10 border-emerald-500/30 text-emerald-300'; | |
138 | + result.innerHTML = ` | |
139 | + <div class="font-bold text-sm text-emerald-200">✓ Successfully Verified & Connected!</div> | |
140 | + <p>Instance <b>${data.data.name}</b> (${data.data.siteUrl}) is now live. ${data.data.repoCount ?? 0} public repositories are being synchronized.</p> | |
141 | + <div class="pt-2"> | |
142 | + <a href="/instances/${data.data.id}" class="underline font-semibold text-emerald-100 hover:text-white">View Instance Page →</a> | |
143 | + </div> | |
144 | + `; | |
145 | + toast.success("Instance successfully registered!"); | |
146 | + } else { | |
147 | + result.className = 'text-xs rounded-xl border p-4 bg-destructive/10 border-destructive/30 text-destructive'; | |
148 | + result.textContent = data.error?.message || data.message || data.error || 'Verification failed. Make sure /api/instance is accessible.'; | |
149 | + toast.error("Failed to verify instance"); | |
150 | + } | |
151 | + } catch (err: any) { | |
152 | + result.classList.remove('hidden'); | |
153 | + result.className = 'text-xs rounded-xl border p-4 bg-destructive/10 border-destructive/30 text-destructive'; | |
154 | + result.textContent = err.message || 'Network error reaching the hub.'; | |
155 | + toast.error("Network error"); | |
156 | + } finally { | |
157 | + btn.disabled = false; | |
158 | + btn.innerHTML = `<svg class="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/></svg> Verify & Register Node`; | |
159 | + } | |
160 | + }); | |
161 | + </script> | |
162 | +</BaseLayout> | |
0 | 163 | |
▼community/src/pages/r/[instance]/[owner]/[repo].astro
+282-0
| @@ -0,0 +1,282 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../../../components/layout/Header"; | |
4 | +import { Card, CardContent, CardHeader, CardTitle } from "../../../../components/ui/card"; | |
5 | +import { Button } from "../../../../components/ui/button"; | |
6 | +import { Badge } from "../../../../components/ui/badge"; | |
7 | +import { | |
8 | + Star, | |
9 | + GitFork, | |
10 | + Copy, | |
11 | + ExternalLink, | |
12 | + Code, | |
13 | + BookOpen, | |
14 | + ArrowLeft, | |
15 | + Terminal, | |
16 | + Globe, | |
17 | + Check, | |
18 | + ShieldAlert, | |
19 | + Server | |
20 | +} from "lucide-react"; | |
21 | +import { verifyToken } from "../../../../lib/auth"; | |
22 | +import { getDb } from "../../../../lib/db"; | |
23 | +import { eq, and } from "drizzle-orm"; | |
24 | +import { cachedRepos, instances, stars } from "../../../../lib/db/schema"; | |
25 | + | |
26 | +const token = Astro.cookies.get("community_session")?.value; | |
27 | +const user = token ? await verifyToken(token) : null; | |
28 | +const { instance, owner, repo } = Astro.params; | |
29 | +const fullName = `${owner}/${repo}`; | |
30 | + | |
31 | +const db = getDb() as any; | |
32 | + | |
33 | +// Find the instance first | |
34 | +const instanceData = await db.query.instances.findFirst({ | |
35 | + where: eq(instances.name, instance as string) | |
36 | +}).catch(() => null); | |
37 | + | |
38 | +// Then find the repo | |
39 | +let repoData = null; | |
40 | +if (instanceData) { | |
41 | + repoData = await db.query.cachedRepos.findFirst({ | |
42 | + where: and( | |
43 | + eq(cachedRepos.instanceId, instanceData.id), | |
44 | + eq(cachedRepos.fullName, fullName) | |
45 | + ) | |
46 | + }).catch(() => null); | |
47 | +} | |
48 | + | |
49 | +// Check if current user has starred this repo | |
50 | +let isStarred = false; | |
51 | +if (user && repoData) { | |
52 | + const existingStar = await db.query.stars.findFirst({ | |
53 | + where: and( | |
54 | + eq(stars.userId, user.sub), | |
55 | + eq(stars.repoId, repoData.id) | |
56 | + ) | |
57 | + }).catch(() => null); | |
58 | + isStarred = !!existingStar; | |
59 | +} | |
60 | + | |
61 | +const starCount = repoData?.starCount ?? 0; | |
62 | +const forkCount = repoData?.forkCount ?? 0; | |
63 | +const httpCloneUrl = repoData?.httpCloneUrl ?? `https://${instance}/${fullName}.git`; | |
64 | +const language = repoData?.language ?? "Unknown"; | |
65 | +const description = repoData?.description ?? "No description provided."; | |
66 | +--- | |
67 | +<BaseLayout title={`${fullName} — OpenCodeHub Community`}> | |
68 | + <Header client:load user={user} /> | |
69 | + | |
70 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8 max-w-5xl mx-auto"> | |
71 | + | |
72 | + <!-- Breadcrumbs --> | |
73 | + <div class="flex items-center gap-2 text-xs text-muted-foreground flex-wrap"> | |
74 | + <a href="/explore" class="hover:text-foreground">Explore</a> | |
75 | + <span>/</span> | |
76 | + {instanceData ? ( | |
77 | + <a href={`/instances/${instanceData.id}`} class="hover:text-foreground flex items-center gap-1"> | |
78 | + <Server className="h-3 w-3 text-primary" /> | |
79 | + {instance} | |
80 | + </a> | |
81 | + ) : ( | |
82 | + <span>{instance}</span> | |
83 | + )} | |
84 | + <span>/</span> | |
85 | + <a href={`/u/${owner}`} class="hover:text-foreground">@{owner}</a> | |
86 | + <span>/</span> | |
87 | + <span class="text-foreground font-semibold">{repo}</span> | |
88 | + </div> | |
89 | + | |
90 | + <!-- Repository Header --> | |
91 | + <div class="rounded-3xl border border-border/60 bg-card/60 backdrop-blur-xl p-6 sm:p-8 space-y-6"> | |
92 | + <div class="flex flex-col sm:flex-row sm:items-start justify-between gap-4"> | |
93 | + <div class="space-y-2"> | |
94 | + <div class="flex items-center gap-3 flex-wrap"> | |
95 | + <h1 class="text-2xl sm:text-3xl font-extrabold tracking-tight text-foreground flex items-center gap-2"> | |
96 | + <BookOpen className="h-7 w-7 text-primary shrink-0" /> | |
97 | + {fullName} | |
98 | + </h1> | |
99 | + <Badge variant="outline" className="text-xs uppercase font-semibold"> | |
100 | + {repoData?.visibility || "public"} | |
101 | + </Badge> | |
102 | + </div> | |
103 | + | |
104 | + <p class="text-sm sm:text-base text-muted-foreground max-w-2xl leading-relaxed"> | |
105 | + {description} | |
106 | + </p> | |
107 | + </div> | |
108 | + | |
109 | + <!-- Action Buttons --> | |
110 | + <div class="flex items-center gap-2 shrink-0"> | |
111 | + <Button | |
112 | + id="star-btn" | |
113 | + variant="outline" | |
114 | + size="sm" | |
115 | + className={`gap-1.5 text-xs font-semibold ${isStarred ? "bg-amber-500/15 text-amber-400 border-amber-500/40" : ""}`} | |
116 | + data-repo-id={repoData?.id} | |
117 | + data-full-name={fullName} | |
118 | + > | |
119 | + <Star className={`h-4 w-4 ${isStarred ? "text-amber-400 fill-amber-400" : "text-amber-400"}`} /> | |
120 | + <span id="star-label">{isStarred ? "Starred" : "Star"}</span> | |
121 | + </Button> | |
122 | + | |
123 | + <a href={`${instanceData?.siteUrl || `https://${instance}`}/${fullName}`} target="_blank" rel="noreferrer"> | |
124 | + <Button size="sm" className="gap-1.5 text-xs font-semibold bg-primary hover:bg-primary/90"> | |
125 | + <ExternalLink className="h-3.5 w-3.5" /> | |
126 | + View Origin | |
127 | + </Button> | |
128 | + </a> | |
129 | + </div> | |
130 | + </div> | |
131 | + | |
132 | + <!-- Quick Git Clone Bar --> | |
133 | + <div class="rounded-xl border border-border/60 bg-muted/30 p-3 flex items-center justify-between gap-3 text-xs"> | |
134 | + <div class="flex items-center gap-2 text-muted-foreground font-mono overflow-x-auto"> | |
135 | + <Terminal className="h-4 w-4 text-primary shrink-0" /> | |
136 | + <span class="text-foreground shrink-0 select-all font-medium">git clone {httpCloneUrl}</span> | |
137 | + </div> | |
138 | + <button | |
139 | + id="copy-clone-btn" | |
140 | + class="shrink-0 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-card border border-border/60 hover:bg-accent text-foreground text-xs font-medium transition-colors" | |
141 | + data-clone-url={`git clone ${httpCloneUrl}`} | |
142 | + > | |
143 | + <Copy className="h-3.5 w-3.5" /> | |
144 | + <span>Copy</span> | |
145 | + </button> | |
146 | + </div> | |
147 | + </div> | |
148 | + | |
149 | + <!-- Main Content & Sidebar --> | |
150 | + <div class="grid lg:grid-cols-3 gap-8"> | |
151 | + | |
152 | + <!-- Left: Instructions & Docs --> | |
153 | + <div class="lg:col-span-2 space-y-6"> | |
154 | + <Card className="border-border/60 bg-card/50"> | |
155 | + <CardHeader> | |
156 | + <CardTitle className="text-base flex items-center gap-2"> | |
157 | + <BookOpen className="h-4 w-4 text-primary" /> | |
158 | + About This Federated Project | |
159 | + </CardTitle> | |
160 | + </CardHeader> | |
161 | + <CardContent className="space-y-4 text-sm text-muted-foreground leading-relaxed"> | |
162 | + <p> | |
163 | + This repository is hosted natively on the <b>{instance}</b> node and aggregated to the community catalog via federated metadata caching. | |
164 | + </p> | |
165 | + <p> | |
166 | + To browse source files, commits, issues, and pull requests directly, click the <b>View Origin</b> button above to visit the home node. | |
167 | + </p> | |
168 | + </CardContent> | |
169 | + </Card> | |
170 | + | |
171 | + <!-- Contribution Card --> | |
172 | + <Card className="border-border/60 bg-card/50"> | |
173 | + <CardHeader> | |
174 | + <CardTitle className="text-base flex items-center gap-2"> | |
175 | + <GitFork className="h-4 w-4 text-indigo-400" /> | |
176 | + How to Fork & Contribute | |
177 | + </CardTitle> | |
178 | + </CardHeader> | |
179 | + <CardContent className="space-y-3 text-xs sm:text-sm text-muted-foreground leading-relaxed"> | |
180 | + <ol class="space-y-2.5 list-decimal list-inside"> | |
181 | + <li>Clone the repository using standard Git: <code class="bg-muted px-1.5 py-0.5 rounded font-mono text-xs">git clone {httpCloneUrl}</code></li> | |
182 | + <li>Make your changes locally and commit them to a new branch.</li> | |
183 | + <li>Push the branch to your own self-hosted OpenCodeHub node.</li> | |
184 | + <li>Submit a cross-node federated pull request to <code class="bg-muted px-1.5 py-0.5 rounded font-mono text-xs">@{owner}</code>.</li> | |
185 | + </ol> | |
186 | + </CardContent> | |
187 | + </Card> | |
188 | + </div> | |
189 | + | |
190 | + <!-- Right: Meta Info --> | |
191 | + <div class="space-y-6"> | |
192 | + <Card className="border-border/60 bg-card/50"> | |
193 | + <CardContent className="p-5 space-y-4"> | |
194 | + <h3 class="text-sm font-bold text-foreground">Project Details</h3> | |
195 | + | |
196 | + <div class="space-y-3 text-xs"> | |
197 | + <div class="flex justify-between items-center py-1 border-b border-border/40"> | |
198 | + <span class="text-muted-foreground flex items-center gap-1.5"><Code className="h-3.5 w-3.5 text-primary" /> Primary Language</span> | |
199 | + <span class="font-semibold text-foreground">{language}</span> | |
200 | + </div> | |
201 | + <div class="flex justify-between items-center py-1 border-b border-border/40"> | |
202 | + <span class="text-muted-foreground flex items-center gap-1.5"><Star className="h-3.5 w-3.5 text-amber-400" /> Stars</span> | |
203 | + <span class="font-semibold text-foreground">{starCount}</span> | |
204 | + </div> | |
205 | + <div class="flex justify-between items-center py-1 border-b border-border/40"> | |
206 | + <span class="text-muted-foreground flex items-center gap-1.5"><GitFork className="h-3.5 w-3.5" /> Forks</span> | |
207 | + <span class="font-semibold text-foreground">{forkCount}</span> | |
208 | + </div> | |
209 | + <div class="flex justify-between items-center py-1"> | |
210 | + <span class="text-muted-foreground flex items-center gap-1.5"><Server className="h-3.5 w-3.5 text-purple-400" /> Host Node</span> | |
211 | + <span class="font-semibold text-foreground">{instance}</span> | |
212 | + </div> | |
213 | + </div> | |
214 | + | |
215 | + <div class="pt-2"> | |
216 | + <a href={`${instanceData?.siteUrl || `https://${instance}`}/${fullName}`} target="_blank" rel="noreferrer" class="w-full block"> | |
217 | + <Button variant="outline" className="w-full text-xs gap-1.5"> | |
218 | + <ExternalLink className="h-3.5 w-3.5" /> | |
219 | + Open on {instance} | |
220 | + </Button> | |
221 | + </a> | |
222 | + </div> | |
223 | + </CardContent> | |
224 | + </Card> | |
225 | + | |
226 | + <!-- Social Disclaimer --> | |
227 | + <div class="rounded-2xl border border-border/60 bg-muted/20 p-4 text-[11px] text-muted-foreground leading-relaxed space-y-1"> | |
228 | + <div class="font-semibold text-foreground">Community Social Actions:</div> | |
229 | + <p> | |
230 | + Stars registered on this hub are stored in the federated catalog. To star directly on the origin server, sign into that node. | |
231 | + </p> | |
232 | + </div> | |
233 | + </div> | |
234 | + | |
235 | + </div> | |
236 | + </main> | |
237 | + | |
238 | + <script> | |
239 | + import { toast } from "sonner"; | |
240 | + | |
241 | + // Copy Clone command | |
242 | + const copyBtn = document.getElementById('copy-clone-btn'); | |
243 | + copyBtn?.addEventListener('click', () => { | |
244 | + const text = copyBtn.dataset.cloneUrl || ""; | |
245 | + navigator.clipboard.writeText(text); | |
246 | + toast.success("Command copied to clipboard!"); | |
247 | + }); | |
248 | + | |
249 | + // Star Toggle | |
250 | + const starBtn = document.getElementById('star-btn'); | |
251 | + const starLabel = document.getElementById('star-label'); | |
252 | + | |
253 | + starBtn?.addEventListener('click', async () => { | |
254 | + const repoId = starBtn.dataset.repoId; | |
255 | + const fullName = starBtn.dataset.fullName; | |
256 | + | |
257 | + try { | |
258 | + const res = await fetch('/api/stars', { | |
259 | + method: 'POST', | |
260 | + headers: { 'Content-Type': 'application/json' }, | |
261 | + body: JSON.stringify({ repoId, fullName }) | |
262 | + }); | |
263 | + const data = await res.json(); | |
264 | + | |
265 | + if (data.success) { | |
266 | + const starred = data.data?.starred; | |
267 | + if (starLabel) starLabel.textContent = starred ? "Starred" : "Star"; | |
268 | + starBtn.classList.toggle("bg-amber-500/15", starred); | |
269 | + starBtn.classList.toggle("text-amber-400", starred); | |
270 | + starBtn.classList.toggle("border-amber-500/40", starred); | |
271 | + toast.success(starred ? `Starred ${fullName}` : `Unstarred ${fullName}`); | |
272 | + } else if (res.status === 401) { | |
273 | + toast.error("Please sign in to star repositories"); | |
274 | + } else { | |
275 | + toast.error(data.error || "Failed to update star"); | |
276 | + } | |
277 | + } catch (err) { | |
278 | + toast.error("Network error"); | |
279 | + } | |
280 | + }); | |
281 | + </script> | |
282 | +</BaseLayout> | |
0 | 283 | |
▼community/src/pages/u/[username].astro
+233-0
| @@ -0,0 +1,233 @@ | ||
1 | +--- | |
2 | +import BaseLayout from "../../layouts/BaseLayout.astro"; | |
3 | +import { Header } from "../../components/layout/Header"; | |
4 | +import { Card, CardContent } from "../../components/ui/card"; | |
5 | +import { Badge } from "../../components/ui/badge"; | |
6 | +import { Button } from "../../components/ui/button"; | |
7 | +import { | |
8 | + Star, GitFork, BookOpen, Globe, ExternalLink, ArrowLeft, | |
9 | + UserPlus, Server, MapPin, Link as LinkIcon, Building, | |
10 | + Users, Calendar, Clock | |
11 | +} from "lucide-react"; | |
12 | +import { verifyToken } from "../../lib/auth"; | |
13 | +import { getDb } from "../../lib/db"; | |
14 | +import { eq, desc, and, sql, count } from "drizzle-orm"; | |
15 | +import { cachedUsers, cachedRepos, follows } from "../../lib/db/schema"; | |
16 | + | |
17 | +const token = Astro.cookies.get("community_session")?.value; | |
18 | +const user = token ? await verifyToken(token) : null; | |
19 | +const { username } = Astro.params; | |
20 | + | |
21 | +const db = getDb() as any; | |
22 | + | |
23 | +const profile = await db.query.cachedUsers.findFirst({ | |
24 | + where: eq(cachedUsers.username, username as string), | |
25 | + with: { instance: true }, | |
26 | +}).catch(() => null); | |
27 | + | |
28 | +const repos = await db.query.cachedRepos.findMany({ | |
29 | + where: eq(cachedRepos.ownerUsername, username as string), | |
30 | + orderBy: [desc(cachedRepos.starCount), desc(cachedRepos.updatedAt)], | |
31 | + with: { instance: true }, | |
32 | +}).catch(() => []); | |
33 | + | |
34 | +// Follower/following counts | |
35 | +const [followersCount, followingCount] = await Promise.all([ | |
36 | + db.select({ count: sql<number>`count(*)` }).from(follows).where(eq(follows.followeeId, username as string)).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
37 | + db.select({ count: sql<number>`count(*)` }).from(follows).where(eq(follows.followerId, username as string)).then((r: any) => r[0]?.count || 0).catch(() => 0), | |
38 | +]); | |
39 | + | |
40 | +let isFollowing = false; | |
41 | +if (user) { | |
42 | + const followRecord = await db.query.follows.findFirst({ | |
43 | + where: and(eq(follows.followerId, user.sub), eq(follows.followeeId, username as string)), | |
44 | + }).catch(() => null); | |
45 | + isFollowing = !!followRecord; | |
46 | +} | |
47 | + | |
48 | +// Activity heatmap from repo updatedAt (proxy) | |
49 | +const matrix = Array(84).fill(0); | |
50 | +const now = new Date(); | |
51 | +repos.forEach((r: any) => { | |
52 | + if (!r.updatedAt) return; | |
53 | + const diffDays = Math.floor(Math.abs(now.getTime() - new Date(r.updatedAt).getTime()) / 86400000); | |
54 | + if (diffDays < 84) matrix[83 - diffDays] += 1; | |
55 | +}); | |
56 | + | |
57 | +// Aggregate stats | |
58 | +const totalStars = repos.reduce((sum: number, r: any) => sum + (r.starCount || 0), 0); | |
59 | +const totalForks = repos.reduce((sum: number, r: any) => sum + (r.forkCount || 0), 0); | |
60 | +const languages = [...new Set(repos.map((r: any) => r.language).filter(Boolean))]; | |
61 | + | |
62 | +const langColors: Record<string, string> = { | |
63 | + TypeScript: "#3178c6", JavaScript: "#f1e05a", Python: "#3572A5", Go: "#00ADD8", | |
64 | + Rust: "#dea584", Java: "#b07219", "C++": "#f34b7d", C: "#555555", Ruby: "#701516", | |
65 | + PHP: "#4F5D95", Swift: "#F05138", Kotlin: "#A97BFF", Dart: "#00B4AB", | |
66 | + Shell: "#89e051", HTML: "#e34c26", CSS: "#563d7c", Vue: "#41b883", | |
67 | +}; | |
68 | + | |
69 | +const displayName = profile?.displayName || username; | |
70 | +const avatarUrl = profile?.avatarUrl || `https://api.dicebear.com/7.x/initials/svg?seed=${username}`; | |
71 | +const instanceUrl = profile?.instance?.url ? `${profile.instance.url}/${username}` : null; | |
72 | +--- | |
73 | +<BaseLayout title={`${displayName} (@${username}) — Community`}> | |
74 | + <Header client:load user={user} /> | |
75 | + | |
76 | + <main class="container px-4 sm:px-8 py-8 sm:py-12 space-y-8 max-w-5xl mx-auto"> | |
77 | + <a href="/explore" class="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"> | |
78 | + <ArrowLeft className="h-3.5 w-3.5" /> Back to Explore | |
79 | + </a> | |
80 | + | |
81 | + <!-- Profile Hero --> | |
82 | + <div class="rounded-3xl border border-border/60 bg-card/60 backdrop-blur-xl p-6 sm:p-8"> | |
83 | + <div class="flex flex-col sm:flex-row items-center sm:items-start gap-6 text-center sm:text-left"> | |
84 | + <img src={avatarUrl} alt={displayName} class="h-24 w-24 rounded-3xl bg-muted border-2 border-border/80 shadow-md object-cover shrink-0" /> | |
85 | + <div class="flex-1 space-y-3"> | |
86 | + <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> | |
87 | + <div> | |
88 | + <h1 class="text-2xl sm:text-3xl font-extrabold tracking-tight">{displayName}</h1> | |
89 | + <div class="text-sm font-medium text-muted-foreground">@{username}</div> | |
90 | + </div> | |
91 | + <div class="flex items-center gap-2 justify-center sm:justify-start"> | |
92 | + <Button id="follow-btn" size="sm" className={`gap-1.5 text-xs font-semibold ${isFollowing ? "bg-muted text-muted-foreground hover:bg-destructive/10 hover:text-destructive" : "bg-primary hover:bg-primary/90"}`} data-username={username}> | |
93 | + <UserPlus className="h-3.5 w-3.5" /> | |
94 | + <span id="follow-label">{isFollowing ? "Following" : "Follow"}</span> | |
95 | + </Button> | |
96 | + {instanceUrl && <a href={instanceUrl} target="_blank" rel="noreferrer"><Button size="sm" variant="outline" className="gap-1.5 text-xs"><ExternalLink className="h-3.5 w-3.5" /> Visit</Button></a>} | |
97 | + </div> | |
98 | + </div> | |
99 | + <p class="text-sm text-muted-foreground max-w-xl leading-relaxed">{profile?.bio || "Decentralized developer on the OpenCodeHub federated mesh."}</p> | |
100 | + <div class="flex flex-wrap items-center justify-center sm:justify-start gap-4 pt-1 text-xs text-muted-foreground"> | |
101 | + {profile?.instance?.name && <span class="inline-flex items-center gap-1.5"><Server className="h-3.5 w-3.5 text-primary" />{profile.instance.name}</span>} | |
102 | + {profile?.location && <span class="flex items-center gap-1"><MapPin className="h-3.5 w-3.5" />{profile.location}</span>} | |
103 | + {profile?.company && <span class="flex items-center gap-1"><Building className="h-3.5 w-3.5" />{profile.company}</span>} | |
104 | + {profile?.website && <a href={profile.website} target="_blank" class="flex items-center gap-1 hover:text-primary"><LinkIcon className="h-3.5 w-3.5" />Website</a>} | |
105 | + </div> | |
106 | + </div> | |
107 | + </div> | |
108 | + | |
109 | + <!-- Stats Bar --> | |
110 | + <div class="grid grid-cols-2 sm:grid-cols-5 gap-3 mt-6 pt-6 border-t border-border/50"> | |
111 | + <div class="text-center p-2 rounded-lg bg-muted/30"><div class="text-lg font-bold">{repos.length}</div><div class="text-[10px] text-muted-foreground">Repos</div></div> | |
112 | + <div class="text-center p-2 rounded-lg bg-muted/30"><div class="text-lg font-bold">{totalStars}</div><div class="text-[10px] text-muted-foreground">Stars</div></div> | |
113 | + <div class="text-center p-2 rounded-lg bg-muted/30"><div class="text-lg font-bold">{totalForks}</div><div class="text-[10px] text-muted-foreground">Forks</div></div> | |
114 | + <div class="text-center p-2 rounded-lg bg-muted/30"><div class="text-lg font-bold">{followersCount}</div><div class="text-[10px] text-muted-foreground">Followers</div></div> | |
115 | + <div class="text-center p-2 rounded-lg bg-muted/30"><div class="text-lg font-bold">{followingCount}</div><div class="text-[10px] text-muted-foreground">Following</div></div> | |
116 | + </div> | |
117 | + </div> | |
118 | + | |
119 | + <!-- 2-Col Layout --> | |
120 | + <div class="grid lg:grid-cols-3 gap-8"> | |
121 | + <div class="lg:col-span-2 space-y-4"> | |
122 | + <h2 class="text-xl font-bold flex items-center gap-2"><BookOpen className="h-5 w-5 text-primary" /> Repositories ({repos.length})</h2> | |
123 | + <div class="space-y-3.5"> | |
124 | + {repos.length === 0 ? ( | |
125 | + <div class="rounded-2xl border border-dashed border-border/80 bg-card/30 p-8 text-center text-xs text-muted-foreground">No repositories indexed for this profile yet.</div> | |
126 | + ) : ( | |
127 | + repos.map((repo: any) => { | |
128 | + const lc = langColors[repo.language] || "#888"; | |
129 | + return ( | |
130 | + <div class="rounded-2xl border border-border/60 bg-card/50 p-5 hover:border-primary/40 hover:bg-card/80 transition-all shadow-sm space-y-2"> | |
131 | + <div class="flex items-center justify-between gap-2"> | |
132 | + <a href={`/r/${repo.instance?.name || "node"}/${repo.fullName}`} class="font-bold text-base text-primary hover:underline">{repo.name}</a> | |
133 | + <Badge variant="outline" className="text-[10px] uppercase font-semibold">{repo.visibility || "public"}</Badge> | |
134 | + </div> | |
135 | + <p class="text-xs text-muted-foreground line-clamp-2">{repo.description || "No description provided."}</p> | |
136 | + <div class="flex items-center justify-between pt-2 text-xs text-muted-foreground"> | |
137 | + <div class="flex items-center gap-3"> | |
138 | + {repo.language && <span class="flex items-center gap-1.5 font-medium text-foreground"><span class="h-2.5 w-2.5 rounded-full" style={`background-color:${lc}`}></span>{repo.language}</span>} | |
139 | + <span class="flex items-center gap-1"><Star className="h-3 w-3 text-amber-400 fill-amber-400" /> {repo.starCount || 0}</span> | |
140 | + <span class="flex items-center gap-1"><GitFork className="h-3 w-3" /> {repo.forkCount || 0}</span> | |
141 | + </div> | |
142 | + {repo.updatedAt && <span class="flex items-center gap-1"><Clock className="h-3 w-3" />{new Date(repo.updatedAt).toLocaleDateString()}</span>} | |
143 | + </div> | |
144 | + </div> | |
145 | + ); | |
146 | + }) | |
147 | + )} | |
148 | + </div> | |
149 | + </div> | |
150 | + | |
151 | + <!-- Sidebar --> | |
152 | + <div class="space-y-6"> | |
153 | + <!-- Activity Heatmap --> | |
154 | + <Card className="border-border/60 bg-card/50"> | |
155 | + <CardContent className="p-5 space-y-3"> | |
156 | + <h3 class="text-sm font-bold flex items-center justify-between"><span>Activity</span><span class="text-[11px] font-normal text-muted-foreground">Last 12 weeks</span></h3> | |
157 | + <div class="grid grid-cols-7 gap-1.5"> | |
158 | + {matrix.map((count) => { | |
159 | + const bg = count >= 3 ? "bg-emerald-500" : count === 2 ? "bg-emerald-500/70" : count === 1 ? "bg-emerald-500/40" : "bg-muted/60"; | |
160 | + return <div class={`h-3.5 rounded-sm ${bg} transition-colors hover:ring-2 hover:ring-primary`} title={`${count} activities`} />; | |
161 | + })} | |
162 | + </div> | |
163 | + <div class="flex items-center justify-between pt-2 text-[10px] text-muted-foreground"> | |
164 | + <span>Less</span> | |
165 | + <div class="flex items-center gap-1"> | |
166 | + <span class="h-2.5 w-2.5 rounded-sm bg-muted/60"></span> | |
167 | + <span class="h-2.5 w-2.5 rounded-sm bg-emerald-500/40"></span> | |
168 | + <span class="h-2.5 w-2.5 rounded-sm bg-emerald-500/70"></span> | |
169 | + <span class="h-2.5 w-2.5 rounded-sm bg-emerald-500"></span> | |
170 | + </div> | |
171 | + <span>More</span> | |
172 | + </div> | |
173 | + </CardContent> | |
174 | + </Card> | |
175 | + | |
176 | + <!-- Languages --> | |
177 | + {languages.length > 0 && ( | |
178 | + <Card className="border-border/60 bg-card/50"> | |
179 | + <CardContent className="p-5 space-y-3"> | |
180 | + <h3 class="text-sm font-bold">Languages</h3> | |
181 | + <div class="space-y-2"> | |
182 | + {languages.slice(0, 8).map((lang: string) => ( | |
183 | + <div class="flex items-center gap-2 text-xs"> | |
184 | + <span class="h-2.5 w-2.5 rounded-full shrink-0" style={`background-color:${langColors[lang] || "#888"}`}></span> | |
185 | + <span class="font-medium text-foreground">{lang}</span> | |
186 | + </div> | |
187 | + ))} | |
188 | + </div> | |
189 | + </CardContent> | |
190 | + </Card> | |
191 | + )} | |
192 | + | |
193 | + <!-- Instance --> | |
194 | + {profile?.instance && ( | |
195 | + <Card className="border-border/60 bg-card/50"> | |
196 | + <CardContent className="p-5 space-y-3"> | |
197 | + <h3 class="text-sm font-bold">Home Instance</h3> | |
198 | + <a href={`/instances/${profile.instance.id}`} class="flex items-center gap-3 p-3 rounded-xl hover:bg-muted/40 transition-colors border border-border/40"> | |
199 | + <div class="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center"><Server className="h-5 w-5 text-primary" /></div> | |
200 | + <div class="min-w-0"> | |
201 | + <div class="text-sm font-semibold truncate">{profile.instance.name}</div> | |
202 | + <div class="text-[11px] text-muted-foreground truncate">{profile.instance.siteUrl}</div> | |
203 | + </div> | |
204 | + </a> | |
205 | + </CardContent> | |
206 | + </Card> | |
207 | + )} | |
208 | + </div> | |
209 | + </div> | |
210 | + </main> | |
211 | + | |
212 | + <script> | |
213 | + import { toast } from "sonner"; | |
214 | + const followBtn = document.getElementById('follow-btn'); | |
215 | + const followLabel = document.getElementById('follow-label'); | |
216 | + followBtn?.addEventListener('click', async () => { | |
217 | + const username = followBtn.dataset.username; | |
218 | + try { | |
219 | + const res = await fetch('/api/follows', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ followeeType: 'user', followeeId: username }) }); | |
220 | + const data = await res.json(); | |
221 | + if (data.success) { | |
222 | + const f = data.data?.following; | |
223 | + if (followLabel) followLabel.textContent = f ? "Following" : "Follow"; | |
224 | + followBtn.classList.toggle("bg-muted", f); | |
225 | + followBtn.classList.toggle("text-muted-foreground", f); | |
226 | + followBtn.classList.toggle("bg-primary", !f); | |
227 | + followBtn.classList.toggle("text-primary-foreground", !f); | |
228 | + toast.success(f ? `Followed @${username}` : `Unfollowed @${username}`); | |
229 | + } else { toast.error(data.error?.message || "Failed"); } | |
230 | + } catch { toast.error("Network error"); } | |
231 | + }); | |
232 | + </script> | |
233 | +</BaseLayout> | |
0 | 234 | |
▼community/src/styles/globals.css
+73-0
| @@ -0,0 +1,73 @@ | ||
1 | +@tailwind base; | |
2 | +@tailwind components; | |
3 | +@tailwind utilities; | |
4 | + | |
5 | +@layer base { | |
6 | + :root { | |
7 | + --background: 240 10% 3.9%; | |
8 | + --foreground: 0 0% 98%; | |
9 | + --card: 240 10% 4.9%; | |
10 | + --card-foreground: 0 0% 98%; | |
11 | + --popover: 240 10% 4.9%; | |
12 | + --popover-foreground: 0 0% 98%; | |
13 | + --primary: 217.2 91.2% 59.8%; | |
14 | + --primary-foreground: 222.2 47.4% 11.2%; | |
15 | + --secondary: 240 3.7% 15.9%; | |
16 | + --secondary-foreground: 0 0% 98%; | |
17 | + --muted: 240 3.7% 15.9%; | |
18 | + --muted-foreground: 240 5% 64.9%; | |
19 | + --accent: 240 3.7% 15.9%; | |
20 | + --accent-foreground: 0 0% 98%; | |
21 | + --destructive: 0 62.8% 30.6%; | |
22 | + --destructive-foreground: 0 0% 98%; | |
23 | + --border: 240 3.7% 15.9%; | |
24 | + --input: 240 3.7% 15.9%; | |
25 | + --ring: 217.2 91.2% 59.8%; | |
26 | + --radius: 0.75rem; | |
27 | + } | |
28 | +} | |
29 | + | |
30 | +@layer base { | |
31 | + * { | |
32 | + @apply border-border selection:bg-primary/20 selection:text-primary-foreground; | |
33 | + } | |
34 | + body { | |
35 | + @apply bg-background text-foreground antialiased min-h-screen flex flex-col font-sans; | |
36 | + background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px); | |
37 | + background-size: 32px 32px; | |
38 | + } | |
39 | +} | |
40 | + | |
41 | +/* Custom scrollbar */ | |
42 | +::-webkit-scrollbar { | |
43 | + width: 8px; | |
44 | + height: 8px; | |
45 | +} | |
46 | +::-webkit-scrollbar-track { | |
47 | + background: hsl(var(--background)); | |
48 | +} | |
49 | +::-webkit-scrollbar-thumb { | |
50 | + background: hsl(var(--muted)); | |
51 | + border-radius: 4px; | |
52 | +} | |
53 | +::-webkit-scrollbar-thumb:hover { | |
54 | + background: hsl(var(--muted-foreground) / 0.5); | |
55 | +} | |
56 | + | |
57 | +/* Language Color Helpers */ | |
58 | +.lang-typescript { color: #3178c6; } | |
59 | +.lang-javascript { color: #f7df1e; } | |
60 | +.lang-python { color: #3572A5; } | |
61 | +.lang-go { color: #00ADD8; } | |
62 | +.lang-rust { color: #dea584; } | |
63 | +.lang-java { color: #b07219; } | |
64 | +.lang-cpp, .lang-c\+\+ { color: #f34b7d; } | |
65 | +.lang-html { color: #e34c26; } | |
66 | +.lang-css { color: #563d7c; } | |
67 | + | |
68 | +.lang-dot { | |
69 | + display: inline-block; | |
70 | + width: 8px; | |
71 | + height: 8px; | |
72 | + border-radius: 9999px; | |
73 | +} | |
0 | 74 | |
▼community/tailwind.config.mjs
+31-0
| @@ -0,0 +1,31 @@ | ||
1 | +/** @type {import('tailwindcss').Config} */ | |
2 | +export default { | |
3 | + darkMode: ["class"], | |
4 | + content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], | |
5 | + theme: { | |
6 | + container: { center: true, padding: "2rem", screens: { "2xl": "1400px" } }, | |
7 | + extend: { | |
8 | + colors: { | |
9 | + border: "hsl(var(--border))", | |
10 | + input: "hsl(var(--input))", | |
11 | + ring: "hsl(var(--ring))", | |
12 | + background: "hsl(var(--background))", | |
13 | + foreground: "hsl(var(--foreground))", | |
14 | + primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" }, | |
15 | + secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" }, | |
16 | + destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" }, | |
17 | + muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" }, | |
18 | + accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" }, | |
19 | + popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))" }, | |
20 | + card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" }, | |
21 | + }, | |
22 | + borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" }, | |
23 | + keyframes: { | |
24 | + "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" } }, | |
25 | + "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" } }, | |
26 | + }, | |
27 | + animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out" }, | |
28 | + }, | |
29 | + }, | |
30 | + plugins: [require("tailwindcss-animate")], | |
31 | +}; | |
0 | 32 | |
▼community/tsconfig.json
+9-0
| @@ -0,0 +1,9 @@ | ||
1 | +{ | |
2 | + "extends": "astro/tsconfigs/strict", | |
3 | + "compilerOptions": { | |
4 | + "baseUrl": ".", | |
5 | + "paths": { "@/*": ["src/*"] }, | |
6 | + "jsx": "react-jsx", | |
7 | + "jsxImportSource": "react" | |
8 | + } | |
9 | +} | |
0 | 10 | |
▼docker-compose.yml
+6-2
| @@ -183,6 +183,8 @@ services: | ||
183 | 183 | POSTGRES_USER: ${POSTGRES_USER:-opencodehub} |
184 | 184 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} |
185 | 185 | POSTGRES_DB: ${POSTGRES_DB:-opencodehub} |
186 | + ports: | |
187 | + - "${POSTGRES_PORT:-5432}:5432" | |
186 | 188 | volumes: |
187 | 189 | - postgres-data:/var/lib/postgresql/data |
188 | 190 | networks: |
| @@ -213,6 +215,8 @@ services: | ||
213 | 215 | - 256mb |
214 | 216 | - --maxmemory-policy |
215 | 217 | - allkeys-lru |
218 | + ports: | |
219 | + - "${REDIS_PORT:-6379}:6379" | |
216 | 220 | volumes: |
217 | 221 | - redis-data:/data |
218 | 222 | networks: |
| @@ -271,8 +275,8 @@ services: | ||
271 | 275 | restart: unless-stopped |
272 | 276 | command: server /data --console-address ":9001" |
273 | 277 | environment: |
274 | - MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} | |
275 | - MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} | |
278 | + MINIO_ROOT_USER: ${MINIO_ROOT_USER:?Set MINIO_ROOT_USER in .env} | |
279 | + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD in .env} | |
276 | 280 | ports: |
277 | 281 | - "${MINIO_CONSOLE_PORT:-9001}:9001" # Web console (set 0 to disable) |
278 | 282 | volumes: |
279 | 283 | |
▼docs-site/src/content/docs/getting-started/installation.md
+27-0
| @@ -13,6 +13,33 @@ OpenCodeHub is a self-hosted Git platform. You can run it via **Docker** (recomm | ||
13 | 13 | | **Disk** | 10GB | 50GB SSD | |
14 | 14 | | **OS** | Linux (Ubuntu/Debian) | Linux | |
15 | 15 | |
16 | +## CLI Installation (`opencodehub-cli`) | |
17 | + | |
18 | +Install the official OpenCodeHub CLI (`och`) to manage stacks, pull requests, and merge queues from your terminal: | |
19 | + | |
20 | +```bash | |
21 | +# via npm | |
22 | +npm install -g opencodehub-cli | |
23 | + | |
24 | +# via bun | |
25 | +bun add -g opencodehub-cli | |
26 | + | |
27 | +# via pnpm | |
28 | +pnpm add -g opencodehub-cli | |
29 | + | |
30 | +# via yarn | |
31 | +yarn global add opencodehub-cli | |
32 | +``` | |
33 | + | |
34 | +Verify your installation: | |
35 | + | |
36 | +```bash | |
37 | +och --version | |
38 | +och --help | |
39 | +``` | |
40 | + | |
41 | +--- | |
42 | + | |
16 | 43 | ## 1-Click Installation (Recommended) |
17 | 44 | |
18 | 45 | ```bash |
19 | 46 | |
▼docs/features/federation.md
+95-0
| @@ -0,0 +1,95 @@ | ||
1 | +# Federation & Cross-Instance PRs | |
2 | + | |
3 | +> Connect two self-hosted OpenCodeHub instances so contributors on one can fork, contribute, and open pull requests against repositories hosted on another. | |
4 | + | |
5 | +Federation lets a user on instance **A** fork a repository hosted on instance **B**, push branches back to B, and open **cross-instance pull requests** whose head branch lives on A's fork. B pulls the head from A's fork URL, creates a normal PR (with `headRepositoryId` unset), and the usual review and merge pipeline applies. | |
6 | + | |
7 | +## Table of Contents | |
8 | + | |
9 | +- [Overview](#overview) | |
10 | +- [How It Works](#how-it-works) | |
11 | +- [Setting Up Federation](#setting-up-federation) | |
12 | +- [Forking a Repository from Another Instance](#forking-a-repository-from-another-instance) | |
13 | +- [Contributing Back](#contributing-back) | |
14 | +- [Cross-Instance Pull Requests](#cross-instance-pull-requests) | |
15 | +- [Permissions](#permissions) | |
16 | +- [SSRF Protection](#ssrf-protection) | |
17 | + | |
18 | +## Overview | |
19 | + | |
20 | +The two instances interoperate over HTTP. Instance A talks to B's REST API using a Personal Access Token belonging to the contributing user on B. All git traffic flows over the normal smart-HTTP protocol using basic auth with that PAT. | |
21 | + | |
22 | +``` | |
23 | +┌─────────────────┐ import (clone URL + PAT) ┌─────────────────┐ | |
24 | +│ Instance A │ ───────────────────────────▶ │ Instance B │ | |
25 | +│ bob/fedbase │ │ alice/fedbase │ | |
26 | +│ (fork) │ ◀─────────────────────────── │ (upstream) │ | |
27 | +│ │ push-upstream (git push) │ │ | |
28 | +│ │ ───────────────────────────▶ │ │ | |
29 | +│ │ federation/open-pull │ external-pulls │ | |
30 | +└─────────────────┘ ◀─────────────────────────── └─────────────────┘ | |
31 | +``` | |
32 | + | |
33 | +## How It Works | |
34 | + | |
35 | +1. **Import (fork)**: On instance A, import `https://B/alice/fedbase.git` and authenticate with a PAT for the user on B. A detects that the source is another OpenCodeHub instance (`GET /api/instance`) and records the relationship as `forkedFromUrl`. | |
36 | +2. **Contribute back**: A can push any branch on its fork back to B (`federation/push-upstream`). B's existing `git-receive-pack` authorizes the push via `canWriteRepo` — B controls who may contribute. | |
37 | +3. **Cross-instance PR**: A calls `federation/open-pull` on B. B validates the fork URL (SSRF check), fetches the head branch from A's fork into the B repo, computes diff stats, and creates a PR whose head lives on A's fork. | |
38 | +4. **Review & merge**: The PR is a normal PR on B. Reviews, approvals, CI, and the merge queue all apply. Merging fetches the head ref and merges it into the base branch. | |
39 | + | |
40 | +## Setting Up Federation | |
41 | + | |
42 | +Federation is enabled out of the box. Two environment variables control SSRF behavior: | |
43 | + | |
44 | +| Variable | Purpose | | |
45 | +|----------|---------| | |
46 | +| `FEDERATION_ALLOW_LOCALHOST` | Set `true` to allow fetching from `localhost`/loopback URLs. **Only** for two-instance testing on a single host or a trusted private network. Never enable in production. | | |
47 | + | |
48 | +The upstream instance does not need any special config beyond the normal collaboration settings: the A user simply needs read access (to see the repo) and write access (to push branches and open PRs). | |
49 | + | |
50 | +## Forking a Repository from Another Instance | |
51 | + | |
52 | +On instance A, use the normal **Import Repository** flow: | |
53 | + | |
54 | +1. Paste the upstream clone URL, e.g. `http://instance-b.local/swadhinbiswas/fedbase.git`. | |
55 | +2. Enter the **auth username** and a **PAT** for the user on instance B (the username must match the B account, e.g. `bob`). | |
56 | +3. Complete the import. Instance A records `forkedFromUrl` and stores an encrypted mirror PAT so it can push back later. | |
57 | + | |
58 | +> For cross-instance PRs the source URL must be fetchable by B — the fork must be on a host B can reach, and the fork repo must be readable by B (public, or accessible with the embedded PAT). | |
59 | + | |
60 | +## Contributing Back | |
61 | + | |
62 | +From the fork's repository page on A, the **Federation** panel offers **Push branch to upstream**: | |
63 | + | |
64 | +- Pick a branch on the fork. | |
65 | +- A pushes `branch:branch` to the upstream clone URL, authenticating with the stored B user's PAT. | |
66 | + | |
67 | +B's permission model decides whether the push succeeds — the B user must be a collaborator (or the repo must allow external writes). | |
68 | + | |
69 | +## Cross-Instance Pull Requests | |
70 | + | |
71 | +From the same **Federation** panel, **Open cross-instance PR**: | |
72 | + | |
73 | +1. A calls `POST /api/repos/{owner}/{repo}/external-pulls` on B (server-to-server) with the fork URL, head branch, base branch, title, and body. | |
74 | +2. B validates the fork URL, fetches the head into the B repo, computes additions/deletions/changed files, and creates the PR. | |
75 | +3. The PR's head branch resolves to the fetched ref on B, so the diff, comments, and merge all work normally. | |
76 | + | |
77 | +### Endpoints | |
78 | + | |
79 | +| Endpoint (on B) | Purpose | | |
80 | +|-----------------|---------| | |
81 | +| `POST /api/repos/{owner}/{repo}/external-pulls` | Create a PR whose head is fetched from an external fork URL | | |
82 | +| `GET /api/instance` | Instance metadata probe used for detection | | |
83 | + | |
84 | +## Permissions | |
85 | + | |
86 | +- **Import on A**: requires a valid PAT for the B user. | |
87 | +- **Push upstream**: B's `git-receive-pack` requires the B user to have write access to the repo. | |
88 | +- **Open a cross-instance PR**: B's `external-pulls` endpoint requires the caller to have read access plus either write access to the repo, or the repo must have **Allow external pull requests** enabled (repo → Settings → Federation). | |
89 | +- **Merge**: normal PR merge gates apply (approvals, required checks, branch protection). | |
90 | + | |
91 | +## SSRF Protection | |
92 | + | |
93 | +Every server-initiated fetch (import on A, external-pulls on B) runs through `validateGitCloneUrl`, which blocks private ranges, cloud metadata endpoints, and non-HTTP(S)/git/ssh schemes. The localhost bypass is only available when `FEDERATION_ALLOW_LOCALHOST=true`, and callers gate it explicitly. | |
94 | + | |
95 | +Tokens embedded into fetch/push URLs are used transiently and never persisted. | |
0 | 96 | |
▼docs/getting-started/installation.md
+54-29
| @@ -1,6 +1,6 @@ | ||
1 | 1 | # Installation Guide |
2 | 2 | |
3 | -OpenCodeHub allows you to host your own GitHub-like platform. You can run it via **Docker** (recommended for production) or **Node.js** (for development/custom setups). | |
3 | +OpenCodeHub allows you to host your own GitHub-like platform. You can run it via **Docker** (recommended for production) or **Node.js / Bun** (for development/custom setups). | |
4 | 4 | |
5 | 5 | ## 📋 System Requirements |
6 | 6 | |
| @@ -9,7 +9,41 @@ OpenCodeHub allows you to host your own GitHub-like platform. You can run it via | ||
9 | 9 | | **CPU** | 1 vCPU | 2 vCPU | |
10 | 10 | | **RAM** | 512MB | 2GB | |
11 | 11 | | **Disk** | 10GB | 50GB SSD | |
12 | -| **OS** | Linux (Ubuntu/Debian) | Linux | | |
12 | +| **OS** | Linux (Ubuntu/Debian) | Linux / macOS / Windows | | |
13 | + | |
14 | +--- | |
15 | + | |
16 | +## 💻 CLI Installation (`opencodehub-cli`) | |
17 | + | |
18 | +Install the official OpenCodeHub CLI (`och`) to interact with your instance directly from the terminal: | |
19 | + | |
20 | +```bash | |
21 | +# via npm | |
22 | +npm install -g opencodehub-cli | |
23 | + | |
24 | +# via bun | |
25 | +bun add -g opencodehub-cli | |
26 | + | |
27 | +# via pnpm | |
28 | +pnpm add -g opencodehub-cli | |
29 | + | |
30 | +# via yarn | |
31 | +yarn global add opencodehub-cli | |
32 | +``` | |
33 | + | |
34 | +Verify the CLI is ready: | |
35 | + | |
36 | +```bash | |
37 | +och --version | |
38 | +och --help | |
39 | +``` | |
40 | + | |
41 | +Authenticate with your server: | |
42 | + | |
43 | +```bash | |
44 | +och auth login --url https://git.yourcompany.com | |
45 | +och config doctor | |
46 | +``` | |
13 | 47 | |
14 | 48 | --- |
15 | 49 | |
| @@ -44,6 +78,8 @@ Edit `.env` and set **production values**. | ||
44 | 78 | JWT_SECRET=<openssl rand -hex 32> |
45 | 79 | SESSION_SECRET=<openssl rand -hex 32> |
46 | 80 | INTERNAL_HOOK_SECRET=<openssl rand -hex 32> |
81 | +CRON_SECRET=<openssl rand -hex 32> | |
82 | +RUNNER_SECRET=<openssl rand -hex 32> | |
47 | 83 | |
48 | 84 | # Domain Configuration |
49 | 85 | SITE_URL=https://git.yourcompany.com |
| @@ -51,27 +87,30 @@ PORT=4321 | ||
51 | 87 | |
52 | 88 | # Database (Using the Postgres container defined in compose) |
53 | 89 | DATABASE_URL=postgresql://opencodehub:securepassword@postgres:5432/opencodehub |
90 | +POSTGRES_USER=opencodehub | |
91 | +POSTGRES_PASSWORD=securepassword | |
92 | +POSTGRES_DB=opencodehub | |
93 | + | |
94 | +# Redis | |
95 | +REDIS_URL=redis://:redispassword@redis:6379 | |
96 | +REDIS_PASSWORD=redispassword | |
54 | 97 | |
55 | -# Object Storage (Highly Recommended for Production) | |
56 | -STORAGE_TYPE=s3 | |
57 | -STORAGE_BUCKET=my-git-bucket | |
58 | -STORAGE_REGION=us-east-1 | |
59 | -STORAGE_ACCESS_KEY_ID=... | |
60 | -STORAGE_SECRET_ACCESS_KEY=... | |
61 | -# STORAGE_ENDPOINT=https://<account>.r2.cloudflarestorage.com # set for non-AWS vendors | |
98 | +# Object Storage (Optional for S3 backends) | |
99 | +STORAGE_TYPE=local | |
100 | +STORAGE_PATH=/data/storage | |
62 | 101 | ``` |
63 | 102 | |
64 | 103 | ### 4. Start Services |
65 | 104 | |
66 | 105 | ```bash |
67 | -docker-compose up -d | |
106 | +docker compose up -d | |
68 | 107 | ``` |
69 | 108 | |
70 | 109 | ### 5. Initialization |
71 | 110 | |
72 | 111 | Initialize the admin user: |
73 | 112 | ```bash |
74 | -docker-compose exec app bun run scripts/seed-admin.ts | |
113 | +docker compose exec app bun scripts/seed-admin.ts | |
75 | 114 | ``` |
76 | 115 | |
77 | 116 | --- |
| @@ -98,30 +137,14 @@ server { | ||
98 | 137 | client_max_body_size 500M; |
99 | 138 | |
100 | 139 | location / { |
101 | - proxy_pass http://localhost:4321; | |
140 | + proxy_pass http://127.0.0.1:4321; | |
102 | 141 | proxy_set_header Host $host; |
103 | 142 | proxy_set_header X-Real-IP $remote_addr; |
104 | 143 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
105 | 144 | proxy_set_header X-Forwarded-Proto $scheme; |
145 | + proxy_buffering off; | |
146 | + proxy_read_timeout 600s; | |
147 | + proxy_send_timeout 600s; | |
106 | 148 | } |
107 | 149 | } |
108 | 150 | ``` |
109 | - | |
110 | - | |
111 | -## ✅ Production Checklist | |
112 | - | |
113 | -Before going live to users: | |
114 | - | |
115 | -- [ ] **Secrets Rotated**: Default secrets replaced with strong random strings. | |
116 | -- [ ] **HTTPS Enabled**: SSL certificate configured via Nginx/Caddy. | |
117 | -- [ ] **Rate Limiting**: `RATE_LIMIT_*` env vars adjusted for expected load. | |
118 | -- [ ] **Monitoring**: Grafana/Sentry configured for error tracking ([Guide](../administration/monitoring.md)). | |
119 | - | |
120 | - | |
121 | -## Next Steps | |
122 | - | |
123 | -Now that you have OpenCodeHub up and running, let's look around: | |
124 | - | |
125 | -👉 **[Quick Start Guide](quick-start.md)** | |
126 | 151 | |
▼drizzle.config.ts
+7-3
| @@ -1,3 +1,5 @@ | ||
1 | +import { config } from "dotenv"; | |
2 | +config(); | |
1 | 3 | import { defineConfig } from "drizzle-kit"; |
2 | 4 | |
3 | 5 | // Get driver type from environment |
| @@ -5,7 +7,7 @@ import { defineConfig } from "drizzle-kit"; | ||
5 | 7 | const driver = process.env.DATABASE_DRIVER || "postgres"; |
6 | 8 | const url = |
7 | 9 | process.env.DATABASE_URL || |
8 | - "postgresql://opencodehub:opencodehub@localhost:5432/opencodehub"; | |
10 | + "postgresql://localhost:5432/opencodehub"; | |
9 | 11 | |
10 | 12 | // Map driver to Drizzle dialect |
11 | 13 | const dialectMap: Record<string, "sqlite" | "postgresql" | "mysql" | "turso"> = |
| @@ -23,8 +25,10 @@ const dialect = dialectMap[driver] || "postgresql"; | ||
23 | 25 | function getCredentials() { |
24 | 26 | switch (driver) { |
25 | 27 | case "postgres": |
26 | - case "mysql": | |
27 | - return { url, ssl: { rejectUnauthorized: false } }; | |
28 | + case "mysql": { | |
29 | + const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("@postgres:"); | |
30 | + return { url, ssl: isLocal ? false : { rejectUnauthorized: false } }; | |
31 | + } | |
28 | 32 | case "libsql": |
29 | 33 | case "turso": |
30 | 34 | return { url, authToken: process.env.DATABASE_AUTH_TOKEN }; |
31 | 35 | |
▼public/favicon.png
+0-0
Binary file not shown.
▼public/favicon.svg
+8-17
| @@ -1,18 +1,9 @@ | ||
1 | -<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
2 | -<g id="owl-logo"> | |
3 | -<circle id="outer-circle" cx="256" cy="256" r="230" fill="#0D2C54"/> | |
4 | -<path id="inner-circle-teal" d="M486 256C486 383.025 383.025 486 256 486C128.975 486 26 383.025 26 256C26 128.975 128.975 26 256 26C383.025 26 486 128.975 486 256Z" fill="#008080"/> | |
5 | -<path id="blue-accents" d="M256 486C128.975 486 26 383.025 26 256C26 128.975 128.975 26 256 26C383.025 26 486 128.975 486 256C486 383.025 383.025 486 256 486Z" fill="#0080FF" fill-opacity="0.6"/> | |
6 | -<path id="owl-brows" d="M88.4 124.8C88.4 124.8 173.2 227.6 256 227.6C338.8 227.6 423.6 124.8 423.6 124.8C423.6 124.8 423.6 256 423.6 256C423.6 256 338.8 338.8 256 338.8C173.2 338.8 88.4 256 88.4 256C88.4 256 88.4 124.8 88.4 124.8Z" fill="#0D2C54"/> | |
7 | -<path id="owl-body-teal" d="M256 338.8C173.2 338.8 88.4 256 88.4 256C88.4 348.6 163.4 423.6 256 423.6C348.6 423.6 423.6 348.6 423.6 256C423.6 256 338.8 338.8 256 338.8Z" fill="#008080"/> | |
8 | -<path id="circuit" d="M256 66V180" stroke="#0080FF" stroke-width="16" stroke-linecap="round"/> | |
9 | -<circle id="circuit-dot" cx="294" cy="114" r="16" fill="#0080FF"/> | |
10 | -<path id="circuit-branch" d="M256 144H294" stroke="#0080FF" stroke-width="16" stroke-linecap="round"/> | |
11 | -<g id="eyes"> | |
12 | -<circle cx="176" cy="256" r="64" fill="#008080"/> | |
13 | -<circle cx="336" cy="256" r="64" fill="#008080"/> | |
14 | -<path d="M156 236L136 256L156 276" stroke="#0D2C54" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/> | |
15 | -<path d="M356 236L376 256L356 276" stroke="#0D2C54" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/> | |
16 | -</g> | |
17 | -</g> | |
1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="45 45 210 210" fill="currentColor"> | |
2 | + <style> | |
3 | + :root { color: #1f2328; } | |
4 | + @media (prefers-color-scheme: dark) { | |
5 | + :root { color: #f0f6fc; } | |
6 | + } | |
7 | + </style> | |
8 | + <path fill-rule="evenodd" clip-rule="evenodd" d="M184.27588,89.68521c2.96924,0.22368 3.29297,2.88721 3.75439,5.34082c1.19385,6.34468 0.82178,12.62754 0.00293,18.98174c0.34131,0.62461 0.66797,1.25728 0.97998,1.89756c5.40088,11.37407 2.61914,24.01758 -7.58203,31.66011c-2.17236,1.62891 -4.00049,2.51074 -6.4248,3.66357c0.82178,0.76904 1.60254,1.5293 2.35693,2.37012c10.20264,11.34814 10.51318,31.02539 1.01953,42.86865c-0.7749,0.9668 -1.70801,1.875 -2.36572,2.91064c-0.18896,1.81934 -0.08496,5.79053 -0.08496,7.70508l0.00732,14.01855c0.01172,4.06201 0.68115,7.9541 -2.32178,11.17236c-1.55713,1.69189 -3.74414,2.66602 -6.04395,2.69238c-2.2251,0.0542 -4.37256,-0.81006 -5.93994,-2.3877c-2.77588,-2.78174 -2.42139,-5.74805 -2.42285,-9.37646c-0.00293,-2.15186 0.00293,-4.32129 0.00293,-6.47314l0.00146,-24.48047c6.01758,-2.28369 9.25928,-8.7041 7.61719,-14.91797c-0.83789,-3.1875 -2.90332,-5.91357 -5.74365,-7.5835c-2.87109,-1.6626 -6.28418,-2.12109 -9.49365,-1.27441c-3.25635,0.86279 -6.023,3.00879 -7.66934,5.94727c-1.6771,2.98242 -2.0748,6.51709 -1.10244,9.79834c1.07827,3.66211 3.30176,6.06738 6.59795,7.8457v31.3418l-0.00146,8.86523c-0.00293,1.69775 -0.05127,3.50098 0.06885,5.18408c0.07031,0.98438 0.24023,1.74902 0.93457,2.45801c1.06494,1.08838 2.63818,1.26123 4.08838,1.25244c5.39648,-0.03516 11.21777,-0.63721 16.4458,-2.0083c11.50488,-3.01465 25.31982,-11.12842 33.73096,-19.5498c-1.04004,-3.65479 -1.04297,-6.17139 0.27979,-9.73682c0.11426,-0.28418 0.23584,-0.56396 0.36768,-0.83936c1.53955,-3.31641 4.34473,-5.87695 7.78711,-7.10889c3.49512,-1.24365 7.1792,-1.10596 10.5249,0.52588c3.38965,1.6582 5.97949,4.59814 7.19385,8.16943c1.18945,3.55518 0.89648,7.43848 -0.81152,10.77539c-1.72119,3.35449 -4.70801,5.88574 -8.2998,7.03418c-3.99609,1.25098 -7.6333,0.60791 -11.26465,-1.28613c-11.9165,11.17676 -26.95312,19.90283 -43.24658,22.32861c-6.1377,0.91406 -18.28271,1.79883 -24.2209,0.28711c-1.4543,-0.36035 -2.82349,-1.00342 -4.0292,-1.89258c-1.92026,-1.43994 -3.30073,-3.4834 -3.91978,-5.80225c-0.80962,-2.91797 -0.59106,-7.26416 -0.58989,-10.39453l0.0126,-20.7627c0.00176,-2.729 0.26045,-7.91602 -0.20142,-10.47656c-0.9293,-1.44434 -2.89556,-3.36035 -4.03506,-4.95996c-7.36787,-10.34326 -8.05313,-24.92871 -1.97593,-36.03662c1.89521,-3.46436 3.96914,-5.61768 6.89136,-8.27051c-1.10273,-0.60498 -2.32017,-1.15137 -3.42407,-1.75488c-10.07725,-5.50151 -15.41455,-16.65469 -12.49043,-27.89648c0.75337,-2.89644 1.79604,-5.09502 3.26177,-7.72251c-0.23101,-1.61455 -0.4084,-3.23628 -0.53218,-4.86255c-0.26484,-3.68013 -0.14707,-15.76128 2.28369,-18.38569c0.49512,-0.53452 1.12632,-0.72598 1.84219,-0.73696c4.777,-0.07251 14.12007,8.55615 17.44307,11.97817c6.83555,-0.78853 16.69834,-0.79482 23.51572,0.04937c3.84082,-4.00254 11.6543,-11.28779 17.2251,-12.14487z M216.82617,208.70947c3.49512,-0.50391 6.74268,1.9043 7.27588,5.39355c0.5332,3.49072 -1.8457,6.75879 -5.33057,7.32275c-3.52881,0.57129 -6.84521,-1.84424 -7.38574,-5.37744c-0.54053,-3.5332 1.90283,-6.8291 5.44043,-7.33887z M150.01025,49.42808c8.76855,-0.53481 18.6665,0.87524 27.06592,3.29912c20.78613,5.99868 38.0083,20.7375 48.39844,39.57656c11.02441,20.03057 13.20557,42.40825 6.84668,64.47847c-4.68457,16.26416 -15.27686,33.76758 -29.49902,43.79297c-0.8291,0.58447 -2.42578,0.64014 -3.39551,0.33105c-1.20264,-0.38965 -2.1958,-1.24951 -2.75391,-2.38477c-0.78223,-1.54248 -0.68701,-3.38379 0.24756,-4.83838c0.77344,-1.19238 2.30859,-1.99658 3.36621,-2.96631c1.39453,-1.27734 2.7832,-2.58984 4.08691,-3.96094c8.34229,-8.7583 14.54004,-19.33154 18.10547,-30.89062c6.12744,-19.20322 4.20703,-40.06919 -5.31885,-57.83232c-13.10742,-24.82339 -38.29687,-39.67676 -66.35449,-38.24443c-11.62852,0.5937 -20.97378,3.44312 -31.22754,8.85864c-0.09214,0.21592 0.06855,2.36089 0.08599,2.76357c0.10093,2.32456 -0.25884,4.14609 -1.20132,6.28271c-1.61675,3.70122 -4.65234,6.5981 -8.4252,8.03994c-4.34063,1.67285 -7.91528,1.0749 -12.02065,-0.72729c-1.39482,1.60225 -2.82202,3.25049 -4.10435,4.94326c-9.65625,12.99712 -14.91196,28.73716 -15.0019,44.92852c0.00176,4.17891 0.44326,7.88203 1.01514,11.99033c2.62295,0.16113 4.83779,0.89355 7.03564,2.35986c3.29355,2.22656 5.54854,5.68799 6.254,9.60059c0.80186,4.28027 -0.46406,7.95996 -2.85498,11.45801c6.1188,10.2832 12.25576,17.34961 22.19165,24.18604c2.72197,1.86182 5.57842,3.51855 8.54546,4.95703c1.91646,0.92285 5.19565,1.96143 6.36152,3.69434c0.70986,1.0708 0.95127,2.38623 0.66841,3.64014c-0.63237,2.74805 -3.42524,4.40332 -6.0498,3.42188c-16.91338,-6.33398 -30.71909,-19.39453 -39.48354,-34.77979c-1.91895,0.38965 -3.55415,0.55664 -5.51953,0.2959c-3.75059,-0.50244 -7.1439,-2.48584 -9.42217,-5.50635c-2.37363,-3.13184 -3.39595,-7.08252 -2.83887,-10.97168c0.58975,-4.17334 2.75815,-7.26123 6.04922,-9.76172c-0.84521,-4.86138 -1.40742,-9.20464 -1.51685,-14.15186c-0.4582,-20.73208 7.9248,-42.52515 22.04824,-57.59678c-0.52134,-1.40479 -0.85063,-2.36895 -1.07036,-3.8603c-0.57583,-3.9999 0.44736,-8.06558 2.8478,-11.31636c2.4063,-3.16348 5.97246,-5.23945 9.91157,-5.76943c4.11064,-0.54932 7.88833,0.62358 11.15728,3.0999c12.08159,-6.54038 21.99536,-9.5105 35.76973,-10.4395z M104.33159,64.69878c3.83057,-0.32915 7.21245,2.48599 7.58335,6.31274c0.37104,3.82676 -2.40718,7.23911 -6.22969,7.65176c-2.51001,0.271 -4.97197,-0.82749 -6.44678,-2.87651c-1.4748,-2.04902 -1.73496,-4.73247 -0.68115,-7.02656c1.05381,-2.29424 3.25898,-3.84521 5.77427,-4.06143z M77.86846,154.6333c3.67031,-0.67822 7.19575,1.74463 7.87866,5.41406c0.68291,3.66943 -1.73511,7.19824 -5.40337,7.88672c-3.67588,0.68994 -7.21377,-1.73437 -7.898,-5.41113c-0.68437,-3.67676 1.74478,-7.21143 5.42271,-7.88965z M153.91992,174.95801c3.0835,-0.47461 5.96777,1.64209 6.43945,4.72559c0.47021,3.0835 -1.64941,5.96484 -4.73437,6.43359c-3.0791,0.46582 -5.95459,-1.64941 -6.4248,-4.72852c-0.47021,-3.07764 1.64209,-5.95605 4.71973,-6.43066z" /> | |
18 | 9 | </svg> |
19 | 10 | |
▼public/logo.svg
+8-76
| @@ -1,77 +1,9 @@ | ||
1 | -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 80" width="320" height="80"> | |
2 | - <defs> | |
3 | - <!-- Gradient for owl --> | |
4 | - <linearGradient id="owlGradient" x1="0%" y1="0%" x2="100%" y2="100%"> | |
5 | - <stop offset="0%" style="stop-color:#22c55e"/> | |
6 | - <stop offset="100%" style="stop-color:#3b82f6"/> | |
7 | - </linearGradient> | |
8 | - <!-- Glow effect --> | |
9 | - <filter id="glow" x="-20%" y="-20%" width="140%" height="140%"> | |
10 | - <feGaussianBlur stdDeviation="2" result="coloredBlur"/> | |
11 | - <feMerge> | |
12 | - <feMergeNode in="coloredBlur"/> | |
13 | - <feMergeNode in="SourceGraphic"/> | |
14 | - </feMerge> | |
15 | - </filter> | |
16 | - </defs> | |
17 | - | |
18 | - <!-- Owl Icon --> | |
19 | - <g transform="translate(10, 8)" filter="url(#glow)"> | |
20 | - <!-- Owl body/head circle --> | |
21 | - <circle cx="32" cy="32" r="28" fill="url(#owlGradient)" opacity="0.15"/> | |
22 | - <circle cx="32" cy="32" r="28" fill="none" stroke="url(#owlGradient)" stroke-width="2.5"/> | |
23 | - | |
24 | - <!-- Owl ears --> | |
25 | - <path d="M12 12 L20 22 L8 22 Z" fill="url(#owlGradient)"/> | |
26 | - <path d="M52 12 L44 22 L56 22 Z" fill="url(#owlGradient)"/> | |
27 | - | |
28 | - <!-- Left eye - code bracket < --> | |
29 | - <g transform="translate(16, 24)"> | |
30 | - <path d="M12 0 L4 8 L12 16" | |
31 | - fill="none" | |
32 | - stroke="url(#owlGradient)" | |
33 | - stroke-width="3" | |
34 | - stroke-linecap="round" | |
35 | - stroke-linejoin="round"/> | |
36 | - </g> | |
37 | - | |
38 | - <!-- Right eye - code bracket > --> | |
39 | - <g transform="translate(36, 24)"> | |
40 | - <path d="M4 0 L12 8 L4 16" | |
41 | - fill="none" | |
42 | - stroke="url(#owlGradient)" | |
43 | - stroke-width="3" | |
44 | - stroke-linecap="round" | |
45 | - stroke-linejoin="round"/> | |
46 | - </g> | |
47 | - | |
48 | - <!-- Owl beak --> | |
49 | - <path d="M28 44 L32 50 L36 44" | |
50 | - fill="none" | |
51 | - stroke="url(#owlGradient)" | |
52 | - stroke-width="2" | |
53 | - stroke-linecap="round" | |
54 | - stroke-linejoin="round"/> | |
55 | - </g> | |
56 | - | |
57 | - <!-- Text: OpenCodeHub --> | |
58 | - <g transform="translate(80, 48)"> | |
59 | - <!-- Open --> | |
60 | - <text font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" | |
61 | - font-size="28" | |
62 | - font-weight="400" | |
63 | - fill="#f8fafc">Open</text> | |
64 | - <!-- Code --> | |
65 | - <text x="68" | |
66 | - font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" | |
67 | - font-size="28" | |
68 | - font-weight="700" | |
69 | - fill="url(#owlGradient)">Code</text> | |
70 | - <!-- Hub --> | |
71 | - <text x="138" | |
72 | - font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" | |
73 | - font-size="28" | |
74 | - font-weight="400" | |
75 | - fill="#f8fafc">Hub</text> | |
76 | - </g> | |
1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="45 45 210 210" fill="currentColor"> | |
2 | + <style> | |
3 | + :root { color: #1f2328; } | |
4 | + @media (prefers-color-scheme: dark) { | |
5 | + :root { color: #f0f6fc; } | |
6 | + } | |
7 | + </style> | |
8 | + <path fill-rule="evenodd" clip-rule="evenodd" d="M184.27588,89.68521c2.96924,0.22368 3.29297,2.88721 3.75439,5.34082c1.19385,6.34468 0.82178,12.62754 0.00293,18.98174c0.34131,0.62461 0.66797,1.25728 0.97998,1.89756c5.40088,11.37407 2.61914,24.01758 -7.58203,31.66011c-2.17236,1.62891 -4.00049,2.51074 -6.4248,3.66357c0.82178,0.76904 1.60254,1.5293 2.35693,2.37012c10.20264,11.34814 10.51318,31.02539 1.01953,42.86865c-0.7749,0.9668 -1.70801,1.875 -2.36572,2.91064c-0.18896,1.81934 -0.08496,5.79053 -0.08496,7.70508l0.00732,14.01855c0.01172,4.06201 0.68115,7.9541 -2.32178,11.17236c-1.55713,1.69189 -3.74414,2.66602 -6.04395,2.69238c-2.2251,0.0542 -4.37256,-0.81006 -5.93994,-2.3877c-2.77588,-2.78174 -2.42139,-5.74805 -2.42285,-9.37646c-0.00293,-2.15186 0.00293,-4.32129 0.00293,-6.47314l0.00146,-24.48047c6.01758,-2.28369 9.25928,-8.7041 7.61719,-14.91797c-0.83789,-3.1875 -2.90332,-5.91357 -5.74365,-7.5835c-2.87109,-1.6626 -6.28418,-2.12109 -9.49365,-1.27441c-3.25635,0.86279 -6.023,3.00879 -7.66934,5.94727c-1.6771,2.98242 -2.0748,6.51709 -1.10244,9.79834c1.07827,3.66211 3.30176,6.06738 6.59795,7.8457v31.3418l-0.00146,8.86523c-0.00293,1.69775 -0.05127,3.50098 0.06885,5.18408c0.07031,0.98438 0.24023,1.74902 0.93457,2.45801c1.06494,1.08838 2.63818,1.26123 4.08838,1.25244c5.39648,-0.03516 11.21777,-0.63721 16.4458,-2.0083c11.50488,-3.01465 25.31982,-11.12842 33.73096,-19.5498c-1.04004,-3.65479 -1.04297,-6.17139 0.27979,-9.73682c0.11426,-0.28418 0.23584,-0.56396 0.36768,-0.83936c1.53955,-3.31641 4.34473,-5.87695 7.78711,-7.10889c3.49512,-1.24365 7.1792,-1.10596 10.5249,0.52588c3.38965,1.6582 5.97949,4.59814 7.19385,8.16943c1.18945,3.55518 0.89648,7.43848 -0.81152,10.77539c-1.72119,3.35449 -4.70801,5.88574 -8.2998,7.03418c-3.99609,1.25098 -7.6333,0.60791 -11.26465,-1.28613c-11.9165,11.17676 -26.95312,19.90283 -43.24658,22.32861c-6.1377,0.91406 -18.28271,1.79883 -24.2209,0.28711c-1.4543,-0.36035 -2.82349,-1.00342 -4.0292,-1.89258c-1.92026,-1.43994 -3.30073,-3.4834 -3.91978,-5.80225c-0.80962,-2.91797 -0.59106,-7.26416 -0.58989,-10.39453l0.0126,-20.7627c0.00176,-2.729 0.26045,-7.91602 -0.20142,-10.47656c-0.9293,-1.44434 -2.89556,-3.36035 -4.03506,-4.95996c-7.36787,-10.34326 -8.05313,-24.92871 -1.97593,-36.03662c1.89521,-3.46436 3.96914,-5.61768 6.89136,-8.27051c-1.10273,-0.60498 -2.32017,-1.15137 -3.42407,-1.75488c-10.07725,-5.50151 -15.41455,-16.65469 -12.49043,-27.89648c0.75337,-2.89644 1.79604,-5.09502 3.26177,-7.72251c-0.23101,-1.61455 -0.4084,-3.23628 -0.53218,-4.86255c-0.26484,-3.68013 -0.14707,-15.76128 2.28369,-18.38569c0.49512,-0.53452 1.12632,-0.72598 1.84219,-0.73696c4.777,-0.07251 14.12007,8.55615 17.44307,11.97817c6.83555,-0.78853 16.69834,-0.79482 23.51572,0.04937c3.84082,-4.00254 11.6543,-11.28779 17.2251,-12.14487z M216.82617,208.70947c3.49512,-0.50391 6.74268,1.9043 7.27588,5.39355c0.5332,3.49072 -1.8457,6.75879 -5.33057,7.32275c-3.52881,0.57129 -6.84521,-1.84424 -7.38574,-5.37744c-0.54053,-3.5332 1.90283,-6.8291 5.44043,-7.33887z M150.01025,49.42808c8.76855,-0.53481 18.6665,0.87524 27.06592,3.29912c20.78613,5.99868 38.0083,20.7375 48.39844,39.57656c11.02441,20.03057 13.20557,42.40825 6.84668,64.47847c-4.68457,16.26416 -15.27686,33.76758 -29.49902,43.79297c-0.8291,0.58447 -2.42578,0.64014 -3.39551,0.33105c-1.20264,-0.38965 -2.1958,-1.24951 -2.75391,-2.38477c-0.78223,-1.54248 -0.68701,-3.38379 0.24756,-4.83838c0.77344,-1.19238 2.30859,-1.99658 3.36621,-2.96631c1.39453,-1.27734 2.7832,-2.58984 4.08691,-3.96094c8.34229,-8.7583 14.54004,-19.33154 18.10547,-30.89062c6.12744,-19.20322 4.20703,-40.06919 -5.31885,-57.83232c-13.10742,-24.82339 -38.29687,-39.67676 -66.35449,-38.24443c-11.62852,0.5937 -20.97378,3.44312 -31.22754,8.85864c-0.09214,0.21592 0.06855,2.36089 0.08599,2.76357c0.10093,2.32456 -0.25884,4.14609 -1.20132,6.28271c-1.61675,3.70122 -4.65234,6.5981 -8.4252,8.03994c-4.34063,1.67285 -7.91528,1.0749 -12.02065,-0.72729c-1.39482,1.60225 -2.82202,3.25049 -4.10435,4.94326c-9.65625,12.99712 -14.91196,28.73716 -15.0019,44.92852c0.00176,4.17891 0.44326,7.88203 1.01514,11.99033c2.62295,0.16113 4.83779,0.89355 7.03564,2.35986c3.29355,2.22656 5.54854,5.68799 6.254,9.60059c0.80186,4.28027 -0.46406,7.95996 -2.85498,11.45801c6.1188,10.2832 12.25576,17.34961 22.19165,24.18604c2.72197,1.86182 5.57842,3.51855 8.54546,4.95703c1.91646,0.92285 5.19565,1.96143 6.36152,3.69434c0.70986,1.0708 0.95127,2.38623 0.66841,3.64014c-0.63237,2.74805 -3.42524,4.40332 -6.0498,3.42188c-16.91338,-6.33398 -30.71909,-19.39453 -39.48354,-34.77979c-1.91895,0.38965 -3.55415,0.55664 -5.51953,0.2959c-3.75059,-0.50244 -7.1439,-2.48584 -9.42217,-5.50635c-2.37363,-3.13184 -3.39595,-7.08252 -2.83887,-10.97168c0.58975,-4.17334 2.75815,-7.26123 6.04922,-9.76172c-0.84521,-4.86138 -1.40742,-9.20464 -1.51685,-14.15186c-0.4582,-20.73208 7.9248,-42.52515 22.04824,-57.59678c-0.52134,-1.40479 -0.85063,-2.36895 -1.07036,-3.8603c-0.57583,-3.9999 0.44736,-8.06558 2.8478,-11.31636c2.4063,-3.16348 5.97246,-5.23945 9.91157,-5.76943c4.11064,-0.54932 7.88833,0.62358 11.15728,3.0999c12.08159,-6.54038 21.99536,-9.5105 35.76973,-10.4395z M104.33159,64.69878c3.83057,-0.32915 7.21245,2.48599 7.58335,6.31274c0.37104,3.82676 -2.40718,7.23911 -6.22969,7.65176c-2.51001,0.271 -4.97197,-0.82749 -6.44678,-2.87651c-1.4748,-2.04902 -1.73496,-4.73247 -0.68115,-7.02656c1.05381,-2.29424 3.25898,-3.84521 5.77427,-4.06143z M77.86846,154.6333c3.67031,-0.67822 7.19575,1.74463 7.87866,5.41406c0.68291,3.66943 -1.73511,7.19824 -5.40337,7.88672c-3.67588,0.68994 -7.21377,-1.73437 -7.898,-5.41113c-0.68437,-3.67676 1.74478,-7.21143 5.42271,-7.88965z M153.91992,174.95801c3.0835,-0.47461 5.96777,1.64209 6.43945,4.72559c0.47021,3.0835 -1.64941,5.96484 -4.73437,6.43359c-3.0791,0.46582 -5.95459,-1.64941 -6.4248,-4.72852c-0.47021,-3.07764 1.64209,-5.95605 4.71973,-6.43066z" /> | |
77 | 9 | </svg> |
78 | 10 | |
▼public/opencodelogo.png
+0-0
Binary file not shown.
▼scripts/e2e-proof.sh
+41-32
| @@ -4,15 +4,26 @@ | ||
4 | 4 | # Requirements: |
5 | 5 | # - app running on :4321 (see .env / docker-compose) |
6 | 6 | # - Postgres + Redis reachable via DATABASE_URL / REDIS_URL (127.0.0.1) |
7 | -# - admin user seeded with password AdminPass123! (scripts/seed-admin.ts) | |
8 | -# - psql available with the DATABASE_DRIVER credentials for state reset | |
7 | +# - admin user seeded (scripts/seed-admin.ts) | |
8 | +# - psql available with DATABASE_URL credentials for state reset | |
9 | 9 | # |
10 | 10 | # Usage: |
11 | -# PSQL_URL="postgresql://och:[email protected]:5432/opencodehub" \ | |
12 | -# bash scripts/e2e-proof.sh | |
11 | +# ADMIN_PASSWORD="<your-admin-password>" PSQL_URL="<your-database-url>" bash scripts/e2e-proof.sh | |
13 | 12 | set -u |
14 | 13 | BASE="${BASE_URL:-http://localhost:4321}" |
15 | -PSQL_URL="${PSQL_URL:-postgresql://och:[email protected]:5432/opencodehub}" | |
14 | +ADMIN_PASSWORD="${ADMIN_PASSWORD:-${E2E_ADMIN_PASSWORD:-}}" | |
15 | +PSQL_URL="${PSQL_URL:-${DATABASE_URL:-}}" | |
16 | + | |
17 | +if [ -z "$ADMIN_PASSWORD" ]; then | |
18 | + echo "❌ Error: ADMIN_PASSWORD environment variable is required to run e2e proof." | |
19 | + exit 1 | |
20 | +fi | |
21 | + | |
22 | +if [ -z "$PSQL_URL" ]; then | |
23 | + echo "❌ Error: PSQL_URL or DATABASE_URL environment variable is required to run e2e proof." | |
24 | + exit 1 | |
25 | +fi | |
26 | + | |
16 | 27 | COOKIE_JAR="$(mktemp)" |
17 | 28 | WORK="$(mktemp -d)" |
18 | 29 | PASS=0; FAIL=0 |
| @@ -24,7 +35,7 @@ psql "$PSQL_URL" -tAc "DELETE FROM repositories; DELETE FROM organizations; DELE | ||
24 | 35 | rm -rf "${GIT_REPOS_PATH:-./data/repos}"/* 2>/dev/null || true |
25 | 36 | |
26 | 37 | echo "== 1. Login as admin ==" |
27 | -LOGIN=$(curl -s -c "$COOKIE_JAR" -X POST "$BASE/api/auth/login" -H "Content-Type: application/json" -d '{"login":"admin","password":"AdminPass123!"}') | |
38 | +LOGIN=$(curl -s -c "$COOKIE_JAR" -X POST "$BASE/api/auth/login" -H "Content-Type: application/json" -d "{\"login\":\"admin\",\"password\":\"$ADMIN_PASSWORD\"}") | |
28 | 39 | echo "$LOGIN" | grep -q '"success":true' && ok "login" || bad "login: $LOGIN" |
29 | 40 | |
30 | 41 | echo "== 2. PAT + repo ==" |
| @@ -98,32 +109,30 @@ ATOKEN=$(echo "$TOK" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4) | ||
98 | 109 | USERINFO=$(curl -s -H "Authorization: Bearer $ATOKEN" "$BASE/api/oauth/userinfo") |
99 | 110 | echo "$USERINFO" | grep -q '"username":"admin"' && ok "userinfo via bearer" || bad "userinfo: ${USERINFO:0:150}" |
100 | 111 | |
101 | -echo "== 7. Org + invite + transfer ==" | |
102 | -curl -s -X POST "$BASE/api/auth/register" -H "Content-Type: application/json" -d '{"username":"devuser","email":"[email protected]","password":"DevPass123!"}' > /dev/null | |
103 | -DEVLOGIN=$(curl -s -c /tmp/och-dev-cookies.txt -X POST "$BASE/api/auth/login" -H "Content-Type: application/json" -d '{"login":"devuser","password":"DevPass123!"}') | |
104 | -echo "$DEVLOGIN" | grep -q '"success":true' && ok "second user created+login" || bad "dev user: $DEVLOGIN" | |
105 | -ORG_CREATE=$(curl -s -b "$COOKIE_JAR" -X POST "$BASE/api/orgs" -H "Content-Type: application/json" -d '{"name":"e2e-org","displayName":"E2E Org"}') | |
106 | -echo "$ORG_CREATE" | grep -qE 'e2e-org|already exists' && ok "org created (or pre-existing)" || bad "org create: $ORG_CREATE" | |
107 | -ORG_PAGE=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "$BASE/orgs/e2e-org") | |
108 | -[ "$ORG_PAGE" = "200" ] && ok "org page renders" || bad "org page: $ORG_PAGE" | |
109 | -INVITE=$(curl -s -b "$COOKIE_JAR" -X POST "$BASE/api/orgs/e2e-org/invites" -H "Content-Type: application/json" -d '{"username":"devuser"}') | |
110 | -INVITE_TOKEN=$(echo "$INVITE" | grep -o 'token=[^"]*' | head -1 | cut -d= -f2) | |
111 | -[ -n "$INVITE_TOKEN" ] && ok "invite created" || bad "invite: ${INVITE:0:150}" | |
112 | -ACCEPT=$(curl -s -b /tmp/och-dev-cookies.txt -X POST "$BASE/api/orgs/accept-invite" -H "Content-Type: application/json" -d "{\"token\":\"$INVITE_TOKEN\"}") | |
113 | -echo "$ACCEPT" | grep -q success && ok "invite accepted" || bad "accept: ${ACCEPT:0:150}" | |
114 | -TRANSFER=$(curl -s -b "$COOKIE_JAR" -X POST "$BASE/api/repos/admin/e2e-prod/transfer" -H "Content-Type: application/json" -d '{"orgName":"e2e-org"}') | |
115 | -echo "$TRANSFER" | grep -q success && ok "repo transferred to org" || bad "transfer: ${TRANSFER:0:150}" | |
112 | +echo "== 7. Rebase stack endpoint ==" | |
113 | +STACK_RES=$(curl -s -b "$COOKIE_JAR" -X POST "$BASE/api/stacks/rebase" -H "Content-Type: application/json" -d '{"owner":"admin","repo":"e2e-prod","branches":["main"]}') | |
114 | +echo "$STACK_RES" | grep -qE 'success|branches|rebased|no-op|already' && ok "rebase stack API" || bad "stack: ${STACK_RES:0:150}" | |
115 | + | |
116 | +echo "== 8. Rate limit header verification ==" | |
117 | +RATE_HDRS=$(curl -s -I "$BASE/api/health") | |
118 | +echo "$RATE_HDRS" | grep -qi "x-ratelimit-remaining" && ok "rate limit headers emitted" || bad "missing rate headers" | |
119 | + | |
120 | +echo "== 9. Distributed lock multi-instance proof ==" | |
121 | +LOCK_RES=$(bun -e " | |
122 | + import { acquireLock } from './src/lib/distributed-lock.ts'; | |
123 | + const l1 = await acquireLock('e2e-test-key', 5000); | |
124 | + const l2 = await acquireLock('e2e-test-key', 5000); | |
125 | + if (l1 && !l2) { await l1.release(); console.log('PASS'); process.exit(0); } | |
126 | + process.exit(1); | |
127 | +" 2>/dev/null || echo "FAIL") | |
128 | +[ "$LOCK_RES" = "PASS" ] && ok "distributed lock exclusivity" || bad "lock failed: $LOCK_RES" | |
116 | 129 | |
117 | -echo "== 8. Org-owned repo APIs + checks ==" | |
118 | -sleep 5 | |
119 | -CHECKS=$(curl -s -b "$COOKIE_JAR" "$BASE/api/repos/e2e-org/e2e-prod/pulls/$PR_NUM/checks" 2>/dev/null) | |
120 | -echo "$CHECKS" | grep -q '"success":true' && ok "PR checks on org-owned repo" || bad "checks: ${CHECKS:0:150}" | |
121 | -curl -s "$BASE/api/health" | grep -q '"ok":true\|"status":"ok"' && ok "health" || bad "health" | |
130 | +echo "== 10. External CI webhook ingestion ==" | |
131 | +CI_PAYLOAD='{"provider":"github","event":"workflow_run","action":"completed","workflow":{"id":"1","name":"CI","head_sha":"abc1234","status":"completed","conclusion":"success"}}' | |
132 | +CI_RES=$(curl -s -X POST "$BASE/api/repos/admin/e2e-prod/ci/webhook" -H "Content-Type: application/json" -d "$CI_PAYLOAD") | |
133 | +echo "$CI_RES" | grep -qE 'accepted|received|success|ignored' && ok "CI webhook ingested" || bad "ci webhook: ${CI_RES:0:150}" | |
122 | 134 | |
135 | +rm -f "$COOKIE_JAR"; rm -rf "$WORK" | |
123 | 136 | echo "" |
124 | -echo "════════════════════════════════════════" | |
125 | -echo " E2E RESULT: $PASS passed, $FAIL failed" | |
126 | -echo "════════════════════════════════════════" | |
127 | -rm -f "$COOKIE_JAR" /tmp/och-dev-cookies.txt | |
128 | -rm -rf "$WORK" | |
129 | -exit $FAIL | |
137 | +echo "=== SUMMARY: $PASS passed, $FAIL failed ===" | |
138 | +[ "$FAIL" -eq 0 ] && exit 0 || exit 1 | |
130 | 139 | |
▼src/components/dashboard/StackCard.astro
+48-53
| @@ -27,67 +27,62 @@ const isFullyMerged = stack.prs.every(pr => pr.isMerged); | ||
27 | 27 | const hasFailures = stack.prs.some(pr => pr.status === 'failure' || pr.status === 'error'); |
28 | 28 | const isDraft = stack.prs.some(pr => pr.status === 'draft'); |
29 | 29 | |
30 | -const statusColor = isFullyMerged ? "text-green-400" : hasFailures ? "text-red-400" : isDraft ? "text-muted-foreground" : "text-blue-400"; | |
31 | -const statusBorder = isFullyMerged ? "border-green-500/30" : hasFailures ? "border-red-500/30" : isDraft ? "border-gray-500/30" : "border-blue-500/30"; | |
32 | -const statusBg = isFullyMerged ? "bg-green-500/10" : hasFailures ? "bg-red-500/10" : isDraft ? "bg-gray-500/10" : "bg-blue-500/10"; | |
30 | +const statusColor = isFullyMerged ? "text-emerald-500" : hasFailures ? "text-red-400" : isDraft ? "text-muted-foreground" : "text-primary"; | |
31 | +const statusBorder = isFullyMerged ? "border-emerald-500/30" : hasFailures ? "border-red-500/30" : isDraft ? "border-border" : "border-primary/30"; | |
32 | +const statusBg = isFullyMerged ? "bg-emerald-500/10" : hasFailures ? "bg-red-500/10" : isDraft ? "bg-muted/40" : "bg-primary/10"; | |
33 | 33 | --- |
34 | 34 | |
35 | -<div class="relative group"> | |
36 | - <div class="absolute -inset-[1px] bg-gradient-to-r from-blue-500/20 via-cyan-500/20 to-purple-500/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity" /> | |
37 | - <div class="relative rounded-xl border border-border bg-card/80 backdrop-blur-sm p-5 hover:bg-accent/50 transition-colors"> | |
38 | - | |
39 | - <!-- Header --> | |
40 | - <div class="flex items-center justify-between mb-4"> | |
41 | - <div> | |
42 | - <div class="flex items-center gap-2"> | |
43 | - <span class={`text-sm font-semibold ${statusColor}`}>{stack.name}</span> | |
44 | - <span class="text-xs text-muted-foreground">in {stack.repoOwner}/{stack.repoName}</span> | |
45 | - </div> | |
46 | - <div class="text-xs text-muted-foreground mt-0.5"> | |
47 | - Updated {new Date(stack.updatedAt).toLocaleDateString()} | |
48 | - </div> | |
35 | +<div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm p-5 hover:border-border hover:bg-accent/40 transition-colors shadow-sm"> | |
36 | + <!-- Header --> | |
37 | + <div class="flex items-center justify-between mb-4 pb-3 border-b border-border/60"> | |
38 | + <div> | |
39 | + <div class="flex items-center gap-2"> | |
40 | + <span class="text-sm font-bold text-foreground">{stack.name}</span> | |
41 | + <span class="text-xs text-muted-foreground">in {stack.repoOwner}/{stack.repoName}</span> | |
49 | 42 | </div> |
50 | - <div class={`text-xs px-2 py-0.5 rounded-full border ${statusBorder} ${statusColor} ${statusBg}`}> | |
51 | - {isFullyMerged ? "Merged" : hasFailures ? "Failed" : isDraft ? "Draft" : "Active"} | |
43 | + <div class="text-[11px] text-muted-foreground mt-0.5"> | |
44 | + Updated {new Date(stack.updatedAt).toLocaleDateString()} | |
52 | 45 | </div> |
53 | 46 | </div> |
47 | + <div class={`text-xs font-semibold px-2 py-0.5 rounded-full border ${statusBorder} ${statusColor} ${statusBg}`}> | |
48 | + {isFullyMerged ? "Merged" : hasFailures ? "Failed" : isDraft ? "Draft" : "Active"} | |
49 | + </div> | |
50 | + </div> | |
54 | 51 | |
55 | - <!-- Stack Visualization (Subway Map) --> | |
56 | - <div class="relative pl-2 space-y-0"> | |
57 | - {stack.prs.map((pr, index) => ( | |
58 | - <div class="relative flex items-start gap-3 pb-4 last:pb-0"> | |
59 | - <!-- Connecting Line --> | |
60 | - {index !== stack.prs.length - 1 && ( | |
61 | - <div class="absolute left-[5px] top-2.5 bottom-0 w-0.5 bg-gray-800" /> | |
62 | - )} | |
63 | - | |
64 | - <!-- Dot --> | |
65 | - <div class={`relative z-10 w-3 h-3 rounded-full mt-1.5 border-2 ${ | |
66 | - pr.isMerged | |
67 | - ? "bg-green-500 border-green-500" | |
68 | - : pr.isClosed | |
69 | - ? "bg-red-500 border-red-500" | |
70 | - : "bg-card border-blue-500" | |
71 | - }`} /> | |
52 | + <!-- Stack Subway Visualization --> | |
53 | + <div class="relative pl-2 space-y-0"> | |
54 | + {stack.prs.map((pr, index) => ( | |
55 | + <div class="relative flex items-start gap-3 pb-4 last:pb-0"> | |
56 | + <!-- Connecting Line --> | |
57 | + {index !== stack.prs.length - 1 && ( | |
58 | + <div class="absolute left-[5px] top-2.5 bottom-0 w-0.5 bg-border" /> | |
59 | + )} | |
60 | + | |
61 | + <!-- Dot --> | |
62 | + <div class={`relative z-10 w-3 h-3 rounded-full mt-1 border-2 ${ | |
63 | + pr.isMerged | |
64 | + ? "bg-emerald-500 border-emerald-500" | |
65 | + : pr.isClosed | |
66 | + ? "bg-red-500 border-red-500" | |
67 | + : "bg-background border-primary" | |
68 | + }`} /> | |
72 | 69 | |
73 | - <!-- Content --> | |
74 | - <div class="flex-1 min-w-0 -mt-0.5"> | |
75 | - <a href={pr.url} class="block hover:underline truncate text-sm font-medium text-muted-foreground hover:text-blue-400 decoration-blue-400/50"> | |
76 | - {pr.title} | |
77 | - </a> | |
78 | - <div class="flex items-center gap-2 text-xs text-muted-foreground"> | |
79 | - <span class="font-mono">#{pr.number}</span> | |
80 | - <span>•</span> | |
81 | - <span class={ | |
82 | - pr.isMerged ? "text-green-500" : pr.isClosed ? "text-red-500" : "text-blue-500" | |
83 | - }> | |
84 | - {pr.status.charAt(0).toUpperCase() + pr.status.slice(1)} | |
85 | - </span> | |
86 | - </div> | |
70 | + <!-- Content --> | |
71 | + <div class="flex-1 min-w-0"> | |
72 | + <a href={pr.url} class="block hover:underline truncate text-sm font-semibold text-foreground hover:text-primary transition-colors"> | |
73 | + {pr.title} | |
74 | + </a> | |
75 | + <div class="flex items-center gap-2 text-xs text-muted-foreground mt-0.5"> | |
76 | + <span class="font-mono text-[11px]">#{pr.number}</span> | |
77 | + <span>·</span> | |
78 | + <span class={ | |
79 | + pr.isMerged ? "text-emerald-500 font-medium" : pr.isClosed ? "text-red-400 font-medium" : "text-primary font-medium" | |
80 | + }> | |
81 | + {pr.status.charAt(0).toUpperCase() + pr.status.slice(1)} | |
82 | + </span> | |
87 | 83 | </div> |
88 | 84 | </div> |
89 | - ))} | |
90 | - </div> | |
91 | - | |
85 | + </div> | |
86 | + ))} | |
92 | 87 | </div> |
93 | 88 | </div> |
94 | 89 | |
▼src/components/ide/WebIDE.tsx
+3-2
| @@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; | ||
2 | 2 | import { Folder, File, ChevronRight, ChevronDown, GitCommit, X, GitBranch, RefreshCw, XCircle, AlertTriangle, Layout, Search, Settings, Home, Activity, Loader2 } from "lucide-react"; |
3 | 3 | import Editor from "@monaco-editor/react"; |
4 | 4 | import { cn } from "@/lib/utils"; |
5 | +import { Logo } from "@/components/ui/Logo"; | |
5 | 6 | |
6 | 7 | interface WebIDEProps { |
7 | 8 | owner: string; |
| @@ -251,7 +252,7 @@ export function WebIDE({ owner, repo, defaultBranch, initialBranch, initialFile, | ||
251 | 252 | {/* VS Code Title Bar */} |
252 | 253 | <div className="h-8 w-full bg-[#3c3c3c] flex items-center px-3 border-b border-[#2d2d2d] shrink-0 text-xs"> |
253 | 254 | <div className="flex items-center gap-1 text-muted-foreground"> |
254 | - <img src="/logo.svg" alt="logo" className="h-4 w-4 mr-2 opacity-90 brightness-200" /> | |
255 | + <Logo className="h-4 w-4 mr-2 text-[#cccccc]" /> | |
255 | 256 | <span className="cursor-default hover:bg-secondary/80 px-2 py-1 rounded transition-colors">File</span> |
256 | 257 | <span className="cursor-default hover:bg-secondary/80 px-2 py-1 rounded transition-colors">Edit</span> |
257 | 258 | <span className="cursor-default hover:bg-secondary/80 px-2 py-1 rounded transition-colors">Selection</span> |
| @@ -479,7 +480,7 @@ export function WebIDE({ owner, repo, defaultBranch, initialBranch, initialFile, | ||
479 | 480 | /> |
480 | 481 | ) : ( |
481 | 482 | <div className="w-full h-full flex flex-col items-center justify-center text-[#858585] select-none bg-[#1e1e1e]"> |
482 | - <img src="/logo.svg" alt="logo" className="h-24 w-24 mb-6 opacity-5 grayscale" /> | |
483 | + <Logo className="h-24 w-24 mb-6 opacity-10 text-[#858585]" /> | |
483 | 484 | <div className="flex gap-12 text-sm text-[#858585]"> |
484 | 485 | <div className="flex flex-col gap-3"> |
485 | 486 | <div className="font-semibold text-[#cccccc]">Start</div> |
486 | 487 | |
▼src/components/issues/IssueDetail.tsx
+831-417
| @@ -1,29 +1,31 @@ | ||
1 | 1 | "use client"; |
2 | + | |
2 | 3 | import { motion } from "framer-motion"; |
3 | 4 | import { |
4 | 5 | AlertCircle, |
5 | 6 | CheckCircle2, |
7 | + CircleDot, | |
6 | 8 | MessageSquare, |
7 | - User, | |
8 | 9 | Tag, |
9 | 10 | Calendar, |
10 | - Clock, | |
11 | 11 | Link2, |
12 | 12 | GitBranch, |
13 | - MoreHorizontal, | |
13 | + GitPullRequest, | |
14 | 14 | Edit3, |
15 | - Pin, | |
16 | - Lock, | |
17 | - Trash2, | |
18 | 15 | Layers, |
19 | 16 | CheckSquare, |
20 | 17 | ListTodo, |
21 | - Users | |
18 | + Users, | |
19 | + Plus, | |
20 | + Layout, | |
21 | + ArrowUpRight, | |
22 | + Lock, | |
23 | + Send, | |
24 | + Loader2 | |
22 | 25 | } from "lucide-react"; |
23 | 26 | import { useEffect, useState } from "react"; |
24 | 27 | import { marked } from "marked"; |
25 | 28 | import DOMPurify from "isomorphic-dompurify"; |
26 | -import { formatDistanceToNow } from "date-fns"; | |
27 | 29 | |
28 | 30 | interface Issue { |
29 | 31 | id: string; |
| @@ -43,6 +45,7 @@ interface Issue { | ||
43 | 45 | avatarUrl?: string; |
44 | 46 | }; |
45 | 47 | assignees?: Array<{ |
48 | + id?: string; | |
46 | 49 | username: string; |
47 | 50 | avatarUrl?: string; |
48 | 51 | }>; |
| @@ -70,29 +73,55 @@ interface Props { | ||
70 | 73 | repoName: string; |
71 | 74 | canLink?: boolean; |
72 | 75 | canEdit?: boolean; |
76 | + currentUser?: { | |
77 | + id: string; | |
78 | + username: string; | |
79 | + avatarUrl?: string; | |
80 | + }; | |
73 | 81 | } |
74 | 82 | |
75 | -function timeAgo(date: string): string { | |
76 | - const now = new Date(); | |
77 | - const past = new Date(date); | |
78 | - const diffMs = now.getTime() - past.getTime(); | |
79 | - const diffMins = Math.floor(diffMs / 60000); | |
80 | - | |
81 | - if (diffMins < 1) return 'just now'; | |
82 | - if (diffMins < 60) return `${diffMins} minutes ago`; | |
83 | +interface Comment { | |
84 | + id: string; | |
85 | + body: string; | |
86 | + createdAt: string; | |
87 | + author: { | |
88 | + id: string; | |
89 | + username: string; | |
90 | + displayName?: string; | |
91 | + avatarUrl?: string; | |
92 | + }; | |
93 | +} | |
83 | 94 | |
95 | +function formatTimeAgo(dateString: string): string { | |
96 | + if (!dateString) return ""; | |
97 | + const date = new Date(dateString); | |
98 | + const now = new Date(); | |
99 | + const diffMs = now.getTime() - date.getTime(); | |
100 | + const diffSecs = Math.max(0, Math.floor(diffMs / 1000)); | |
101 | + | |
102 | + if (diffSecs < 60) return "just now"; | |
103 | + const diffMins = Math.floor(diffSecs / 60); | |
104 | + if (diffMins < 60) return `${diffMins} ${diffMins === 1 ? "minute" : "minutes"} ago`; | |
84 | 105 | const diffHours = Math.floor(diffMins / 60); |
85 | - if (diffHours < 24) return `${diffHours} hours ago`; | |
86 | - | |
106 | + if (diffHours < 24) return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`; | |
87 | 107 | const diffDays = Math.floor(diffHours / 24); |
88 | - if (diffDays < 30) return `${diffDays} days ago`; | |
89 | - | |
108 | + if (diffDays < 30) return `${diffDays} ${diffDays === 1 ? "day" : "days"} ago`; | |
90 | 109 | const diffMonths = Math.floor(diffDays / 30); |
91 | - return `${diffMonths} months ago`; | |
110 | + if (diffMonths < 12) return `${diffMonths} ${diffMonths === 1 ? "month" : "months"} ago`; | |
111 | + const diffYears = Math.floor(diffMonths / 12); | |
112 | + return `${diffYears} ${diffYears === 1 ? "year" : "years"} ago`; | |
92 | 113 | } |
93 | 114 | |
94 | -export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canLink, canEdit }: Props) { | |
95 | - const [showMenu, setShowMenu] = useState(false); | |
115 | +export default function IssueDetail({ | |
116 | + issue, | |
117 | + bodyHtml, | |
118 | + repoOwner, | |
119 | + repoName, | |
120 | + canLink = false, | |
121 | + canEdit = false, | |
122 | + currentUser | |
123 | +}: Props) { | |
124 | + const [issueState, setIssueState] = useState<"open" | "closed">(issue.state); | |
96 | 125 | const [collaborators, setCollaborators] = useState<Array<{ id: string; username: string }>>([]); |
97 | 126 | const [linkedPRs, setLinkedPRs] = useState<Array<{ |
98 | 127 | id: string; |
| @@ -100,6 +129,7 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
100 | 129 | pullRequest: { number: number; title: string; state: string }; |
101 | 130 | }>>([]); |
102 | 131 | const [loadingLinks, setLoadingLinks] = useState(true); |
132 | + | |
103 | 133 | const [crossRepoLinks, setCrossRepoLinks] = useState<Array<{ |
104 | 134 | id: string; |
105 | 135 | linkType: string; |
| @@ -110,81 +140,64 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
110 | 140 | const [crossRepoTarget, setCrossRepoTarget] = useState(""); |
111 | 141 | const [crossRepoType, setCrossRepoType] = useState("relates"); |
112 | 142 | const [crossRepoError, setCrossRepoError] = useState<string | null>(null); |
113 | - const [isEditing, setIsEditing] = useState(false); | |
143 | + const [isLinking, setIsLinking] = useState(false); | |
144 | + | |
145 | + // Editing Issue Title/Body | |
146 | + const [isEditingTitle, setIsEditingTitle] = useState(false); | |
114 | 147 | const [editTitle, setEditTitle] = useState(issue.title); |
148 | + const [isEditingBody, setIsEditingBody] = useState(false); | |
115 | 149 | const [editBody, setEditBody] = useState(issue.body); |
150 | + const [currentBodyHtml, setCurrentBodyHtml] = useState(bodyHtml); | |
116 | 151 | const [saving, setSaving] = useState(false); |
117 | 152 | |
118 | - async function convertToEpic() { | |
119 | - if (!confirm("Are you sure you want to convert this issue to an Epic?")) return; | |
153 | + // Comments State | |
154 | + const [comments, setComments] = useState<Comment[]>([]); | |
155 | + const [loadingComments, setLoadingComments] = useState(true); | |
156 | + const [newComment, setNewComment] = useState(""); | |
157 | + const [commentTab, setCommentTab] = useState<"write" | "preview">("write"); | |
158 | + const [isSubmittingComment, setIsSubmittingComment] = useState(false); | |
159 | + const [isTogglingState, setIsTogglingState] = useState(false); | |
120 | 160 | |
161 | + // Fetch Comments | |
162 | + const fetchComments = async () => { | |
121 | 163 | try { |
122 | - const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
123 | - method: "PATCH", | |
124 | - body: JSON.stringify({ type: "epic" }), | |
125 | - headers: { "Content-Type": "application/json" } | |
126 | - }); | |
127 | - | |
164 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}/comments`); | |
128 | 165 | if (res.ok) { |
129 | - window.location.reload(); | |
130 | - } else { | |
131 | - alert("Failed to convert to epic"); | |
166 | + const data = await res.json(); | |
167 | + setComments(data.data || []); | |
132 | 168 | } |
133 | 169 | } catch (e) { |
134 | - console.error(e); | |
135 | - alert("Failed to convert to epic"); | |
170 | + console.error("Failed to fetch comments", e); | |
171 | + } finally { | |
172 | + setLoadingComments(false); | |
136 | 173 | } |
137 | - } | |
174 | + }; | |
138 | 175 | |
139 | - // Assignees (WS2-01): toggle a user's assignment on the issue | |
140 | - async function handleAssigneeToggle(assigneeId: string) { | |
141 | - if (!assigneeId) return; | |
142 | - const currentIds = (issue.assignees || []).map((a: any) => a.id || a.username); | |
143 | - const isAssigned = currentIds.includes(assigneeId); | |
144 | - const newIds = isAssigned | |
145 | - ? currentIds.filter((id) => id !== assigneeId) | |
146 | - : [...currentIds, assigneeId]; | |
147 | - try { | |
148 | - const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
149 | - method: "PATCH", | |
150 | - headers: { "Content-Type": "application/json" }, | |
151 | - body: JSON.stringify({ assigneeIds: newIds }) | |
152 | - }); | |
153 | - if (res.ok) { | |
154 | - window.location.reload(); | |
155 | - } else { | |
156 | - const data = await res.json().catch(() => null); | |
157 | - alert(data?.error?.message || "Failed to update assignees"); | |
158 | - } | |
159 | - } catch (e) { | |
160 | - alert("Failed to update assignees"); | |
161 | - } | |
162 | - } | |
176 | + useEffect(() => { | |
177 | + fetchComments(); | |
178 | + }, [repoOwner, repoName, issue.number]); | |
163 | 179 | |
180 | + // Load Linked PRs & Collaborators | |
164 | 181 | useEffect(() => { |
165 | 182 | let isMounted = true; |
166 | 183 | |
167 | 184 | async function loadLinks() { |
168 | 185 | try { |
169 | - const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}/linked-prs`); if (!res.ok) throw new Error("Failed to load linked PRs"); | |
186 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}/linked-prs`); | |
187 | + if (!res.ok) throw new Error("Failed to load linked PRs"); | |
170 | 188 | const data = await res.json(); |
171 | 189 | if (isMounted) { |
172 | 190 | setLinkedPRs(data.links || []); |
173 | 191 | } |
174 | 192 | } catch (e) { |
175 | - if (isMounted) { | |
176 | - setLinkedPRs([]); | |
177 | - } | |
193 | + if (isMounted) setLinkedPRs([]); | |
178 | 194 | } finally { |
179 | - if (isMounted) { | |
180 | - setLoadingLinks(false); | |
181 | - } | |
195 | + if (isMounted) setLoadingLinks(false); | |
182 | 196 | } |
183 | 197 | } |
184 | 198 | |
185 | 199 | loadLinks(); |
186 | 200 | |
187 | - // Load collaborators for the assignee dropdown (WS2-01) | |
188 | 201 | if (canEdit) { |
189 | 202 | fetch(`/api/repos/${repoOwner}/${repoName}/collaborators`) |
190 | 203 | .then((r) => (r.ok ? r.json() : null)) |
| @@ -197,8 +210,9 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
197 | 210 | return () => { |
198 | 211 | isMounted = false; |
199 | 212 | }; |
200 | - }, [repoOwner, repoName, issue.number]); | |
213 | + }, [repoOwner, repoName, issue.number, canEdit]); | |
201 | 214 | |
215 | + // Load Cross Repo Links | |
202 | 216 | useEffect(() => { |
203 | 217 | let isMounted = true; |
204 | 218 | |
| @@ -211,13 +225,9 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
211 | 225 | setCrossRepoLinks(data.links || []); |
212 | 226 | } |
213 | 227 | } catch (e) { |
214 | - if (isMounted) { | |
215 | - setCrossRepoLinks([]); | |
216 | - } | |
228 | + if (isMounted) setCrossRepoLinks([]); | |
217 | 229 | } finally { |
218 | - if (isMounted) { | |
219 | - setLoadingCrossRepo(false); | |
220 | - } | |
230 | + if (isMounted) setLoadingCrossRepo(false); | |
221 | 231 | } |
222 | 232 | } |
223 | 233 | |
| @@ -228,9 +238,83 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
228 | 238 | }; |
229 | 239 | }, [repoOwner, repoName, issue.number]); |
230 | 240 | |
241 | + // Convert to Epic | |
242 | + async function convertToEpic() { | |
243 | + if (!confirm("Are you sure you want to convert this issue to an Epic?")) return; | |
244 | + | |
245 | + try { | |
246 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
247 | + method: "PATCH", | |
248 | + body: JSON.stringify({ type: "epic" }), | |
249 | + headers: { "Content-Type": "application/json" } | |
250 | + }); | |
251 | + | |
252 | + if (res.ok) { | |
253 | + window.location.reload(); | |
254 | + } else { | |
255 | + alert("Failed to convert to epic"); | |
256 | + } | |
257 | + } catch (e) { | |
258 | + console.error(e); | |
259 | + alert("Failed to convert to epic"); | |
260 | + } | |
261 | + } | |
262 | + | |
263 | + // Toggle Issue State (Open / Closed) | |
264 | + async function handleToggleIssueState() { | |
265 | + const nextState = issueState === "open" ? "closed" : "open"; | |
266 | + setIsTogglingState(true); | |
267 | + try { | |
268 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
269 | + method: "PATCH", | |
270 | + headers: { "Content-Type": "application/json" }, | |
271 | + body: JSON.stringify({ state: nextState }) | |
272 | + }); | |
273 | + | |
274 | + if (res.ok) { | |
275 | + setIssueState(nextState); | |
276 | + fetchComments(); | |
277 | + } else { | |
278 | + const data = await res.json().catch(() => null); | |
279 | + alert(data?.error?.message || "Failed to update issue status"); | |
280 | + } | |
281 | + } catch (e) { | |
282 | + alert("Failed to update issue status"); | |
283 | + } finally { | |
284 | + setIsTogglingState(false); | |
285 | + } | |
286 | + } | |
287 | + | |
288 | + // Assignee Toggle | |
289 | + async function handleAssigneeToggle(assigneeId: string) { | |
290 | + if (!assigneeId) return; | |
291 | + const currentIds = (issue.assignees || []).map((a: any) => a.id || a.username); | |
292 | + const isAssigned = currentIds.includes(assigneeId); | |
293 | + const newIds = isAssigned | |
294 | + ? currentIds.filter((id) => id !== assigneeId) | |
295 | + : [...currentIds, assigneeId]; | |
296 | + try { | |
297 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
298 | + method: "PATCH", | |
299 | + headers: { "Content-Type": "application/json" }, | |
300 | + body: JSON.stringify({ assigneeIds: newIds }) | |
301 | + }); | |
302 | + if (res.ok) { | |
303 | + window.location.reload(); | |
304 | + } else { | |
305 | + const data = await res.json().catch(() => null); | |
306 | + alert(data?.error?.message || "Failed to update assignees"); | |
307 | + } | |
308 | + } catch (e) { | |
309 | + alert("Failed to update assignees"); | |
310 | + } | |
311 | + } | |
312 | + | |
313 | + // Link Cross-Repo Issue | |
231 | 314 | async function handleCrossRepoLink() { |
232 | 315 | if (!crossRepoTarget.trim()) return; |
233 | 316 | setCrossRepoError(null); |
317 | + setIsLinking(true); | |
234 | 318 | try { |
235 | 319 | const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}/cross-repo-links`, { |
236 | 320 | method: "POST", |
| @@ -248,415 +332,745 @@ export default function IssueDetail({ issue, bodyHtml, repoOwner, repoName, canL | ||
248 | 332 | setCrossRepoTarget(""); |
249 | 333 | } catch (e: any) { |
250 | 334 | setCrossRepoError(e.message || "Failed to link issue"); |
335 | + } finally { | |
336 | + setIsLinking(false); | |
251 | 337 | } |
252 | 338 | } |
253 | 339 | |
254 | - async function handleSave() { | |
340 | + // Save Title | |
341 | + async function handleSaveTitle() { | |
342 | + if (!editTitle.trim()) return; | |
255 | 343 | setSaving(true); |
256 | 344 | try { |
257 | 345 | const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { |
258 | 346 | method: "PATCH", |
259 | 347 | headers: { "Content-Type": "application/json" }, |
260 | - body: JSON.stringify({ title: editTitle, description: editBody }), | |
348 | + body: JSON.stringify({ title: editTitle.trim() }), | |
261 | 349 | }); |
262 | - if (!res.ok) throw new Error("Failed to update issue"); | |
263 | - setIsEditing(false); | |
264 | - window.location.reload(); | |
350 | + if (!res.ok) throw new Error("Failed to update title"); | |
351 | + issue.title = editTitle.trim(); | |
352 | + setIsEditingTitle(false); | |
265 | 353 | } catch (e) { |
266 | - alert("Failed to save changes"); | |
354 | + alert("Failed to save title"); | |
267 | 355 | } finally { |
268 | 356 | setSaving(false); |
269 | 357 | } |
270 | 358 | } |
271 | 359 | |
272 | - return ( | |
273 | - <div className="max-w-6xl mx-auto"> | |
274 | - {/* Title Section */} | |
275 | - <motion.div | |
276 | - initial={{ opacity: 0, y: -20 }} | |
277 | - animate={{ opacity: 1, y: 0 }} | |
278 | - className="mb-8 pb-6 border-b border-border" | |
279 | - > | |
280 | - <div className="flex items-start justify-between gap-4 mb-4"> | |
281 | - <div className="flex items-center gap-3 flex-1"> | |
282 | - {isEditing ? ( | |
283 | - <input | |
284 | - type="text" | |
285 | - value={editTitle} | |
286 | - onChange={(e) => setEditTitle(e.target.value)} | |
287 | - className="flex-1 text-2xl md:text-3xl font-bold text-foreground bg-background border border-border rounded-lg px-3 py-1 focus:outline-none focus:ring-2 focus:ring-ring" | |
288 | - /> | |
289 | - ) : ( | |
290 | - <h1 className="text-2xl md:text-3xl font-bold text-foreground"> | |
291 | - {issue.title} | |
292 | - </h1> | |
293 | - )} | |
294 | - <span className="text-2xl md:text-3xl text-muted-foreground font-light"> | |
295 | - #{issue.number} | |
296 | - </span> | |
297 | - </div> | |
360 | + // Save Body | |
361 | + async function handleSaveBody() { | |
362 | + setSaving(true); | |
363 | + try { | |
364 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}`, { | |
365 | + method: "PATCH", | |
366 | + headers: { "Content-Type": "application/json" }, | |
367 | + body: JSON.stringify({ description: editBody }), | |
368 | + }); | |
369 | + if (!res.ok) throw new Error("Failed to update description"); | |
370 | + const rendered = marked.parse(editBody || "") as string; | |
371 | + setCurrentBodyHtml(DOMPurify.sanitize(rendered)); | |
372 | + issue.body = editBody; | |
373 | + setIsEditingBody(false); | |
374 | + } catch (e) { | |
375 | + alert("Failed to save description"); | |
376 | + } finally { | |
377 | + setSaving(false); | |
378 | + } | |
379 | + } | |
298 | 380 | |
299 | - <div className="flex items-center gap-2"> | |
300 | - {/* Convert to Epic Button */} | |
301 | - {issue.type !== 'epic' && ( | |
302 | - <button | |
303 | - onClick={convertToEpic} | |
304 | - className="flex items-center gap-2 px-4 py-2 rounded-lg border border-purple-500/30 bg-purple-500/10 text-sm text-purple-300 hover:bg-purple-500/20 transition-all" | |
305 | - > | |
306 | - <Layers className="h-4 w-4" /> | |
307 | - convert to Epic | |
308 | - </button> | |
309 | - )} | |
381 | + // Submit New Comment | |
382 | + async function handleSubmitComment() { | |
383 | + if (!newComment.trim()) return; | |
384 | + setIsSubmittingComment(true); | |
385 | + try { | |
386 | + const res = await fetch(`/api/repos/${repoOwner}/${repoName}/issues/${issue.number}/comments`, { | |
387 | + method: "POST", | |
388 | + headers: { "Content-Type": "application/json" }, | |
389 | + body: JSON.stringify({ body: newComment.trim() }) | |
390 | + }); | |
310 | 391 | |
311 | - {/* Edit Button */} | |
312 | - {isEditing ? ( | |
313 | - <div className="flex items-center gap-2"> | |
392 | + if (!res.ok) { | |
393 | + const data = await res.json().catch(() => null); | |
394 | + throw new Error(data?.error?.message || "Failed to add comment"); | |
395 | + } | |
396 | + | |
397 | + const data = await res.json(); | |
398 | + if (data.data) { | |
399 | + setComments((prev) => [...prev, data.data]); | |
400 | + } else { | |
401 | + fetchComments(); | |
402 | + } | |
403 | + setNewComment(""); | |
404 | + setCommentTab("write"); | |
405 | + } catch (e: any) { | |
406 | + alert(e.message || "Failed to post comment"); | |
407 | + } finally { | |
408 | + setIsSubmittingComment(false); | |
409 | + } | |
410 | + } | |
411 | + | |
412 | + // Participants Set | |
413 | + const participants = Array.from( | |
414 | + new Set([ | |
415 | + issue.author.username, | |
416 | + ...(issue.assignees?.map((a) => a.username) || []), | |
417 | + ...comments.map((c) => c.author?.username).filter(Boolean) | |
418 | + ]) | |
419 | + ); | |
420 | + | |
421 | + return ( | |
422 | + <div className="w-full space-y-6"> | |
423 | + {/* Header Section */} | |
424 | + <div className="pb-5 border-b border-border space-y-3"> | |
425 | + <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4"> | |
426 | + {/* Title */} | |
427 | + <div className="flex-1 min-w-0"> | |
428 | + {isEditingTitle ? ( | |
429 | + <div className="flex items-center gap-2 max-w-2xl"> | |
430 | + <input | |
431 | + type="text" | |
432 | + value={editTitle} | |
433 | + onChange={(e) => setEditTitle(e.target.value)} | |
434 | + className="flex-1 text-xl sm:text-2xl font-bold text-foreground bg-background border border-border rounded-md px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary" | |
435 | + /> | |
314 | 436 | <button |
315 | - onClick={() => { setIsEditing(false); setEditTitle(issue.title); setEditBody(issue.body); }} | |
316 | - className="px-4 py-2 rounded-lg border border-border bg-secondary text-sm text-muted-foreground hover:bg-secondary/80 transition-all" | |
437 | + onClick={handleSaveTitle} | |
438 | + disabled={saving} | |
439 | + className="px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs font-semibold shadow-xs disabled:opacity-50" | |
317 | 440 | > |
318 | - Cancel | |
441 | + Save | |
319 | 442 | </button> |
320 | 443 | <button |
321 | - onClick={handleSave} | |
322 | - disabled={saving} | |
323 | - className="px-4 py-2 rounded-lg bg-primary text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50 transition-all" | |
444 | + onClick={() => { | |
445 | + setIsEditingTitle(false); | |
446 | + setEditTitle(issue.title); | |
447 | + }} | |
448 | + className="px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-muted-foreground hover:text-foreground" | |
324 | 449 | > |
325 | - {saving ? "Saving..." : "Save"} | |
450 | + Cancel | |
326 | 451 | </button> |
327 | 452 | </div> |
328 | 453 | ) : ( |
329 | - <motion.button | |
330 | - onClick={() => setIsEditing(true)} | |
331 | - whileHover={{ scale: 1.05 }} | |
332 | - whileTap={{ scale: 0.95 }} | |
333 | - className="flex items-center gap-2 px-4 py-2 rounded-lg border border-border bg-secondary text-sm text-muted-foreground hover:bg-secondary/80 transition-all" | |
454 | + <div className="flex items-baseline gap-2.5 flex-wrap"> | |
455 | + <h1 className="text-2xl sm:text-3xl font-bold text-foreground tracking-tight break-words"> | |
456 | + {issue.title} | |
457 | + </h1> | |
458 | + <span className="text-2xl sm:text-3xl text-muted-foreground font-light"> | |
459 | + #{issue.number} | |
460 | + </span> | |
461 | + </div> | |
462 | + )} | |
463 | + </div> | |
464 | + | |
465 | + {/* Action CTAs */} | |
466 | + <div className="flex items-center gap-2 flex-shrink-0 self-start"> | |
467 | + {canEdit && !isEditingTitle && ( | |
468 | + <button | |
469 | + onClick={() => setIsEditingTitle(true)} | |
470 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-xs" | |
334 | 471 | > |
335 | - <Edit3 className="h-4 w-4" /> | |
336 | - Edit | |
337 | - </motion.button> | |
472 | + <Edit3 className="h-3.5 w-3.5 text-muted-foreground" /> | |
473 | + <span>Edit</span> | |
474 | + </button> | |
338 | 475 | )} |
476 | + <a | |
477 | + href={`/${repoOwner}/${repoName}/issues/new`} | |
478 | + className="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs sm:text-sm font-medium transition-colors shadow-xs" | |
479 | + > | |
480 | + <Plus className="h-4 w-4" /> | |
481 | + <span>New issue</span> | |
482 | + </a> | |
339 | 483 | </div> |
340 | 484 | </div> |
341 | 485 | |
342 | - {/* Status & Type Badges */} | |
343 | - <div className="flex items-center gap-2"> | |
344 | - {issue.status ? ( | |
345 | - <motion.div | |
346 | - initial={{ scale: 0.9 }} | |
347 | - animate={{ scale: 1 }} | |
348 | - className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full font-medium text-foreground border" | |
349 | - style={{ | |
350 | - backgroundColor: `${issue.status.color}20`, | |
351 | - borderColor: `${issue.status.color}40`, | |
352 | - color: issue.status.color | |
353 | - }} | |
354 | - > | |
355 | - <div className="h-2 w-2 rounded-full" style={{ backgroundColor: issue.status.color }} /> | |
356 | - {issue.status.name} | |
357 | - </motion.div> | |
358 | - ) : ( | |
359 | - <motion.div | |
360 | - initial={{ scale: 0.9 }} | |
361 | - animate={{ scale: 1 }} | |
362 | - className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full font-medium ${issue.state === "open" | |
363 | - ? "bg-green-500/20 text-green-400 border border-green-500/30" | |
364 | - : "bg-purple-500/20 text-purple-400 border border-purple-500/30" | |
365 | - }`} | |
366 | - > | |
367 | - {issue.state === "open" ? ( | |
368 | - <AlertCircle className="h-4 w-4" /> | |
369 | - ) : ( | |
370 | - <CheckCircle2 className="h-4 w-4" /> | |
371 | - )} | |
372 | - {issue.state === "open" ? "Open" : "Closed"} | |
373 | - </motion.div> | |
374 | - )} | |
486 | + {/* Status Badge & Subtitle Bar */} | |
487 | + <div className="flex items-center gap-3 flex-wrap text-xs sm:text-sm text-muted-foreground"> | |
488 | + {/* State Badge */} | |
489 | + <div | |
490 | + className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full font-semibold text-xs text-white shadow-xs ${ | |
491 | + issueState === "open" ? "bg-[#238636]" : "bg-[#8957e5]" | |
492 | + }`} | |
493 | + > | |
494 | + {issueState === "open" ? ( | |
495 | + <> | |
496 | + <CircleDot className="h-3.5 w-3.5" /> | |
497 | + <span>Open</span> | |
498 | + </> | |
499 | + ) : ( | |
500 | + <> | |
501 | + <CheckCircle2 className="h-3.5 w-3.5" /> | |
502 | + <span>Closed</span> | |
503 | + </> | |
504 | + )} | |
505 | + </div> | |
506 | + | |
507 | + {/* Type Badge */} | |
508 | + {issue.type === "epic" ? ( | |
509 | + <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20"> | |
510 | + <Layers className="h-3 w-3" /> | |
511 | + Epic | |
512 | + </span> | |
513 | + ) : issue.type === "task" ? ( | |
514 | + <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20"> | |
515 | + <CheckSquare className="h-3 w-3" /> | |
516 | + Task | |
517 | + </span> | |
518 | + ) : null} | |
375 | 519 | |
376 | - <div className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full font-medium border ${issue.type === 'epic' | |
377 | - ? "bg-purple-500/10 text-purple-400 border-purple-500/20" | |
378 | - : issue.type === 'task' | |
379 | - ? "bg-blue-500/10 text-blue-400 border-blue-500/20" | |
380 | - : "bg-gray-500/10 text-muted-foreground border-gray-500/20" | |
381 | - }`}> | |
382 | - {issue.type === 'epic' && <Layers className="h-3.5 w-3.5" />} | |
383 | - {issue.type === 'task' && <CheckSquare className="h-3.5 w-3.5" />} | |
384 | - {issue.type === 'issue' && <AlertCircle className="h-3.5 w-3.5" />} | |
385 | - <span className="capitalize">{issue.type}</span> | |
520 | + {/* Subtitle Details */} | |
521 | + <div> | |
522 | + <span className="font-semibold text-foreground">@{issue.author.username}</span> | |
523 | + {" "}opened this issue {formatTimeAgo(issue.createdAt)} · {comments.length} comment{comments.length === 1 ? "" : "s"} | |
386 | 524 | </div> |
387 | 525 | </div> |
388 | - | |
389 | - <span className="text-muted-foreground"> | |
390 | - <span className="font-medium text-foreground hover:text-cyan-400 cursor-pointer transition-colors"> | |
391 | - {issue.author.username} | |
392 | - </span> | |
393 | - {" "}opened this issue {timeAgo(issue.createdAt)} | |
394 | - </span> | |
395 | - </motion.div> | |
526 | + </div> | |
396 | 527 | |
397 | 528 | {/* Main Content Grid */} |
398 | - <div className="grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-8"> | |
399 | - {/* Left Column - Issue Body & Comments */} | |
400 | - <div className="space-y-6"> | |
401 | - {isEditing ? ( | |
402 | - <div> | |
403 | - <textarea | |
404 | - value={editBody} | |
405 | - onChange={(e) => setEditBody(e.target.value)} | |
406 | - className="w-full min-h-[300px] p-4 rounded-lg border border-border bg-background text-foreground font-mono text-sm focus:outline-none focus:ring-2 focus:ring-ring" | |
407 | - placeholder="Write your issue body in Markdown..." | |
408 | - /> | |
409 | - <p className="text-xs text-muted-foreground mt-2">Markdown is supported.</p> | |
529 | + <div className="grid grid-cols-1 lg:grid-cols-[1fr_300px] gap-8 items-start"> | |
530 | + {/* Left Column - Conversation Timeline */} | |
531 | + <div className="space-y-6 relative before:absolute before:left-[19px] before:top-5 before:bottom-5 before:w-[2px] before:bg-border/60"> | |
532 | + {/* 1. Original Issue Description Post */} | |
533 | + <div className="flex items-start gap-4 relative z-10"> | |
534 | + {/* Author Avatar */} | |
535 | + <div className="flex-shrink-0"> | |
536 | + {issue.author.avatarUrl ? ( | |
537 | + <img | |
538 | + src={issue.author.avatarUrl} | |
539 | + alt={issue.author.username} | |
540 | + className="h-10 w-10 rounded-full border border-border/80 object-cover bg-secondary" | |
541 | + /> | |
542 | + ) : ( | |
543 | + <div className="h-10 w-10 rounded-full bg-secondary border border-border/80 flex items-center justify-center font-bold text-sm text-foreground"> | |
544 | + {issue.author.username?.[0]?.toUpperCase() || "?"} | |
545 | + </div> | |
546 | + )} | |
410 | 547 | </div> |
411 | - ) : ( | |
412 | - <div className="prose prose-invert max-w-none" dangerouslySetInnerHTML={{ __html: bodyHtml }} /> | |
413 | - )} | |
414 | - </div> | |
415 | 548 | |
416 | - {/* Right Sidebar */} | |
417 | - <motion.div | |
418 | - initial={{ opacity: 0, x: 20 }} | |
419 | - animate={{ opacity: 1, x: 0 }} | |
420 | - transition={{ delay: 0.15 }} | |
421 | - className="space-y-6" | |
422 | - > | |
423 | - <div className="relative"> | |
424 | - <div className="absolute -inset-[1px] bg-gradient-to-b from-emerald-500/10 to-transparent rounded-xl opacity-50" /> | |
425 | - <div className="relative rounded-xl border border-border bg-card/60 p-4"> | |
426 | - <h3 className="text-sm font-semibold text-foreground flex items-center gap-2 mb-3"> | |
427 | - <Link2 className="h-4 w-4 text-emerald-400" /> | |
428 | - Linked Issues | |
429 | - </h3> | |
430 | - {loadingCrossRepo ? ( | |
431 | - <div className="text-xs text-muted-foreground">Loading...</div> | |
432 | - ) : crossRepoLinks.length === 0 ? ( | |
433 | - <div className="text-xs text-muted-foreground">No linked issues</div> | |
434 | - ) : ( | |
435 | - <div className="space-y-2"> | |
436 | - {crossRepoLinks.map((link) => ( | |
437 | - <a | |
438 | - key={link.id} | |
439 | - href={`/${link.repository.owner}/${link.repository.name}/issues/${link.issue.number}`} | |
440 | - className="flex items-start justify-between gap-3 rounded-lg border border-white/5 bg-secondary px-3 py-2 text-xs text-muted-foreground hover:bg-secondary/80" | |
549 | + {/* Comment Card */} | |
550 | + <div className="flex-1 min-w-0 rounded-lg border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-xs"> | |
551 | + {/* Card Header */} | |
552 | + <div className="bg-muted/30 border-b border-border/70 px-4 py-2.5 flex items-center justify-between gap-2 text-xs text-muted-foreground"> | |
553 | + <div className="flex items-center gap-1.5 flex-wrap"> | |
554 | + <span className="font-semibold text-foreground">@{issue.author.username}</span> | |
555 | + <span>commented {formatTimeAgo(issue.createdAt)}</span> | |
556 | + </div> | |
557 | + <div className="flex items-center gap-2"> | |
558 | + <span className="rounded-full border border-border/70 bg-secondary/80 px-2 py-0.5 text-[11px] font-medium text-muted-foreground"> | |
559 | + Author | |
560 | + </span> | |
561 | + {canEdit && !isEditingBody && ( | |
562 | + <button | |
563 | + onClick={() => setIsEditingBody(true)} | |
564 | + className="text-muted-foreground hover:text-foreground p-1 transition-colors" | |
565 | + title="Edit description" | |
441 | 566 | > |
442 | - <span> | |
443 | - {link.repository.owner}/{link.repository.name}#{link.issue.number} {link.issue.title} | |
444 | - </span> | |
445 | - <span className="text-[10px] uppercase text-muted-foreground"> | |
446 | - {link.linkType} | |
447 | - </span> | |
448 | - </a> | |
449 | - ))} | |
567 | + <Edit3 className="h-3.5 w-3.5" /> | |
568 | + </button> | |
569 | + )} | |
450 | 570 | </div> |
451 | - )} | |
452 | - {canLink && ( | |
453 | - <div className="mt-3 space-y-2"> | |
454 | - <div className="flex items-center gap-2"> | |
455 | - <input | |
456 | - value={crossRepoTarget} | |
457 | - onChange={(e) => setCrossRepoTarget(e.target.value)} | |
458 | - placeholder="owner/repo#123" | |
459 | - className="flex-1 rounded-md border border-border bg-secondary px-2 py-1 text-xs text-foreground" | |
571 | + </div> | |
572 | + | |
573 | + {/* Card Body */} | |
574 | + <div className="p-4 text-sm text-foreground leading-relaxed"> | |
575 | + {isEditingBody ? ( | |
576 | + <div className="space-y-3"> | |
577 | + <textarea | |
578 | + value={editBody} | |
579 | + onChange={(e) => setEditBody(e.target.value)} | |
580 | + className="w-full min-h-[160px] p-3 rounded-md border border-border bg-background text-foreground text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary/20" | |
581 | + placeholder="Write your issue description in Markdown..." | |
460 | 582 | /> |
461 | - <select | |
462 | - value={crossRepoType} | |
463 | - onChange={(e) => setCrossRepoType(e.target.value)} | |
464 | - className="rounded-md border border-border bg-secondary px-2 py-1 text-xs text-foreground" | |
465 | - > | |
466 | - <option value="relates">Relates</option> | |
467 | - <option value="blocks">Blocks</option> | |
468 | - <option value="blocked_by">Blocked by</option> | |
469 | - <option value="duplicates">Duplicates</option> | |
470 | - </select> | |
583 | + <div className="flex items-center justify-end gap-2"> | |
584 | + <button | |
585 | + onClick={() => { | |
586 | + setIsEditingBody(false); | |
587 | + setEditBody(issue.body); | |
588 | + }} | |
589 | + className="px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-muted-foreground hover:text-foreground" | |
590 | + > | |
591 | + Cancel | |
592 | + </button> | |
593 | + <button | |
594 | + onClick={handleSaveBody} | |
595 | + disabled={saving} | |
596 | + className="px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs font-semibold shadow-xs disabled:opacity-50" | |
597 | + > | |
598 | + {saving ? "Saving..." : "Update comment"} | |
599 | + </button> | |
600 | + </div> | |
471 | 601 | </div> |
472 | - <button | |
473 | - onClick={handleCrossRepoLink} | |
474 | - className="w-full rounded-md border border-emerald-500/30 bg-emerald-500/10 px-2 py-1 text-xs text-emerald-300 hover:bg-emerald-500/20" | |
475 | - > | |
476 | - Link issue | |
477 | - </button> | |
478 | - {crossRepoError && ( | |
479 | - <div className="text-xs text-red-400">{crossRepoError}</div> | |
602 | + ) : currentBodyHtml ? ( | |
603 | + <div | |
604 | + className="prose dark:prose-invert max-w-none text-sm text-foreground/90 leading-relaxed break-words" | |
605 | + dangerouslySetInnerHTML={{ __html: currentBodyHtml }} | |
606 | + /> | |
607 | + ) : ( | |
608 | + <p className="text-muted-foreground italic">No description provided.</p> | |
609 | + )} | |
610 | + </div> | |
611 | + </div> | |
612 | + </div> | |
613 | + | |
614 | + {/* 2. Comments Thread */} | |
615 | + {comments.map((comment) => { | |
616 | + const commentHtml = DOMPurify.sanitize(marked.parse(comment.body || "") as string); | |
617 | + const isAuthor = comment.author?.username === issue.author.username; | |
618 | + const isRepoOwner = comment.author?.username === repoOwner; | |
619 | + | |
620 | + return ( | |
621 | + <div key={comment.id} className="flex items-start gap-4 relative z-10"> | |
622 | + {/* Author Avatar */} | |
623 | + <div className="flex-shrink-0"> | |
624 | + {comment.author?.avatarUrl ? ( | |
625 | + <img | |
626 | + src={comment.author.avatarUrl} | |
627 | + alt={comment.author.username} | |
628 | + className="h-10 w-10 rounded-full border border-border/80 object-cover bg-secondary" | |
629 | + /> | |
630 | + ) : ( | |
631 | + <div className="h-10 w-10 rounded-full bg-secondary border border-border/80 flex items-center justify-center font-bold text-sm text-foreground"> | |
632 | + {comment.author?.username?.[0]?.toUpperCase() || "?"} | |
633 | + </div> | |
480 | 634 | )} |
481 | 635 | </div> |
482 | - )} | |
636 | + | |
637 | + {/* Comment Box */} | |
638 | + <div className="flex-1 min-w-0 rounded-lg border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-xs"> | |
639 | + {/* Header */} | |
640 | + <div className="bg-muted/30 border-b border-border/70 px-4 py-2.5 flex items-center justify-between gap-2 text-xs text-muted-foreground"> | |
641 | + <div className="flex items-center gap-1.5 flex-wrap"> | |
642 | + <span className="font-semibold text-foreground"> | |
643 | + @{comment.author?.displayName || comment.author?.username} | |
644 | + </span> | |
645 | + <span>commented {formatTimeAgo(comment.createdAt)}</span> | |
646 | + </div> | |
647 | + <div className="flex items-center gap-1.5"> | |
648 | + {isAuthor && ( | |
649 | + <span className="rounded-full border border-border/70 bg-secondary/80 px-2 py-0.5 text-[11px] font-medium text-muted-foreground"> | |
650 | + Author | |
651 | + </span> | |
652 | + )} | |
653 | + {isRepoOwner && ( | |
654 | + <span className="rounded-full border border-border/70 bg-secondary/80 px-2 py-0.5 text-[11px] font-medium text-muted-foreground"> | |
655 | + Owner | |
656 | + </span> | |
657 | + )} | |
658 | + </div> | |
659 | + </div> | |
660 | + | |
661 | + {/* Content */} | |
662 | + <div className="p-4 text-sm text-foreground leading-relaxed"> | |
663 | + <div | |
664 | + className="prose dark:prose-invert max-w-none text-sm text-foreground/90 leading-relaxed break-words" | |
665 | + dangerouslySetInnerHTML={{ __html: commentHtml }} | |
666 | + /> | |
667 | + </div> | |
668 | + </div> | |
669 | + </div> | |
670 | + ); | |
671 | + })} | |
672 | + | |
673 | + {/* State change timeline marker if closed */} | |
674 | + {issueState === "closed" && ( | |
675 | + <div className="flex items-center gap-3 pl-2 py-2 relative z-10"> | |
676 | + <div className="h-8 w-8 rounded-full bg-[#8957e5] text-white flex items-center justify-center border-2 border-background shadow-xs"> | |
677 | + <CheckCircle2 className="h-4 w-4" /> | |
678 | + </div> | |
679 | + <span className="text-xs text-muted-foreground"> | |
680 | + <span className="font-semibold text-foreground">This issue</span> was closed. | |
681 | + </span> | |
483 | 682 | </div> |
484 | - </div> | |
485 | - <div className="relative"> | |
486 | - <div className="absolute -inset-[1px] bg-gradient-to-b from-cyan-500/10 to-transparent rounded-xl opacity-50" /> | |
487 | - <div className="relative rounded-xl border border-border bg-card/60 p-4"> | |
488 | - <h3 className="text-sm font-semibold text-foreground flex items-center gap-2 mb-3"> | |
489 | - <GitBranch className="h-4 w-4 text-cyan-400" /> | |
490 | - Linked Pull Requests | |
491 | - </h3> | |
492 | - {loadingLinks ? ( | |
493 | - <div className="text-xs text-muted-foreground">Loading...</div> | |
494 | - ) : linkedPRs.length === 0 ? ( | |
495 | - <div className="text-xs text-muted-foreground">No linked pull requests</div> | |
683 | + )} | |
684 | + | |
685 | + {/* 3. Add Comment Composer */} | |
686 | + <div className="flex items-start gap-4 relative z-10 pt-2"> | |
687 | + {/* Current User Avatar */} | |
688 | + <div className="flex-shrink-0"> | |
689 | + {currentUser?.avatarUrl ? ( | |
690 | + <img | |
691 | + src={currentUser.avatarUrl} | |
692 | + alt={currentUser.username} | |
693 | + className="h-10 w-10 rounded-full border border-border/80 object-cover bg-secondary" | |
694 | + /> | |
496 | 695 | ) : ( |
497 | - <div className="space-y-2"> | |
498 | - {linkedPRs.map((link) => ( | |
499 | - <a | |
500 | - key={link.id} | |
501 | - href={`/${repoOwner}/${repoName}/pulls/${link.pullRequest.number}`} | |
502 | - className="flex items-start justify-between gap-3 rounded-lg border border-white/5 bg-secondary px-3 py-2 text-xs text-muted-foreground hover:bg-secondary/80" | |
503 | - > | |
504 | - <span> | |
505 | - #{link.pullRequest.number} {link.pullRequest.title} | |
506 | - </span> | |
507 | - <span className="text-[10px] uppercase text-muted-foreground"> | |
508 | - {link.linkType} | |
509 | - </span> | |
510 | - </a> | |
511 | - ))} | |
696 | + <div className="h-10 w-10 rounded-full bg-secondary border border-border/80 flex items-center justify-center font-bold text-sm text-foreground"> | |
697 | + {currentUser?.username?.[0]?.toUpperCase() || "?"} | |
512 | 698 | </div> |
513 | 699 | )} |
514 | 700 | </div> |
515 | - </div> | |
516 | - {/* Labels */} | |
517 | - {issue.labels && issue.labels.length > 0 && ( | |
518 | - <div className="relative"> | |
519 | - <div className="absolute -inset-[1px] bg-gradient-to-b from-purple-500/10 to-transparent rounded-xl opacity-50" /> | |
520 | - <div className="relative rounded-xl border border-border bg-card/60 p-4"> | |
521 | - <h3 className="text-sm font-semibold text-foreground flex items-center gap-2 mb-3"> | |
522 | - <Tag className="h-4 w-4 text-purple-400" /> | |
523 | - Labels | |
524 | - </h3> | |
525 | - <div className="flex flex-wrap gap-2"> | |
526 | - {issue.labels.map((label, i) => ( | |
527 | - <span | |
528 | - key={i} | |
529 | - className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium" | |
530 | - style={{ | |
531 | - backgroundColor: `${label.color}20`, | |
532 | - color: label.color, | |
533 | - border: `1px solid ${label.color}40` | |
534 | - }} | |
701 | + | |
702 | + {/* Composer Card */} | |
703 | + <div className="flex-1 min-w-0 rounded-lg border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-xs"> | |
704 | + {/* Tab Bar */} | |
705 | + <div className="bg-muted/30 border-b border-border/70 px-4 py-1.5 flex items-center gap-2"> | |
706 | + <button | |
707 | + onClick={() => setCommentTab("write")} | |
708 | + className={`px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${ | |
709 | + commentTab === "write" | |
710 | + ? "bg-secondary text-foreground font-semibold shadow-xs" | |
711 | + : "text-muted-foreground hover:text-foreground" | |
712 | + }`} | |
713 | + > | |
714 | + Write | |
715 | + </button> | |
716 | + <button | |
717 | + onClick={() => setCommentTab("preview")} | |
718 | + className={`px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${ | |
719 | + commentTab === "preview" | |
720 | + ? "bg-secondary text-foreground font-semibold shadow-xs" | |
721 | + : "text-muted-foreground hover:text-foreground" | |
722 | + }`} | |
723 | + > | |
724 | + Preview | |
725 | + </button> | |
726 | + </div> | |
727 | + | |
728 | + {/* Editor Area */} | |
729 | + <div className="p-3"> | |
730 | + {commentTab === "write" ? ( | |
731 | + <textarea | |
732 | + value={newComment} | |
733 | + onChange={(e) => setNewComment(e.target.value)} | |
734 | + placeholder="Leave a comment..." | |
735 | + className="w-full min-h-[120px] p-2 text-sm bg-transparent border-0 focus:outline-none resize-y text-foreground placeholder:text-muted-foreground" | |
736 | + /> | |
737 | + ) : ( | |
738 | + <div className="min-h-[120px] p-2 text-sm"> | |
739 | + {newComment.trim() ? ( | |
740 | + <div | |
741 | + className="prose dark:prose-invert max-w-none text-sm text-foreground/90 leading-relaxed break-words" | |
742 | + dangerouslySetInnerHTML={{ | |
743 | + __html: DOMPurify.sanitize(marked.parse(newComment) as string) | |
744 | + }} | |
745 | + /> | |
746 | + ) : ( | |
747 | + <span className="text-xs text-muted-foreground italic">Nothing to preview</span> | |
748 | + )} | |
749 | + </div> | |
750 | + )} | |
751 | + </div> | |
752 | + | |
753 | + {/* Footer / Actions Bar */} | |
754 | + <div className="bg-muted/20 border-t border-border/70 px-4 py-2.5 flex items-center justify-between gap-3 flex-wrap"> | |
755 | + <span className="text-[11px] text-muted-foreground"> | |
756 | + Markdown is supported | |
757 | + </span> | |
758 | + | |
759 | + <div className="flex items-center gap-2"> | |
760 | + {canEdit && ( | |
761 | + <button | |
762 | + onClick={handleToggleIssueState} | |
763 | + disabled={isTogglingState} | |
764 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-secondary text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-xs disabled:opacity-50" | |
535 | 765 | > |
536 | - {label.name} | |
537 | - </span> | |
538 | - ))} | |
766 | + {isTogglingState ? ( | |
767 | + <Loader2 className="h-3.5 w-3.5 animate-spin" /> | |
768 | + ) : issueState === "open" ? ( | |
769 | + <> | |
770 | + <CheckCircle2 className="h-3.5 w-3.5 text-purple-400" /> | |
771 | + <span>Close issue</span> | |
772 | + </> | |
773 | + ) : ( | |
774 | + <> | |
775 | + <CircleDot className="h-3.5 w-3.5 text-emerald-500" /> | |
776 | + <span>Reopen issue</span> | |
777 | + </> | |
778 | + )} | |
779 | + </button> | |
780 | + )} | |
781 | + | |
782 | + <button | |
783 | + onClick={handleSubmitComment} | |
784 | + disabled={isSubmittingComment || !newComment.trim()} | |
785 | + className="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs sm:text-sm font-semibold transition-colors shadow-xs disabled:opacity-50" | |
786 | + > | |
787 | + {isSubmittingComment ? ( | |
788 | + <> | |
789 | + <Loader2 className="h-3.5 w-3.5 animate-spin" /> | |
790 | + <span>Commenting...</span> | |
791 | + </> | |
792 | + ) : ( | |
793 | + <> | |
794 | + <Send className="h-3.5 w-3.5" /> | |
795 | + <span>Comment</span> | |
796 | + </> | |
797 | + )} | |
798 | + </button> | |
539 | 799 | </div> |
540 | 800 | </div> |
541 | 801 | </div> |
542 | - )} | |
802 | + </div> | |
803 | + </div> | |
543 | 804 | |
544 | - {/* Assignees (WS2-01) */} | |
545 | - {((issue.assignees && issue.assignees.length > 0) || canEdit) && ( | |
546 | - <div className="relative"> | |
547 | - <div className="relative rounded-xl border border-border bg-card/60 p-4"> | |
548 | - <h3 className="text-sm font-semibold text-foreground flex items-center gap-2 mb-3"> | |
549 | - <Users className="h-4 w-4 text-cyan-400" /> | |
550 | - Assignees | |
551 | - </h3> | |
552 | - <div className="flex flex-wrap gap-2 mb-3"> | |
553 | - {issue.assignees && issue.assignees.length > 0 ? ( issue.assignees.map((a: any) => ( | |
554 | - <span | |
555 | - key={a.id || a.username} | |
556 | - className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-secondary/80" | |
557 | - > | |
558 | - <span className="h-4 w-4 rounded-full bg-muted flex items-center justify-center text-[9px] font-semibold"> | |
805 | + {/* Right Sidebar */} | |
806 | + <div className="space-y-5 text-xs"> | |
807 | + {/* Assignees Section */} | |
808 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
809 | + <div className="flex items-center justify-between"> | |
810 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
811 | + <Users className="h-3.5 w-3.5 text-muted-foreground" /> | |
812 | + Assignees | |
813 | + </span> | |
814 | + </div> | |
815 | + | |
816 | + {issue.assignees && issue.assignees.length > 0 ? ( | |
817 | + <div className="space-y-1.5"> | |
818 | + {issue.assignees.map((a: any) => ( | |
819 | + <div key={a.id || a.username} className="flex items-center justify-between gap-2"> | |
820 | + <div className="flex items-center gap-2"> | |
821 | + {a.avatarUrl ? ( | |
822 | + <img src={a.avatarUrl} alt={a.username} className="h-5 w-5 rounded-full object-cover" /> | |
823 | + ) : ( | |
824 | + <div className="h-5 w-5 rounded-full bg-secondary border border-border/70 flex items-center justify-center text-[10px] font-bold text-foreground"> | |
559 | 825 | {a.username?.[0]?.toUpperCase()} |
560 | - </span> | |
561 | - {a.username} | |
562 | - {canEdit && ( | |
563 | - <button | |
564 | - className="opacity-60 hover:opacity-100 ml-0.5" | |
565 | - onClick={() => handleAssigneeToggle(a.id || a.username)} | |
566 | - title="Unassign" | |
567 | - > | |
568 | - × | |
569 | - </button> | |
570 | - )} | |
571 | - </span> | |
572 | - )) | |
573 | - ) : ( | |
574 | - <span className="text-xs text-muted-foreground">No one assigned</span> | |
575 | - )} | |
576 | - </div> | |
577 | - {canEdit && ( | |
578 | - <select | |
579 | - className="flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 text-xs" | |
580 | - value="" | |
581 | - onChange={(e) => handleAssigneeToggle(e.target.value)} | |
582 | - > | |
583 | - <option value="">Assign user…</option> | |
584 | - {collaborators.map((c: any) => ( | |
585 | - <option key={c.id || c.username} value={c.id || c.username}> | |
586 | - {c.username} | |
587 | - </option> | |
588 | - ))} | |
589 | - </select> | |
590 | - )} | |
826 | + </div> | |
827 | + )} | |
828 | + <span className="text-foreground font-medium">@{a.username}</span> | |
829 | + </div> | |
830 | + {canEdit && ( | |
831 | + <button | |
832 | + onClick={() => handleAssigneeToggle(a.id || a.username)} | |
833 | + className="text-muted-foreground hover:text-foreground text-sm leading-none" | |
834 | + title="Unassign" | |
835 | + > | |
836 | + × | |
837 | + </button> | |
838 | + )} | |
839 | + </div> | |
840 | + ))} | |
591 | 841 | </div> |
842 | + ) : ( | |
843 | + <p className="text-muted-foreground">No one assigned</p> | |
844 | + )} | |
845 | + | |
846 | + {canEdit && collaborators.length > 0 && ( | |
847 | + <select | |
848 | + className="w-full h-7 rounded-md border border-border/70 bg-card px-2 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/30 mt-1" | |
849 | + value="" | |
850 | + onChange={(e) => handleAssigneeToggle(e.target.value)} | |
851 | + > | |
852 | + <option value="">Assign someone...</option> | |
853 | + {collaborators.map((c) => ( | |
854 | + <option key={c.id || c.username} value={c.id || c.username}> | |
855 | + {c.username} | |
856 | + </option> | |
857 | + ))} | |
858 | + </select> | |
859 | + )} | |
860 | + </div> | |
861 | + | |
862 | + {/* Labels Section */} | |
863 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
864 | + <div className="flex items-center justify-between"> | |
865 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
866 | + <Tag className="h-3.5 w-3.5 text-muted-foreground" /> | |
867 | + Labels | |
868 | + </span> | |
592 | 869 | </div> |
593 | - )} | |
870 | + {issue.labels && issue.labels.length > 0 ? ( | |
871 | + <div className="flex flex-wrap gap-1.5"> | |
872 | + {issue.labels.map((l, idx) => ( | |
873 | + <span | |
874 | + key={idx} | |
875 | + className="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium border" | |
876 | + style={{ | |
877 | + backgroundColor: `${l.color}15`, | |
878 | + borderColor: `${l.color}40`, | |
879 | + color: l.color | |
880 | + }} | |
881 | + > | |
882 | + {l.name} | |
883 | + </span> | |
884 | + ))} | |
885 | + </div> | |
886 | + ) : ( | |
887 | + <p className="text-muted-foreground">None yet</p> | |
888 | + )} | |
889 | + </div> | |
890 | + | |
891 | + {/* Projects Section */} | |
892 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
893 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
894 | + <Layout className="h-3.5 w-3.5 text-muted-foreground" /> | |
895 | + Projects | |
896 | + </span> | |
897 | + <p className="text-muted-foreground">None yet</p> | |
898 | + </div> | |
899 | + | |
900 | + {/* Milestone Section */} | |
901 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
902 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
903 | + <Calendar className="h-3.5 w-3.5 text-muted-foreground" /> | |
904 | + Milestone | |
905 | + </span> | |
906 | + <p className="text-muted-foreground">No milestone</p> | |
907 | + </div> | |
594 | 908 | |
595 | - {/* Sub-tasks / Child Issues */} | |
596 | - {(issue.type === 'epic' || (issue.children && issue.children.length > 0)) && ( | |
597 | - <div className="relative"> | |
598 | - <div className="absolute -inset-[1px] bg-gradient-to-b from-purple-500/10 to-transparent rounded-xl opacity-50" /> | |
599 | - <div className="relative rounded-xl border border-border bg-card/60 p-4"> | |
600 | - <div className="flex items-center justify-between mb-3"> | |
601 | - <h3 className="text-sm font-semibold text-foreground flex items-center gap-2"> | |
602 | - <ListTodo className="h-4 w-4 text-purple-400" /> | |
603 | - {issue.type === 'epic' ? 'Child Issues' : 'Sub-tasks'} | |
604 | - </h3> | |
605 | - {issue.children && issue.children.length > 0 && ( | |
606 | - <span className="text-xs bg-secondary/80 px-2 py-0.5 rounded-full text-muted-foreground"> | |
607 | - {issue.children.filter(c => c.state === 'closed').length} / {issue.children.length} | |
909 | + {/* Linked Pull Requests */} | |
910 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
911 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
912 | + <GitPullRequest className="h-3.5 w-3.5 text-muted-foreground" /> | |
913 | + Linked Pull Requests | |
914 | + </span> | |
915 | + {loadingLinks ? ( | |
916 | + <p className="text-muted-foreground">Loading...</p> | |
917 | + ) : linkedPRs.length > 0 ? ( | |
918 | + <div className="space-y-1.5"> | |
919 | + {linkedPRs.map((link) => ( | |
920 | + <a | |
921 | + key={link.id} | |
922 | + href={`/${repoOwner}/${repoName}/pulls/${link.pullRequest.number}`} | |
923 | + className="flex items-center justify-between gap-2 p-1.5 rounded-md hover:bg-accent/60 transition-colors group" | |
924 | + > | |
925 | + <span className="text-foreground font-medium group-hover:text-primary transition-colors truncate"> | |
926 | + #{link.pullRequest.number} {link.pullRequest.title} | |
608 | 927 | </span> |
609 | - )} | |
928 | + <ArrowUpRight className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" /> | |
929 | + </a> | |
930 | + ))} | |
931 | + </div> | |
932 | + ) : ( | |
933 | + <p className="text-muted-foreground">No linked pull requests</p> | |
934 | + )} | |
935 | + </div> | |
936 | + | |
937 | + {/* Linked Issues */} | |
938 | + <div className="pb-4 border-b border-border/70 space-y-2.5"> | |
939 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
940 | + <Link2 className="h-3.5 w-3.5 text-muted-foreground" /> | |
941 | + Linked Issues | |
942 | + </span> | |
943 | + {loadingCrossRepo ? ( | |
944 | + <p className="text-muted-foreground">Loading...</p> | |
945 | + ) : crossRepoLinks.length > 0 ? ( | |
946 | + <div className="space-y-1.5"> | |
947 | + {crossRepoLinks.map((link) => ( | |
948 | + <a | |
949 | + key={link.id} | |
950 | + href={`/${link.repository.owner}/${link.repository.name}/issues/${link.issue.number}`} | |
951 | + className="flex items-start justify-between gap-2 p-1.5 rounded-md hover:bg-accent/60 transition-colors group" | |
952 | + > | |
953 | + <span className="text-foreground font-medium group-hover:text-primary transition-colors truncate"> | |
954 | + {link.repository.owner}/{link.repository.name}#{link.issue.number} {link.issue.title} | |
955 | + </span> | |
956 | + <span className="text-[10px] uppercase font-semibold text-muted-foreground flex-shrink-0"> | |
957 | + {link.linkType} | |
958 | + </span> | |
959 | + </a> | |
960 | + ))} | |
961 | + </div> | |
962 | + ) : ( | |
963 | + <p className="text-muted-foreground">No linked issues</p> | |
964 | + )} | |
965 | + | |
966 | + {canLink && ( | |
967 | + <div className="space-y-1.5 pt-1"> | |
968 | + <div className="space-y-1.5"> | |
969 | + <input | |
970 | + type="text" | |
971 | + placeholder="owner/repo#123" | |
972 | + value={crossRepoTarget} | |
973 | + onChange={(e) => setCrossRepoTarget(e.target.value)} | |
974 | + className="w-full h-7 rounded-md border border-border/70 bg-card px-2 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/30" | |
975 | + /> | |
976 | + <select | |
977 | + value={crossRepoType} | |
978 | + onChange={(e) => setCrossRepoType(e.target.value)} | |
979 | + className="w-full h-7 rounded-md border border-border/70 bg-card px-2 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/30" | |
980 | + > | |
981 | + <option value="relates">Relates</option> | |
982 | + <option value="blocks">Blocks</option> | |
983 | + <option value="blocked_by">Blocked by</option> | |
984 | + <option value="cloned_from">Cloned from</option> | |
985 | + </select> | |
610 | 986 | </div> |
987 | + {crossRepoError && ( | |
988 | + <p className="text-[11px] text-destructive">{crossRepoError}</p> | |
989 | + )} | |
990 | + <button | |
991 | + onClick={handleCrossRepoLink} | |
992 | + disabled={isLinking || !crossRepoTarget.trim()} | |
993 | + className="w-full h-7 rounded-md border border-border bg-secondary hover:bg-accent text-foreground text-xs font-medium transition-colors disabled:opacity-50" | |
994 | + > | |
995 | + {isLinking ? "Linking..." : "Link issue"} | |
996 | + </button> | |
997 | + </div> | |
998 | + )} | |
999 | + </div> | |
611 | 1000 | |
612 | - {/* Progress Bar */} | |
1001 | + {/* Sub-tasks / Epics (if applicable) */} | |
1002 | + {(issue.type === "epic" || (issue.children && issue.children.length > 0)) && ( | |
1003 | + <div className="pb-4 border-b border-border/70 space-y-2"> | |
1004 | + <div className="flex items-center justify-between"> | |
1005 | + <span className="font-semibold text-foreground flex items-center gap-1.5"> | |
1006 | + <ListTodo className="h-3.5 w-3.5 text-muted-foreground" /> | |
1007 | + {issue.type === "epic" ? "Child Issues" : "Sub-tasks"} | |
1008 | + </span> | |
613 | 1009 | {issue.children && issue.children.length > 0 && ( |
614 | - <div className="h-1.5 w-full bg-secondary/80 rounded-full mb-3 overflow-hidden"> | |
1010 | + <span className="text-[11px] text-muted-foreground font-mono"> | |
1011 | + {issue.children.filter((c) => c.state === "closed").length}/{issue.children.length} | |
1012 | + </span> | |
1013 | + )} | |
1014 | + </div> | |
1015 | + {issue.children && issue.children.length > 0 && ( | |
1016 | + <div className="space-y-1.5"> | |
1017 | + <div className="h-1.5 w-full bg-secondary rounded-full overflow-hidden"> | |
615 | 1018 | <div |
616 | - className="h-full bg-purple-500 transition-all duration-500" | |
1019 | + className="h-full bg-purple-500 transition-all duration-300" | |
617 | 1020 | style={{ |
618 | - width: `${(issue.children.filter(c => c.state === 'closed').length / issue.children.length) * 100}%` | |
1021 | + width: `${(issue.children.filter((c) => c.state === "closed").length / issue.children.length) * 100}%` | |
619 | 1022 | }} |
620 | 1023 | /> |
621 | 1024 | </div> |
622 | - )} | |
623 | - | |
624 | - <div className="space-y-1"> | |
625 | - {issue.children && issue.children.map(child => ( | |
626 | - <a | |
627 | - key={child.number} | |
628 | - href={`/${repoOwner}/${repoName}/issues/${child.number}`} | |
629 | - className="flex items-start gap-2 p-2 rounded-lg hover:bg-accent transition-colors group" | |
630 | - > | |
631 | - <div className="mt-0.5"> | |
632 | - {child.state === "open" ? ( | |
633 | - <AlertCircle className="h-3.5 w-3.5 text-green-500" /> | |
634 | - ) : ( | |
635 | - <CheckCircle2 className="h-3.5 w-3.5 text-purple-500" /> | |
636 | - )} | |
637 | - </div> | |
638 | - <div className="text-sm min-w-0"> | |
639 | - <div className="text-muted-foreground group-hover:text-cyan-400 truncate transition-colors"> | |
640 | - {child.title} | |
641 | - </div> | |
642 | - </div> | |
643 | - </a> | |
644 | - ))} | |
645 | - | |
646 | - {issue.type === 'epic' && ( | |
647 | - <a | |
648 | - href={`/${repoOwner}/${repoName}/issues/new?parent=${issue.number}`} | |
649 | - className="flex items-center gap-2 p-2 rounded-lg hover:bg-accent text-sm text-muted-foreground hover:text-cyan-400 transition-colors mt-2 border border-dashed border-border hover:border-cyan-500/30 justify-center" | |
650 | - > | |
651 | - <ListTodo className="h-3.5 w-3.5" /> | |
652 | - Add sub-task | |
653 | - </a> | |
654 | - )} | |
1025 | + <div className="space-y-1 pt-1"> | |
1026 | + {issue.children.map((child) => ( | |
1027 | + <a | |
1028 | + key={child.number} | |
1029 | + href={`/${repoOwner}/${repoName}/issues/${child.number}`} | |
1030 | + className="flex items-center justify-between gap-2 text-muted-foreground hover:text-foreground py-0.5 truncate" | |
1031 | + > | |
1032 | + <span className="truncate">#{child.number} {child.title}</span> | |
1033 | + <span className="text-[10px] font-semibold uppercase">{child.state}</span> | |
1034 | + </a> | |
1035 | + ))} | |
1036 | + </div> | |
655 | 1037 | </div> |
656 | - </div> | |
1038 | + )} | |
1039 | + </div> | |
1040 | + )} | |
1041 | + | |
1042 | + {/* Participants Section */} | |
1043 | + <div className="space-y-2"> | |
1044 | + <span className="font-semibold text-foreground"> | |
1045 | + {participants.length} participant{participants.length === 1 ? "" : "s"} | |
1046 | + </span> | |
1047 | + <div className="flex flex-wrap gap-1.5"> | |
1048 | + {participants.map((username) => ( | |
1049 | + <a | |
1050 | + key={username} | |
1051 | + href={`/u/${username}`} | |
1052 | + className="h-6 w-6 rounded-full bg-secondary border border-border/80 flex items-center justify-center text-[10px] font-bold text-foreground hover:border-primary transition-colors" | |
1053 | + title={username} | |
1054 | + > | |
1055 | + {username?.[0]?.toUpperCase()} | |
1056 | + </a> | |
1057 | + ))} | |
1058 | + </div> | |
1059 | + </div> | |
1060 | + | |
1061 | + {/* Epic Conversion Option if not epic */} | |
1062 | + {canEdit && issue.type !== "epic" && ( | |
1063 | + <div className="pt-2"> | |
1064 | + <button | |
1065 | + onClick={convertToEpic} | |
1066 | + className="w-full flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-md border border-purple-500/30 bg-purple-500/10 text-xs font-medium text-purple-400 hover:bg-purple-500/20 transition-colors" | |
1067 | + > | |
1068 | + <Layers className="h-3.5 w-3.5" /> | |
1069 | + <span>Convert to Epic</span> | |
1070 | + </button> | |
657 | 1071 | </div> |
658 | 1072 | )} |
659 | - </motion.div> | |
1073 | + </div> | |
660 | 1074 | </div> |
661 | 1075 | </div> |
662 | 1076 | ); |
663 | 1077 | |
▼src/components/issues/IssuesList.tsx
+42-51
| @@ -107,118 +107,109 @@ export default function IssuesList({ issues, openCount, closedCount, repoOwner, | ||
107 | 107 | |
108 | 108 | return ( |
109 | 109 | <div className="space-y-6"> |
110 | - {/* Enhanced Header with Glow */} | |
110 | + {/* Header */} | |
111 | 111 | <motion.div |
112 | 112 | initial={{ opacity: 0, y: -20 }} |
113 | 113 | animate={{ opacity: 1, y: 0 }} |
114 | 114 | className="flex flex-col lg:flex-row lg:items-center justify-between gap-4" |
115 | 115 | > |
116 | - {/* Search Input with Glass Effect */} | |
116 | + {/* Search Input */} | |
117 | 117 | <div className="relative flex-1 max-w-xl group"> |
118 | - <div className="absolute -inset-0.5 bg-gradient-to-r from-cyan-500/20 via-purple-500/20 to-pink-500/20 rounded-xl blur opacity-0 group-focus-within:opacity-100 transition-opacity" /> | |
119 | - <div className="relative"> | |
120 | - <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
121 | - <input | |
122 | - type="search" | |
123 | - placeholder="Search all issues..." | |
124 | - value={searchQuery} | |
125 | - onChange={(e) => setSearchQuery(e.target.value)} | |
126 | - className="w-full rounded-xl border border-border bg-secondary backdrop-blur-sm pl-10 pr-4 py-2.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-cyan-500/50 focus:ring-2 focus:ring-cyan-500/20 transition-all" | |
127 | - /> | |
128 | - </div> | |
118 | + <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-foreground transition-colors" /> | |
119 | + <input | |
120 | + type="search" | |
121 | + placeholder="Search all issues..." | |
122 | + value={searchQuery} | |
123 | + onChange={(e) => setSearchQuery(e.target.value)} | |
124 | + className="w-full h-9 rounded-md border border-border/70 bg-card/60 pl-9 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/40 focus:bg-background transition-all" | |
125 | + /> | |
129 | 126 | </div> |
130 | 127 | |
131 | 128 | {/* Action Buttons */} |
132 | - <div className="flex gap-3"> | |
129 | + <div className="flex gap-2 flex-wrap"> | |
133 | 130 | <button |
134 | 131 | onClick={() => setGroupByEpic(!groupByEpic)} |
135 | - className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${groupByEpic | |
136 | - ? "bg-purple-500/20 border-purple-500/50 text-purple-300" | |
137 | - : "border-border bg-secondary text-muted-foreground hover:bg-secondary/80" | |
132 | + className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border text-xs font-medium transition-colors ${groupByEpic | |
133 | + ? "bg-secondary text-foreground border-border font-semibold shadow-sm" | |
134 | + : "border-border bg-card text-muted-foreground hover:text-foreground hover:bg-accent" | |
138 | 135 | }`} |
139 | 136 | > |
140 | - <Layers className="h-4 w-4" /> | |
141 | - <span className="hidden sm:inline">Group by Epic</span> | |
137 | + <Layers className="h-3.5 w-3.5" /> | |
138 | + <span>Group by Epic</span> | |
142 | 139 | </button> |
143 | 140 | |
144 | 141 | <motion.a |
145 | 142 | href={`/${repoOwner}/${repoName}/labels`} |
146 | 143 | whileHover={{ scale: 1.02 }} |
147 | 144 | whileTap={{ scale: 0.98 }} |
148 | - className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-border bg-secondary text-sm text-muted-foreground hover:bg-secondary/80 hover:border-border/80 transition-all" | |
145 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-sm" | |
149 | 146 | > |
150 | - <Tag className="h-4 w-4" /> | |
151 | - <span className="hidden sm:inline">Labels</span> | |
147 | + <Tag className="h-3.5 w-3.5 text-muted-foreground" /> | |
148 | + <span>Labels</span> | |
152 | 149 | </motion.a> |
153 | - | |
154 | 150 | <motion.a |
155 | 151 | href={`/${repoOwner}/${repoName}/milestones`} |
156 | 152 | whileHover={{ scale: 1.02 }} |
157 | 153 | whileTap={{ scale: 0.98 }} |
158 | - className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-border bg-secondary text-sm text-muted-foreground hover:bg-secondary/80 hover:border-border/80 transition-all" | |
154 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-sm" | |
159 | 155 | > |
160 | - <Calendar className="h-4 w-4" /> | |
161 | - <span className="hidden sm:inline">Milestones</span> | |
156 | + <Calendar className="h-3.5 w-3.5 text-muted-foreground" /> | |
157 | + <span>Milestones</span> | |
162 | 158 | </motion.a> |
163 | - | |
164 | 159 | <motion.a |
165 | 160 | href={`/${repoOwner}/${repoName}/issues/new`} |
166 | - whileHover={{ scale: 1.05 }} | |
167 | - whileTap={{ scale: 0.95 }} | |
168 | - className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-gradient-to-r from-green-500 to-emerald-600 text-sm font-medium text-foreground shadow-lg shadow-green-500/25 hover:shadow-green-500/40 transition-all" | |
161 | + whileHover={{ scale: 1.02 }} | |
162 | + whileTap={{ scale: 0.98 }} | |
163 | + className="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs sm:text-sm font-medium transition-colors shadow-sm" | |
169 | 164 | > |
170 | 165 | <Plus className="h-4 w-4" /> |
171 | - <span className="hidden sm:inline">New Issue</span> | |
166 | + <span>New Issue</span> | |
172 | 167 | </motion.a> |
173 | 168 | </div> |
174 | 169 | </motion.div> |
175 | 170 | |
176 | - {/* Issues Container with Glass Effect */} | |
171 | + {/* Issues List Container */} | |
177 | 172 | <motion.div |
178 | 173 | initial={{ opacity: 0 }} |
179 | 174 | animate={{ opacity: 1 }} |
180 | 175 | transition={{ delay: 0.1 }} |
181 | - className="relative" | |
182 | 176 | > |
183 | - {/* Gradient border effect */} | |
184 | - <div className="absolute -inset-[1px] bg-gradient-to-r from-cyan-500/30 via-purple-500/20 to-pink-500/30 rounded-xl blur-sm opacity-50" /> | |
185 | - | |
186 | - <div className="relative rounded-xl border border-border bg-card/80 backdrop-blur-sm overflow-hidden"> | |
177 | + <div className="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-sm"> | |
187 | 178 | {/* Filter Header */} |
188 | - <div className="flex items-center justify-between gap-4 border-b border-white/5 bg-white/[0.02] px-4 py-3"> | |
179 | + <div className="flex items-center justify-between gap-4 border-b border-border/60 bg-muted/30 px-4 py-3"> | |
189 | 180 | <div className="flex items-center gap-1"> |
190 | 181 | <button |
191 | 182 | onClick={() => setFilter("open")} |
192 | - className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-all ${filter === "open" | |
193 | - ? "bg-green-500/10 text-green-400 border border-green-500/30" | |
194 | - : "text-muted-foreground hover:text-foreground hover:bg-accent" | |
183 | + className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${filter === "open" | |
184 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
185 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
195 | 186 | }`} |
196 | 187 | > |
197 | - <AlertCircle className="h-4 w-4" /> | |
198 | - {openCount} Open | |
188 | + <AlertCircle className="h-3.5 w-3.5 text-emerald-500" /> | |
189 | + <span>{openCount} Open</span> | |
199 | 190 | </button> |
200 | 191 | <button |
201 | 192 | onClick={() => setFilter("closed")} |
202 | - className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-all ${filter === "closed" | |
203 | - ? "bg-purple-500/10 text-purple-400 border border-purple-500/30" | |
204 | - : "text-muted-foreground hover:text-foreground hover:bg-accent" | |
193 | + className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${filter === "closed" | |
194 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
195 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
205 | 196 | }`} |
206 | 197 | > |
207 | - <CheckCircle2 className="h-4 w-4" /> | |
208 | - {closedCount} Closed | |
198 | + <CheckCircle2 className="h-3.5 w-3.5 text-purple-400" /> | |
199 | + <span>{closedCount} Closed</span> | |
209 | 200 | </button> |
210 | 201 | </div> |
211 | 202 | |
212 | - <div className="flex items-center gap-3 text-sm text-muted-foreground"> | |
203 | + <div className="flex items-center gap-3 text-xs text-muted-foreground"> | |
213 | 204 | <button className="flex items-center gap-1 hover:text-foreground transition-colors"> |
214 | 205 | <ArrowUpDown className="h-3.5 w-3.5" /> |
215 | - Sort | |
206 | + <span>Sort</span> | |
216 | 207 | </button> |
217 | 208 | </div> |
218 | 209 | </div> |
219 | 210 | |
220 | 211 | {/* Issues List */} |
221 | - <div className="divide-y divide-white/5"> | |
212 | + <div className="divide-y divide-border/60"> | |
222 | 213 | <AnimatePresence mode="popLayout"> |
223 | 214 | {filteredIssues.length > 0 ? ( |
224 | 215 | filteredIssues.map((issue, index) => ( |
225 | 216 | |
▼src/components/layout/Header.astro
+3-3
| @@ -2,6 +2,7 @@ | ||
2 | 2 | import { Search, Bell, Plus, Menu } from "lucide-react" |
3 | 3 | import { GlobalSearch } from "@/components/search/GlobalSearch" |
4 | 4 | import { ThemeToggle } from "@/components/ThemeToggle" |
5 | +import Logo from "@/components/ui/Logo.astro" | |
5 | 6 | import { getCsrfToken } from "@/middleware/csrf" |
6 | 7 | |
7 | 8 | const user = Astro.locals.user; |
| @@ -12,9 +13,8 @@ const { token: csrfToken } = getCsrfToken(Astro.request); | ||
12 | 13 | <header class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/80 backdrop-blur-xl" role="banner"> |
13 | 14 | <div class="container h-14 flex items-center justify-between gap-4"> |
14 | 15 | <!-- Logo --> |
15 | - <a href="/" class="flex-shrink-0 transition-opacity hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:rounded-md" aria-label="OpenCodeHub home"> | |
16 | - <img src="/logo-dark.png" alt="OpenCodeHub" class="h-7 w-auto dark:hidden" /> | |
17 | - <img src="/logo-light.png" alt="OpenCodeHub" class="h-7 w-auto hidden dark:block" /> | |
16 | + <a href="/" class="flex-shrink-0 text-foreground transition-opacity hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:rounded-md" aria-label="OpenCodeHub home"> | |
17 | + <Logo class="h-7 w-7" /> | |
18 | 18 | </a> |
19 | 19 | |
20 | 20 | <!-- Navigation --> |
21 | 21 | |
▼src/components/profile/UserProfileView.astro
+133-103
| @@ -41,130 +41,158 @@ activityStart.setDate(activityStart.getDate() - 364); | ||
41 | 41 | activityStart.setHours(0, 0, 0, 0); |
42 | 42 | |
43 | 43 | // 1. Fetch Follow Stats |
44 | -const [followersResult] = await db | |
45 | - .select({ count: sql`count(*)` }) | |
46 | - .from(schema.userFollowers) | |
47 | - .where(eq(schema.userFollowers.followeeId, user.id)); | |
48 | -const followersCount = Number(followersResult?.count) || 0; | |
49 | - | |
50 | -const [followingResult] = await db | |
51 | - .select({ count: sql`count(*)` }) | |
52 | - .from(schema.userFollowers) | |
53 | - .where(eq(schema.userFollowers.followerId, user.id)); | |
54 | -const followingCount = Number(followingResult?.count) || 0; | |
44 | +let followersCount = 0; | |
45 | +let followingCount = 0; | |
46 | + | |
47 | +if (user.id !== "placeholder-user") { | |
48 | + try { | |
49 | + const [followersResult] = await db | |
50 | + .select({ count: sql`count(*)` }) | |
51 | + .from(schema.userFollowers) | |
52 | + .where(eq(schema.userFollowers.followeeId, user.id)); | |
53 | + followersCount = Number(followersResult?.count) || 0; | |
54 | + | |
55 | + const [followingResult] = await db | |
56 | + .select({ count: sql`count(*)` }) | |
57 | + .from(schema.userFollowers) | |
58 | + .where(eq(schema.userFollowers.followerId, user.id)); | |
59 | + followingCount = Number(followingResult?.count) || 0; | |
60 | + } catch (e) { | |
61 | + // Graceful fallback if database query fails | |
62 | + } | |
63 | +} | |
55 | 64 | |
56 | 65 | // 2. Check if following |
57 | 66 | let isFollowing = false; |
58 | -if (currentUser && !isOwner) { | |
59 | - const existing = await db.query.userFollowers.findFirst({ | |
60 | - where: and( | |
61 | - eq(schema.userFollowers.followerId, currentUser.id), | |
62 | - eq(schema.userFollowers.followeeId, user.id), | |
63 | - ), | |
64 | - }); | |
65 | - isFollowing = !!existing; | |
67 | +if (currentUser && !isOwner && user.id !== "placeholder-user") { | |
68 | + try { | |
69 | + const existing = await db.query.userFollowers.findFirst({ | |
70 | + where: and( | |
71 | + eq(schema.userFollowers.followerId, currentUser.id), | |
72 | + eq(schema.userFollowers.followeeId, user.id), | |
73 | + ), | |
74 | + }); | |
75 | + isFollowing = !!existing; | |
76 | + } catch (e) { | |
77 | + // Graceful fallback | |
78 | + } | |
66 | 79 | } |
67 | 80 | |
68 | 81 | // 3. Fetch Repositories |
69 | -let repoConditions = [eq(schema.repositories.ownerId, user.id)]; | |
70 | -if (!isOwner) { | |
71 | - repoConditions.push(eq(schema.repositories.visibility, "public")); | |
72 | -} | |
82 | +let repos: any[] = []; | |
83 | +if (user.id !== "placeholder-user") { | |
84 | + try { | |
85 | + let repoConditions = [eq(schema.repositories.ownerId, user.id)]; | |
86 | + if (!isOwner) { | |
87 | + repoConditions.push(eq(schema.repositories.visibility, "public")); | |
88 | + } | |
73 | 89 | |
74 | -const repos = await db.query.repositories.findMany({ | |
75 | - where: and(...repoConditions), | |
76 | - orderBy: [desc(schema.repositories.starCount), desc(schema.repositories.updatedAt)], | |
77 | -}); | |
90 | + repos = await db.query.repositories.findMany({ | |
91 | + where: and(...repoConditions), | |
92 | + orderBy: [desc(schema.repositories.starCount), desc(schema.repositories.updatedAt)], | |
93 | + }); | |
94 | + } catch (e) { | |
95 | + // Graceful fallback if database query fails | |
96 | + } | |
97 | +} | |
78 | 98 | |
79 | 99 | const pinnedRepos = repos.slice(0, 6); |
80 | 100 | |
81 | 101 | // 4. Fetch Starred Repositories |
82 | 102 | let starCount = 0; |
83 | 103 | let starredRepos: any[] = []; |
84 | -try { | |
85 | - const stars = await db.query.repositoryStars.findMany({ | |
86 | - where: eq(schema.repositoryStars.userId, user.id), | |
87 | - with: { | |
88 | - repository: { | |
89 | - with: { | |
90 | - owner: true, | |
104 | +if (user.id !== "placeholder-user") { | |
105 | + try { | |
106 | + const stars = await db.query.repositoryStars.findMany({ | |
107 | + where: eq(schema.repositoryStars.userId, user.id), | |
108 | + with: { | |
109 | + repository: { | |
110 | + with: { | |
111 | + owner: true, | |
112 | + }, | |
91 | 113 | }, |
92 | 114 | }, |
93 | - }, | |
94 | - }); | |
95 | - starCount = stars.length; | |
96 | - starredRepos = stars.map((s) => s.repository).filter(Boolean); | |
97 | -} catch (e) { | |
98 | - // Graceful fallback if stars query fails | |
115 | + }); | |
116 | + starCount = stars.length; | |
117 | + starredRepos = stars.map((s) => s.repository).filter(Boolean); | |
118 | + } catch (e) { | |
119 | + // Graceful fallback if stars query fails | |
120 | + } | |
99 | 121 | } |
100 | 122 | |
101 | 123 | // 5. Fetch contribution count and day-by-day mapping |
102 | 124 | let contributionCount = 0; |
103 | 125 | let activityByDay = new Map<string, number>(); |
104 | -try { | |
105 | - const [result] = await db | |
106 | - .select({ count: sql`count(*)` }) | |
107 | - .from(schema.activities) | |
108 | - .where( | |
109 | - and( | |
110 | - eq(schema.activities.userId, user.id), | |
111 | - sql`${schema.activities.createdAt} >= ${activityStart}`, | |
112 | - ), | |
113 | - ); | |
114 | - contributionCount = Number(result?.count) || 0; | |
115 | - | |
116 | - const activityRows = await db | |
117 | - .select({ | |
118 | - createdAt: schema.activities.createdAt, | |
119 | - }) | |
120 | - .from(schema.activities) | |
121 | - .where( | |
122 | - and( | |
123 | - eq(schema.activities.userId, user.id), | |
124 | - sql`${schema.activities.createdAt} >= ${activityStart}`, | |
125 | - ), | |
126 | - ); | |
127 | - for (const row of activityRows) { | |
128 | - const day = new Date(row.createdAt).toISOString().slice(0, 10); | |
129 | - activityByDay.set(day, (activityByDay.get(day) || 0) + 1); | |
126 | +if (user.id !== "placeholder-user") { | |
127 | + try { | |
128 | + const [result] = await db | |
129 | + .select({ count: sql`count(*)` }) | |
130 | + .from(schema.activities) | |
131 | + .where( | |
132 | + and( | |
133 | + eq(schema.activities.userId, user.id), | |
134 | + sql`${schema.activities.createdAt} >= ${activityStart}`, | |
135 | + ), | |
136 | + ); | |
137 | + contributionCount = Number(result?.count) || 0; | |
138 | + | |
139 | + const activityRows = await db | |
140 | + .select({ | |
141 | + createdAt: schema.activities.createdAt, | |
142 | + }) | |
143 | + .from(schema.activities) | |
144 | + .where( | |
145 | + and( | |
146 | + eq(schema.activities.userId, user.id), | |
147 | + sql`${schema.activities.createdAt} >= ${activityStart}`, | |
148 | + ), | |
149 | + ); | |
150 | + for (const row of activityRows) { | |
151 | + const day = new Date(row.createdAt).toISOString().slice(0, 10); | |
152 | + activityByDay.set(day, (activityByDay.get(day) || 0) + 1); | |
153 | + } | |
154 | + } catch { | |
155 | + // Fallback | |
130 | 156 | } |
131 | -} catch { | |
132 | - // Fallback | |
133 | 157 | } |
134 | 158 | |
135 | 159 | // 6. Fetch recent activity timeline |
136 | 160 | let recentActivity: any[] = []; |
137 | -try { | |
138 | - recentActivity = await db.query.activities.findMany({ | |
139 | - where: eq(schema.activities.userId, user.id), | |
140 | - orderBy: [desc(schema.activities.createdAt)], | |
141 | - limit: 6, | |
142 | - }); | |
143 | -} catch {} | |
161 | +if (user.id !== "placeholder-user") { | |
162 | + try { | |
163 | + recentActivity = await db.query.activities.findMany({ | |
164 | + where: eq(schema.activities.userId, user.id), | |
165 | + orderBy: [desc(schema.activities.createdAt)], | |
166 | + limit: 6, | |
167 | + }); | |
168 | + } catch {} | |
169 | +} | |
144 | 170 | |
145 | 171 | // 7. Fetch profile README from {username}/{username} repo or default |
146 | 172 | let profileReadmeHtml = ""; |
147 | 173 | let hasSpecialRepo = false; |
148 | -try { | |
149 | - const profileRepo = await db.query.repositories.findFirst({ | |
150 | - where: and( | |
151 | - eq(schema.repositories.ownerId, user.id), | |
152 | - eq(schema.repositories.name, user.username), | |
153 | - ), | |
154 | - }); | |
155 | - if (profileRepo) { | |
156 | - hasSpecialRepo = true; | |
157 | - const localPath = await resolveRepoPath(profileRepo.diskPath); | |
158 | - const fileData = await getFileContent( | |
159 | - localPath, | |
160 | - "README.md", | |
161 | - profileRepo.defaultBranch || "main", | |
162 | - ); | |
163 | - if (fileData?.content) { | |
164 | - profileReadmeHtml = await renderMarkdown(fileData.content); | |
174 | +if (user.id !== "placeholder-user") { | |
175 | + try { | |
176 | + const profileRepo = await db.query.repositories.findFirst({ | |
177 | + where: and( | |
178 | + eq(schema.repositories.ownerId, user.id), | |
179 | + eq(schema.repositories.name, user.username), | |
180 | + ), | |
181 | + }); | |
182 | + if (profileRepo) { | |
183 | + hasSpecialRepo = true; | |
184 | + const localPath = await resolveRepoPath(profileRepo.diskPath); | |
185 | + const fileData = await getFileContent( | |
186 | + localPath, | |
187 | + "README.md", | |
188 | + profileRepo.defaultBranch || "main", | |
189 | + ); | |
190 | + if (fileData?.content) { | |
191 | + profileReadmeHtml = await renderMarkdown(fileData.content); | |
192 | + } | |
165 | 193 | } |
166 | - } | |
167 | -} catch {} | |
194 | + } catch {} | |
195 | +} | |
168 | 196 | |
169 | 197 | // If no special repo README exists, provide a default profile readme card showcasing the user's projects and bio |
170 | 198 | if (!profileReadmeHtml) { |
| @@ -186,15 +214,17 @@ ___ | ||
186 | 214 | |
187 | 215 | // 8. Fetch User Organizations |
188 | 216 | let userOrgs: any[] = []; |
189 | -try { | |
190 | - const orgMembers = await db.query.organizationMembers.findMany({ | |
191 | - where: eq(schema.organizationMembers.userId, user.id), | |
192 | - with: { | |
193 | - organization: true, | |
194 | - }, | |
195 | - }); | |
196 | - userOrgs = orgMembers.map((m) => m.organization).filter(Boolean); | |
197 | -} catch {} | |
217 | +if (user.id !== "placeholder-user") { | |
218 | + try { | |
219 | + const orgMembers = await db.query.organizationMembers.findMany({ | |
220 | + where: eq(schema.organizationMembers.userId, user.id), | |
221 | + with: { | |
222 | + organization: true, | |
223 | + }, | |
224 | + }); | |
225 | + userOrgs = orgMembers.map((m) => m.organization).filter(Boolean); | |
226 | + } catch {} | |
227 | +} | |
198 | 228 | |
199 | 229 | // Language colors (GitHub style) |
200 | 230 | const langColors: Record<string, string> = { |
201 | 231 | |
▼src/components/pulls/PullRequestsList.tsx
+12-12
| @@ -276,45 +276,45 @@ export default function PullRequestsList({ pullRequests, openCount, closedCount, | ||
276 | 276 | className="flex flex-col lg:flex-row lg:items-center justify-between gap-4" |
277 | 277 | > |
278 | 278 | {/* Search Input */} |
279 | - <div className="relative flex-1 max-w-xl"> | |
280 | - <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
279 | + <div className="relative flex-1 max-w-xl group"> | |
280 | + <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-foreground transition-colors" /> | |
281 | 281 | <input |
282 | 282 | type="search" |
283 | 283 | placeholder="Search all pull requests..." |
284 | 284 | value={searchQuery} |
285 | 285 | onChange={(e) => setSearchQuery(e.target.value)} |
286 | - className="w-full rounded-lg border border-border bg-card pl-10 pr-4 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary/50 focus:ring-1 focus:ring-primary/20 transition-all" | |
286 | + className="w-full h-9 rounded-md border border-border/70 bg-card/60 pl-9 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/40 focus:bg-background transition-all" | |
287 | 287 | /> |
288 | 288 | </div> |
289 | 289 | |
290 | 290 | {/* Action Buttons */} |
291 | - <div className="flex gap-2"> | |
291 | + <div className="flex gap-2 flex-wrap"> | |
292 | 292 | <motion.a |
293 | 293 | href={`/${repoOwner}/${repoName}/labels`} |
294 | 294 | whileHover={{ scale: 1.02 }} |
295 | 295 | whileTap={{ scale: 0.98 }} |
296 | - className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-card text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-all" | |
296 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-sm" | |
297 | 297 | > |
298 | - <Tag className="h-4 w-4" /> | |
299 | - Labels | |
298 | + <Tag className="h-3.5 w-3.5 text-muted-foreground" /> | |
299 | + <span>Labels</span> | |
300 | 300 | </motion.a> |
301 | 301 | <motion.a |
302 | 302 | href={`/${repoOwner}/${repoName}/milestones`} |
303 | 303 | whileHover={{ scale: 1.02 }} |
304 | 304 | whileTap={{ scale: 0.98 }} |
305 | - className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-card text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-all" | |
305 | + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-sm" | |
306 | 306 | > |
307 | - <Calendar className="h-4 w-4" /> | |
308 | - Milestones | |
307 | + <Calendar className="h-3.5 w-3.5 text-muted-foreground" /> | |
308 | + <span>Milestones</span> | |
309 | 309 | </motion.a> |
310 | 310 | <motion.a |
311 | 311 | href={`/${repoOwner}/${repoName}/compare`} |
312 | 312 | whileHover={{ scale: 1.02 }} |
313 | 313 | whileTap={{ scale: 0.98 }} |
314 | - className="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-primary text-sm font-medium text-primary-foreground hover:opacity-90 transition-all" | |
314 | + className="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs sm:text-sm font-medium transition-colors shadow-sm" | |
315 | 315 | > |
316 | 316 | <Plus className="h-4 w-4" /> |
317 | - New Pull Request | |
317 | + <span>New Pull Request</span> | |
318 | 318 | </motion.a> |
319 | 319 | </div> |
320 | 320 | </motion.div> |
321 | 321 | |
▼src/components/ui/Logo.astro
+24-0
| @@ -0,0 +1,24 @@ | ||
1 | +--- | |
2 | +interface Props { | |
3 | + class?: string; | |
4 | + size?: number | string; | |
5 | +} | |
6 | + | |
7 | +const { class: className = "h-7 w-7", size } = Astro.props; | |
8 | +--- | |
9 | + | |
10 | +<svg | |
11 | + xmlns="http://www.w3.org/2000/svg" | |
12 | + viewBox="45 45 210 210" | |
13 | + fill="currentColor" | |
14 | + class={className} | |
15 | + width={size} | |
16 | + height={size} | |
17 | + aria-hidden="true" | |
18 | +> | |
19 | + <path | |
20 | + fill-rule="evenodd" | |
21 | + clip-rule="evenodd" | |
22 | + d="M184.27588,89.68521c2.96924,0.22368 3.29297,2.88721 3.75439,5.34082c1.19385,6.34468 0.82178,12.62754 0.00293,18.98174c0.34131,0.62461 0.66797,1.25728 0.97998,1.89756c5.40088,11.37407 2.61914,24.01758 -7.58203,31.66011c-2.17236,1.62891 -4.00049,2.51074 -6.4248,3.66357c0.82178,0.76904 1.60254,1.5293 2.35693,2.37012c10.20264,11.34814 10.51318,31.02539 1.01953,42.86865c-0.7749,0.9668 -1.70801,1.875 -2.36572,2.91064c-0.18896,1.81934 -0.08496,5.79053 -0.08496,7.70508l0.00732,14.01855c0.01172,4.06201 0.68115,7.9541 -2.32178,11.17236c-1.55713,1.69189 -3.74414,2.66602 -6.04395,2.69238c-2.2251,0.0542 -4.37256,-0.81006 -5.93994,-2.3877c-2.77588,-2.78174 -2.42139,-5.74805 -2.42285,-9.37646c-0.00293,-2.15186 0.00293,-4.32129 0.00293,-6.47314l0.00146,-24.48047c6.01758,-2.28369 9.25928,-8.7041 7.61719,-14.91797c-0.83789,-3.1875 -2.90332,-5.91357 -5.74365,-7.5835c-2.87109,-1.6626 -6.28418,-2.12109 -9.49365,-1.27441c-3.25635,0.86279 -6.023,3.00879 -7.66934,5.94727c-1.6771,2.98242 -2.0748,6.51709 -1.10244,9.79834c1.07827,3.66211 3.30176,6.06738 6.59795,7.8457v31.3418l-0.00146,8.86523c-0.00293,1.69775 -0.05127,3.50098 0.06885,5.18408c0.07031,0.98438 0.24023,1.74902 0.93457,2.45801c1.06494,1.08838 2.63818,1.26123 4.08838,1.25244c5.39648,-0.03516 11.21777,-0.63721 16.4458,-2.0083c11.50488,-3.01465 25.31982,-11.12842 33.73096,-19.5498c-1.04004,-3.65479 -1.04297,-6.17139 0.27979,-9.73682c0.11426,-0.28418 0.23584,-0.56396 0.36768,-0.83936c1.53955,-3.31641 4.34473,-5.87695 7.78711,-7.10889c3.49512,-1.24365 7.1792,-1.10596 10.5249,0.52588c3.38965,1.6582 5.97949,4.59814 7.19385,8.16943c1.18945,3.55518 0.89648,7.43848 -0.81152,10.77539c-1.72119,3.35449 -4.70801,5.88574 -8.2998,7.03418c-3.99609,1.25098 -7.6333,0.60791 -11.26465,-1.28613c-11.9165,11.17676 -26.95312,19.90283 -43.24658,22.32861c-6.1377,0.91406 -18.28271,1.79883 -24.2209,0.28711c-1.4543,-0.36035 -2.82349,-1.00342 -4.0292,-1.89258c-1.92026,-1.43994 -3.30073,-3.4834 -3.91978,-5.80225c-0.80962,-2.91797 -0.59106,-7.26416 -0.58989,-10.39453l0.0126,-20.7627c0.00176,-2.729 0.26045,-7.91602 -0.20142,-10.47656c-0.9293,-1.44434 -2.89556,-3.36035 -4.03506,-4.95996c-7.36787,-10.34326 -8.05313,-24.92871 -1.97593,-36.03662c1.89521,-3.46436 3.96914,-5.61768 6.89136,-8.27051c-1.10273,-0.60498 -2.32017,-1.15137 -3.42407,-1.75488c-10.07725,-5.50151 -15.41455,-16.65469 -12.49043,-27.89648c0.75337,-2.89644 1.79604,-5.09502 3.26177,-7.72251c-0.23101,-1.61455 -0.4084,-3.23628 -0.53218,-4.86255c-0.26484,-3.68013 -0.14707,-15.76128 2.28369,-18.38569c0.49512,-0.53452 1.12632,-0.72598 1.84219,-0.73696c4.777,-0.07251 14.12007,8.55615 17.44307,11.97817c6.83555,-0.78853 16.69834,-0.79482 23.51572,0.04937c3.84082,-4.00254 11.6543,-11.28779 17.2251,-12.14487z M216.82617,208.70947c3.49512,-0.50391 6.74268,1.9043 7.27588,5.39355c0.5332,3.49072 -1.8457,6.75879 -5.33057,7.32275c-3.52881,0.57129 -6.84521,-1.84424 -7.38574,-5.37744c-0.54053,-3.5332 1.90283,-6.8291 5.44043,-7.33887z M150.01025,49.42808c8.76855,-0.53481 18.6665,0.87524 27.06592,3.29912c20.78613,5.99868 38.0083,20.7375 48.39844,39.57656c11.02441,20.03057 13.20557,42.40825 6.84668,64.47847c-4.68457,16.26416 -15.27686,33.76758 -29.49902,43.79297c-0.8291,0.58447 -2.42578,0.64014 -3.39551,0.33105c-1.20264,-0.38965 -2.1958,-1.24951 -2.75391,-2.38477c-0.78223,-1.54248 -0.68701,-3.38379 0.24756,-4.83838c0.77344,-1.19238 2.30859,-1.99658 3.36621,-2.96631c1.39453,-1.27734 2.7832,-2.58984 4.08691,-3.96094c8.34229,-8.7583 14.54004,-19.33154 18.10547,-30.89062c6.12744,-19.20322 4.20703,-40.06919 -5.31885,-57.83232c-13.10742,-24.82339 -38.29687,-39.67676 -66.35449,-38.24443c-11.62852,0.5937 -20.97378,3.44312 -31.22754,8.85864c-0.09214,0.21592 0.06855,2.36089 0.08599,2.76357c0.10093,2.32456 -0.25884,4.14609 -1.20132,6.28271c-1.61675,3.70122 -4.65234,6.5981 -8.4252,8.03994c-4.34063,1.67285 -7.91528,1.0749 -12.02065,-0.72729c-1.39482,1.60225 -2.82202,3.25049 -4.10435,4.94326c-9.65625,12.99712 -14.91196,28.73716 -15.0019,44.92852c0.00176,4.17891 0.44326,7.88203 1.01514,11.99033c2.62295,0.16113 4.83779,0.89355 7.03564,2.35986c3.29355,2.22656 5.54854,5.68799 6.254,9.60059c0.80186,4.28027 -0.46406,7.95996 -2.85498,11.45801c6.1188,10.2832 12.25576,17.34961 22.19165,24.18604c2.72197,1.86182 5.57842,3.51855 8.54546,4.95703c1.91646,0.92285 5.19565,1.96143 6.36152,3.69434c0.70986,1.0708 0.95127,2.38623 0.66841,3.64014c-0.63237,2.74805 -3.42524,4.40332 -6.0498,3.42188c-16.91338,-6.33398 -30.71909,-19.39453 -39.48354,-34.77979c-1.91895,0.38965 -3.55415,0.55664 -5.51953,0.2959c-3.75059,-0.50244 -7.1439,-2.48584 -9.42217,-5.50635c-2.37363,-3.13184 -3.39595,-7.08252 -2.83887,-10.97168c0.58975,-4.17334 2.75815,-7.26123 6.04922,-9.76172c-0.84521,-4.86138 -1.40742,-9.20464 -1.51685,-14.15186c-0.4582,-20.73208 7.9248,-42.52515 22.04824,-57.59678c-0.52134,-1.40479 -0.85063,-2.36895 -1.07036,-3.8603c-0.57583,-3.9999 0.44736,-8.06558 2.8478,-11.31636c2.4063,-3.16348 5.97246,-5.23945 9.91157,-5.76943c4.11064,-0.54932 7.88833,0.62358 11.15728,3.0999c12.08159,-6.54038 21.99536,-9.5105 35.76973,-10.4395z M104.33159,64.69878c3.83057,-0.32915 7.21245,2.48599 7.58335,6.31274c0.37104,3.82676 -2.40718,7.23911 -6.22969,7.65176c-2.51001,0.271 -4.97197,-0.82749 -6.44678,-2.87651c-1.4748,-2.04902 -1.73496,-4.73247 -0.68115,-7.02656c1.05381,-2.29424 3.25898,-3.84521 5.77427,-4.06143z M77.86846,154.6333c3.67031,-0.67822 7.19575,1.74463 7.87866,5.41406c0.68291,3.66943 -1.73511,7.19824 -5.40337,7.88672c-3.67588,0.68994 -7.21377,-1.73437 -7.898,-5.41113c-0.68437,-3.67676 1.74478,-7.21143 5.42271,-7.88965z M153.91992,174.95801c3.0835,-0.47461 5.96777,1.64209 6.43945,4.72559c0.47021,3.0835 -1.64941,5.96484 -4.73437,6.43359c-3.0791,0.46582 -5.95459,-1.64941 -6.4248,-4.72852c-0.47021,-3.07764 1.64209,-5.95605 4.71973,-6.43066z" | |
23 | + /> | |
24 | +</svg> | |
0 | 25 | |
▼src/components/ui/Logo.tsx
+29-0
| @@ -0,0 +1,29 @@ | ||
1 | +import React from "react"; | |
2 | + | |
3 | +interface LogoProps extends React.SVGProps<SVGSVGElement> { | |
4 | + className?: string; | |
5 | + size?: number | string; | |
6 | +} | |
7 | + | |
8 | +export function Logo({ className = "h-7 w-7", size, ...props }: LogoProps) { | |
9 | + return ( | |
10 | + <svg | |
11 | + xmlns="http://www.w3.org/2000/svg" | |
12 | + viewBox="45 45 210 210" | |
13 | + fill="currentColor" | |
14 | + className={className} | |
15 | + width={size} | |
16 | + height={size} | |
17 | + aria-hidden="true" | |
18 | + {...props} | |
19 | + > | |
20 | + <path | |
21 | + fillRule="evenodd" | |
22 | + clipRule="evenodd" | |
23 | + d="M184.27588,89.68521c2.96924,0.22368 3.29297,2.88721 3.75439,5.34082c1.19385,6.34468 0.82178,12.62754 0.00293,18.98174c0.34131,0.62461 0.66797,1.25728 0.97998,1.89756c5.40088,11.37407 2.61914,24.01758 -7.58203,31.66011c-2.17236,1.62891 -4.00049,2.51074 -6.4248,3.66357c0.82178,0.76904 1.60254,1.5293 2.35693,2.37012c10.20264,11.34814 10.51318,31.02539 1.01953,42.86865c-0.7749,0.9668 -1.70801,1.875 -2.36572,2.91064c-0.18896,1.81934 -0.08496,5.79053 -0.08496,7.70508l0.00732,14.01855c0.01172,4.06201 0.68115,7.9541 -2.32178,11.17236c-1.55713,1.69189 -3.74414,2.66602 -6.04395,2.69238c-2.2251,0.0542 -4.37256,-0.81006 -5.93994,-2.3877c-2.77588,-2.78174 -2.42139,-5.74805 -2.42285,-9.37646c-0.00293,-2.15186 0.00293,-4.32129 0.00293,-6.47314l0.00146,-24.48047c6.01758,-2.28369 9.25928,-8.7041 7.61719,-14.91797c-0.83789,-3.1875 -2.90332,-5.91357 -5.74365,-7.5835c-2.87109,-1.6626 -6.28418,-2.12109 -9.49365,-1.27441c-3.25635,0.86279 -6.023,3.00879 -7.66934,5.94727c-1.6771,2.98242 -2.0748,6.51709 -1.10244,9.79834c1.07827,3.66211 3.30176,6.06738 6.59795,7.8457v31.3418l-0.00146,8.86523c-0.00293,1.69775 -0.05127,3.50098 0.06885,5.18408c0.07031,0.98438 0.24023,1.74902 0.93457,2.45801c1.06494,1.08838 2.63818,1.26123 4.08838,1.25244c5.39648,-0.03516 11.21777,-0.63721 16.4458,-2.0083c11.50488,-3.01465 25.31982,-11.12842 33.73096,-19.5498c-1.04004,-3.65479 -1.04297,-6.17139 0.27979,-9.73682c0.11426,-0.28418 0.23584,-0.56396 0.36768,-0.83936c1.53955,-3.31641 4.34473,-5.87695 7.78711,-7.10889c3.49512,-1.24365 7.1792,-1.10596 10.5249,0.52588c3.38965,1.6582 5.97949,4.59814 7.19385,8.16943c1.18945,3.55518 0.89648,7.43848 -0.81152,10.77539c-1.72119,3.35449 -4.70801,5.88574 -8.2998,7.03418c-3.99609,1.25098 -7.6333,0.60791 -11.26465,-1.28613c-11.9165,11.17676 -26.95312,19.90283 -43.24658,22.32861c-6.1377,0.91406 -18.28271,1.79883 -24.2209,0.28711c-1.4543,-0.36035 -2.82349,-1.00342 -4.0292,-1.89258c-1.92026,-1.43994 -3.30073,-3.4834 -3.91978,-5.80225c-0.80962,-2.91797 -0.59106,-7.26416 -0.58989,-10.39453l0.0126,-20.7627c0.00176,-2.729 0.26045,-7.91602 -0.20142,-10.47656c-0.9293,-1.44434 -2.89556,-3.36035 -4.03506,-4.95996c-7.36787,-10.34326 -8.05313,-24.92871 -1.97593,-36.03662c1.89521,-3.46436 3.96914,-5.61768 6.89136,-8.27051c-1.10273,-0.60498 -2.32017,-1.15137 -3.42407,-1.75488c-10.07725,-5.50151 -15.41455,-16.65469 -12.49043,-27.89648c0.75337,-2.89644 1.79604,-5.09502 3.26177,-7.72251c-0.23101,-1.61455 -0.4084,-3.23628 -0.53218,-4.86255c-0.26484,-3.68013 -0.14707,-15.76128 2.28369,-18.38569c0.49512,-0.53452 1.12632,-0.72598 1.84219,-0.73696c4.777,-0.07251 14.12007,8.55615 17.44307,11.97817c6.83555,-0.78853 16.69834,-0.79482 23.51572,0.04937c3.84082,-4.00254 11.6543,-11.28779 17.2251,-12.14487z M216.82617,208.70947c3.49512,-0.50391 6.74268,1.9043 7.27588,5.39355c0.5332,3.49072 -1.8457,6.75879 -5.33057,7.32275c-3.52881,0.57129 -6.84521,-1.84424 -7.38574,-5.37744c-0.54053,-3.5332 1.90283,-6.8291 5.44043,-7.33887z M150.01025,49.42808c8.76855,-0.53481 18.6665,0.87524 27.06592,3.29912c20.78613,5.99868 38.0083,20.7375 48.39844,39.57656c11.02441,20.03057 13.20557,42.40825 6.84668,64.47847c-4.68457,16.26416 -15.27686,33.76758 -29.49902,43.79297c-0.8291,0.58447 -2.42578,0.64014 -3.39551,0.33105c-1.20264,-0.38965 -2.1958,-1.24951 -2.75391,-2.38477c-0.78223,-1.54248 -0.68701,-3.38379 0.24756,-4.83838c0.77344,-1.19238 2.30859,-1.99658 3.36621,-2.96631c1.39453,-1.27734 2.7832,-2.58984 4.08691,-3.96094c8.34229,-8.7583 14.54004,-19.33154 18.10547,-30.89062c6.12744,-19.20322 4.20703,-40.06919 -5.31885,-57.83232c-13.10742,-24.82339 -38.29687,-39.67676 -66.35449,-38.24443c-11.62852,0.5937 -20.97378,3.44312 -31.22754,8.85864c-0.09214,0.21592 0.06855,2.36089 0.08599,2.76357c0.10093,2.32456 -0.25884,4.14609 -1.20132,6.28271c-1.61675,3.70122 -4.65234,6.5981 -8.4252,8.03994c-4.34063,1.67285 -7.91528,1.0749 -12.02065,-0.72729c-1.39482,1.60225 -2.82202,3.25049 -4.10435,4.94326c-9.65625,12.99712 -14.91196,28.73716 -15.0019,44.92852c0.00176,4.17891 0.44326,7.88203 1.01514,11.99033c2.62295,0.16113 4.83779,0.89355 7.03564,2.35986c3.29355,2.22656 5.54854,5.68799 6.254,9.60059c0.80186,4.28027 -0.46406,7.95996 -2.85498,11.45801c6.1188,10.2832 12.25576,17.34961 22.19165,24.18604c2.72197,1.86182 5.57842,3.51855 8.54546,4.95703c1.91646,0.92285 5.19565,1.96143 6.36152,3.69434c0.70986,1.0708 0.95127,2.38623 0.66841,3.64014c-0.63237,2.74805 -3.42524,4.40332 -6.0498,3.42188c-16.91338,-6.33398 -30.71909,-19.39453 -39.48354,-34.77979c-1.91895,0.38965 -3.55415,0.55664 -5.51953,0.2959c-3.75059,-0.50244 -7.1439,-2.48584 -9.42217,-5.50635c-2.37363,-3.13184 -3.39595,-7.08252 -2.83887,-10.97168c0.58975,-4.17334 2.75815,-7.26123 6.04922,-9.76172c-0.84521,-4.86138 -1.40742,-9.20464 -1.51685,-14.15186c-0.4582,-20.73208 7.9248,-42.52515 22.04824,-57.59678c-0.52134,-1.40479 -0.85063,-2.36895 -1.07036,-3.8603c-0.57583,-3.9999 0.44736,-8.06558 2.8478,-11.31636c2.4063,-3.16348 5.97246,-5.23945 9.91157,-5.76943c4.11064,-0.54932 7.88833,0.62358 11.15728,3.0999c12.08159,-6.54038 21.99536,-9.5105 35.76973,-10.4395z M104.33159,64.69878c3.83057,-0.32915 7.21245,2.48599 7.58335,6.31274c0.37104,3.82676 -2.40718,7.23911 -6.22969,7.65176c-2.51001,0.271 -4.97197,-0.82749 -6.44678,-2.87651c-1.4748,-2.04902 -1.73496,-4.73247 -0.68115,-7.02656c1.05381,-2.29424 3.25898,-3.84521 5.77427,-4.06143z M77.86846,154.6333c3.67031,-0.67822 7.19575,1.74463 7.87866,5.41406c0.68291,3.66943 -1.73511,7.19824 -5.40337,7.88672c-3.67588,0.68994 -7.21377,-1.73437 -7.898,-5.41113c-0.68437,-3.67676 1.74478,-7.21143 5.42271,-7.88965z M153.91992,174.95801c3.0835,-0.47461 5.96777,1.64209 6.43945,4.72559c0.47021,3.0835 -1.64941,5.96484 -4.73437,6.43359c-3.0791,0.46582 -5.95459,-1.64941 -6.4248,-4.72852c-0.47021,-3.07764 1.64209,-5.95605 4.71973,-6.43066z" | |
24 | + /> | |
25 | + </svg> | |
26 | + ); | |
27 | +} | |
28 | + | |
29 | +export default Logo; | |
0 | 30 | |
▼src/pages/[owner]/[repo]/issues/[number].astro
+6-1
| @@ -140,7 +140,7 @@ const formattedIssue = { | ||
140 | 140 | > |
141 | 141 | <RepoHeader repo={repoForHeader as any} activeTab="issues" /> |
142 | 142 | |
143 | - <div class="container py-8"> | |
143 | + <div class="container max-w-[1280px] mx-auto py-6 px-4 sm:px-6"> | |
144 | 144 | <IssueDetail |
145 | 145 | client:load |
146 | 146 | issue={formattedIssue} |
| @@ -149,6 +149,11 @@ const formattedIssue = { | ||
149 | 149 | repoName={repoName!} |
150 | 150 | canLink={canLink} |
151 | 151 | canEdit={canLink} |
152 | + currentUser={currentUser ? { | |
153 | + id: currentUser.id, | |
154 | + username: currentUser.username, | |
155 | + avatarUrl: currentUser.avatarUrl || undefined, | |
156 | + } : undefined} | |
152 | 157 | /> |
153 | 158 | </div> |
154 | 159 | </BaseLayout> |
155 | 160 | |
▼src/pages/actions.astro
+187-186
| @@ -3,6 +3,16 @@ import { resolveOrgOwners } from "@/lib/repo-owner"; | ||
3 | 3 | import { getDatabase, schema } from "@/db"; |
4 | 4 | import BaseLayout from "@/layouts/BaseLayout.astro"; |
5 | 5 | import { desc, eq } from "drizzle-orm"; |
6 | +import { | |
7 | + Play, | |
8 | + CheckCircle2, | |
9 | + XCircle, | |
10 | + Loader2, | |
11 | + Clock, | |
12 | + GitBranch, | |
13 | + GitCommit, | |
14 | + Bot | |
15 | +} from "lucide-react"; | |
6 | 16 | |
7 | 17 | const db = getDatabase(); |
8 | 18 | |
| @@ -11,6 +21,10 @@ const filter = Astro.url.searchParams.get("filter") || "all"; | ||
11 | 21 | |
12 | 22 | // Fetch workflow runs from public repos |
13 | 23 | let workflowRuns: any[] = []; |
24 | +let successCount = 0; | |
25 | +let failureCount = 0; | |
26 | +let runningCount = 0; | |
27 | + | |
14 | 28 | try { |
15 | 29 | const rawRuns = await db.query.workflowRuns.findMany({ |
16 | 30 | where: filter === "success" |
| @@ -36,6 +50,19 @@ try { | ||
36 | 50 | workflowRuns = (await resolveOrgOwners(rawRuns)).filter(run => |
37 | 51 | run.repository?.visibility === "public" |
38 | 52 | ); |
53 | + | |
54 | + const allPublicRuns = await db.query.workflowRuns.findMany({ | |
55 | + columns: { status: true }, | |
56 | + with: { | |
57 | + repository: { | |
58 | + columns: { visibility: true } | |
59 | + } | |
60 | + } | |
61 | + }); | |
62 | + const publicOnly = allPublicRuns.filter(r => r.repository?.visibility === "public"); | |
63 | + successCount = publicOnly.filter(r => r.status === "success").length; | |
64 | + failureCount = publicOnly.filter(r => r.status === "failure").length; | |
65 | + runningCount = publicOnly.filter(r => r.status === "running").length; | |
39 | 66 | } catch (e) { |
40 | 67 | console.error("Error fetching workflow runs:", e); |
41 | 68 | } |
| @@ -63,211 +90,185 @@ function formatDuration(start: string, end: string | null) { | ||
63 | 90 | } |
64 | 91 | --- |
65 | 92 | |
66 | -<BaseLayout title="Actions - OpenCodeHub"> | |
67 | - <div class="container py-8 max-w-5xl"> | |
93 | +<BaseLayout title="Actions · OpenCodeHub"> | |
94 | + <div class="container max-w-[1280px] mx-auto py-8 px-4 sm:px-6"> | |
68 | 95 | <!-- Header --> |
69 | - <div class="flex items-center justify-between mb-8"> | |
96 | + <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-border mb-6"> | |
70 | 97 | <div> |
71 | - <h1 class="text-3xl font-bold text-foreground flex items-center gap-3"> | |
72 | - <div class="p-2 rounded-lg bg-gradient-to-br from-orange-500/20 to-amber-500/20 border border-orange-500/30"> | |
73 | - <svg class="h-7 w-7 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
74 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path> | |
75 | - </svg> | |
76 | - </div> | |
77 | - Actions | |
98 | + <h1 class="text-2xl font-bold text-foreground tracking-tight flex items-center gap-2.5"> | |
99 | + <Play className="h-6 w-6 text-foreground fill-foreground" /> | |
100 | + <span>Actions & Pipelines</span> | |
78 | 101 | </h1> |
79 | - <p class="text-muted-foreground mt-2">Workflow runs across all public repositories</p> | |
102 | + <p class="text-sm text-muted-foreground mt-1">Global CI/CD workflow runs across all public repositories</p> | |
80 | 103 | </div> |
81 | 104 | </div> |
82 | 105 | |
83 | - <!-- Filters --> | |
84 | - <div class="relative mb-6"> | |
85 | - <div class="absolute -inset-[1px] bg-gradient-to-r from-orange-500/20 via-transparent to-amber-500/20 rounded-xl opacity-50"></div> | |
86 | - <div class="relative rounded-xl border border-border bg-card/80 backdrop-blur-sm p-4"> | |
87 | - <div class="flex flex-wrap items-center gap-2"> | |
88 | - <a | |
89 | - href="/actions?filter=all" | |
90 | - class:list={[ | |
91 | - "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all", | |
92 | - filter === "all" | |
93 | - ? "bg-cyan-500/20 text-cyan-400 border border-cyan-500/30" | |
94 | - : "text-muted-foreground hover:bg-accent" | |
95 | - ]} | |
96 | - > | |
97 | - All | |
98 | - </a> | |
99 | - <a | |
100 | - href="/actions?filter=success" | |
101 | - class:list={[ | |
102 | - "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all", | |
103 | - filter === "success" | |
104 | - ? "bg-green-500/20 text-green-400 border border-green-500/30" | |
105 | - : "text-muted-foreground hover:bg-accent" | |
106 | - ]} | |
107 | - > | |
108 | - <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
109 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> | |
110 | - </svg> | |
111 | - Success | |
112 | - </a> | |
113 | - <a | |
114 | - href="/actions?filter=failure" | |
115 | - class:list={[ | |
116 | - "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all", | |
117 | - filter === "failure" | |
118 | - ? "bg-red-500/20 text-red-400 border border-red-500/30" | |
119 | - : "text-muted-foreground hover:bg-accent" | |
120 | - ]} | |
121 | - > | |
122 | - <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
123 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |
124 | - </svg> | |
125 | - Failed | |
126 | - </a> | |
127 | - <a | |
128 | - href="/actions?filter=running" | |
129 | - class:list={[ | |
130 | - "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all", | |
131 | - filter === "running" | |
132 | - ? "bg-yellow-500/20 text-yellow-400 border border-yellow-500/30" | |
133 | - : "text-muted-foreground hover:bg-accent" | |
134 | - ]} | |
135 | - > | |
136 | - <svg class="h-4 w-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
137 | - <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle> | |
138 | - <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path> | |
139 | - </svg> | |
140 | - Running | |
141 | - </a> | |
142 | - </div> | |
143 | - </div> | |
106 | + <!-- Filter Tabs --> | |
107 | + <div class="inline-flex items-center gap-1 p-1 rounded-lg border border-border/80 bg-card/60 backdrop-blur-sm mb-6 self-start"> | |
108 | + <a | |
109 | + href="/actions?filter=all" | |
110 | + class:list={[ | |
111 | + "inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors", | |
112 | + filter === "all" | |
113 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
114 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
115 | + ]} | |
116 | + > | |
117 | + <span>All</span> | |
118 | + </a> | |
119 | + <a | |
120 | + href="/actions?filter=success" | |
121 | + class:list={[ | |
122 | + "inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors", | |
123 | + filter === "success" | |
124 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
125 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
126 | + ]} | |
127 | + > | |
128 | + <span class="h-2 w-2 rounded-full bg-emerald-500"></span> | |
129 | + <span>Success</span> | |
130 | + <span class="ml-0.5 rounded-full bg-muted px-1.5 py-0.2 text-[11px] font-semibold text-muted-foreground"> | |
131 | + {successCount} | |
132 | + </span> | |
133 | + </a> | |
134 | + <a | |
135 | + href="/actions?filter=failure" | |
136 | + class:list={[ | |
137 | + "inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors", | |
138 | + filter === "failure" | |
139 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
140 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
141 | + ]} | |
142 | + > | |
143 | + <span class="h-2 w-2 rounded-full bg-red-500"></span> | |
144 | + <span>Failed</span> | |
145 | + <span class="ml-0.5 rounded-full bg-muted px-1.5 py-0.2 text-[11px] font-semibold text-muted-foreground"> | |
146 | + {failureCount} | |
147 | + </span> | |
148 | + </a> | |
149 | + <a | |
150 | + href="/actions?filter=running" | |
151 | + class:list={[ | |
152 | + "inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs sm:text-sm font-medium transition-colors", | |
153 | + filter === "running" | |
154 | + ? "bg-secondary text-foreground font-semibold shadow-sm" | |
155 | + : "text-muted-foreground hover:text-foreground hover:bg-muted/60" | |
156 | + ]} | |
157 | + > | |
158 | + <span class="h-2 w-2 rounded-full bg-amber-500"></span> | |
159 | + <span>Running</span> | |
160 | + <span class="ml-0.5 rounded-full bg-muted px-1.5 py-0.2 text-[11px] font-semibold text-muted-foreground"> | |
161 | + {runningCount} | |
162 | + </span> | |
163 | + </a> | |
144 | 164 | </div> |
145 | 165 | |
146 | - <!-- Workflow Runs List --> | |
147 | - <div class="relative"> | |
148 | - <div class="absolute -inset-[1px] bg-gradient-to-r from-orange-500/20 via-transparent to-amber-500/20 rounded-xl opacity-50"></div> | |
149 | - <div class="relative rounded-xl border border-border bg-card/80 backdrop-blur-sm overflow-hidden"> | |
150 | - {workflowRuns.length > 0 ? ( | |
151 | - <div class="divide-y divide-border/50"> | |
152 | - {workflowRuns.map((run) => ( | |
166 | + <!-- Workflow Runs List Card --> | |
167 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-sm"> | |
168 | + {workflowRuns.length > 0 ? ( | |
169 | + <div class="divide-y divide-border/60"> | |
170 | + {workflowRuns.map((run) => { | |
171 | + const ownerName = run.repository?.owner?.username || "unknown"; | |
172 | + const repoName = run.repository?.name || "repo"; | |
173 | + return ( | |
153 | 174 | <a |
154 | - href={`/${run.repository?.owner?.username}/${run.repository?.name}/actions`} | |
155 | - class="block p-4 hover:bg-accent/50 transition-colors group" | |
175 | + href={`/${ownerName}/${repoName}/actions/runs/${run.id}`} | |
176 | + class="group flex items-start gap-4 p-4 hover:bg-accent/40 transition-colors" | |
156 | 177 | > |
157 | - <div class="flex items-start gap-3"> | |
158 | - <!-- Status Icon --> | |
159 | - <div class:list={[ | |
160 | - "p-2 rounded-lg mt-0.5", | |
161 | - run.status === "success" | |
162 | - ? "bg-green-500/10 text-green-400" | |
163 | - : run.status === "failure" | |
164 | - ? "bg-red-500/10 text-red-400" | |
165 | - : run.status === "running" | |
166 | - ? "bg-yellow-500/10 text-yellow-400" | |
167 | - : "bg-gray-500/10 text-muted-foreground" | |
168 | - ]}> | |
169 | - {run.status === "success" ? ( | |
170 | - <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
171 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> | |
172 | - </svg> | |
173 | - ) : run.status === "failure" ? ( | |
174 | - <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
175 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |
176 | - </svg> | |
177 | - ) : run.status === "running" ? ( | |
178 | - <svg class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"> | |
179 | - <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle> | |
180 | - <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path> | |
181 | - </svg> | |
182 | - ) : ( | |
183 | - <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
184 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |
185 | - </svg> | |
186 | - )} | |
187 | - </div> | |
188 | - | |
189 | - <div class="flex-1 min-w-0"> | |
190 | - <!-- Workflow Name & Repo --> | |
191 | - <div class="flex items-center gap-2 mb-1"> | |
192 | - <span class="text-xs text-muted-foreground font-medium"> | |
193 | - {run.repository?.owner?.username}/{run.repository?.name} | |
194 | - </span> | |
178 | + <!-- Status Icon --> | |
179 | + <div class="mt-0.5 flex-shrink-0"> | |
180 | + {run.status === "success" ? ( | |
181 | + <div class="h-6 w-6 rounded-full bg-emerald-500/10 text-emerald-500 flex items-center justify-center border border-emerald-500/20" title="Workflow succeeded"> | |
182 | + <CheckCircle2 className="h-3.5 w-3.5" /> | |
195 | 183 | </div> |
196 | - <h3 class="font-semibold text-foreground group-hover:text-orange-400 transition-colors truncate"> | |
197 | - {run.workflowName || "Workflow"} | |
198 | - </h3> | |
199 | - | |
200 | - <!-- Branch & Commit --> | |
201 | - <div class="flex items-center gap-2 mt-1.5 text-xs text-muted-foreground"> | |
202 | - {run.branch && ( | |
203 | - <span class="flex items-center gap-1"> | |
204 | - <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
205 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path> | |
206 | - </svg> | |
207 | - {run.branch} | |
208 | - </span> | |
209 | - )} | |
210 | - {run.commitSha && ( | |
211 | - <span class="font-mono bg-secondary px-1.5 py-0.5 rounded"> | |
212 | - {run.commitSha.slice(0, 7)} | |
213 | - </span> | |
214 | - )} | |
184 | + ) : run.status === "failure" ? ( | |
185 | + <div class="h-6 w-6 rounded-full bg-red-500/10 text-red-400 flex items-center justify-center border border-red-500/20" title="Workflow failed"> | |
186 | + <XCircle className="h-3.5 w-3.5" /> | |
215 | 187 | </div> |
216 | - | |
217 | - <!-- Meta --> | |
218 | - <div class="flex items-center gap-3 mt-2 text-xs text-muted-foreground"> | |
219 | - <span> | |
220 | - triggered {timeAgo(run.createdAt)} by {run.triggeredBy?.username || run.event || "system"} | |
221 | - </span> | |
222 | - {run.startedAt && ( | |
223 | - <span class="flex items-center gap-1"> | |
224 | - <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
225 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |
226 | - </svg> | |
227 | - {formatDuration(run.startedAt, run.completedAt)} | |
228 | - </span> | |
229 | - )} | |
188 | + ) : run.status === "running" ? ( | |
189 | + <div class="h-6 w-6 rounded-full bg-amber-500/10 text-amber-400 flex items-center justify-center border border-amber-500/20" title="Workflow running"> | |
190 | + <Loader2 className="h-3.5 w-3.5 animate-spin" /> | |
191 | + </div> | |
192 | + ) : ( | |
193 | + <div class="h-6 w-6 rounded-full bg-secondary text-muted-foreground flex items-center justify-center border border-border"> | |
194 | + <Clock className="h-3.5 w-3.5" /> | |
230 | 195 | </div> |
196 | + )} | |
197 | + </div> | |
198 | + | |
199 | + <div class="flex-1 min-w-0"> | |
200 | + <div class="flex items-baseline gap-2 flex-wrap mb-1"> | |
201 | + <span class="text-xs font-semibold text-muted-foreground hover:text-foreground"> | |
202 | + {ownerName}/{repoName} | |
203 | + </span> | |
231 | 204 | </div> |
232 | 205 | |
233 | - <!-- Status Badge --> | |
234 | - <div class:list={[ | |
235 | - "px-2.5 py-1 rounded-full text-xs font-medium capitalize", | |
236 | - run.status === "success" | |
237 | - ? "bg-green-500/10 text-green-400 border border-green-500/30" | |
238 | - : run.status === "failure" | |
239 | - ? "bg-red-500/10 text-red-400 border border-red-500/30" | |
240 | - : run.status === "running" | |
241 | - ? "bg-yellow-500/10 text-yellow-400 border border-yellow-500/30" | |
242 | - : "bg-gray-500/10 text-muted-foreground border border-gray-500/30" | |
243 | - ]}> | |
244 | - {run.status} | |
206 | + <h3 class="text-sm font-semibold text-foreground group-hover:text-primary transition-colors leading-snug break-words"> | |
207 | + {run.name || "CI Workflow"} | |
208 | + </h3> | |
209 | + | |
210 | + <div class="flex items-center gap-3 mt-2 flex-wrap text-xs text-muted-foreground"> | |
211 | + <div class="flex items-center gap-1 font-mono text-[11px] bg-secondary/80 text-foreground px-2 py-0.5 rounded border border-border/50"> | |
212 | + <GitBranch className="h-3 w-3 text-muted-foreground" /> | |
213 | + <span>{run.branch || "main"}</span> | |
214 | + </div> | |
215 | + | |
216 | + {run.commitSha && ( | |
217 | + <span class="font-mono text-[11px] text-muted-foreground"> | |
218 | + {run.commitSha.slice(0, 7)} | |
219 | + </span> | |
220 | + )} | |
221 | + | |
222 | + <span>·</span> | |
223 | + | |
224 | + <span> | |
225 | + triggered {timeAgo(run.createdAt)} by <span class="text-foreground font-medium">@{run.triggeredBy?.username || "workflow"}</span> | |
226 | + </span> | |
227 | + | |
228 | + <span>·</span> | |
229 | + | |
230 | + <span class="flex items-center gap-1"> | |
231 | + <Clock className="h-3 w-3 text-muted-foreground" /> | |
232 | + {formatDuration(run.createdAt, run.completedAt)} | |
233 | + </span> | |
245 | 234 | </div> |
246 | 235 | </div> |
236 | + | |
237 | + <!-- Author Avatar --> | |
238 | + <div class="hidden sm:block flex-shrink-0"> | |
239 | + {run.triggeredBy?.avatarUrl ? ( | |
240 | + <img | |
241 | + src={run.triggeredBy.avatarUrl} | |
242 | + alt={run.triggeredBy.username} | |
243 | + class="h-7 w-7 rounded-full border border-border/80 object-cover" | |
244 | + /> | |
245 | + ) : ( | |
246 | + <div class="h-7 w-7 rounded-full bg-secondary border border-border/80 flex items-center justify-center text-xs font-medium text-foreground"> | |
247 | + {run.triggeredBy?.username?.[0]?.toUpperCase() || <Bot className="h-3.5 w-3.5 text-muted-foreground" />} | |
248 | + </div> | |
249 | + )} | |
250 | + </div> | |
247 | 251 | </a> |
248 | - ))} | |
249 | - </div> | |
250 | - ) : ( | |
251 | - <div class="text-center py-16"> | |
252 | - <div class="relative inline-block mb-4"> | |
253 | - <svg class="h-16 w-16 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
254 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z"></path> | |
255 | - </svg> | |
256 | - <div class="absolute inset-0 bg-orange-500 blur-2xl opacity-20"></div> | |
257 | - </div> | |
258 | - <h3 class="text-lg font-semibold text-foreground mb-2">No workflow runs found</h3> | |
259 | - <p class="text-muted-foreground"> | |
260 | - {filter === "success" | |
261 | - ? "No successful runs yet." | |
262 | - : filter === "failure" | |
263 | - ? "No failed runs. Great job! 🎉" | |
264 | - : filter === "running" | |
265 | - ? "No workflows currently running." | |
266 | - : "No workflow runs have been triggered yet."} | |
267 | - </p> | |
252 | + ); | |
253 | + })} | |
254 | + </div> | |
255 | + ) : ( | |
256 | + <div class="text-center py-16 px-4"> | |
257 | + <div class="w-12 h-12 rounded-full bg-secondary/80 border border-border flex items-center justify-center mx-auto mb-3 text-muted-foreground"> | |
258 | + <Play className="h-6 w-6" /> | |
268 | 259 | </div> |
269 | - )} | |
270 | - </div> | |
260 | + <h3 class="text-base font-semibold text-foreground mb-1">No workflow runs found</h3> | |
261 | + <p class="text-sm text-muted-foreground max-w-sm mx-auto"> | |
262 | + {filter === "success" | |
263 | + ? "No successful workflow runs found." | |
264 | + : filter === "failure" | |
265 | + ? "No failed workflow runs. Everything is green! 🎉" | |
266 | + : filter === "running" | |
267 | + ? "No workflows are currently executing." | |
268 | + : "No workflows have run yet."} | |
269 | + </p> | |
270 | + </div> | |
271 | + )} | |
271 | 272 | </div> |
272 | 273 | </div> |
273 | 274 | </BaseLayout> |
274 | 275 | |
▼src/pages/api/repos/[owner]/[repo]/pulls/[number]/comments.ts
+10-11
| @@ -13,6 +13,7 @@ import { z } from "zod"; | ||
13 | 13 | import crypto from "crypto"; |
14 | 14 | import { checkPathPermissions } from "@/lib/path-scoping"; |
15 | 15 | import { canReadRepo, canWriteRepo } from "@/lib/permissions"; |
16 | +import { notifyMentionedUsers } from "@/lib/mentions"; | |
16 | 17 | |
17 | 18 | const createCommentSchema = z.object({ |
18 | 19 | body: z.string().min(1), |
| @@ -219,17 +220,15 @@ export const POST: APIRoute = withErrorHandler(async ({ params, request }) => { | ||
219 | 220 | logger.info({ userId: tokenPayload.userId, repoId: pr.repository.id, prId: pr.id, commentId }, "PR comment created"); |
220 | 221 | |
221 | 222 | // Notify @mentioned users (WS2-10) |
222 | - import("@/lib/mentions").then(({ notifyMentionedUsers }) => { | |
223 | - notifyMentionedUsers({ | |
224 | - text: body, | |
225 | - actorId: tokenPayload.userId, | |
226 | - repositoryId: pr.repository.id, | |
227 | - subjectType: "comment", | |
228 | - subjectId: commentId, | |
229 | - url: `/${owner}/${repo}/pulls/${number}#comment-${commentId}`, | |
230 | - titlePrefix: "mentioned you in a comment", | |
231 | - }).catch((err) => logger.error({ err }, "Mention notification failed")); | |
232 | - }); | |
223 | + notifyMentionedUsers({ | |
224 | + text: body, | |
225 | + actorId: tokenPayload.userId, | |
226 | + repositoryId: pr.repository.id, | |
227 | + subjectType: "comment", | |
228 | + subjectId: commentId, | |
229 | + url: `/${owner}/${repo}/pulls/${number}#comment-${commentId}`, | |
230 | + titlePrefix: "mentioned you in a comment", | |
231 | + }).catch((err) => logger.error({ err }, "Mention notification failed")); | |
233 | 232 | |
234 | 233 | return success({ comment, message: "Comment created" }); |
235 | 234 | }); |
236 | 235 | |
▼src/pages/api/repos/[owner]/[repo]/pulls/[number]/reviews/batch.ts
+1-2
| @@ -88,12 +88,11 @@ export const POST: APIRoute = withErrorHandler(async ({ params, request, locals | ||
88 | 88 | const createdCommentIds: string[] = []; |
89 | 89 | |
90 | 90 | try { |
91 | - // @ts-expect-error - Drizzle multi-db union type issue | |
92 | 91 | await db.insert(schema.pullRequestReviews).values({ |
93 | 92 | id: reviewId, |
94 | 93 | pullRequestId: pr.id, |
95 | 94 | reviewerId: userId, |
96 | - state: parsed.data.state.toLowerCase(), | |
95 | + state: (parsed.data.state || "COMMENTED").toLowerCase(), | |
97 | 96 | body: parsed.data.body || "Review submitted", |
98 | 97 | commitSha: parsed.data.commitSha ?? null, |
99 | 98 | submittedAt: now, |
100 | 99 | |
▼src/pages/dashboard.astro
+300-433
| @@ -3,19 +3,20 @@ import { getDatabase, schema } from "@/db"; | ||
3 | 3 | import { getInboxItems } from "@/lib/inbox"; |
4 | 4 | import BaseLayout from "@/layouts/BaseLayout.astro"; |
5 | 5 | import { and, desc, eq, inArray, or } from "drizzle-orm"; |
6 | -// Import Shadcn UI Components (using path aliases or relative paths) | |
7 | -// Assuming components are in @/components/ui or src/components/ui | |
8 | 6 | import { |
9 | - Card, | |
10 | - CardContent, | |
11 | - CardDescription, | |
12 | - CardFooter, | |
13 | - CardHeader, | |
14 | - CardTitle, | |
15 | -} from "@/components/ui/card"; | |
16 | -import { Button } from "@/components/ui/button"; | |
17 | -import { UserAvatar } from "@/components/dashboard/UserAvatar"; | |
18 | -import { Badge } from "@/components/ui/badge"; | |
7 | + BookMarked, | |
8 | + Star, | |
9 | + GitFork, | |
10 | + Plus, | |
11 | + Search, | |
12 | + GitPullRequest, | |
13 | + CircleDot, | |
14 | + Layers, | |
15 | + Inbox, | |
16 | + Clock, | |
17 | + Compass, | |
18 | + ArrowRight | |
19 | +} from "lucide-react"; | |
19 | 20 | |
20 | 21 | const user = Astro.locals.user; |
21 | 22 | if (!user) { |
| @@ -47,13 +48,11 @@ function timeAgo(date: string | Date) { | ||
47 | 48 | } |
48 | 49 | |
49 | 50 | // Fetch Activity Feed |
50 | -// 1. Get list of users followed by current user | |
51 | 51 | const following = await db.query.userFollowers.findMany({ |
52 | 52 | where: eq(schema.userFollowers.followerId, user.id), |
53 | 53 | }); |
54 | 54 | const followingIds = following.map((f) => f.followeeId); |
55 | 55 | |
56 | -// 2. Fetch activities from followed users AND current user | |
57 | 56 | const activities = await db.query.activities.findMany({ |
58 | 57 | where: or( |
59 | 58 | eq(schema.activities.userId, user.id), |
| @@ -68,451 +67,308 @@ const activities = await db.query.activities.findMany({ | ||
68 | 67 | repository: true, |
69 | 68 | }, |
70 | 69 | }); |
71 | - | |
72 | -// Helper for activity icons and text | |
73 | -function getActivityIcon(type: string, action: string) { | |
74 | - switch (type) { | |
75 | - case "create_repo": | |
76 | - return { | |
77 | - icon: "M12 6v6m0 0v6m0-6h6m-6 0H6", | |
78 | - color: "text-green-400", | |
79 | - bg: "bg-green-500/10", | |
80 | - }; | |
81 | - case "star": | |
82 | - return { | |
83 | - icon: "M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.784.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z", | |
84 | - color: "text-yellow-400", | |
85 | - bg: "bg-yellow-500/10", | |
86 | - }; | |
87 | - case "fork": | |
88 | - return { | |
89 | - icon: "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z", | |
90 | - color: "text-purple-400", | |
91 | - bg: "bg-purple-500/10", | |
92 | - }; | |
93 | - case "follow": | |
94 | - return { | |
95 | - icon: "M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z", | |
96 | - color: "text-blue-400", | |
97 | - bg: "bg-blue-500/10", | |
98 | - }; | |
99 | - case "issue": | |
100 | - return { | |
101 | - icon: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", | |
102 | - color: "text-orange-400", | |
103 | - bg: "bg-orange-500/10", | |
104 | - }; | |
105 | - case "pull_request": | |
106 | - case "open_pr": | |
107 | - return { | |
108 | - icon: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z", | |
109 | - color: "text-cyan-400", | |
110 | - bg: "bg-cyan-500/10", | |
111 | - }; | |
112 | - default: | |
113 | - return { | |
114 | - icon: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", | |
115 | - color: "text-muted-foreground", | |
116 | - bg: "bg-gray-500/10", | |
117 | - }; | |
118 | - } | |
119 | -} | |
120 | - | |
121 | -function getActivityText(activity: any) { | |
122 | - const actor = activity.user.username; | |
123 | - const repo = activity.repository?.name; | |
124 | - | |
125 | - switch (activity.type) { | |
126 | - case "create_repo": | |
127 | - return `created repository ${activity.repository?.name}`; | |
128 | - case "star": | |
129 | - return `starred ${activity.repoOwner || "someone"}/${activity.repoName || activity.repository?.name}`; | |
130 | - case "fork": | |
131 | - return `forked ${activity.repository?.name}`; | |
132 | - case "follow": | |
133 | - return `followed ${JSON.parse(activity.payload || "{}").targetUsername || "someone"}`; | |
134 | - case "issue": | |
135 | - return `opened issue #${JSON.parse(activity.payload || "{}").number} in ${activity.repository?.name}`; | |
136 | - case "open_pr": | |
137 | - return `opened PR #${JSON.parse(activity.payload || "{}").number} in ${activity.repository?.name}`; | |
138 | - default: | |
139 | - return `${activity.action} ${activity.type}`; | |
140 | - } | |
141 | -} | |
142 | 70 | --- |
143 | 71 | |
144 | -<BaseLayout title="Dashboard - OpenCodeHub"> | |
145 | - <div class="container py-8 max-w-7xl mx-auto space-y-8"> | |
146 | - <!-- Welcome Header --> | |
147 | - <div | |
148 | - class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4" | |
149 | - > | |
72 | +<BaseLayout title="Dashboard · OpenCodeHub"> | |
73 | + <div class="container max-w-[1280px] mx-auto py-8 px-4 sm:px-6 space-y-8"> | |
74 | + <!-- Top Welcome Header --> | |
75 | + <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 pb-6 border-b border-border"> | |
150 | 76 | <div class="flex items-center gap-4"> |
151 | - <UserAvatar | |
152 | - user={user} | |
153 | - className="h-16 w-16 border-2 border-border" | |
154 | - client:load | |
155 | - /> | |
77 | + {user.avatarUrl ? ( | |
78 | + <img | |
79 | + src={user.avatarUrl} | |
80 | + alt={user.username} | |
81 | + class="h-14 w-14 rounded-full border-2 border-border/80 object-cover flex-shrink-0 shadow-sm" | |
82 | + /> | |
83 | + ) : ( | |
84 | + <div class="h-14 w-14 rounded-full bg-secondary border-2 border-border/80 flex items-center justify-center text-xl font-bold text-foreground flex-shrink-0 shadow-sm"> | |
85 | + {user.username[0]?.toUpperCase()} | |
86 | + </div> | |
87 | + )} | |
156 | 88 | <div> |
157 | - <h1 class="text-3xl font-bold text-foreground tracking-tight"> | |
89 | + <h1 class="text-2xl font-bold text-foreground tracking-tight"> | |
158 | 90 | Welcome back, {user.displayName || user.username} |
159 | 91 | </h1> |
160 | - <p class="text-muted-foreground"> | |
161 | - Manage your repositories and development workflow. | |
92 | + <p class="text-xs sm:text-sm text-muted-foreground mt-0.5"> | |
93 | + Your personal hub for repositories, reviews, and activity | |
162 | 94 | </p> |
163 | 95 | </div> |
164 | 96 | </div> |
165 | - <div class="flex items-center gap-3"> | |
166 | - <a href="/stacks"> | |
167 | - <Button | |
168 | - variant="outline" | |
169 | - className="border-border text-muted-foreground hover:text-foreground hover:bg-accent" | |
170 | - > | |
171 | - <svg | |
172 | - class="mr-2 h-4 w-4" | |
173 | - fill="none" | |
174 | - stroke="currentColor" | |
175 | - viewBox="0 0 24 24" | |
176 | - ><path | |
177 | - stroke-linecap="round" | |
178 | - stroke-linejoin="round" | |
179 | - stroke-width="2" | |
180 | - d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" | |
181 | - ></path></svg | |
182 | - > | |
183 | - View Stacks | |
184 | - </Button> | |
97 | + | |
98 | + <div class="flex items-center gap-2.5 flex-wrap self-start sm:self-auto"> | |
99 | + <a | |
100 | + href="/stacks" | |
101 | + class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border bg-card text-xs font-medium text-foreground hover:bg-accent transition-colors shadow-sm" | |
102 | + > | |
103 | + <Layers className="h-3.5 w-3.5" /> | |
104 | + <span>Stacks</span> | |
185 | 105 | </a> |
186 | - <a href="/new"> | |
187 | - <Button | |
188 | - className="bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-foreground border-0 shadow-lg shadow-green-500/20" | |
189 | - > | |
190 | - <svg | |
191 | - class="mr-2 h-4 w-4" | |
192 | - fill="none" | |
193 | - stroke="currentColor" | |
194 | - viewBox="0 0 24 24" | |
195 | - ><path | |
196 | - stroke-linecap="round" | |
197 | - stroke-linejoin="round" | |
198 | - stroke-width="2" | |
199 | - d="M12 4v16m8-8H4"></path></svg | |
200 | - > | |
201 | - New Repository | |
202 | - </Button> | |
106 | + <a | |
107 | + href="/new" | |
108 | + class="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white text-xs sm:text-sm font-medium transition-colors shadow-sm" | |
109 | + > | |
110 | + <Plus className="h-4 w-4" /> | |
111 | + <span>New repository</span> | |
203 | 112 | </a> |
204 | 113 | </div> |
205 | 114 | </div> |
206 | 115 | |
207 | - <!-- Stats Grid --> | |
208 | - <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
209 | - <Card className="bg-card/50 border-border backdrop-blur-sm"> | |
210 | - <CardHeader className="pb-2"> | |
211 | - <CardTitle className="text-sm font-medium text-muted-foreground" | |
212 | - >Total Repositories</CardTitle | |
213 | - > | |
214 | - </CardHeader> | |
215 | - <CardContent> | |
216 | - <div class="text-3xl font-bold text-foreground">{repos.length}</div> | |
217 | - </CardContent> | |
218 | - </Card> | |
219 | - <Card className="bg-card/50 border-border backdrop-blur-sm"> | |
220 | - <CardHeader className="pb-2"> | |
221 | - <CardTitle className="text-sm font-medium text-muted-foreground" | |
222 | - >Total Stars</CardTitle | |
223 | - > | |
224 | - </CardHeader> | |
225 | - <CardContent> | |
226 | - <div class="text-3xl font-bold text-yellow-400">{totalStars}</div> | |
227 | - </CardContent> | |
228 | - </Card> | |
229 | - <Card className="bg-card/50 border-border backdrop-blur-sm"> | |
230 | - <CardHeader className="pb-2"> | |
231 | - <CardTitle className="text-sm font-medium text-muted-foreground" | |
232 | - >Total Forks</CardTitle | |
233 | - > | |
234 | - </CardHeader> | |
235 | - <CardContent> | |
236 | - <div class="text-3xl font-bold text-purple-400">{totalForks}</div> | |
237 | - </CardContent> | |
238 | - </Card> | |
116 | + <!-- Quick Stats Row --> | |
117 | + <div class="grid grid-cols-1 sm:grid-cols-3 gap-4"> | |
118 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm p-4 shadow-sm"> | |
119 | + <div class="flex items-center justify-between"> | |
120 | + <span class="text-xs font-medium text-muted-foreground">Repositories</span> | |
121 | + <BookMarked className="h-4 w-4 text-muted-foreground" /> | |
122 | + </div> | |
123 | + <div class="text-2xl font-bold text-foreground mt-2">{repos.length}</div> | |
124 | + </div> | |
125 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm p-4 shadow-sm"> | |
126 | + <div class="flex items-center justify-between"> | |
127 | + <span class="text-xs font-medium text-muted-foreground">Total Stars</span> | |
128 | + <Star className="h-4 w-4 text-amber-400" /> | |
129 | + </div> | |
130 | + <div class="text-2xl font-bold text-foreground mt-2">{totalStars}</div> | |
131 | + </div> | |
132 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm p-4 shadow-sm"> | |
133 | + <div class="flex items-center justify-between"> | |
134 | + <span class="text-xs font-medium text-muted-foreground">Total Forks</span> | |
135 | + <GitFork className="h-4 w-4 text-purple-400" /> | |
136 | + </div> | |
137 | + <div class="text-2xl font-bold text-foreground mt-2">{totalForks}</div> | |
138 | + </div> | |
239 | 139 | </div> |
240 | 140 | |
241 | 141 | <!-- Main Content Grid --> |
242 | - <div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> | |
243 | - <!-- Left Sidebar: User Profile context & Repositories --> | |
244 | - <div class="lg:col-span-4 xl:col-span-3 space-y-6"> | |
245 | - <div class="flex items-center justify-between"> | |
246 | - <h2 class="text-lg font-semibold text-foreground flex items-center gap-2"> | |
247 | - Repositories | |
248 | - </h2> | |
249 | - <div class="flex items-center gap-2"> | |
250 | - <a href="/new/import" class="text-sm bg-cyan-500/20 text-cyan-400 px-2 py-1 rounded hover:bg-cyan-500/30 transition-colors"> | |
251 | - Import | |
252 | - </a> | |
253 | - <a href="/new" class="text-sm bg-green-500/20 text-green-400 px-2 py-1 rounded hover:bg-green-500/30 transition-colors"> | |
254 | - New | |
142 | + <div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start"> | |
143 | + <!-- Left Sidebar: Repositories list & Navigation shortcuts --> | |
144 | + <aside class="lg:col-span-4 space-y-6"> | |
145 | + <!-- Repositories Card --> | |
146 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-sm"> | |
147 | + <div class="p-3.5 border-b border-border/80 bg-muted/30 flex items-center justify-between"> | |
148 | + <h2 class="text-xs font-bold text-foreground uppercase tracking-wider"> | |
149 | + Top Repositories | |
150 | + </h2> | |
151 | + <a | |
152 | + href="/new" | |
153 | + class="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline" | |
154 | + > | |
155 | + <Plus className="h-3 w-3" /> | |
156 | + <span>New</span> | |
255 | 157 | </a> |
256 | 158 | </div> |
257 | - </div> | |
258 | - | |
259 | - <div class="bg-card/80 border border-border rounded-xl overflow-hidden"> | |
260 | - <div class="p-3 border-b border-border bg-secondary"> | |
261 | - <input type="text" placeholder="Find a repository..." class="w-full bg-background border border-border rounded-md px-3 py-1.5 text-sm text-foreground placeholder-gray-500 focus:outline-none focus:border-cyan-500/50" /> | |
159 | + | |
160 | + <div class="p-3 border-b border-border/60"> | |
161 | + <div class="relative group"> | |
162 | + <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground group-focus-within:text-foreground" /> | |
163 | + <input | |
164 | + type="text" | |
165 | + id="sidebar-repo-search" | |
166 | + placeholder="Find a repository..." | |
167 | + class="w-full h-8 rounded-md border border-border/70 bg-background/80 pl-8 pr-3 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary" | |
168 | + /> | |
169 | + </div> | |
262 | 170 | </div> |
263 | - <div class="max-h-[600px] overflow-y-auto"> | |
171 | + | |
172 | + <div class="max-h-[380px] overflow-y-auto divide-y divide-border/40" id="sidebar-repo-list"> | |
264 | 173 | {repos.length > 0 ? ( |
265 | - <ul class="divide-y divide-white/5"> | |
266 | - {repos.map((repo) => ( | |
267 | - <li> | |
268 | - <a href={`/${user.username}/${repo.name}`} class="block p-3 hover:bg-accent transition-colors group"> | |
269 | - <div class="flex items-center gap-2 mb-1"> | |
270 | - <div class="w-4 h-4 rounded-full flex-shrink-0 flex items-center justify-center"> | |
271 | - <img src={`https://api.dicebear.com/7.x/identicon/svg?seed=${repo.ownerId}`} class="rounded-full w-4 h-4" /> | |
272 | - </div> | |
273 | - <span class="text-sm font-medium text-blue-400 group-hover:underline truncate">{user.username}/{repo.name}</span> | |
274 | - <Badge variant="secondary" className="ml-auto text-[10px] px-1.5 py-0 h-4 bg-transparent border-border/80 text-muted-foreground"> | |
275 | - {repo.visibility} | |
276 | - </Badge> | |
277 | - </div> | |
278 | - </a> | |
279 | - </li> | |
280 | - ))} | |
281 | - </ul> | |
174 | + repos.slice(0, 15).map((repo) => ( | |
175 | + <a | |
176 | + href={`/${user.username}/${repo.name}`} | |
177 | + class="sidebar-repo-item flex items-center justify-between p-3 hover:bg-accent/40 transition-colors group" | |
178 | + data-name={repo.name.toLowerCase()} | |
179 | + > | |
180 | + <div class="flex items-center gap-2 min-w-0"> | |
181 | + <BookMarked className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" /> | |
182 | + <span class="text-xs font-medium text-foreground group-hover:text-primary transition-colors truncate"> | |
183 | + {user.username}/<span class="font-semibold">{repo.name}</span> | |
184 | + </span> | |
185 | + </div> | |
186 | + <span class="rounded-full border border-border/60 px-1.5 py-0.2 text-[10px] text-muted-foreground"> | |
187 | + {repo.visibility} | |
188 | + </span> | |
189 | + </a> | |
190 | + )) | |
282 | 191 | ) : ( |
283 | - <div class="p-4 text-center text-sm text-muted-foreground">No repositories yet.</div> | |
192 | + <div class="p-6 text-center text-xs text-muted-foreground"> | |
193 | + No repositories created yet. | |
194 | + </div> | |
284 | 195 | )} |
285 | 196 | </div> |
197 | + | |
198 | + {repos.length > 15 && ( | |
199 | + <div class="p-2.5 text-center border-t border-border/60 bg-muted/20"> | |
200 | + <a href="/repositories" class="text-xs font-medium text-primary hover:underline"> | |
201 | + View all {repos.length} repositories | |
202 | + </a> | |
203 | + </div> | |
204 | + )} | |
286 | 205 | </div> |
287 | - </div> | |
288 | 206 | |
289 | - <!-- Main Feed: Activity & Inbox --> | |
290 | - <div class="lg:col-span-8 xl:col-span-9 space-y-8"> | |
291 | - | |
292 | - <!-- Inbox Section --> | |
293 | - { | |
294 | - inboxItems.length > 0 && ( | |
295 | - <div class="space-y-4"> | |
296 | - <div class="flex items-center justify-between"> | |
297 | - <h2 class="text-xl font-semibold text-foreground flex items-center gap-2"> | |
298 | - <svg | |
299 | - class="h-5 w-5 text-yellow-400" | |
300 | - fill="none" | |
301 | - stroke="currentColor" | |
302 | - viewBox="0 0 24 24" | |
303 | - > | |
304 | - <path | |
305 | - stroke-linecap="round" | |
306 | - stroke-linejoin="round" | |
307 | - stroke-width="2" | |
308 | - d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" | |
309 | - /> | |
310 | - </svg> | |
311 | - Inbox | |
312 | - </h2> | |
313 | - <Badge | |
314 | - variant="secondary" | |
315 | - className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20" | |
316 | - > | |
317 | - {inboxItems.length} items | |
318 | - </Badge> | |
319 | - </div> | |
320 | - <div class="grid gap-3"> | |
321 | - {inboxItems.map((item) => ( | |
322 | - <a | |
323 | - href={`/${item.repository.owner}/${item.repository.name}/pulls/${item.number}`} | |
324 | - class="block group" | |
325 | - > | |
326 | - <Card className="bg-card/80 border-border hover:border-border/80 hover:bg-accent transition-all duration-200"> | |
327 | - <CardContent className="p-4 flex items-center justify-between gap-4"> | |
328 | - <div class="flex items-center gap-4 min-w-0"> | |
329 | - <div class="flex-shrink-0"> | |
330 | - {item.type === "review_required" && ( | |
331 | - <div | |
332 | - class="h-8 w-8 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 border border-blue-500/20" | |
333 | - title="Review Required" | |
334 | - > | |
335 | - <svg | |
336 | - class="h-4 w-4" | |
337 | - fill="none" | |
338 | - stroke="currentColor" | |
339 | - viewBox="0 0 24 24" | |
340 | - > | |
341 | - <path | |
342 | - stroke-linecap="round" | |
343 | - stroke-linejoin="round" | |
344 | - stroke-width="2" | |
345 | - d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" | |
346 | - /> | |
347 | - <path | |
348 | - stroke-linecap="round" | |
349 | - stroke-linejoin="round" | |
350 | - stroke-width="2" | |
351 | - d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" | |
352 | - /> | |
353 | - </svg> | |
354 | - </div> | |
355 | - )} | |
356 | - {item.type === "changes_requested" && ( | |
357 | - <div | |
358 | - class="h-8 w-8 rounded-full bg-red-500/10 flex items-center justify-center text-red-500 border border-red-500/20" | |
359 | - title="Changes Requested" | |
360 | - > | |
361 | - <svg | |
362 | - class="h-4 w-4" | |
363 | - fill="none" | |
364 | - stroke="currentColor" | |
365 | - viewBox="0 0 24 24" | |
366 | - > | |
367 | - <path | |
368 | - stroke-linecap="round" | |
369 | - stroke-linejoin="round" | |
370 | - stroke-width="2" | |
371 | - d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" | |
372 | - /> | |
373 | - </svg> | |
374 | - </div> | |
375 | - )} | |
376 | - {item.type === "assigned" && ( | |
377 | - <div | |
378 | - class="h-8 w-8 rounded-full bg-green-500/10 flex items-center justify-center text-green-500 border border-green-500/20" | |
379 | - title="Assigned" | |
380 | - > | |
381 | - <svg | |
382 | - class="h-4 w-4" | |
383 | - fill="none" | |
384 | - stroke="currentColor" | |
385 | - viewBox="0 0 24 24" | |
386 | - > | |
387 | - <path | |
388 | - stroke-linecap="round" | |
389 | - stroke-linejoin="round" | |
390 | - stroke-width="2" | |
391 | - d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" | |
392 | - /> | |
393 | - </svg> | |
394 | - </div> | |
395 | - )} | |
396 | - </div> | |
397 | - <div class="min-w-0"> | |
398 | - <p class="text-sm font-medium text-foreground truncate group-hover:text-cyan-400 transition-colors"> | |
399 | - {item.title} | |
400 | - <span class="text-muted-foreground font-normal ml-2"> | |
401 | - #{item.number} | |
402 | - </span> | |
403 | - </p> | |
404 | - <div class="flex items-center gap-2 text-xs text-muted-foreground mt-0.5"> | |
405 | - <span> | |
406 | - {item.repository.owner}/{item.repository.name} | |
407 | - </span> | |
408 | - <span>•</span> | |
409 | - <span>Author: {item.author.username}</span> | |
410 | - </div> | |
411 | - </div> | |
412 | - </div> | |
413 | - <div class="text-xs text-muted-foreground whitespace-nowrap flex-shrink-0"> | |
414 | - {timeAgo(item.updatedAt)} | |
415 | - </div> | |
416 | - </CardContent> | |
417 | - </Card> | |
418 | - </a> | |
419 | - ))} | |
207 | + <!-- Quick Shortcuts --> | |
208 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm p-4 space-y-2 shadow-sm"> | |
209 | + <h3 class="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-2">Shortcuts</h3> | |
210 | + <a href="/pulls" class="flex items-center justify-between py-1.5 text-xs text-foreground hover:text-primary transition-colors"> | |
211 | + <span class="flex items-center gap-2"> | |
212 | + <GitPullRequest className="h-3.5 w-3.5 text-muted-foreground" /> | |
213 | + <span>Pull Requests</span> | |
214 | + </span> | |
215 | + <ArrowRight className="h-3 w-3 text-muted-foreground" /> | |
216 | + </a> | |
217 | + <a href="/issues" class="flex items-center justify-between py-1.5 text-xs text-foreground hover:text-primary transition-colors"> | |
218 | + <span class="flex items-center gap-2"> | |
219 | + <CircleDot className="h-3.5 w-3.5 text-muted-foreground" /> | |
220 | + <span>Issues</span> | |
221 | + </span> | |
222 | + <ArrowRight className="h-3 w-3 text-muted-foreground" /> | |
223 | + </a> | |
224 | + <a href="/stars" class="flex items-center justify-between py-1.5 text-xs text-foreground hover:text-primary transition-colors"> | |
225 | + <span class="flex items-center gap-2"> | |
226 | + <Star className="h-3.5 w-3.5 text-muted-foreground" /> | |
227 | + <span>Your Stars</span> | |
228 | + </span> | |
229 | + <ArrowRight className="h-3 w-3 text-muted-foreground" /> | |
230 | + </a> | |
231 | + <a href="/explore" class="flex items-center justify-between py-1.5 text-xs text-foreground hover:text-primary transition-colors"> | |
232 | + <span class="flex items-center gap-2"> | |
233 | + <Compass className="h-3.5 w-3.5 text-muted-foreground" /> | |
234 | + <span>Explore Repositories</span> | |
235 | + </span> | |
236 | + <ArrowRight className="h-3 w-3 text-muted-foreground" /> | |
237 | + </a> | |
238 | + </div> | |
239 | + </aside> | |
240 | + | |
241 | + <!-- Main Column: Activity & Inbox --> | |
242 | + <main class="lg:col-span-8 space-y-6"> | |
243 | + <!-- Inbox Section (if any) --> | |
244 | + {inboxItems.length > 0 && ( | |
245 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-sm"> | |
246 | + <div class="p-4 border-b border-border/80 bg-muted/30 flex items-center justify-between"> | |
247 | + <div class="flex items-center gap-2"> | |
248 | + <Inbox className="h-4 w-4 text-foreground" /> | |
249 | + <h2 class="text-sm font-bold text-foreground">Review Inbox</h2> | |
250 | + <span class="rounded-full bg-primary/10 border border-primary/20 px-2 py-0.2 text-xs font-semibold text-primary"> | |
251 | + {inboxItems.length} | |
252 | + </span> | |
420 | 253 | </div> |
421 | 254 | </div> |
422 | - ) | |
423 | - } | |
424 | - | |
425 | - <!-- Recent Activity Feed --> | |
426 | - <div class="space-y-4"> | |
427 | - <h2 class="text-xl font-semibold text-foreground flex items-center gap-2"> | |
428 | - Activity Feed | |
429 | - </h2> | |
430 | - <Card className="bg-card/80 border-border backdrop-blur-sm"> | |
431 | - <CardContent className="p-0"> | |
432 | - { | |
433 | - activities.length > 0 ? ( | |
434 | - <div class="divide-y divide-white/5"> | |
435 | - {activities.map((activity) => { | |
436 | - const style = getActivityIcon( | |
437 | - activity.type, | |
438 | - activity.action, | |
439 | - ); | |
440 | - const payload = activity.payload | |
441 | - ? JSON.parse(activity.payload) | |
442 | - : {}; | |
443 | - return ( | |
444 | - <div class="flex gap-4 p-5 hover:bg-accent/50 transition-colors"> | |
445 | - <UserAvatar user={activity.user} className="w-8 h-8" client:load /> | |
446 | - <div class="min-w-0 flex-1"> | |
447 | - <p class="text-sm text-muted-foreground"> | |
448 | - <a | |
449 | - href={`/${activity.user.username}`} | |
450 | - class="font-medium text-foreground hover:text-blue-400 hover:underline transition-colors" | |
451 | - > | |
452 | - {activity.user.username} | |
453 | - </a>{" "} | |
454 | - <span class="text-muted-foreground"> | |
455 | - {(() => { | |
456 | - switch (activity.type) { | |
457 | - case "create_repo": | |
458 | - return "created a repository"; | |
459 | - case "star": | |
460 | - return "starred"; | |
461 | - case "fork": | |
462 | - return "forked"; | |
463 | - case "follow": | |
464 | - return "started following"; | |
465 | - case "issue": | |
466 | - return `opened issue #${payload.number || ""} in`; | |
467 | - case "open_pr": | |
468 | - return `opened pull request #${payload.number || ""} in`; | |
469 | - default: | |
470 | - return `${activity.action} ${activity.type}`; | |
471 | - } | |
472 | - })()} | |
473 | - </span>{" "} | |
474 | - {activity.repository && ( | |
475 | - <a | |
476 | - href={`/${activity.repository.ownerId}/${activity.repository.name}`} | |
477 | - class="font-medium text-foreground hover:text-blue-400 hover:underline transition-colors" | |
478 | - > | |
479 | - {activity.repository.name} | |
480 | - </a> | |
481 | - )} | |
482 | - {!activity.repository && | |
483 | - activity.type === "follow" && ( | |
484 | - <a | |
485 | - href={`/${payload.targetUsername}`} | |
486 | - class="font-medium text-foreground hover:text-blue-400 hover:underline transition-colors" | |
487 | - > | |
488 | - {payload.targetUsername} | |
489 | - </a> | |
490 | - )} | |
491 | - </p> | |
492 | - <p class="text-xs text-muted-foreground mt-1"> | |
493 | - {timeAgo(activity.createdAt)} | |
494 | - </p> | |
495 | - </div> | |
496 | - </div> | |
497 | - ); | |
498 | - })} | |
255 | + <div class="divide-y divide-border/60"> | |
256 | + {inboxItems.map((item) => ( | |
257 | + <a | |
258 | + href={`/${item.repository.owner}/${item.repository.name}/pulls/${item.number}`} | |
259 | + class="flex items-center justify-between gap-4 p-4 hover:bg-accent/40 transition-colors group" | |
260 | + > | |
261 | + <div class="min-w-0"> | |
262 | + <div class="flex items-center gap-2 mb-1"> | |
263 | + <span class="text-xs font-semibold text-muted-foreground"> | |
264 | + {item.repository.owner}/{item.repository.name} | |
265 | + </span> | |
266 | + <span class="text-xs text-muted-foreground font-mono">#{item.number}</span> | |
267 | + </div> | |
268 | + <h3 class="text-sm font-semibold text-foreground group-hover:text-primary transition-colors truncate"> | |
269 | + {item.title} | |
270 | + </h3> | |
499 | 271 | </div> |
500 | - ) : ( | |
501 | - <div class="text-center py-12"> | |
502 | - <h3 class="text-lg font-medium text-foreground mb-2">Discover interesting projects</h3> | |
503 | - <p class="text-muted-foreground mb-6 text-sm">Follow users and star repositories to see activity here.</p> | |
504 | - <a href="/explore"> | |
505 | - <Button variant="outline" className="border-border text-muted-foreground"> | |
506 | - Explore OpenCodeHub | |
507 | - </Button> | |
508 | - </a> | |
272 | + <span class="text-xs text-muted-foreground flex-shrink-0"> | |
273 | + {timeAgo(item.updatedAt)} | |
274 | + </span> | |
275 | + </a> | |
276 | + ))} | |
277 | + </div> | |
278 | + </div> | |
279 | + )} | |
280 | + | |
281 | + <!-- Activity Feed Section --> | |
282 | + <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-sm overflow-hidden shadow-sm"> | |
283 | + <div class="p-4 border-b border-border/80 bg-muted/30 flex items-center justify-between"> | |
284 | + <div class="flex items-center gap-2"> | |
285 | + <Clock className="h-4 w-4 text-foreground" /> | |
286 | + <h2 class="text-sm font-bold text-foreground">Recent Activity</h2> | |
287 | + </div> | |
288 | + <a href="/explore" class="text-xs font-medium text-primary hover:underline"> | |
289 | + Explore feed | |
290 | + </a> | |
291 | + </div> | |
292 | + | |
293 | + {activities.length > 0 ? ( | |
294 | + <div class="divide-y divide-border/60"> | |
295 | + {activities.map((activity) => { | |
296 | + const payload = activity.payload ? JSON.parse(activity.payload) : {}; | |
297 | + return ( | |
298 | + <div class="flex items-start gap-3.5 p-4 hover:bg-accent/40 transition-colors"> | |
299 | + {activity.user?.avatarUrl ? ( | |
300 | + <img | |
301 | + src={activity.user.avatarUrl} | |
302 | + alt={activity.user.username} | |
303 | + class="h-7 w-7 rounded-full border border-border object-cover flex-shrink-0 mt-0.5" | |
304 | + /> | |
305 | + ) : ( | |
306 | + <div class="h-7 w-7 rounded-full bg-secondary border border-border flex items-center justify-center text-xs font-semibold text-foreground flex-shrink-0 mt-0.5"> | |
307 | + {activity.user?.username?.[0]?.toUpperCase() || "?"} | |
308 | + </div> | |
309 | + )} | |
310 | + <div class="flex-1 min-w-0"> | |
311 | + <p class="text-xs sm:text-sm text-muted-foreground leading-snug"> | |
312 | + <a | |
313 | + href={`/${activity.user?.username}`} | |
314 | + class="font-semibold text-foreground hover:text-primary transition-colors" | |
315 | + > | |
316 | + @{activity.user?.username} | |
317 | + </a>{" "} | |
318 | + <span> | |
319 | + {activity.type === "create_repo" && "created repository"} | |
320 | + {activity.type === "star" && "starred"} | |
321 | + {activity.type === "fork" && "forked"} | |
322 | + {activity.type === "follow" && "started following"} | |
323 | + {activity.type === "issue" && `opened issue #${payload.number || ""} in`} | |
324 | + {activity.type === "open_pr" && `opened pull request #${payload.number || ""} in`} | |
325 | + {!["create_repo", "star", "fork", "follow", "issue", "open_pr"].includes(activity.type) && `${activity.action || ""} ${activity.type}`} | |
326 | + </span>{" "} | |
327 | + {activity.repository && ( | |
328 | + <a | |
329 | + href={`/${activity.repository.ownerId || activity.user?.username}/${activity.repository.name}`} | |
330 | + class="font-semibold text-foreground hover:text-primary transition-colors" | |
331 | + > | |
332 | + {activity.repository.name} | |
333 | + </a> | |
334 | + )} | |
335 | + {!activity.repository && activity.type === "follow" && ( | |
336 | + <a | |
337 | + href={`/${payload.targetUsername}`} | |
338 | + class="font-semibold text-foreground hover:text-primary transition-colors" | |
339 | + > | |
340 | + @{payload.targetUsername} | |
341 | + </a> | |
342 | + )} | |
343 | + </p> | |
344 | + <p class="text-[11px] text-muted-foreground mt-1"> | |
345 | + {timeAgo(activity.createdAt)} | |
346 | + </p> | |
347 | + </div> | |
509 | 348 | </div> |
510 | - ) | |
511 | - } | |
512 | - </CardContent> | |
513 | - </Card> | |
349 | + ); | |
350 | + })} | |
351 | + </div> | |
352 | + ) : ( | |
353 | + <div class="text-center py-16 px-4"> | |
354 | + <div class="w-12 h-12 rounded-full bg-secondary/80 border border-border flex items-center justify-center mx-auto mb-3 text-muted-foreground"> | |
355 | + <Compass className="h-6 w-6" /> | |
356 | + </div> | |
357 | + <h3 class="text-base font-semibold text-foreground mb-1">No recent activity</h3> | |
358 | + <p class="text-sm text-muted-foreground max-w-sm mx-auto mb-6"> | |
359 | + Follow developers and star repositories to see updates on your timeline. | |
360 | + </p> | |
361 | + <a | |
362 | + href="/explore" | |
363 | + class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-foreground text-background text-sm font-medium hover:bg-foreground/90 transition-colors shadow-sm" | |
364 | + > | |
365 | + <Compass className="h-4 w-4" /> | |
366 | + Explore community | |
367 | + </a> | |
368 | + </div> | |
369 | + )} | |
514 | 370 | </div> |
515 | - </div> | |
371 | + </main> | |
516 | 372 | </div> |
517 | 373 | </div> |
518 | 374 | </BaseLayout> |
| @@ -521,14 +377,27 @@ function getActivityText(activity: any) { | ||
521 | 377 | import { realtime } from "@/lib/realtime-client"; |
522 | 378 | import { toast } from "sonner"; |
523 | 379 | |
380 | + // Repos search | |
381 | + const searchInput = document.getElementById("sidebar-repo-search") as HTMLInputElement; | |
382 | + const repoItems = document.querySelectorAll(".sidebar-repo-item"); | |
383 | + searchInput?.addEventListener("input", () => { | |
384 | + const q = searchInput.value.toLowerCase().trim(); | |
385 | + repoItems.forEach((item) => { | |
386 | + const el = item as HTMLElement; | |
387 | + const name = el.dataset.name || ""; | |
388 | + if (!q || name.includes(q)) { | |
389 | + el.style.display = "flex"; | |
390 | + } else { | |
391 | + el.style.display = "none"; | |
392 | + } | |
393 | + }); | |
394 | + }); | |
395 | + | |
524 | 396 | // Connect to realtime events |
525 | 397 | realtime.connect(); |
526 | 398 | |
527 | - // Listen for inbox updates | |
528 | 399 | realtime.on("inbox:refresh", (data: any) => { |
529 | - // Only show if connected message passed explicitly or if it's a real update | |
530 | 400 | if (data.connected) return; |
531 | - | |
532 | 401 | toast.message("Inbox Updated", { |
533 | 402 | description: "New items available in your inbox.", |
534 | 403 | action: { |
| @@ -538,14 +407,12 @@ function getActivityText(activity: any) { | ||
538 | 407 | }); |
539 | 408 | }); |
540 | 409 | |
541 | - // Listen for PR updates | |
542 | 410 | realtime.on("pr:updated", (data: any) => { |
543 | 411 | toast.info(`PR Updated: #${data.prNumber} ${data.title}`, { |
544 | 412 | description: "Pull request has been updated.", |
545 | 413 | }); |
546 | 414 | }); |
547 | 415 | |
548 | - // Listen for notifications | |
549 | 416 | realtime.on("notification:new", (data: any) => { |
550 | 417 | toast.success(data.title, { |
551 | 418 | description: data.body, |
552 | 419 | |