akhaliq HF Staff commited on
Commit
1e9598c
·
1 Parent(s): fe27a3c

Fix transformers.js deployment: Upload all files (html, js, css) individually

Browse files

Issue: Transformers.js apps were not deploying correctly - CSS and JavaScript
files were missing from the deployed space.

Root cause: backend_deploy.py was using upload_folder() for all file types,
but transformers.js requires individual file uploads like the original deploy.py.

Changes to backend_deploy.py:

1. Added validation for transformers.js files:
- Check all three files exist (index.html, index.js, style.css)
- Return error if any file is missing

2. Template space duplication (NEW):
- Use duplicate_space() from static-templates/transformers.js template
- Matches original deploy.py behavior
- Falls back to create_repo() if duplication fails

3. Individual file upload for transformers.js:
- Added use_individual_uploads flag
- Upload each file separately using api.upload_file()
- NOT using upload_folder() for transformers.js

4. Retry logic with error handling:
- 3 attempts per file
- 2-second delay between retries
- Proper permission error detection
- User-friendly error messages

5. Separate commit messages:
- Each file gets its own commit: "{message} - {filename}"
- Preserves git history for each file

Upload flow for transformers.js:
1. Parse code to extract index.html, index.js, style.css
2. Validate all three files are present
3. Duplicate template space (or create static space)
4. Upload index.html individually (with retry)
5. Upload index.js individually (with retry)
6. Upload style.css individually (with retry)
7. Add anycoder tag to README

This matches the original deploy.py pattern where transformers.js files
are uploaded one at a time rather than as a folder.

Other languages (html, gradio, streamlit, react) continue to use
upload_folder() which works fine for them.

Files changed (1) hide show
  1. backend_deploy.py +82 -14
backend_deploy.py CHANGED
@@ -321,14 +321,22 @@ def deploy_to_huggingface_space(
321
 
322
  # Parse code based on language
323
  app_port = None # Track if we need app_port for Docker spaces
 
324
 
325
  if language == "transformers.js":
326
  files = parse_transformers_js_output(code)
327
 
 
 
 
 
328
  # Write transformers.js files
329
  for filename, content in files.items():
330
  (temp_path / filename).write_text(content, encoding='utf-8')
331
 
 
 
 
332
  elif language == "html":
333
  html_code = parse_html_code(code)
334
  (temp_path / "index.html").write_text(html_code, encoding='utf-8')
@@ -387,13 +395,36 @@ def deploy_to_huggingface_space(
387
  # Create the space (only for new deployments)
388
  if not is_update:
389
  try:
390
- api.create_repo(
391
- repo_id=repo_id,
392
- repo_type="space",
393
- space_sdk=sdk,
394
- private=private,
395
- exist_ok=False
396
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  except Exception as e:
398
  if "already exists" in str(e).lower():
399
  # Space exists, treat as update
@@ -401,17 +432,54 @@ def deploy_to_huggingface_space(
401
  else:
402
  return False, f"Failed to create space: {str(e)}", None
403
 
404
- # Upload all files
405
  if not commit_message:
406
  commit_message = "Update from anycoder" if is_update else "Deploy from anycoder"
407
 
408
  try:
409
- api.upload_folder(
410
- folder_path=str(temp_path),
411
- repo_id=repo_id,
412
- repo_type="space",
413
- commit_message=commit_message
414
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  except Exception as e:
416
  return False, f"Failed to upload files: {str(e)}", None
417
 
 
321
 
322
  # Parse code based on language
323
  app_port = None # Track if we need app_port for Docker spaces
324
+ use_individual_uploads = False # Flag for transformers.js
325
 
326
  if language == "transformers.js":
327
  files = parse_transformers_js_output(code)
328
 
329
+ # Validate all three files are present
330
+ if not files.get('index.html') or not files.get('index.js') or not files.get('style.css'):
331
+ return False, "Error: Could not parse transformers.js output. Missing index.html, index.js, or style.css", None
332
+
333
  # Write transformers.js files
334
  for filename, content in files.items():
335
  (temp_path / filename).write_text(content, encoding='utf-8')
336
 
337
+ # For transformers.js, we'll upload files individually (not via upload_folder)
338
+ use_individual_uploads = True
339
+
340
  elif language == "html":
341
  html_code = parse_html_code(code)
342
  (temp_path / "index.html").write_text(html_code, encoding='utf-8')
 
395
  # Create the space (only for new deployments)
396
  if not is_update:
397
  try:
398
+ if language == "transformers.js":
399
+ # For transformers.js, duplicate the template space
400
+ from huggingface_hub import duplicate_space
401
+
402
+ try:
403
+ duplicate_space(
404
+ from_id="static-templates/transformers.js",
405
+ to_id=repo_id,
406
+ token=token,
407
+ exist_ok=True
408
+ )
409
+ except Exception as e:
410
+ # If template duplication fails, fall back to regular create
411
+ print(f"[Deploy] Template duplication failed, creating regular static space: {e}")
412
+ api.create_repo(
413
+ repo_id=repo_id,
414
+ repo_type="space",
415
+ space_sdk=sdk,
416
+ private=private,
417
+ exist_ok=False
418
+ )
419
+ else:
420
+ # For other languages, create space normally
421
+ api.create_repo(
422
+ repo_id=repo_id,
423
+ repo_type="space",
424
+ space_sdk=sdk,
425
+ private=private,
426
+ exist_ok=False
427
+ )
428
  except Exception as e:
429
  if "already exists" in str(e).lower():
430
  # Space exists, treat as update
 
432
  else:
433
  return False, f"Failed to create space: {str(e)}", None
434
 
435
+ # Upload files
436
  if not commit_message:
437
  commit_message = "Update from anycoder" if is_update else "Deploy from anycoder"
438
 
439
  try:
440
+ if use_individual_uploads:
441
+ # For transformers.js, upload each file individually (matches original deploy.py)
442
+ import time
443
+ files_to_upload = ["index.html", "index.js", "style.css"]
444
+
445
+ max_attempts = 3
446
+ for filename in files_to_upload:
447
+ file_path = temp_path / filename
448
+ if not file_path.exists():
449
+ return False, f"Failed to upload: {filename} not found", None
450
+
451
+ # Upload with retry logic (like original)
452
+ success = False
453
+ last_error = None
454
+
455
+ for attempt in range(max_attempts):
456
+ try:
457
+ api.upload_file(
458
+ path_or_fileobj=str(file_path),
459
+ path_in_repo=filename,
460
+ repo_id=repo_id,
461
+ repo_type="space",
462
+ commit_message=f"{commit_message} - {filename}"
463
+ )
464
+ success = True
465
+ break
466
+ except Exception as e:
467
+ last_error = e
468
+ if "403" in str(e) or "Forbidden" in str(e):
469
+ return False, f"Permission denied uploading {filename}. Check your token has write access.", None
470
+ if attempt < max_attempts - 1:
471
+ time.sleep(2) # Wait before retry
472
+
473
+ if not success:
474
+ return False, f"Failed to upload {filename} after {max_attempts} attempts: {last_error}", None
475
+ else:
476
+ # For other languages, use upload_folder
477
+ api.upload_folder(
478
+ folder_path=str(temp_path),
479
+ repo_id=repo_id,
480
+ repo_type="space",
481
+ commit_message=commit_message
482
+ )
483
  except Exception as e:
484
  return False, f"Failed to upload files: {str(e)}", None
485