update: remove docker
This commit is contained in:
@@ -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
@@ -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"]
|
|
||||||
@@ -1,23 +1,14 @@
|
|||||||
# upload-GLTF
|
# 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.
|
- **Next.js 16** (App Router) + React 19 + TypeScript
|
||||||
|
- **Three.js** (@react-three/fiber + @react-three/drei) for 3D preview
|
||||||
## Features
|
- **Tailwind CSS** for styling
|
||||||
|
- **Octokit** for pushing via the GitHub API
|
||||||
- **Drag-and-drop upload** for 3D models (.glb, .gltf, .fbx) and textures (.png, .jpg, .jpeg, .webp, .ktx2)
|
- **Vercel** for hosting
|
||||||
- **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
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -29,42 +20,23 @@ npm install
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Environment Variables
|
Copy `.env.example` to `.env.local` and fill in the values:
|
||||||
|
|
||||||
Create a `.env.local` file in the root directory:
|
|
||||||
|
|
||||||
```env
|
```env
|
||||||
UPLOAD_SECRET_KEY=your-secret-key-here
|
UPLOAD_SECRET_KEY=your-secret-key-here
|
||||||
|
GITHUB_TOKEN=ghp_your-github-personal-access-token
|
||||||
GIT_BRANCH=main
|
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 |
|
| Variable | Description | Required |
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `UPLOAD_SECRET_KEY` | Secret key for authentication | Yes |
|
| `UPLOAD_SECRET_KEY` | Secret key for upload authentication | Yes |
|
||||||
| `GIT_BRANCH` | Git branch to push to (default: main) | No |
|
| `GITHUB_TOKEN` | GitHub Personal Access Token (scope `repo`) | Yes |
|
||||||
| `GIT_REPO_URL` | Git repository URL for SSH push | No |
|
| `GIT_BRANCH` | Target branch (default: main) | No |
|
||||||
|
| `GIT_REPO_URL` | Target GitHub repository URL | Yes |
|
||||||
|
|
||||||
### Git LFS Setup
|
> To create a token: GitHub > Settings > Developer settings > Personal access tokens > Generate new token (classic) with the `repo` scope.
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -76,76 +48,40 @@ npm run dev
|
|||||||
|
|
||||||
Access the app at `http://localhost:3000`
|
Access the app at `http://localhost:3000`
|
||||||
|
|
||||||
### Production
|
### Production (Vercel)
|
||||||
|
|
||||||
```bash
|
Deploy to Vercel and configure environment variables in the dashboard.
|
||||||
npm run build
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lint
|
## How it works
|
||||||
|
|
||||||
```bash
|
1. The user enters their access key
|
||||||
npm run lint
|
2. They select a folder containing:
|
||||||
```
|
- `model.glb` or `model.gltf` (required)
|
||||||
|
- Textures: `roughness`, `normal`, `metalness`, `color`, `displace` (`.png/.jpg/.webp`, optional)
|
||||||
## Docker Deployment
|
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
|
||||||
### Docker Compose
|
5. The API validates the file and pushes it directly to the GitHub repo via the API (no git CLI needed)
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
app/
|
||||||
├── app/
|
├── api/upload/route.ts # API: validation + GitHub push via Octokit
|
||||||
│ ├── api/upload/route.ts # Upload API + git push
|
├── globals.css # Tailwind + Google Fonts
|
||||||
│ ├── globals.css
|
├── layout.tsx # Root layout
|
||||||
│ ├── layout.tsx
|
└── page.tsx # Home page
|
||||||
│ └── page.tsx
|
components/
|
||||||
├── components/
|
├── UploadZone.tsx # UI: key input, folder picker, validation, upload
|
||||||
│ └── UploadZone.tsx # Upload component
|
├── ModelViewer.tsx # Lazy wrapper for the 3D viewer
|
||||||
├── public/
|
└── SceneViewer.tsx # Three.js Canvas
|
||||||
│ ├── models/ # Uploaded 3D models
|
|
||||||
│ └── textures/ # Uploaded textures
|
|
||||||
├── Dockerfile
|
|
||||||
├── package.json
|
|
||||||
├── tailwind.config.ts
|
|
||||||
└── tsconfig.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supported File Formats
|
## Supported Formats
|
||||||
|
|
||||||
| Type | Extensions |
|
| Type | Extensions |
|
||||||
|------|------------|
|
|------|------------|
|
||||||
| 3D Models | .glb, .gltf, .fbx |
|
| 3D Models | `.glb`, `.gltf` |
|
||||||
| Textures | .png, .jpg, .jpeg, .webp, .ktx2 |
|
| Textures | `.png`, `.jpg`, `.jpeg`, `.webp` |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+95
-83
@@ -1,7 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { writeFileSync, mkdirSync, existsSync, rmSync } from 'fs'
|
import { Octokit } from '@octokit/rest'
|
||||||
import { join, extname, basename } from 'path'
|
import { extname, basename } from 'path'
|
||||||
import { execSync } from 'child_process'
|
|
||||||
|
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
export const dynamic = 'force-dynamic'
|
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 TEXTURE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp'])
|
||||||
const ALL_ALLOWED_EXTENSIONS = new Set([...MODEL_EXTENSIONS, ...TEXTURE_EXTENSIONS])
|
const ALL_ALLOWED_EXTENSIONS = new Set([...MODEL_EXTENSIONS, ...TEXTURE_EXTENSIONS])
|
||||||
|
|
||||||
const ASSETS_DIR = join(process.cwd(), 'public', 'assets')
|
|
||||||
|
|
||||||
function sanitizeFilename(name: string): string {
|
function sanitizeFilename(name: string): string {
|
||||||
return basename(name)
|
return basename(name)
|
||||||
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
||||||
@@ -19,7 +16,28 @@ function sanitizeFilename(name: string): string {
|
|||||||
.toLowerCase()
|
.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 formData = await req.formData()
|
||||||
const file = formData.get('file') as File | null
|
const file = formData.get('file') as File | null
|
||||||
const folderName = (formData.get('folderName') as string | null)?.trim() || 'assets'
|
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 safeFolderName = sanitizeFilename(folderName).replace(/[^a-zA-Z0-9-_]/g, '-')
|
||||||
const destDir = join(ASSETS_DIR, safeFolderName)
|
|
||||||
|
|
||||||
let filename: string
|
let filename: string
|
||||||
if (fileType === 'texture' && textureName) {
|
if (fileType === 'texture' && textureName) {
|
||||||
@@ -47,82 +64,79 @@ async function parseUpload(req: NextRequest): Promise<{ filename: string; savedP
|
|||||||
filename = originalSafe
|
filename = originalSafe
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdirSync(destDir, { recursive: true })
|
|
||||||
|
|
||||||
const savedPath = join(destDir, filename)
|
|
||||||
const buffer = Buffer.from(await file.arrayBuffer())
|
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, content, path, folderName: safeFolderName }
|
||||||
|
|
||||||
return { filename, savedPath, relPath, 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 branch = process.env.GIT_BRANCH ?? 'main'
|
||||||
const repoUrl = process.env.GIT_REPO_URL
|
|
||||||
|
|
||||||
const execOpts = {
|
// 1. Get the current commit SHA on the branch
|
||||||
cwd: process.cwd(),
|
const { data: ref } = await octokit.git.getRef({
|
||||||
encoding: 'utf-8' as const,
|
owner,
|
||||||
timeout: 120_000,
|
repo,
|
||||||
env: {
|
ref: `heads/${branch}`,
|
||||||
...process.env,
|
})
|
||||||
GIT_SSH_COMMAND: 'ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/root/.ssh/known_hosts',
|
const latestCommitSha = ref.object.sha
|
||||||
GIT_AUTHOR_NAME: 'Asset Bridge 3D',
|
|
||||||
GIT_AUTHOR_EMAIL: 'deploy@assetbridge.local',
|
|
||||||
GIT_COMMITTER_NAME: 'Asset Bridge 3D',
|
|
||||||
GIT_COMMITTER_EMAIL: 'deploy@assetbridge.local',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// 2. Get the tree of the latest commit
|
||||||
execSync('git rev-parse --git-dir', { cwd: process.cwd(), stdio: 'ignore' })
|
const { data: commit } = await octokit.git.getCommit({
|
||||||
} catch {
|
owner,
|
||||||
execSync('git init && git lfs install', { ...execOpts, stdio: 'pipe' })
|
repo,
|
||||||
if (repoUrl) {
|
commit_sha: latestCommitSha,
|
||||||
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' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repoUrl) {
|
// 3. Create a blob for the file
|
||||||
execSync(`git remote set-url origin "${repoUrl}"`, { ...execOpts, stdio: 'pipe' })
|
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()
|
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)
|
// 6. Update the branch reference
|
||||||
output += execSync(`git reset --hard origin/${branch}`, execOpts)
|
await octokit.git.updateRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: `heads/${branch}`,
|
||||||
|
sha: newCommit.sha,
|
||||||
|
})
|
||||||
|
|
||||||
const folderPath = join('public', 'assets', folderName)
|
return { commitUrl: newCommit.html_url }
|
||||||
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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
@@ -131,45 +145,43 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
if (!expectedSecret) {
|
if (!expectedSecret) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'Configuration serveur incomplète' },
|
{ success: false, error: 'Configuration serveur incomplète (UPLOAD_SECRET_KEY manquant)' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!secret || secret !== expectedSecret) {
|
if (!secret || secret !== expectedSecret) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, error: 'Clé d\'authentification invalide' },
|
{ success: false, error: "Clé d'authentification invalide" },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename: string
|
let filename: string
|
||||||
let savedPath: string
|
let content: string
|
||||||
let relPath: string
|
let path: string
|
||||||
let folderName: string
|
let folderName: string
|
||||||
|
|
||||||
try {
|
try {
|
||||||
;({ filename, savedPath, relPath, folderName } = await parseUpload(req))
|
;({ filename, content, path, folderName } = await parseUpload(req))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : 'Erreur inconnue'
|
const message = err instanceof Error ? err.message : 'Erreur inconnue'
|
||||||
return NextResponse.json({ success: false, error: message }, { status: 400 })
|
return NextResponse.json({ success: false, error: message }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { output } = runGitPush(folderName)
|
const { commitUrl } = await pushToGitHub(path, content, folderName)
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
filename,
|
filename,
|
||||||
message: `"${filename}" ajouté au dossier "${folderName}" et poussé sur Git.`,
|
message: `"${filename}" ajouté au dossier "${folderName}" et poussé sur GitHub.`,
|
||||||
gitOutput: output,
|
commitUrl,
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : 'Erreur git inconnue'
|
const message = err instanceof Error ? err.message : 'Erreur GitHub inconnue'
|
||||||
const stderr = (err as NodeJS.ErrnoException & { stderr?: string })?.stderr ?? ''
|
|
||||||
|
|
||||||
return NextResponse.json(
|
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 }
|
{ status: 500 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-white/50 border border-white/20"
|
||||||
style={{ color: '#000000' }}
|
style={{ color: '#000000' }}
|
||||||
>
|
>
|
||||||
Upload & Push to Git
|
Upload & Push to GitHub
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -1,16 +1,5 @@
|
|||||||
import type { NextConfig } from 'next'
|
import type { NextConfig } from 'next'
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig
|
||||||
|
|||||||
Generated
+207
-102
@@ -1,32 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "asset-bridge-3d",
|
"name": "upload-gltf",
|
||||||
"version": "0.1.0",
|
"version": "0.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "asset-bridge-3d",
|
"name": "upload-gltf",
|
||||||
"version": "0.1.0",
|
"version": "0.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-three/drei": "~10.7.0",
|
"@octokit/rest": "^22.0.1",
|
||||||
"@react-three/fiber": "~9.5.0",
|
"@react-three/drei": "^10.7.0",
|
||||||
"next": "~16.2.1",
|
"@react-three/fiber": "^9.5.0",
|
||||||
"react": "~19.0.0",
|
"next": "^16.2.1",
|
||||||
"react-dom": "~19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dropzone": "~14.2.3",
|
"react-dom": "^19.0.0",
|
||||||
"three": "~0.183.0",
|
"three": "^0.183.0"
|
||||||
"zustand": "~5.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/busboy": "~1.5.4",
|
"@types/node": "^22.13.0",
|
||||||
"@types/node": "~22.13.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react": "~19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@types/react-dom": "~19.0.0",
|
"@types/three": "^0.183.0",
|
||||||
"@types/three": "~0.183.0",
|
"autoprefixer": "^10.4.20",
|
||||||
"autoprefixer": "~10.4.20",
|
"postcss": "^8.5.1",
|
||||||
"postcss": "~8.5.1",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss": "~3.4.17",
|
"typescript": "^5.7.3"
|
||||||
"typescript": "~5.7.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -822,6 +820,161 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/@react-three/drei": {
|
||||||
"version": "10.7.7",
|
"version": "10.7.7",
|
||||||
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
|
||||||
@@ -925,16 +1078,6 @@
|
|||||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/draco3d": {
|
||||||
"version": "1.4.10",
|
"version": "1.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
||||||
@@ -1064,15 +1207,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.27",
|
"version": "10.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
||||||
@@ -1142,6 +1276,12 @@
|
|||||||
"node": ">=6.0.0"
|
"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": {
|
"node_modules/bidi-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||||
@@ -1439,6 +1579,22 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
@@ -1485,18 +1641,6 @@
|
|||||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -1709,10 +1853,10 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/json-with-bigint": {
|
||||||
"version": "4.0.0",
|
"version": "3.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lie": {
|
"node_modules/lie": {
|
||||||
@@ -1744,18 +1888,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/maath": {
|
||||||
"version": "0.10.8",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
||||||
@@ -1937,6 +2069,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -2186,17 +2319,6 @@
|
|||||||
"lie": "^3.0.2"
|
"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": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"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==",
|
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/react-use-measure": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
||||||
@@ -2820,6 +2919,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
|||||||
+4
-6
@@ -9,17 +9,15 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@octokit/rest": "^22.0.1",
|
||||||
|
"@react-three/drei": "^10.7.0",
|
||||||
|
"@react-three/fiber": "^9.5.0",
|
||||||
"next": "^16.2.1",
|
"next": "^16.2.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"three": "^0.183.0"
|
||||||
"three": "^0.183.0",
|
|
||||||
"@react-three/fiber": "^9.5.0",
|
|
||||||
"@react-three/drei": "^10.7.0",
|
|
||||||
"zustand": "^5.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/busboy": "^1.5.4",
|
|
||||||
"@types/node": "^22.13.0",
|
"@types/node": "^22.13.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user