diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2df4e58 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +UPLOAD_SECRET_KEY=your-secret-key-here +GITHUB_TOKEN=ghp_your-github-personal-access-token +GIT_BRANCH=ta-branch +GIT_REPO_URL=https://github.com/your-org/your-repo.git diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 3cab81d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -# ============================================================================= -# Asset Bridge 3D — Dockerfile optimisé pour Coolify -# Node 20 Alpine · Git LFS · SSH · Multi-stage build -# ============================================================================= - -# ─── Stage 1 : Dépendances ─────────────────────────────────────────────────── -FROM node:20-alpine AS deps - -# Outils système nécessaires : git, git-lfs, openssh (pour push SSH) -RUN apk add --no-cache \ - git \ - git-lfs \ - openssh-client \ - ca-certificates - -# Initialiser Git LFS globalement -RUN git lfs install --system - -WORKDIR /app - -# Copier uniquement les fichiers de dépendances pour profiter du cache Docker -COPY package.json package-lock.json* ./ - -RUN npm ci --ignore-scripts - -# ─── Stage 2 : Build ───────────────────────────────────────────────────────── -FROM node:20-alpine AS builder - -RUN apk add --no-cache git git-lfs openssh-client ca-certificates -RUN git lfs install --system - -WORKDIR /app - -# Récupérer node_modules depuis le stage deps -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -# Build Next.js en mode production -ENV NEXT_TELEMETRY_DISABLED=1 -ENV NODE_ENV=production - -RUN npm run build - -# ─── Stage 3 : Production ──────────────────────────────────────────────────── -FROM node:20-alpine AS runner - -LABEL maintainer="Asset Bridge 3D" -LABEL description="Interface d'upload 3D sécurisée avec Git LFS" - -# Outils runtime : git, git-lfs, openssh (pour le git push au runtime) -RUN apk add --no-cache \ - git \ - git-lfs \ - openssh-client \ - ca-certificates \ - tini - -# Initialiser Git LFS au niveau système -RUN git lfs install --system - -WORKDIR /app - -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 -ENV PORT=3000 -ENV HOSTNAME=0.0.0.0 - -# Créer un utilisateur non-root pour la sécurité -# Note : on garde root pour les opérations git/SSH en contexte Coolify -# Si vous souhaitez un user non-root, adaptez les permissions SSH en conséquence - -# Copier les artefacts de build -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static -COPY --from=builder /app/public ./public -COPY --from=builder /app/.gitattributes ./.gitattributes - -# S'assurer que le dossier models existe (sera écrasé par le volume Coolify) -RUN mkdir -p ./public/models - -# Copier le script d'entrée -COPY docker-entrypoint.sh /docker-entrypoint.sh -RUN chmod +x /docker-entrypoint.sh - -EXPOSE 3000 - -# Utiliser tini comme PID 1 pour une gestion correcte des signaux -ENTRYPOINT ["/sbin/tini", "--", "/docker-entrypoint.sh"] -CMD ["node", "server.js"] diff --git a/README.md b/README.md index 0a6ecd2..ec72f68 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,14 @@ # upload-GLTF -A secure upload interface for 3D assets with automatic deployment to GitHub via Git LFS. Designers can drag-and-drop models and textures, which are automatically versioned and pushed to a GitHub repository for easy collaboration and asset management. Built with Next.js, Tailwind CSS, and React. +A secure web interface for uploading 3D assets (GLTF/GLB + textures) and automatically pushing them to a GitHub repository via the GitHub API. Built for La Fabrik Durable. -## Description +## Stack -This is a Next.js web application that allows designers and developers to upload 3D models and textures via a modern drag-and-drop interface. Uploaded files are automatically committed and pushed to a GitHub repository using Git LFS for efficient version control of large binary files. - -## Features - -- **Drag-and-drop upload** for 3D models (.glb, .gltf, .fbx) and textures (.png, .jpg, .jpeg, .webp, .ktx2) -- **2 GB max** file size per upload -- **Automatic Git deployment** — files are committed and pushed to GitHub via Git LFS -- **Secret key authentication** for secure uploads -- **Modern UI** built with Tailwind CSS and React - -## Prerequisites - -- Node.js 18+ -- npm or yarn +- **Next.js 16** (App Router) + React 19 + TypeScript +- **Three.js** (@react-three/fiber + @react-three/drei) for 3D preview +- **Tailwind CSS** for styling +- **Octokit** for pushing via the GitHub API +- **Vercel** for hosting ## Installation @@ -29,42 +20,23 @@ npm install ## Configuration -### Environment Variables - -Create a `.env.local` file in the root directory: +Copy `.env.example` to `.env.local` and fill in the values: ```env UPLOAD_SECRET_KEY=your-secret-key-here +GITHUB_TOKEN=ghp_your-github-personal-access-token GIT_BRANCH=main -GIT_REPO_URL=git@github.com:username/repo.git +GIT_REPO_URL=https://github.com/your-org/your-repo.git ``` | Variable | Description | Required | |----------|-------------|----------| -| `UPLOAD_SECRET_KEY` | Secret key for authentication | Yes | -| `GIT_BRANCH` | Git branch to push to (default: main) | No | -| `GIT_REPO_URL` | Git repository URL for SSH push | No | +| `UPLOAD_SECRET_KEY` | Secret key for upload authentication | Yes | +| `GITHUB_TOKEN` | GitHub Personal Access Token (scope `repo`) | Yes | +| `GIT_BRANCH` | Target branch (default: main) | No | +| `GIT_REPO_URL` | Target GitHub repository URL | Yes | -### Git LFS Setup - -Install Git LFS and track the asset file types by creating a `.gitattributes` file: - -```bash -git lfs install -``` - -Create `.gitattributes`: - -``` -*.glb filter=lfs diff=lfs merge=lfs -text -*.gltf filter=lfs diff=lfs merge=lfs -text -*.fbx filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text -*.jpg filter=lfs diff=lfs merge=lfs -text -*.jpeg filter=lfs diff=lfs merge=lfs -text -*.webp filter=lfs diff=lfs merge=lfs -text -*.ktx2 filter=lfs diff=lfs merge=lfs -text -``` +> To create a token: GitHub > Settings > Developer settings > Personal access tokens > Generate new token (classic) with the `repo` scope. ## Usage @@ -76,77 +48,41 @@ npm run dev Access the app at `http://localhost:3000` -### Production +### Production (Vercel) -```bash -npm run build -npm start -``` +Deploy to Vercel and configure environment variables in the dashboard. -### Lint +## How it works -```bash -npm run lint -``` - -## Docker Deployment - -### Docker Compose - -Create `docker-compose.yml`: - -```yaml -services: - app: - build: . - ports: - - "3000:3000" - environment: - - UPLOAD_SECRET_KEY=your-secret-key - - GIT_BRANCH=main - - GIT_REPO_URL=git@github.com:username/repo.git - volumes: - - ./public:/app/public - - ~/.ssh:/root/.ssh -``` - -Build and run: - -```bash -docker-compose up --build -``` - -### Coolify - -The Dockerfile is optimized for Coolify deployment. Configure environment variables through the Coolify interface. +1. The user enters their access key +2. They select a folder containing: + - `model.glb` or `model.gltf` (required) + - Textures: `roughness`, `normal`, `metalness`, `color`, `displace` (`.png/.jpg/.webp`, optional) +3. The model is displayed in a 3D preview +4. On clicking "Upload & Push to GitHub", each file is sent to the `/api/upload` endpoint +5. The API validates the file and pushes it directly to the GitHub repo via the API (no git CLI needed) ## Project Structure ``` -. -├── app/ -│ ├── api/upload/route.ts # Upload API + git push -│ ├── globals.css -│ ├── layout.tsx -│ └── page.tsx -├── components/ -│ └── UploadZone.tsx # Upload component -├── public/ -│ ├── models/ # Uploaded 3D models -│ └── textures/ # Uploaded textures -├── Dockerfile -├── package.json -├── tailwind.config.ts -└── tsconfig.json +app/ +├── api/upload/route.ts # API: validation + GitHub push via Octokit +├── globals.css # Tailwind + Google Fonts +├── layout.tsx # Root layout +└── page.tsx # Home page +components/ +├── UploadZone.tsx # UI: key input, folder picker, validation, upload +├── ModelViewer.tsx # Lazy wrapper for the 3D viewer +└── SceneViewer.tsx # Three.js Canvas ``` -## Supported File Formats +## Supported Formats | Type | Extensions | |------|------------| -| 3D Models | .glb, .gltf, .fbx | -| Textures | .png, .jpg, .jpeg, .webp, .ktx2 | +| 3D Models | `.glb`, `.gltf` | +| Textures | `.png`, `.jpg`, `.jpeg`, `.webp` | ## License -MIT \ No newline at end of file +MIT diff --git a/app/api/upload/route.ts b/app/api/upload/route.ts index beb18ec..2fb1211 100644 --- a/app/api/upload/route.ts +++ b/app/api/upload/route.ts @@ -1,7 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' -import { writeFileSync, mkdirSync, existsSync, rmSync } from 'fs' -import { join, extname, basename } from 'path' -import { execSync } from 'child_process' +import { Octokit } from '@octokit/rest' +import { extname, basename } from 'path' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' @@ -10,8 +9,6 @@ const MODEL_EXTENSIONS = new Set(['.glb', '.gltf']) const TEXTURE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp']) const ALL_ALLOWED_EXTENSIONS = new Set([...MODEL_EXTENSIONS, ...TEXTURE_EXTENSIONS]) -const ASSETS_DIR = join(process.cwd(), 'public', 'assets') - function sanitizeFilename(name: string): string { return basename(name) .replace(/[^a-zA-Z0-9._-]/g, '_') @@ -19,7 +16,28 @@ function sanitizeFilename(name: string): string { .toLowerCase() } -async function parseUpload(req: NextRequest): Promise<{ filename: string; savedPath: string; relPath: string; folderName: string }> { +function getOctokit(): Octokit { + const token = process.env.GITHUB_TOKEN + if (!token) throw new Error('GITHUB_TOKEN non configuré') + return new Octokit({ auth: token }) +} + +function parseRepoUrl(): { owner: string; repo: string } { + const url = process.env.GIT_REPO_URL + if (!url) throw new Error('GIT_REPO_URL non configuré') + + // Support formats: https://github.com/owner/repo.git, owner/repo, git@github.com:owner/repo.git + const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.]+)/) + const sshMatch = url.match(/github\.com:([^/]+)\/([^/.]+)/) + const shortMatch = url.match(/^([^/]+)\/([^/]+)$/) + + const match = httpsMatch || sshMatch || shortMatch + if (!match) throw new Error(`Format GIT_REPO_URL invalide: "${url}"`) + + return { owner: match[1], repo: match[2] } +} + +async function parseUpload(req: NextRequest) { const formData = await req.formData() const file = formData.get('file') as File | null const folderName = (formData.get('folderName') as string | null)?.trim() || 'assets' @@ -38,8 +56,7 @@ async function parseUpload(req: NextRequest): Promise<{ filename: string; savedP } const safeFolderName = sanitizeFilename(folderName).replace(/[^a-zA-Z0-9-_]/g, '-') - const destDir = join(ASSETS_DIR, safeFolderName) - + let filename: string if (fileType === 'texture' && textureName) { filename = sanitizeFilename(textureName) @@ -47,82 +64,79 @@ async function parseUpload(req: NextRequest): Promise<{ filename: string; savedP filename = originalSafe } - mkdirSync(destDir, { recursive: true }) - - const savedPath = join(destDir, filename) const buffer = Buffer.from(await file.arrayBuffer()) - writeFileSync(savedPath, buffer) + const content = buffer.toString('base64') + const path = `public/assets/${safeFolderName}/${filename}` - const relPath = join('public', 'assets', safeFolderName, filename) - - return { filename, savedPath, relPath, folderName: safeFolderName } + return { filename, content, path, folderName: safeFolderName } } -function runGitPush(folderName: string): { output: string } { +async function pushToGitHub( + filePath: string, + content: string, + folderName: string +): Promise<{ commitUrl: string }> { + const octokit = getOctokit() + const { owner, repo } = parseRepoUrl() const branch = process.env.GIT_BRANCH ?? 'main' - const repoUrl = process.env.GIT_REPO_URL - const execOpts = { - cwd: process.cwd(), - encoding: 'utf-8' as const, - timeout: 120_000, - env: { - ...process.env, - GIT_SSH_COMMAND: 'ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/root/.ssh/known_hosts', - GIT_AUTHOR_NAME: 'Asset Bridge 3D', - GIT_AUTHOR_EMAIL: 'deploy@assetbridge.local', - GIT_COMMITTER_NAME: 'Asset Bridge 3D', - GIT_COMMITTER_EMAIL: 'deploy@assetbridge.local', - }, - } + // 1. Get the current commit SHA on the branch + const { data: ref } = await octokit.git.getRef({ + owner, + repo, + ref: `heads/${branch}`, + }) + const latestCommitSha = ref.object.sha - try { - execSync('git rev-parse --git-dir', { cwd: process.cwd(), stdio: 'ignore' }) - } catch { - execSync('git init && git lfs install', { ...execOpts, stdio: 'pipe' }) - if (repoUrl) { - execSync(`git remote add origin "${repoUrl}"`, { ...execOpts, stdio: 'pipe' }) - } - try { - execSync(`git fetch origin ${branch}`, { ...execOpts, stdio: 'pipe', timeout: 30_000 }) - execSync(`git checkout -b ${branch} --track origin/${branch}`, { ...execOpts, stdio: 'pipe' }) - } catch { - execSync(`git checkout -b ${branch}`, { ...execOpts, stdio: 'pipe' }) - } - } + // 2. Get the tree of the latest commit + const { data: commit } = await octokit.git.getCommit({ + owner, + repo, + commit_sha: latestCommitSha, + }) - if (repoUrl) { - execSync(`git remote set-url origin "${repoUrl}"`, { ...execOpts, stdio: 'pipe' }) - } + // 3. Create a blob for the file + const { data: blob } = await octokit.git.createBlob({ + owner, + repo, + content, + encoding: 'base64', + }) + // 4. Create a new tree with the file added + const { data: newTree } = await octokit.git.createTree({ + owner, + repo, + base_tree: commit.tree.sha, + tree: [ + { + path: filePath, + mode: '100644', + type: 'blob', + sha: blob.sha, + }, + ], + }) + + // 5. Create a new commit const timestamp = new Date().toISOString() - let output = '' + const { data: newCommit } = await octokit.git.createCommit({ + owner, + repo, + message: `Design Update: ${folderName} [${timestamp}]`, + tree: newTree.sha, + parents: [latestCommitSha], + }) - output += execSync(`git fetch origin ${branch}`, execOpts) - output += execSync(`git reset --hard origin/${branch}`, execOpts) + // 6. Update the branch reference + await octokit.git.updateRef({ + owner, + repo, + ref: `heads/${branch}`, + sha: newCommit.sha, + }) - const folderPath = join('public', 'assets', folderName) - if (existsSync(folderPath)) { - output += execSync(`git add "${folderPath}"`, execOpts) - } - - try { - output += execSync(`git commit -m "Design Update: ${folderName} [${timestamp}]"`, execOpts) - } catch (err) { - const msg = [ - err instanceof Error ? err.message : '', - (err as NodeJS.ErrnoException & { stdout?: string })?.stdout ?? '', - (err as NodeJS.ErrnoException & { stderr?: string })?.stderr ?? '', - ].join(' ') - if (!msg.includes('nothing to commit') && !msg.includes('nothing added to commit')) { - throw err - } - output += '[rien à committer]\n' - } - - output += execSync(`git push origin ${branch}`, execOpts) - - return { output } + return { commitUrl: newCommit.html_url } } export async function POST(req: NextRequest) { @@ -131,46 +145,44 @@ export async function POST(req: NextRequest) { if (!expectedSecret) { return NextResponse.json( - { success: false, error: 'Configuration serveur incomplète' }, + { success: false, error: 'Configuration serveur incomplète (UPLOAD_SECRET_KEY manquant)' }, { status: 500 } ) } if (!secret || secret !== expectedSecret) { return NextResponse.json( - { success: false, error: 'Clé d\'authentification invalide' }, + { success: false, error: "Clé d'authentification invalide" }, { status: 401 } ) } let filename: string - let savedPath: string - let relPath: string + let content: string + let path: string let folderName: string try { - ;({ filename, savedPath, relPath, folderName } = await parseUpload(req)) + ;({ filename, content, path, folderName } = await parseUpload(req)) } catch (err) { const message = err instanceof Error ? err.message : 'Erreur inconnue' return NextResponse.json({ success: false, error: message }, { status: 400 }) } try { - const { output } = runGitPush(folderName) + const { commitUrl } = await pushToGitHub(path, content, folderName) return NextResponse.json({ success: true, filename, - message: `"${filename}" ajouté au dossier "${folderName}" et poussé sur Git.`, - gitOutput: output, + message: `"${filename}" ajouté au dossier "${folderName}" et poussé sur GitHub.`, + commitUrl, }) } catch (err) { - const message = err instanceof Error ? err.message : 'Erreur git inconnue' - const stderr = (err as NodeJS.ErrnoException & { stderr?: string })?.stderr ?? '' - + const message = err instanceof Error ? err.message : 'Erreur GitHub inconnue' return NextResponse.json( - { success: false, error: `Fichier sauvegardé mais git push a échoué: ${message}`, gitError: stderr }, + { success: false, error: `Push GitHub échoué: ${message}` }, { status: 500 } ) } -} \ No newline at end of file +} diff --git a/components/UploadZone.tsx b/components/UploadZone.tsx index 22cab82..9ddf41d 100644 --- a/components/UploadZone.tsx +++ b/components/UploadZone.tsx @@ -437,7 +437,7 @@ export default function UploadZone() { hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-white/50 border border-white/20" style={{ color: '#000000' }} > - Upload & Push to Git + Upload & Push to GitHub )} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100644 index 75b213c..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh -# ============================================================================= -# Asset Bridge 3D — Entrypoint Docker -# Configure Git, SSH et LFS au démarrage du container -# ============================================================================= -set -e - -echo "[entrypoint] Démarrage d'Asset Bridge 3D..." - -# ─── 1. Configurer l'identité Git ──────────────────────────────────────────── -git config --global user.email "${GIT_USER_EMAIL:-deploy@asset-bridge.local}" -git config --global user.name "${GIT_USER_NAME:-Asset Bridge Bot}" -git config --global init.defaultBranch "${GIT_BRANCH:-main}" - -# Autoriser le dossier /app comme safe.directory (requis depuis Git 2.35.2) -git config --global --add safe.directory /app - -echo "[entrypoint] Identité Git configurée." - -# ─── 2. Injecter la clé SSH privée ─────────────────────────────────────────── -if [ -n "$SSH_PRIVATE_KEY" ]; then - mkdir -p /root/.ssh - chmod 700 /root/.ssh - - # Écrire la clé — Coolify préserve les vrais sauts de ligne en multiline - # On strip les \r éventuels (Windows CRLF) pour éviter "error in libcrypto" - printf '%s\n' "$SSH_PRIVATE_KEY" | tr -d '\r' > /root/.ssh/id_rsa - chmod 600 /root/.ssh/id_rsa - - # Ajouter github.com aux known_hosts pour éviter l'invite interactive - ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> /root/.ssh/known_hosts 2>/dev/null - chmod 644 /root/.ssh/known_hosts - - echo "[entrypoint] Clé SSH configurée et github.com ajouté aux known_hosts." -else - echo "[entrypoint] AVERTISSEMENT : SSH_PRIVATE_KEY non définie — git push SSH ne fonctionnera pas." -fi - -# ─── 3. Initialiser Git LFS dans le dossier de travail ─────────────────────── -cd /app - -git lfs install --local 2>/dev/null || true - -# ─── 4. Initialiser le repo Git si nécessaire ──────────────────────────────── -# Le volume persistant Coolify monte /app/public/models -# Si le repo n'est pas initialisé, on le fait ici - -if [ ! -d ".git" ]; then - echo "[entrypoint] Initialisation du dépôt Git local..." - - git init - git lfs install - - # Copier .gitattributes si pas déjà présent - if [ -f ".gitattributes" ]; then - echo "[entrypoint] .gitattributes détecté." - fi - - if [ -n "$GIT_REPO_URL" ]; then - git remote add origin "$GIT_REPO_URL" - echo "[entrypoint] Remote origin configuré : $GIT_REPO_URL" - - BRANCH="${GIT_BRANCH:-main}" - - # Tenter de récupérer l'historique distant - if git fetch origin "$BRANCH" 2>/dev/null; then - git checkout -b "$BRANCH" --track "origin/$BRANCH" 2>/dev/null || \ - git checkout "$BRANCH" 2>/dev/null || true - echo "[entrypoint] Branch '$BRANCH' récupérée depuis origin." - else - git checkout -b "$BRANCH" 2>/dev/null || true - echo "[entrypoint] Branch locale '$BRANCH' créée." - fi - else - echo "[entrypoint] AVERTISSEMENT : GIT_REPO_URL non définie — les pushes Git échoueront." - git checkout -b "${GIT_BRANCH:-main}" 2>/dev/null || true - fi - - echo "[entrypoint] Dépôt Git initialisé." -else - echo "[entrypoint] Dépôt Git existant détecté." - - # Vérifier que le remote origin est configuré - if [ -n "$GIT_REPO_URL" ]; then - if ! git remote get-url origin >/dev/null 2>&1; then - git remote add origin "$GIT_REPO_URL" - echo "[entrypoint] Remote origin ajouté : $GIT_REPO_URL" - fi - fi -fi - -echo "[entrypoint] Configuration terminée. Lancement de l'application..." - -# ─── 5. Lancer la commande principale ──────────────────────────────────────── -exec "$@" diff --git a/next.config.ts b/next.config.ts index 5dcce0e..e4f5738 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,16 +1,5 @@ import type { NextConfig } from 'next' -const nextConfig: NextConfig = { - // Output standalone pour Docker (inclut uniquement les fichiers nécessaires) - output: 'standalone', - - // Augmenter la taille max des réponses pour les gros fichiers - // Note : la limite d'upload est gérée par le proxy Nginx (client_max_body_size 500M dans Coolify) - experimental: { - serverActions: { - bodySizeLimit: '2gb', - }, - }, -} +const nextConfig: NextConfig = {} export default nextConfig diff --git a/package-lock.json b/package-lock.json index 4ab4949..024bdce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,30 @@ { - "name": "asset-bridge-3d", - "version": "0.1.0", + "name": "upload-gltf", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "asset-bridge-3d", - "version": "0.1.0", + "name": "upload-gltf", + "version": "0.0.2", "dependencies": { - "@react-three/drei": "~10.7.0", - "@react-three/fiber": "~9.5.0", - "next": "~16.2.1", - "react": "~19.0.0", - "react-dom": "~19.0.0", - "react-dropzone": "~14.2.3", - "three": "~0.183.0", - "zustand": "~5.0.3" + "@octokit/rest": "^22.0.1", + "@react-three/drei": "^10.7.0", + "@react-three/fiber": "^9.5.0", + "next": "^16.2.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "three": "^0.183.0" }, "devDependencies": { - "@types/busboy": "~1.5.4", - "@types/node": "~22.13.0", - "@types/react": "~19.0.0", - "@types/react-dom": "~19.0.0", - "@types/three": "~0.183.0", - "autoprefixer": "~10.4.20", - "postcss": "~8.5.1", - "tailwindcss": "~3.4.17", - "typescript": "~5.7.3" + "@types/node": "^22.13.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/three": "^0.183.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.3" } }, "node_modules/@alloc/quick-lru": { @@ -822,6 +820,161 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz", + "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, "node_modules/@react-three/drei": { "version": "10.7.7", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", @@ -925,16 +1078,6 @@ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "license": "MIT" }, - "node_modules/@types/busboy": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", - "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", @@ -1064,15 +1207,6 @@ "dev": true, "license": "MIT" }, - "node_modules/attr-accept": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", - "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -1142,6 +1276,12 @@ "node": ">=6.0.0" } }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -1439,6 +1579,22 @@ "node": ">=6" } }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1485,18 +1641,6 @@ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "license": "MIT" }, - "node_modules/file-selector": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", - "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1709,10 +1853,10 @@ "jiti": "bin/jiti.js" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", "license": "MIT" }, "node_modules/lie": { @@ -1744,18 +1888,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/maath": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", @@ -1937,6 +2069,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2186,17 +2319,6 @@ "lie": "^3.0.2" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2245,29 +2367,6 @@ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, - "node_modules/react-dropzone": { - "version": "14.2.10", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.10.tgz", - "integrity": "sha512-Y98LOCYxGO2jOFWREeKJlL7gbrHcOlTBp+9DCM1dh9XQ8+P/8ThhZT7kFb05C+bPcTXq/rixpU+5+LzwYrFLUw==", - "license": "MIT", - "dependencies": { - "attr-accept": "^2.2.2", - "file-selector": "^0.6.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "react": ">= 16.8 || 18.0.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/react-use-measure": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", @@ -2820,6 +2919,12 @@ "dev": true, "license": "MIT" }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/package.json b/package.json index 8b8de36..83dcdf7 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,15 @@ "lint": "next lint" }, "dependencies": { + "@octokit/rest": "^22.0.1", + "@react-three/drei": "^10.7.0", + "@react-three/fiber": "^9.5.0", "next": "^16.2.1", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-dropzone": "^14.2.3", - "three": "^0.183.0", - "@react-three/fiber": "^9.5.0", - "@react-three/drei": "^10.7.0", - "zustand": "^5.0.3" + "three": "^0.183.0" }, "devDependencies": { - "@types/busboy": "^1.5.4", "@types/node": "^22.13.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0",