update: remove docker

This commit is contained in:
Tom Boullay
2026-04-14 10:54:43 +02:00
parent 58826ac98a
commit 0a3d159bad
9 changed files with 352 additions and 492 deletions
+4
View File
@@ -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
-89
View File
@@ -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"]
+37 -101
View File
@@ -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,76 +48,40 @@ 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
+95 -83
View File
@@ -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,7 +56,6 @@ 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) {
@@ -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,45 +145,43 @@ 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 }
)
}
+1 -1
View File
@@ -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
</button>
)}
-95
View File
@@ -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 "$@"
+1 -12
View File
@@ -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
+207 -102
View File
@@ -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",
+4 -6
View File
@@ -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",