R-Kentaren commited on
Commit
ff86d3d
Β·
verified Β·
1 Parent(s): 1e1a8bc

Upload folder using huggingface_hub

Browse files
code/config/constants.py CHANGED
@@ -109,6 +109,17 @@ For single-file code, use standard markdown fenced blocks:
109
  ```typescript for TypeScript
110
  etc.
111
 
 
 
 
 
 
 
 
 
 
 
 
112
  When generating web apps with HTML/CSS/JS, return a single self-contained HTML document with all CSS and JavaScript inline. Make the page fully responsive: html/body at margin:0 and 100% width/height, use flexbox/grid layouts, and size any canvas to its container.
113
 
114
  When generating Gradio apps, create a complete app.py with:
 
109
  ```typescript for TypeScript
110
  etc.
111
 
112
+ JAVASCRIPT / TYPESCRIPT PROJECTS:
113
+ For React, Next.js, Vue.js, Express, NestJS, or any JS/TS framework:
114
+ - ALWAYS use the @@FILE: multi-file format
115
+ - Include a package.json with name, version, scripts, and dependencies
116
+ - Include all source files (src/App.jsx, src/index.js, etc.)
117
+ - For React+Vite: include vite.config.js and index.html
118
+ - For Next.js: include next.config.js with output: 'standalone'
119
+ - For Express: main entry is index.js with app.listen(7860)
120
+ - Server ports MUST be 7860 and bind to 0.0.0.0
121
+ - Do NOT include node_modules or lock files
122
+
123
  When generating web apps with HTML/CSS/JS, return a single self-contained HTML document with all CSS and JavaScript inline. Make the page fully responsive: html/body at margin:0 and 100% width/height, use flexbox/grid layouts, and size any canvas to its container.
124
 
125
  When generating Gradio apps, create a complete app.py with:
code/huggingface/dockerfile_gen.py ADDED
@@ -0,0 +1,626 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dockerfile and package.json generator for JS/TS frameworks.
2
+
3
+ Auto-generates Dockerfile, package.json, and .dockerignore for
4
+ React, Next.js, Vue.js, Express, NestJS, and plain Node.js projects
5
+ so they can be pushed to HuggingFace Docker Spaces.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Any
12
+
13
+ # ─── Framework Detection ────────────────────────────────────────────────
14
+
15
+ # Keywords in code that identify a framework
16
+ FRAMEWORK_SIGNALS: dict[str, list[str]] = {
17
+ "nextjs": [
18
+ "from 'next", 'from "next',
19
+ "next/link", "next/image", "next/router", "next/head",
20
+ "NextResponse", "NextRequest", "next/navigation",
21
+ "getServerSideProps", "getStaticProps",
22
+ ],
23
+ "react": [
24
+ "from 'react", 'from "react',
25
+ "react-dom", "ReactDOM", "useState", "useEffect",
26
+ "jsx", "tsx", "React.Component", "React.createElement",
27
+ ],
28
+ "vue": [
29
+ "from 'vue", 'from "vue',
30
+ "createApp", "Vue.createApp", "<template>",
31
+ "defineComponent", "ref(", "reactive(",
32
+ ],
33
+ "express": [
34
+ "require('express')", 'require("express")',
35
+ "from 'express", 'from "express',
36
+ "express()", "express.Router",
37
+ ],
38
+ "nestjs": [
39
+ "@Module", "@Controller", "@Get", "@Post", "@Put", "@Delete",
40
+ "from '@nestjs", 'from "@nestjs',
41
+ "NestFactory.create",
42
+ ],
43
+ "nodejs": [
44
+ "require('http')", "http.createServer",
45
+ "const http = require", "import http from",
46
+ ],
47
+ }
48
+
49
+
50
+ def detect_framework(files: dict[str, str]) -> str:
51
+ """Detect the JS/TS framework from project files.
52
+
53
+ Returns one of: 'nextjs', 'react', 'vue', 'express', 'nestjs', 'nodejs', 'static'
54
+ """
55
+ all_code = "\n".join(files.values())
56
+
57
+ # Check file names first (strong signal)
58
+ has_next_config = any(
59
+ f.startswith("next.config") for f in files
60
+ )
61
+ if has_next_config:
62
+ return "nextjs"
63
+
64
+ # Check package.json if present
65
+ for fname, content in files.items():
66
+ if fname == "package.json" or fname.endswith("/package.json"):
67
+ try:
68
+ import json
69
+ pkg = json.loads(content)
70
+ deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
71
+ if "next" in deps:
72
+ return "nextjs"
73
+ if "vue" in deps or "@vue/cli-service" in deps:
74
+ return "vue"
75
+ if "@nestjs/core" in deps:
76
+ return "nestjs"
77
+ if "express" in deps:
78
+ return "express"
79
+ if "react" in deps or "react-dom" in deps:
80
+ return "react"
81
+ except Exception:
82
+ pass
83
+
84
+ # Check code content for framework signals
85
+ for fw, signals in FRAMEWORK_SIGNALS.items():
86
+ for signal in signals:
87
+ if signal in all_code:
88
+ return fw
89
+
90
+ # Check file extensions
91
+ has_jsx_tsx = any(f.endswith((".jsx", ".tsx")) for f in files)
92
+ has_vue = any(f.endswith(".vue") for f in files)
93
+
94
+ if has_vue:
95
+ return "vue"
96
+ if has_jsx_tsx:
97
+ return "react"
98
+
99
+ return "static"
100
+
101
+
102
+ def is_js_project(files: dict[str, str]) -> bool:
103
+ """Check if the project is a JavaScript/TypeScript project."""
104
+ js_extensions = {".js", ".jsx", ".ts", ".tsx", ".vue", ".mjs", ".cjs"}
105
+ has_package_json = any("package.json" in f for f in files)
106
+ has_js_files = any(
107
+ any(f.endswith(ext) for ext in js_extensions)
108
+ for f in files
109
+ )
110
+ return has_package_json or has_js_files
111
+
112
+
113
+ # ─── Dockerfile Templates ───────────────────────────────────────────────
114
+
115
+ def _dockerfile_nextjs() -> str:
116
+ """Dockerfile for Next.js projects."""
117
+ return """FROM node:20-slim AS base
118
+
119
+ # Install dependencies only when needed
120
+ FROM base AS deps
121
+ WORKDIR /app
122
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
123
+ RUN \\
124
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
125
+ elif [ -f package-lock.json ]; then npm ci; \\
126
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
127
+ else npm i; \\
128
+ fi
129
+
130
+ # Rebuild source code only when needed
131
+ FROM base AS builder
132
+ WORKDIR /app
133
+ COPY --from=deps /app/node_modules ./node_modules
134
+ COPY . .
135
+ RUN npm run build
136
+
137
+ # Production image
138
+ FROM base AS runner
139
+ WORKDIR /app
140
+ ENV NODE_ENV=production
141
+ RUN addgroup --system --gid 1001 nodejs
142
+ RUN adduser --system --uid 1001 nextjs
143
+ COPY --from=builder /app/public ./public
144
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
145
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
146
+ USER nextjs
147
+ EXPOSE 3000
148
+ ENV PORT=3000
149
+ ENV HOSTNAME="0.0.0.0"
150
+ CMD ["node", "server.js"]
151
+ """
152
+
153
+
154
+ def _dockerfile_react() -> str:
155
+ """Dockerfile for React (Vite/CRA) projects β€” served with nginx."""
156
+ return """FROM node:20-slim AS build
157
+ WORKDIR /app
158
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
159
+ RUN \\
160
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
161
+ elif [ -f package-lock.json ]; then npm ci; \\
162
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
163
+ else npm i; \\
164
+ fi
165
+ COPY . .
166
+ RUN npm run build
167
+
168
+ FROM nginx:alpine
169
+ COPY --from=build /app/build /usr/share/nginx/html
170
+ COPY --from=build /app/dist /usr/share/nginx/html 2>/dev/null || true
171
+ RUN cat > /etc/nginx/conf.d/default.conf << 'EOF'
172
+ server {
173
+ listen 7860;
174
+ server_name localhost;
175
+ root /usr/share/nginx/html;
176
+ index index.html;
177
+ location / {
178
+ try_files $uri $uri/ /index.html;
179
+ }
180
+ }
181
+ EOF
182
+ EXPOSE 7860
183
+ CMD ["nginx", "-g", "daemon off;"]
184
+ """
185
+
186
+
187
+ def _dockerfile_vue() -> str:
188
+ """Dockerfile for Vue.js projects β€” served with nginx."""
189
+ return """FROM node:20-slim AS build
190
+ WORKDIR /app
191
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
192
+ RUN \\
193
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
194
+ elif [ -f package-lock.json ]; then npm ci; \\
195
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
196
+ else npm i; \\
197
+ fi
198
+ COPY . .
199
+ RUN npm run build
200
+
201
+ FROM nginx:alpine
202
+ COPY --from=build /app/dist /usr/share/nginx/html
203
+ RUN cat > /etc/nginx/conf.d/default.conf << 'EOF'
204
+ server {
205
+ listen 7860;
206
+ server_name localhost;
207
+ root /usr/share/nginx/html;
208
+ index index.html;
209
+ location / {
210
+ try_files $uri $uri/ /index.html;
211
+ }
212
+ }
213
+ EOF
214
+ EXPOSE 7860
215
+ CMD ["nginx", "-g", "daemon off;"]
216
+ """
217
+
218
+
219
+ def _dockerfile_express() -> str:
220
+ """Dockerfile for Express/Node.js server projects."""
221
+ return """FROM node:20-slim
222
+ WORKDIR /app
223
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
224
+ RUN \\
225
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
226
+ elif [ -f package-lock.json ]; then npm ci; \\
227
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
228
+ else npm i; \\
229
+ fi
230
+ COPY . .
231
+ RUN addgroup --system --gid 1001 appuser && adduser --system --uid 1001 appuser
232
+ USER appuser
233
+ EXPOSE 7860
234
+ ENV PORT=7860
235
+ ENV HOST=0.0.0.0
236
+ CMD ["node", "index.js"]
237
+ """
238
+
239
+
240
+ def _dockerfile_nestjs() -> str:
241
+ """Dockerfile for NestJS projects."""
242
+ return """FROM node:20-slim AS build
243
+ WORKDIR /app
244
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
245
+ RUN \\
246
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
247
+ elif [ -f package-lock.json ]; then npm ci; \\
248
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
249
+ else npm i; \\
250
+ fi
251
+ COPY . .
252
+ RUN npm run build
253
+
254
+ FROM node:20-slim
255
+ WORKDIR /app
256
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
257
+ RUN \\
258
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile --production; \\
259
+ elif [ -f package-lock.json ]; then npm ci --only=production; \\
260
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile --prod; \\
261
+ else npm i --only=production; \\
262
+ fi
263
+ COPY --from=build /app/dist ./dist
264
+ RUN addgroup --system --gid 1001 appuser && adduser --system --uid 1001 appuser
265
+ USER appuser
266
+ EXPOSE 7860
267
+ ENV PORT=7860
268
+ CMD ["node", "dist/main.js"]
269
+ """
270
+
271
+
272
+ def generate_dockerfile(framework: str) -> str:
273
+ """Generate a Dockerfile for the given framework.
274
+
275
+ Args:
276
+ framework: One of 'nextjs', 'react', 'vue', 'express', 'nestjs', 'nodejs', 'static'
277
+
278
+ Returns:
279
+ Dockerfile content as string.
280
+ """
281
+ templates = {
282
+ "nextjs": _dockerfile_nextjs,
283
+ "react": _dockerfile_react,
284
+ "vue": _dockerfile_vue,
285
+ "express": _dockerfile_express,
286
+ "nestjs": _dockerfile_nestjs,
287
+ "nodejs": _dockerfile_express, # Same as express for plain Node
288
+ }
289
+ gen = templates.get(framework)
290
+ if gen:
291
+ return gen()
292
+ # Fallback: generic node server
293
+ return _dockerfile_express()
294
+
295
+
296
+ # ─── package.json Generator ─────────────────────────────────────────────
297
+
298
+ def _scan_js_imports(code: str) -> set[str]:
299
+ """Scan JS/TS code for import/require statements and return package names."""
300
+ packages = set()
301
+
302
+ # ESM: import xxx from 'pkg' / import 'pkg'
303
+ for m in re.finditer(r"import\s+.*?\s+from\s+['\"](@?[\w-]+/[\w-]+|[\w-]+)", code):
304
+ packages.add(m.group(1))
305
+
306
+ # ESM: import 'pkg'
307
+ for m in re.finditer(r"import\s+['\"](@?[\w-]+/[\w-]+|[\w-]+)['\"]", code):
308
+ packages.add(m.group(1))
309
+
310
+ # CJS: require('pkg')
311
+ for m in re.finditer(r"require\s*\(\s*['\"](@?[\w-]+/[\w-]+|[\w-]+)['\"]\s*\)", code):
312
+ packages.add(m.group(1))
313
+
314
+ return packages
315
+
316
+
317
+ # Known packages that should NOT go in dependencies (built-in or types)
318
+ _SKIP_PACKAGES = {
319
+ "react", "react-dom", "next", "vue", "express",
320
+ "path", "fs", "http", "https", "url", "os", "crypto",
321
+ "stream", "util", "events", "buffer", "child_process",
322
+ "net", "tls", "zlib", "assert", "querystring",
323
+ }
324
+
325
+ # Framework-specific dependency sets
326
+ _FRAMEWORK_DEPS: dict[str, dict[str, str]] = {
327
+ "nextjs": {
328
+ "next": "14.2.0",
329
+ "react": "^18.3.0",
330
+ "react-dom": "^18.3.0",
331
+ },
332
+ "react": {
333
+ "react": "^18.3.0",
334
+ "react-dom": "^18.3.0",
335
+ "react-scripts": "5.0.1",
336
+ },
337
+ "vue": {
338
+ "vue": "^3.4.0",
339
+ },
340
+ "express": {
341
+ "express": "^4.19.0",
342
+ },
343
+ "nestjs": {
344
+ "@nestjs/core": "^10.3.0",
345
+ "@nestjs/common": "^10.3.0",
346
+ "@nestjs/platform-express": "^10.3.0",
347
+ "reflect-metadata": "^0.2.0",
348
+ "rxjs": "^7.8.0",
349
+ },
350
+ "nodejs": {},
351
+ }
352
+
353
+ # Common package version mapping
354
+ _PACKAGE_VERSIONS: dict[str, str] = {
355
+ "axios": "^1.6.0",
356
+ "lodash": "^4.17.21",
357
+ "cors": "^2.8.5",
358
+ "dotenv": "^16.4.0",
359
+ "mongoose": "^8.2.0",
360
+ "prisma": "^5.10.0",
361
+ "@prisma/client": "^5.10.0",
362
+ "zod": "^3.22.0",
363
+ "socket.io": "^4.7.0",
364
+ "multer": "^1.4.4",
365
+ "cookie-parser": "^1.4.6",
366
+ "express-session": "^1.18.0",
367
+ "jsonwebtoken": "^9.0.0",
368
+ "bcrypt": "^5.1.0",
369
+ "uuid": "^9.0.0",
370
+ "dayjs": "^1.11.10",
371
+ "chart.js": "^4.4.0",
372
+ "framer-motion": "^11.0.0",
373
+ "lucide-react": "^0.350.0",
374
+ "tailwindcss": "^3.4.0",
375
+ "postcss": "^8.4.0",
376
+ "autoprefixer": "^10.4.0",
377
+ "@vitejs/plugin-react": "^4.2.0",
378
+ "vite": "^5.1.0",
379
+ "typescript": "^5.3.0",
380
+ "@types/react": "^18.3.0",
381
+ "@types/react-dom": "^18.3.0",
382
+ "@types/node": "^20.11.0",
383
+ "tailwind-merge": "^2.2.0",
384
+ "clsx": "^2.1.0",
385
+ "class-variance-authority": "^0.7.0",
386
+ "@radix-ui/react-slot": "^1.0.2",
387
+ "next-themes": "^0.3.0",
388
+ "recharts": "^2.12.0",
389
+ "react-hook-form": "^7.50.0",
390
+ "@hookform/resolvers": "^3.3.0",
391
+ "zustand": "^4.5.0",
392
+ "jotai": "^2.6.0",
393
+ "tanstack": "^5.24.0",
394
+ "@tanstack/react-query": "^5.24.0",
395
+ "swr": "^2.2.0",
396
+ "nodemon": "^3.1.0",
397
+ "ts-node": "^10.9.0",
398
+ "ts-node-dev": "^2.0.0",
399
+ }
400
+
401
+
402
+ def generate_package_json(
403
+ framework: str,
404
+ project_name: str = "my-app",
405
+ extra_deps: set[str] | None = None,
406
+ existing_content: str | None = None,
407
+ ) -> str:
408
+ """Generate a package.json for the given framework.
409
+
410
+ If existing_content is provided, merges dependencies into it.
411
+ """
412
+ import json
413
+
414
+ # Start with existing or fresh
415
+ if existing_content:
416
+ try:
417
+ pkg = json.loads(existing_content)
418
+ except Exception:
419
+ pkg = {}
420
+ else:
421
+ pkg = {}
422
+
423
+ pkg.setdefault("name", project_name)
424
+ pkg.setdefault("version", "1.0.0")
425
+ pkg.setdefault("private", True)
426
+
427
+ deps = pkg.get("dependencies", {})
428
+ dev_deps = pkg.get("devDependencies", {})
429
+
430
+ # Add framework core deps
431
+ fw_deps = _FRAMEWORK_DEPS.get(framework, {})
432
+ for name, version in fw_deps.items():
433
+ deps[name] = version
434
+
435
+ # Add scanned extra deps
436
+ if extra_deps:
437
+ for dep in extra_deps:
438
+ if dep in _SKIP_PACKAGES:
439
+ continue
440
+ if dep in deps or dep in dev_deps:
441
+ continue
442
+ version = _PACKAGE_VERSIONS.get(dep, "^1.0.0")
443
+ # Dev deps
444
+ if dep.startswith("@types/") or dep in {"typescript", "nodemon", "ts-node", "ts-node-dev"}:
445
+ dev_deps[dep] = version
446
+ else:
447
+ deps[dep] = version
448
+
449
+ pkg["dependencies"] = deps
450
+ if dev_deps:
451
+ pkg["devDependencies"] = dev_deps
452
+
453
+ # Add scripts based on framework
454
+ scripts = pkg.get("scripts", {})
455
+ if framework == "nextjs":
456
+ scripts.setdefault("dev", "next dev")
457
+ scripts.setdefault("build", "next build")
458
+ scripts.setdefault("start", "next start -p 7860")
459
+ elif framework in ("react",):
460
+ scripts.setdefault("dev", "vite")
461
+ scripts.setdefault("build", "vite build")
462
+ scripts.setdefault("start", "vite preview --port 7860 --host 0.0.0.0")
463
+ # Ensure vite is in devDeps
464
+ if "vite" not in dev_deps and "vite" not in deps:
465
+ dev_deps["vite"] = "^5.1.0"
466
+ if "@vitejs/plugin-react" not in dev_deps:
467
+ dev_deps["@vitejs/plugin-react"] = "^4.2.0"
468
+ elif framework == "vue":
469
+ scripts.setdefault("dev", "vite")
470
+ scripts.setdefault("build", "vite build")
471
+ scripts.setdefault("start", "vite preview --port 7860 --host 0.0.0.0")
472
+ if "vite" not in dev_deps and "vite" not in deps:
473
+ dev_deps["vite"] = "^5.1.0"
474
+ elif framework in ("express", "nodejs"):
475
+ scripts.setdefault("dev", "node index.js")
476
+ scripts.setdefault("start", "node index.js")
477
+ elif framework == "nestjs":
478
+ scripts.setdefault("build", "nest build")
479
+ scripts.setdefault("start", "node dist/main.js")
480
+
481
+ pkg["scripts"] = scripts
482
+ pkg["dependencies"] = deps
483
+ if dev_deps:
484
+ pkg["devDependencies"] = dev_deps
485
+
486
+ return json.dumps(pkg, indent=2) + "\n"
487
+
488
+
489
+ # ─── .dockerignore ──────────────────────────────────────────────────────
490
+
491
+ DOCKERIGNORE = """node_modules
492
+ npm-debug.log*
493
+ yarn-debug.log*
494
+ yarn-error.log*
495
+ .next
496
+ .git
497
+ .gitignore
498
+ README.md
499
+ .env
500
+ .env.local
501
+ .env.production
502
+ .DS_Store
503
+ """
504
+
505
+
506
+ # ─── Vite Config Generators ─────────────────────────────────────────────
507
+
508
+ def generate_vite_config(framework: str) -> str | None:
509
+ """Generate a vite.config.js/ts if needed for React or Vue."""
510
+ if framework == "react":
511
+ return """import { defineConfig } from 'vite'
512
+ import react from '@vitejs/plugin-react'
513
+
514
+ export default defineConfig({
515
+ plugins: [react()],
516
+ server: {
517
+ host: '0.0.0.0',
518
+ port: 7860,
519
+ },
520
+ })
521
+ """
522
+ if framework == "vue":
523
+ return """import { defineConfig } from 'vite'
524
+ import vue from '@vitejs/plugin-vue'
525
+
526
+ export default defineConfig({
527
+ plugins: [vue()],
528
+ server: {
529
+ host: '0.0.0.0',
530
+ port: 7860,
531
+ },
532
+ })
533
+ """
534
+ return None
535
+
536
+
537
+ # ─── Next.js Config ────────────────────────────────────────────────────
538
+
539
+ def generate_next_config() -> str:
540
+ """Generate next.config.js with standalone output for Docker."""
541
+ return """/** @type {import('next').NextConfig} */
542
+ const nextConfig = {
543
+ output: 'standalone',
544
+ }
545
+
546
+ module.exports = nextConfig
547
+ """
548
+
549
+
550
+ # ─── Public index.html for Vite ─────────────────────────────────────────
551
+
552
+ def generate_index_html(title: str = "App") -> str:
553
+ """Generate a minimal index.html for Vite projects."""
554
+ return f"""<!DOCTYPE html>
555
+ <html lang="en">
556
+ <head>
557
+ <meta charset="UTF-8" />
558
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
559
+ <title>{title}</title>
560
+ </head>
561
+ <body>
562
+ <div id="root"></div>
563
+ <script type="module" src="/src/main.jsx"></script>
564
+ </body>
565
+ </html>
566
+ """
567
+
568
+
569
+ # ─── Full Project Scaffold ──────────────────────────────────────────────
570
+
571
+ def scaffold_js_project(
572
+ files: dict[str, str],
573
+ framework: str,
574
+ project_name: str = "my-app",
575
+ ) -> dict[str, str]:
576
+ """Add Dockerfile, package.json, and config files to a JS project.
577
+
578
+ Takes existing generated files and returns an augmented dict with
579
+ Docker support files added.
580
+ """
581
+ augmented = dict(files)
582
+
583
+ # Detect imports from all JS/TS files
584
+ all_js_code = "\n".join(
585
+ content for fname, content in files.items()
586
+ if fname.endswith((".js", ".jsx", ".ts", ".tsx", ".vue", ".mjs"))
587
+ )
588
+ extra_deps = _scan_js_imports(all_js_code)
589
+
590
+ # Add Dockerfile
591
+ if "Dockerfile" not in augmented:
592
+ augmented["Dockerfile"] = generate_dockerfile(framework)
593
+
594
+ # Add .dockerignore
595
+ if ".dockerignore" not in augmented:
596
+ augmented[".dockerignore"] = DOCKERIGNORE
597
+
598
+ # Add or merge package.json
599
+ if "package.json" in augmented:
600
+ augmented["package.json"] = generate_package_json(
601
+ framework=framework,
602
+ project_name=project_name,
603
+ extra_deps=extra_deps,
604
+ existing_content=augmented["package.json"],
605
+ )
606
+ else:
607
+ augmented["package.json"] = generate_package_json(
608
+ framework=framework,
609
+ project_name=project_name,
610
+ extra_deps=extra_deps,
611
+ )
612
+
613
+ # Framework-specific config files
614
+ if framework == "nextjs" and "next.config.js" not in augmented and "next.config.mjs" not in augmented:
615
+ augmented["next.config.js"] = generate_next_config()
616
+
617
+ if framework in ("react", "vue"):
618
+ vite_cfg = generate_vite_config(framework)
619
+ if vite_cfg and "vite.config.js" not in augmented and "vite.config.ts" not in augmented:
620
+ augmented["vite.config.js"] = vite_cfg
621
+
622
+ # Add index.html entry point for Vite if not present
623
+ if "index.html" not in augmented:
624
+ augmented["index.html"] = generate_index_html(project_name)
625
+
626
+ return augmented
code/huggingface/push.py CHANGED
@@ -15,6 +15,11 @@ from pathlib import Path
15
  from typing import Any
16
 
17
  from code.config.constants import MODEL_ID
 
 
 
 
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
@@ -117,12 +122,18 @@ def _find_entry_point(files: dict[str, str]) -> str:
117
 
118
  Looks for app.py, main.py, or any Python file with a launcher pattern.
119
  """
120
- # Priority order for entry points
121
  candidates = ["app.py", "main.py", "index.py", "server.py", "run.py"]
122
  for c in candidates:
123
  if c in files:
124
  return c
125
 
 
 
 
 
 
 
126
  # Look for any .py file with if __name__ == "__main__" or .launch()
127
  for fname, content in files.items():
128
  if fname.endswith(".py"):
@@ -146,6 +157,11 @@ def _detect_sdk(files: dict[str, str], entry: str) -> str:
146
  return "streamlit"
147
  if "import gradio" in all_code or "from gradio" in all_code:
148
  return "gradio"
 
 
 
 
 
149
  if any(f.endswith(".html") for f in files):
150
  return "static"
151
  if entry.endswith(".py"):
@@ -180,7 +196,7 @@ def push_to_huggingface(
180
  """Push generated project to HuggingFace Hub.
181
 
182
  Creates the repo if it doesn't exist, writes all files,
183
- and adds README.md and requirements.txt as needed.
184
  """
185
  try:
186
  from huggingface_hub import HfApi, create_repo
@@ -199,10 +215,24 @@ def push_to_huggingface(
199
  entry_point = _find_entry_point(files)
200
  detected_sdk = _detect_sdk(files, entry_point)
201
 
202
- # Use detected SDK if user left it as "static" but project is Python
203
  if space_sdk == "static" and detected_sdk != "static":
204
  space_sdk = detected_sdk
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  try:
207
  if is_space:
208
  create_repo(
@@ -229,8 +259,8 @@ def push_to_huggingface(
229
  os.makedirs(os.path.dirname(full_path), exist_ok=True)
230
  Path(full_path).write_text(content, encoding="utf-8")
231
 
232
- # Ensure the entry point is named app.py for HF Spaces
233
- if entry_point != "app.py" and entry_point.endswith(".py") and is_space:
234
  src = os.path.join(tmp_dir, entry_point)
235
  dst = os.path.join(tmp_dir, "app.py")
236
  if os.path.exists(src) and not os.path.exists(dst):
@@ -238,13 +268,19 @@ def push_to_huggingface(
238
  shutil.copy2(src, dst)
239
 
240
  # Determine app_file for README
241
- app_file = "app.py" if is_space and space_sdk in ("gradio", "streamlit") else entry_point
242
- if is_space and space_sdk == "static":
243
- # For static spaces, look for index.html
 
 
244
  if "index.html" in files:
245
  app_file = "index.html"
246
  elif any(f.endswith(".html") for f in files):
247
  app_file = next(f for f in files if f.endswith(".html"))
 
 
 
 
248
 
249
  # Add README.md if not present
250
  readme_path = os.path.join(tmp_dir, "README.md")
@@ -281,7 +317,7 @@ Generated by Fullstack Code Builder using {MODEL_ID}.
281
  req_path = os.path.join(tmp_dir, "requirements.txt")
282
  has_python = any(f.endswith(".py") for f in files.keys())
283
 
284
- if has_python:
285
  # Scan all Python code for imports
286
  all_py_code = "\n".join(
287
  content for fname, content in files.items()
@@ -296,9 +332,6 @@ Generated by Fullstack Code Builder using {MODEL_ID}.
296
  for line in (existing_reqs + "\n" + auto_reqs).splitlines():
297
  line = line.strip()
298
  if line and not line.startswith("#"):
299
- # Normalize: take the package name (before >=, ==, etc.)
300
- pkg_name = re.split(r"[><=!~]", line)[0].strip().lower()
301
- # Keep the more specific version spec
302
  merged.add(line)
303
 
304
  Path(req_path).write_text("\n".join(sorted(merged)) + "\n", encoding="utf-8")
 
15
  from typing import Any
16
 
17
  from code.config.constants import MODEL_ID
18
+ from code.huggingface.dockerfile_gen import (
19
+ detect_framework,
20
+ is_js_project,
21
+ scaffold_js_project,
22
+ )
23
 
24
  logger = logging.getLogger(__name__)
25
 
 
122
 
123
  Looks for app.py, main.py, or any Python file with a launcher pattern.
124
  """
125
+ # Priority order for Python entry points
126
  candidates = ["app.py", "main.py", "index.py", "server.py", "run.py"]
127
  for c in candidates:
128
  if c in files:
129
  return c
130
 
131
+ # Priority order for JS entry points
132
+ js_candidates = ["index.js", "server.js", "src/index.js", "src/main.jsx", "src/main.tsx"]
133
+ for c in js_candidates:
134
+ if c in files:
135
+ return c
136
+
137
  # Look for any .py file with if __name__ == "__main__" or .launch()
138
  for fname, content in files.items():
139
  if fname.endswith(".py"):
 
157
  return "streamlit"
158
  if "import gradio" in all_code or "from gradio" in all_code:
159
  return "gradio"
160
+
161
+ # JS/TS projects β†’ Docker
162
+ if is_js_project(files):
163
+ return "docker"
164
+
165
  if any(f.endswith(".html") for f in files):
166
  return "static"
167
  if entry.endswith(".py"):
 
196
  """Push generated project to HuggingFace Hub.
197
 
198
  Creates the repo if it doesn't exist, writes all files,
199
+ and adds README.md, Dockerfile, package.json, and requirements.txt as needed.
200
  """
201
  try:
202
  from huggingface_hub import HfApi, create_repo
 
215
  entry_point = _find_entry_point(files)
216
  detected_sdk = _detect_sdk(files, entry_point)
217
 
218
+ # Use detected SDK if user left it as "static" but project needs something else
219
  if space_sdk == "static" and detected_sdk != "static":
220
  space_sdk = detected_sdk
221
 
222
+ # For JS projects, scaffold Docker support files
223
+ if is_js_project(files) or space_sdk == "docker":
224
+ framework = detect_framework(files)
225
+ if framework == "static":
226
+ # Single HTML file or simple JS β€” keep as static
227
+ if any(f.endswith(".html") for f in files) and not is_js_project(files):
228
+ space_sdk = "static"
229
+ else:
230
+ framework = "nodejs"
231
+ space_sdk = "docker"
232
+
233
+ if space_sdk == "docker":
234
+ files = scaffold_js_project(files, framework, project_name)
235
+
236
  try:
237
  if is_space:
238
  create_repo(
 
259
  os.makedirs(os.path.dirname(full_path), exist_ok=True)
260
  Path(full_path).write_text(content, encoding="utf-8")
261
 
262
+ # Ensure the entry point is named app.py for HF Spaces (Python)
263
+ if entry_point != "app.py" and entry_point.endswith(".py") and is_space and space_sdk in ("gradio", "streamlit"):
264
  src = os.path.join(tmp_dir, entry_point)
265
  dst = os.path.join(tmp_dir, "app.py")
266
  if os.path.exists(src) and not os.path.exists(dst):
 
268
  shutil.copy2(src, dst)
269
 
270
  # Determine app_file for README
271
+ if space_sdk == "docker":
272
+ app_file = "Dockerfile"
273
+ elif space_sdk in ("gradio", "streamlit"):
274
+ app_file = "app.py"
275
+ elif space_sdk == "static":
276
  if "index.html" in files:
277
  app_file = "index.html"
278
  elif any(f.endswith(".html") for f in files):
279
  app_file = next(f for f in files if f.endswith(".html"))
280
+ else:
281
+ app_file = entry_point
282
+ else:
283
+ app_file = entry_point
284
 
285
  # Add README.md if not present
286
  readme_path = os.path.join(tmp_dir, "README.md")
 
317
  req_path = os.path.join(tmp_dir, "requirements.txt")
318
  has_python = any(f.endswith(".py") for f in files.keys())
319
 
320
+ if has_python and space_sdk != "docker":
321
  # Scan all Python code for imports
322
  all_py_code = "\n".join(
323
  content for fname, content in files.items()
 
332
  for line in (existing_reqs + "\n" + auto_reqs).splitlines():
333
  line = line.strip()
334
  if line and not line.startswith("#"):
 
 
 
335
  merged.add(line)
336
 
337
  Path(req_path).write_text("\n".join(sorted(merged)) + "\n", encoding="utf-8")
code/server/routes.py CHANGED
@@ -475,7 +475,7 @@ def handle_push_hf(
475
  exec_context_json: str,
476
  repo_name: str,
477
  hf_token: str,
478
- space_sdk: str = "static",
479
  is_space: str = "true",
480
  ) -> str:
481
  """Push generated project to HuggingFace Hub."""
@@ -488,13 +488,22 @@ def handle_push_hf(
488
  if not project_files and code:
489
  lang = execution_context.get("language", "python")
490
  is_gradio = execution_context.get("is_gradio", False)
491
- ext_map = {
492
- "python": "app.py", "py": "app.py",
493
- "javascript": "index.html", "js": "index.html",
494
- "html": "index.html", "web": "index.html",
495
- "typescript": "index.ts", "ts": "index.ts",
496
- }
497
- filename = ext_map.get(lang, "app.py")
 
 
 
 
 
 
 
 
 
498
  project_files = {filename: code}
499
 
500
  # Auto-detect SDK for Gradio apps
@@ -513,6 +522,10 @@ def handle_push_hf(
513
  })
514
  return
515
 
 
 
 
 
516
  project_name = repo_name.split("/")[-1] if "/" in repo_name else repo_name
517
 
518
  result = push_to_huggingface(
 
475
  exec_context_json: str,
476
  repo_name: str,
477
  hf_token: str,
478
+ space_sdk: str = "auto",
479
  is_space: str = "true",
480
  ) -> str:
481
  """Push generated project to HuggingFace Hub."""
 
488
  if not project_files and code:
489
  lang = execution_context.get("language", "python")
490
  is_gradio = execution_context.get("is_gradio", False)
491
+
492
+ # Map language to entry file β€” JS/TS single-files get wrapped for Docker
493
+ if lang in ("javascript", "js", "typescript", "ts"):
494
+ # For single-file JS/TS code that is HTML (vanilla), keep as index.html
495
+ if "<!doctype" in code.lower() or "<html" in code.lower():
496
+ filename = "index.html"
497
+ else:
498
+ filename = "index.js"
499
+ elif lang in ("html", "web"):
500
+ filename = "index.html"
501
+ else:
502
+ ext_map = {
503
+ "python": "app.py", "py": "app.py",
504
+ }
505
+ filename = ext_map.get(lang, "app.py")
506
+
507
  project_files = {filename: code}
508
 
509
  # Auto-detect SDK for Gradio apps
 
522
  })
523
  return
524
 
525
+ # "auto" SDK means let push_to_huggingface decide
526
+ if space_sdk == "auto":
527
+ space_sdk = "static" # push_to_huggingface will auto-detect from files
528
+
529
  project_name = repo_name.split("/")[-1] if "/" in repo_name else repo_name
530
 
531
  result = push_to_huggingface(
index.html CHANGED
@@ -1202,11 +1202,13 @@ body.hide-thinking .think-block { display: none; }
1202
  <div class="deploy-field">
1203
  <label for="hf-space-sdk">Space SDK</label>
1204
  <select id="hf-space-sdk">
 
 
1205
  <option value="static">Static (HTML/CSS/JS)</option>
1206
- <option value="gradio">Gradio</option>
1207
- <option value="streamlit">Streamlit</option>
1208
- <option value="docker">Docker</option>
1209
  </select>
 
1210
  </div>
1211
  <button id="btn-push-hf" onclick="pushToHuggingFace()" disabled>&#128640; Push to HuggingFace</button>
1212
  <div class="deploy-status" id="deploy-status"></div>
@@ -1445,7 +1447,37 @@ function onLanguageChange() {
1445
 
1446
  fwSelect.onchange = () => {
1447
  state.targetFramework = fwSelect.value;
 
1448
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1449
  }
1450
 
1451
  // ═══════════════════════════════════════════════════════
 
1202
  <div class="deploy-field">
1203
  <label for="hf-space-sdk">Space SDK</label>
1204
  <select id="hf-space-sdk">
1205
+ <option value="auto">Auto-detect</option>
1206
+ <option value="docker">Docker (React/Next/Vue/Express/Node)</option>
1207
  <option value="static">Static (HTML/CSS/JS)</option>
1208
+ <option value="gradio">Gradio (Python)</option>
1209
+ <option value="streamlit">Streamlit (Python)</option>
 
1210
  </select>
1211
+ <div class="deploy-hint">JS frameworks auto-use Docker with Dockerfile build</div>
1212
  </div>
1213
  <button id="btn-push-hf" onclick="pushToHuggingFace()" disabled>&#128640; Push to HuggingFace</button>
1214
  <div class="deploy-status" id="deploy-status"></div>
 
1447
 
1448
  fwSelect.onchange = () => {
1449
  state.targetFramework = fwSelect.value;
1450
+ autoSelectSDK(selectedLang, fwSelect.value);
1451
  };
1452
+
1453
+ // Auto-select SDK based on language/framework
1454
+ autoSelectSDK(selectedLang, fwSelect.value);
1455
+ }
1456
+
1457
+ function autoSelectSDK(lang, framework) {
1458
+ const sdkSelect = document.getElementById('hf-space-sdk');
1459
+ if (!sdkSelect) return;
1460
+
1461
+ const jsLangs = ['JavaScript', 'TypeScript'];
1462
+ const jsFrameworks = ['React', 'Next.js', 'Vue.js', 'Express.js', 'Node.js', 'NestJS'];
1463
+ const pythonFrameworks = ['Gradio', 'Streamlit', 'Flask', 'Django', 'FastAPI'];
1464
+
1465
+ if (jsLangs.includes(lang) || jsFrameworks.includes(framework)) {
1466
+ // JS frameworks need Docker
1467
+ if (jsFrameworks.includes(framework) || jsLangs.includes(lang)) {
1468
+ sdkSelect.value = 'docker';
1469
+ }
1470
+ } else if (framework === 'Gradio') {
1471
+ sdkSelect.value = 'gradio';
1472
+ } else if (framework === 'Streamlit') {
1473
+ sdkSelect.value = 'streamlit';
1474
+ } else if (lang === 'Python' && pythonFrameworks.includes(framework)) {
1475
+ sdkSelect.value = 'gradio';
1476
+ } else if (lang === 'HTML/CSS/JS' || framework === 'Vanilla' || framework === 'Tailwind CSS' || framework === 'Bootstrap') {
1477
+ sdkSelect.value = 'static';
1478
+ } else {
1479
+ sdkSelect.value = 'auto';
1480
+ }
1481
  }
1482
 
1483
  // ═══════════════════════════════════════════════════════