fastled 1.2.83__tar.gz → 1.2.84__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. {fastled-1.2.83 → fastled-1.2.84}/.gitignore +1 -0
  2. {fastled-1.2.83 → fastled-1.2.84}/PKG-INFO +1 -1
  3. fastled-1.2.84/build_local_docker.py +4 -0
  4. {fastled-1.2.83 → fastled-1.2.84}/compiler/server.py +48 -41
  5. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/__init__.py +1 -1
  6. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/client_server.py +8 -2
  7. fastled-1.2.84/src/fastled/server_flask.py +462 -0
  8. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/PKG-INFO +1 -1
  9. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/SOURCES.txt +1 -0
  10. {fastled-1.2.83 → fastled-1.2.84}/test +4 -0
  11. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_fetch_source_files.py +3 -0
  12. fastled-1.2.83/src/fastled/server_flask.py +0 -267
  13. {fastled-1.2.83 → fastled-1.2.84}/.aiderignore +0 -0
  14. {fastled-1.2.83 → fastled-1.2.84}/.dockerignore +0 -0
  15. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/build_multi_docker_image.yml +0 -0
  16. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/build_webpage.yml +0 -0
  17. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/lint.yml +0 -0
  18. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/publish_release.yml +0 -0
  19. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/template_build_docker_image.yml +0 -0
  20. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/test_build_exe.yml +0 -0
  21. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/test_macos.yml +0 -0
  22. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/test_ubuntu.yml +0 -0
  23. {fastled-1.2.83 → fastled-1.2.84}/.github/workflows/test_win.yml +0 -0
  24. {fastled-1.2.83 → fastled-1.2.84}/.pylintrc +0 -0
  25. {fastled-1.2.83 → fastled-1.2.84}/.vscode/launch.json +0 -0
  26. {fastled-1.2.83 → fastled-1.2.84}/.vscode/settings.json +0 -0
  27. {fastled-1.2.83 → fastled-1.2.84}/.vscode/tasks.json +0 -0
  28. {fastled-1.2.83 → fastled-1.2.84}/Dockerfile +0 -0
  29. {fastled-1.2.83 → fastled-1.2.84}/LICENSE +0 -0
  30. {fastled-1.2.83 → fastled-1.2.84}/MANIFEST.in +0 -0
  31. {fastled-1.2.83 → fastled-1.2.84}/README.md +0 -0
  32. {fastled-1.2.83 → fastled-1.2.84}/RELEASE.md +0 -0
  33. {fastled-1.2.83 → fastled-1.2.84}/TODO.md +0 -0
  34. {fastled-1.2.83 → fastled-1.2.84}/build_exe.py +0 -0
  35. {fastled-1.2.83 → fastled-1.2.84}/build_site.py +0 -0
  36. {fastled-1.2.83 → fastled-1.2.84}/clean +0 -0
  37. {fastled-1.2.83 → fastled-1.2.84}/compiler/CMakeLists.txt +0 -0
  38. {fastled-1.2.83 → fastled-1.2.84}/compiler/__init__.py +0 -0
  39. {fastled-1.2.83 → fastled-1.2.84}/compiler/arduino-pre-process.sh +0 -0
  40. {fastled-1.2.83 → fastled-1.2.84}/compiler/build.sh +0 -0
  41. {fastled-1.2.83 → fastled-1.2.84}/compiler/build_archive.sh +0 -0
  42. {fastled-1.2.83 → fastled-1.2.84}/compiler/build_fast.sh +0 -0
  43. {fastled-1.2.83 → fastled-1.2.84}/compiler/code_sync.py +0 -0
  44. {fastled-1.2.83 → fastled-1.2.84}/compiler/compile.py +0 -0
  45. {fastled-1.2.83 → fastled-1.2.84}/compiler/compile_lock.py +0 -0
  46. {fastled-1.2.83 → fastled-1.2.84}/compiler/debug.sh +0 -0
  47. {fastled-1.2.83 → fastled-1.2.84}/compiler/entrypoint.sh +0 -0
  48. {fastled-1.2.83 → fastled-1.2.84}/compiler/extra/100dots.html +0 -0
  49. {fastled-1.2.83 → fastled-1.2.84}/compiler/extra/demo_threejs.html +0 -0
  50. {fastled-1.2.83 → fastled-1.2.84}/compiler/extra/micdemo.html +0 -0
  51. {fastled-1.2.83 → fastled-1.2.84}/compiler/extra/mp3upload.html +0 -0
  52. {fastled-1.2.83 → fastled-1.2.84}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
  53. {fastled-1.2.83 → fastled-1.2.84}/compiler/final_prewarm.sh +0 -0
  54. {fastled-1.2.83 → fastled-1.2.84}/compiler/init_runtime.py +0 -0
  55. {fastled-1.2.83 → fastled-1.2.84}/compiler/install-arduino-cli.sh +0 -0
  56. {fastled-1.2.83 → fastled-1.2.84}/compiler/libcompile/CMakeLists.txt +0 -0
  57. {fastled-1.2.83 → fastled-1.2.84}/compiler/paths.py +0 -0
  58. {fastled-1.2.83 → fastled-1.2.84}/compiler/pre-process.sh +0 -0
  59. {fastled-1.2.83 → fastled-1.2.84}/compiler/prewarm.sh +0 -0
  60. {fastled-1.2.83 → fastled-1.2.84}/compiler/process-ino.py +0 -0
  61. {fastled-1.2.83 → fastled-1.2.84}/compiler/process_extended.py +0 -0
  62. {fastled-1.2.83 → fastled-1.2.84}/compiler/pyproject.toml +0 -0
  63. {fastled-1.2.83 → fastled-1.2.84}/compiler/run.py +0 -0
  64. {fastled-1.2.83 → fastled-1.2.84}/compiler/sketch_hasher.py +0 -0
  65. {fastled-1.2.83 → fastled-1.2.84}/compiler/wasm_compiler_flags.py +0 -0
  66. {fastled-1.2.83 → fastled-1.2.84}/docker-compose.yml +0 -0
  67. {fastled-1.2.83 → fastled-1.2.84}/install +0 -0
  68. {fastled-1.2.83 → fastled-1.2.84}/install_linux.sh +0 -0
  69. {fastled-1.2.83 → fastled-1.2.84}/lint +0 -0
  70. {fastled-1.2.83 → fastled-1.2.84}/pyproject.toml +0 -0
  71. {fastled-1.2.83 → fastled-1.2.84}/requirements.testing.txt +0 -0
  72. {fastled-1.2.83 → fastled-1.2.84}/setup.cfg +0 -0
  73. {fastled-1.2.83 → fastled-1.2.84}/setup.py +0 -0
  74. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/app.py +0 -0
  75. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/assets/example.txt +0 -0
  76. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/assets/localhost-key.pem +0 -0
  77. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/assets/localhost.pem +0 -0
  78. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/cli.py +0 -0
  79. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/cli_test.py +0 -0
  80. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/cli_test_interactive.py +0 -0
  81. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/compile_server.py +0 -0
  82. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/compile_server_impl.py +0 -0
  83. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/docker_manager.py +0 -0
  84. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/filewatcher.py +0 -0
  85. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/keyboard.py +0 -0
  86. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/keyz.py +0 -0
  87. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/live_client.py +0 -0
  88. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/open_browser.py +0 -0
  89. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/parse_args.py +0 -0
  90. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/paths.py +0 -0
  91. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/print_filter.py +0 -0
  92. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/project_init.py +0 -0
  93. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/select_sketch_directory.py +0 -0
  94. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/server_start.py +0 -0
  95. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/settings.py +0 -0
  96. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/site/build.py +0 -0
  97. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/site/examples.py +0 -0
  98. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/sketch.py +0 -0
  99. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/spinner.py +0 -0
  100. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/string_diff.py +0 -0
  101. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/test/can_run_local_docker_tests.py +0 -0
  102. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/test/examples.py +0 -0
  103. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/types.py +0 -0
  104. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/util.py +0 -0
  105. {fastled-1.2.83 → fastled-1.2.84}/src/fastled/web_compile.py +0 -0
  106. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/dependency_links.txt +0 -0
  107. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/entry_points.txt +0 -0
  108. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/requires.txt +0 -0
  109. {fastled-1.2.83 → fastled-1.2.84}/src/fastled.egg-info/top_level.txt +0 -0
  110. {fastled-1.2.83 → fastled-1.2.84}/tests/integration/test_build_examples.py +0 -0
  111. {fastled-1.2.83 → fastled-1.2.84}/tests/integration/test_examples.py +0 -0
  112. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/html/index.html +0 -0
  113. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_api.py +0 -0
  114. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_bad_ino.py +0 -0
  115. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_cli.py +0 -0
  116. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_compile_server.py +0 -0
  117. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_docker_linux_on_windows.py +0 -0
  118. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_embedded_data.py +0 -0
  119. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_filechanger.py +0 -0
  120. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_http_server.py +0 -0
  121. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/bad/bad.ino +0 -0
  122. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
  123. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
  124. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
  125. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/embedded/wasm.ino +0 -0
  126. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_ino/wasm/wasm.ino +0 -0
  127. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_print_filter.py +0 -0
  128. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_project_init.py +0 -0
  129. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_server_and_client_seperatly.py +0 -0
  130. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_string_diff.py +0 -0
  131. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_version.py +0 -0
  132. {fastled-1.2.83 → fastled-1.2.84}/tests/unit/test_webcompile.py +0 -0
  133. {fastled-1.2.83 → fastled-1.2.84}/upload_package.sh +0 -0
@@ -150,3 +150,4 @@ tmp/
150
150
  cache/
151
151
  dist/
152
152
  /fastled/
153
+ logs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.83
3
+ Version: 1.2.84
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ os.system("docker compose down --remove-orphans --rmi all")
4
+ os.system("docker compose build")
@@ -431,6 +431,32 @@ def get_settings() -> dict:
431
431
  return settings
432
432
 
433
433
 
434
+ # Return content and media type
435
+ def fetch_file(full_path: Path) -> tuple[bytes, str] | HTTPException:
436
+ """Fetch the file from the server."""
437
+ print(f"Fetching file: {full_path}")
438
+ if not full_path.exists():
439
+ raise HTTPException(status_code=404, detail="File not found.")
440
+ if not full_path.is_file():
441
+ raise HTTPException(status_code=400, detail="Not a file.")
442
+ if not full_path.is_relative_to(FASTLED_SRC):
443
+ raise HTTPException(status_code=400, detail="Invalid file path.")
444
+
445
+ content = full_path.read_bytes()
446
+ # Determine media type based on file extension
447
+ media_type = "text/plain"
448
+ if full_path.suffix in [".h", ".cpp"]:
449
+ media_type = "text/plain"
450
+ elif full_path.suffix == ".html":
451
+ media_type = "text/html"
452
+ elif full_path.suffix == ".js":
453
+ media_type = "application/javascript"
454
+ elif full_path.suffix == ".css":
455
+ media_type = "text/css"
456
+
457
+ return content, media_type
458
+
459
+
434
460
  def startup() -> None:
435
461
  print("Starting FastLED wasm compiler server...")
436
462
  try:
@@ -599,37 +625,22 @@ def project_init_example(
599
625
 
600
626
 
601
627
  @app.get("/sourcefiles/{filepath:path}")
602
- def source_file(filepath: str) -> FileResponse:
628
+ def source_file(filepath: str) -> Response:
603
629
  """Get the source file from the server."""
604
630
  print(f"Endpoint accessed: /sourcefiles/{filepath}")
605
631
  if ".." in filepath:
606
632
  raise HTTPException(status_code=400, detail="Invalid file path.")
607
633
  full_path = Path(FASTLED_SRC / filepath)
608
- if not full_path.exists():
609
- raise HTTPException(status_code=404, detail="File not found.")
610
- if not full_path.is_file():
611
- raise HTTPException(status_code=400, detail="Not a file.")
612
- if not full_path.is_relative_to(FASTLED_SRC):
613
- raise HTTPException(status_code=400, detail="Invalid file path.")
614
-
615
- suffix = full_path.suffix
616
- if suffix in [".h", ".cpp"]:
617
- media_type = "text/plain"
618
- elif suffix == ".html":
619
- media_type = "text/html"
620
- elif suffix == ".js":
621
- media_type = "application/javascript"
622
- elif suffix == ".css":
623
- media_type = "text/css"
624
- else:
625
- media_type = "application/octet-stream"
626
-
627
- fr = FileResponse(
628
- path=full_path,
629
- media_type=media_type,
630
- filename=filepath,
631
- )
632
- return fr
634
+ result: tuple[bytes, str] | HTTPException = fetch_file(full_path=full_path)
635
+ if isinstance(result, HTTPException):
636
+ assert isinstance(result, HTTPException)
637
+ return Response(
638
+ content=result.detail, # type: ignore
639
+ media_type="text/plain",
640
+ status_code=result.status_code, # type: ignore
641
+ )
642
+ content, media_type = result
643
+ return Response(content=content, media_type=media_type)
633
644
 
634
645
 
635
646
  @app.get("/drawfsource/{file_path:path}")
@@ -642,21 +653,17 @@ async def static_files(file_path: str) -> Response:
642
653
  # Extract the path after "js/fastled/src/"
643
654
  relative_path = file_path[len("js/fastled/src/") :]
644
655
  full_path = FASTLED_SRC / relative_path
645
-
646
- if full_path.exists() and full_path.is_file():
647
- content = full_path.read_bytes()
648
- # Determine media type based on file extension
649
- media_type = "text/plain"
650
- if full_path.suffix in [".h", ".cpp"]:
651
- media_type = "text/plain"
652
- elif full_path.suffix == ".html":
653
- media_type = "text/html"
654
- elif full_path.suffix == ".js":
655
- media_type = "application/javascript"
656
- elif full_path.suffix == ".css":
657
- media_type = "text/css"
658
-
659
- return Response(content=content, media_type=media_type)
656
+ result: tuple[bytes, str] | HTTPException = fetch_file(full_path=full_path)
657
+
658
+ # return Response(content=content, media_type=media_type)
659
+ if isinstance(result, HTTPException):
660
+ return Response(
661
+ content=result.detail, # type: ignore
662
+ media_type="text/plain",
663
+ status_code=result.status_code, # type: ignore
664
+ )
665
+ content, media_type = result
666
+ return Response(content=content, media_type=media_type)
660
667
 
661
668
  # If file not found or path doesn't match expected format
662
669
  return Response(
@@ -14,7 +14,7 @@ from .types import BuildMode, CompileResult, CompileServerError, FileResponse
14
14
  # IMPORTANT! There's a bug in github which will REJECT any version update
15
15
  # that has any other change in the repo. Please bump the version as the
16
16
  # ONLY change in a commit, or else the pypi update and the release will fail.
17
- __version__ = "1.2.83"
17
+ __version__ = "1.2.84"
18
18
 
19
19
 
20
20
  class Api:
@@ -63,6 +63,12 @@ def TEST_BEFORE_COMPILE(url) -> None:
63
63
  pass
64
64
 
65
65
 
66
+ def _chunked_print(stdout: str) -> None:
67
+ lines = stdout.splitlines()
68
+ for line in lines:
69
+ print(line)
70
+
71
+
66
72
  def _run_web_compiler(
67
73
  directory: Path,
68
74
  host: str,
@@ -80,7 +86,7 @@ def _run_web_compiler(
80
86
  if not web_result.success:
81
87
  print("\nWeb compilation failed:")
82
88
  print(f"Time taken: {diff:.2f} seconds")
83
- print(web_result.stdout)
89
+ _chunked_print(web_result.stdout)
84
90
  # Create error page
85
91
  output_dir.mkdir(exist_ok=True)
86
92
  error_html = _create_error_html(web_result.stdout)
@@ -117,7 +123,7 @@ def _run_web_compiler(
117
123
  # Extract zip contents
118
124
  shutil.unpack_archive(temp_zip, output_dir, "zip")
119
125
 
120
- print(web_result.stdout)
126
+ _chunked_print(web_result.stdout)
121
127
  print_results()
122
128
  return web_result
123
129
 
@@ -0,0 +1,462 @@
1
+ import argparse
2
+ import logging
3
+ import time
4
+ from multiprocessing import Process
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+ from livereload import Server
9
+
10
+ # Logging configuration
11
+ _ENABLE_LOGGING = False # Set to False to disable logging
12
+
13
+ if _ENABLE_LOGGING:
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
17
+ )
18
+ logger = logging.getLogger("flask_server")
19
+ else:
20
+ # Disable all logging
21
+ logging.getLogger("flask_server").addHandler(logging.NullHandler())
22
+ logging.getLogger("flask_server").propagate = False
23
+ logger = logging.getLogger("flask_server")
24
+ logger.disabled = True
25
+
26
+ _DRAWF_SOURCE_PREFIX = "drawfsource/js/fastled/src/"
27
+
28
+
29
+ def _run_flask_server(
30
+ fastled_js: Path,
31
+ port: int,
32
+ compile_server_port: int,
33
+ certfile: Path | None = None,
34
+ keyfile: Path | None = None,
35
+ ) -> None:
36
+ """Run Flask server with live reload in a subprocess
37
+
38
+ Args:
39
+ fastled_js: Path to the fastled_js directory
40
+ port: Port to run the server on
41
+ certfile: Path to the SSL certificate file
42
+ keyfile: Path to the SSL key file
43
+ """
44
+ try:
45
+ from flask import Flask, Response, request, send_from_directory
46
+
47
+ app = Flask(__name__)
48
+
49
+ # Must be a full path or flask will fail to find the file.
50
+ fastled_js = fastled_js.resolve()
51
+
52
+ # logger.error(f"Server error: {e}")
53
+
54
+ @app.before_request
55
+ def log_request_info():
56
+ """Log details of each request before processing"""
57
+ if _ENABLE_LOGGING:
58
+ logger.info("=" * 80)
59
+ logger.info(f"Request: {request.method} {request.url}")
60
+ logger.info(f"Headers: {dict(request.headers)}")
61
+ logger.info(f"Args: {dict(request.args)}")
62
+ if request.is_json:
63
+ logger.info(f"JSON: {request.json}")
64
+ if request.form:
65
+ logger.info(f"Form: {dict(request.form)}")
66
+ logger.info(f"Remote addr: {request.remote_addr}")
67
+ logger.info(f"User agent: {request.user_agent}")
68
+
69
+ @app.route("/")
70
+ def serve_index():
71
+ if _ENABLE_LOGGING:
72
+ logger.info("Serving index.html")
73
+ response = send_from_directory(fastled_js, "index.html")
74
+ if _ENABLE_LOGGING:
75
+ logger.info(f"Index response status: {response.status_code}")
76
+ return response
77
+
78
+ @app.route("/sourcefiles/<path:path>")
79
+ def serve_source_files(path):
80
+ """Proxy requests to /sourcefiles/* to the compile server"""
81
+ from flask import request
82
+
83
+ start_time = time.time()
84
+ logger.info(f"Serving source file: {path}")
85
+
86
+ # Forward the request to the compile server
87
+ target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
88
+ logger.info(f"Forwarding to: {target_url}")
89
+
90
+ # Log request headers
91
+ request_headers = {
92
+ key: value for key, value in request.headers if key != "Host"
93
+ }
94
+ logger.debug(f"Request headers: {request_headers}")
95
+
96
+ # Forward the request with the same method, headers, and body
97
+ try:
98
+ with httpx.Client() as client:
99
+ resp = client.request(
100
+ method=request.method,
101
+ url=target_url,
102
+ headers=request_headers,
103
+ content=request.get_data(),
104
+ cookies=request.cookies,
105
+ follow_redirects=True,
106
+ )
107
+
108
+ logger.info(f"Response status: {resp.status_code}")
109
+ logger.debug(f"Response headers: {dict(resp.headers)}")
110
+
111
+ # Create a Flask Response object from the httpx response
112
+ raw_data = resp.content
113
+ logger.debug(f"Response size: {len(raw_data)} bytes")
114
+
115
+ response = Response(
116
+ raw_data, status=resp.status_code, headers=dict(resp.headers)
117
+ )
118
+
119
+ elapsed_time = time.time() - start_time
120
+ logger.info(f"Request completed in {elapsed_time:.3f} seconds")
121
+
122
+ return response
123
+
124
+ except Exception as e:
125
+ logger.error(f"Error forwarding request: {e}", exc_info=True)
126
+ return Response(f"Error: {str(e)}", status=500)
127
+
128
+ def handle_dwarfsource(path: str) -> Response:
129
+ """Handle requests to /drawfsource/js/fastled/src/"""
130
+ from flask import request
131
+
132
+ start_time = time.time()
133
+ logger.info(f"Processing request: {request.method} {request.url}")
134
+
135
+ if not path.startswith(_DRAWF_SOURCE_PREFIX):
136
+ # unexpected
137
+ error_msg = f"Unexpected path: {path}"
138
+ logger.error(error_msg)
139
+ # Logging disabled
140
+ return Response("Malformed path", status=400)
141
+
142
+ path = path.replace("drawfsource/js/fastled/src/", "")
143
+ logger.info(f"Transformed path: {path}")
144
+
145
+ # Forward the request to the compile server
146
+ target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
147
+ logger.info(f"Requesting: {target_url}")
148
+ logger.info(f"Processing dwarfsource request for {path}")
149
+
150
+ # Log request headers
151
+ request_headers = {
152
+ key: value for key, value in request.headers if key != "Host"
153
+ }
154
+ logger.debug(f"Request headers: {request_headers}")
155
+
156
+ try:
157
+ # Forward the request with the same method, headers, and body
158
+ with httpx.Client() as client:
159
+ resp = client.request(
160
+ method=request.method,
161
+ url=target_url,
162
+ headers=request_headers,
163
+ content=request.get_data(),
164
+ cookies=request.cookies,
165
+ follow_redirects=True,
166
+ )
167
+
168
+ logger.info(f"Response status: {resp.status_code}")
169
+ logger.debug(f"Response headers: {dict(resp.headers)}")
170
+
171
+ # Create a Flask Response object from the httpx response
172
+ payload = resp.content
173
+ assert isinstance(payload, bytes)
174
+
175
+ # Check if the payload is empty
176
+ if len(payload) == 0:
177
+ logger.error("Empty payload received from compile server")
178
+ return Response("Empty payload", status=400)
179
+
180
+ response = Response(
181
+ payload, status=resp.status_code, headers=dict(resp.headers)
182
+ )
183
+
184
+ elapsed_time = time.time() - start_time
185
+ logger.info(f"Request completed in {elapsed_time:.3f} seconds")
186
+
187
+ return response
188
+
189
+ except Exception as e:
190
+ logger.error(f"Error handling dwarfsource request: {e}", exc_info=True)
191
+ return Response(f"Error: {str(e)}", status=500)
192
+
193
+ def handle_sourcefile(path: str) -> Response:
194
+ """Handle requests to /sourcefiles/*"""
195
+ from flask import Response, request
196
+
197
+ start_time = time.time()
198
+ logger.info("\n##################################")
199
+ logger.info(f"# Serving source file /sourcefiles/ {path}")
200
+ logger.info("##################################\n")
201
+
202
+ logger.info(f"Processing sourcefile request for {path}")
203
+
204
+ # Forward the request to the compile server
205
+ target_url = f"http://localhost:{compile_server_port}/sourcefiles/{path}"
206
+ logger.info(f"Forwarding to: {target_url}")
207
+
208
+ # Log request headers
209
+ request_headers = {
210
+ key: value for key, value in request.headers if key != "Host"
211
+ }
212
+ logger.debug(f"Request headers: {request_headers}")
213
+
214
+ try:
215
+ # Forward the request with the same method, headers, and body
216
+ with httpx.Client() as client:
217
+ resp = client.request(
218
+ method=request.method,
219
+ url=target_url,
220
+ headers=request_headers,
221
+ content=request.get_data(),
222
+ cookies=request.cookies,
223
+ follow_redirects=True,
224
+ )
225
+
226
+ logger.info(f"Response status: {resp.status_code}")
227
+ logger.debug(f"Response headers: {dict(resp.headers)}")
228
+
229
+ # Create a Flask Response object from the httpx response
230
+ raw_data = resp.content
231
+ logger.debug(f"Response size: {len(raw_data)} bytes")
232
+
233
+ response = Response(
234
+ raw_data, status=resp.status_code, headers=dict(resp.headers)
235
+ )
236
+
237
+ elapsed_time = time.time() - start_time
238
+ logger.info(f"Request completed in {elapsed_time:.3f} seconds")
239
+
240
+ return response
241
+
242
+ except Exception as e:
243
+ logger.error(f"Error handling sourcefile request: {e}", exc_info=True)
244
+ return Response(f"Error: {str(e)}", status=500)
245
+
246
+ def handle_local_file_fetch(path: str) -> Response:
247
+ start_time = time.time()
248
+ logger.info("\n##################################")
249
+ logger.info(f"# Serving generic file {path}")
250
+ logger.info("##################################\n")
251
+
252
+ logger.info(f"Processing local file request for {path}")
253
+
254
+ try:
255
+ file_path = fastled_js / path
256
+ logger.info(f"Full file path: {file_path}")
257
+ logger.info(f"File exists: {file_path.exists()}")
258
+
259
+ # Check if file exists before trying to serve it
260
+ if not file_path.exists():
261
+ logger.warning(f"File not found: {file_path}")
262
+ return Response(f"File not found: {path}", status=404)
263
+
264
+ response = send_from_directory(fastled_js, path)
265
+
266
+ # Some servers don't set the Content-Type header for a bunch of files.
267
+ content_type = None
268
+ if path.endswith(".js"):
269
+ content_type = "application/javascript"
270
+ elif path.endswith(".css"):
271
+ content_type = "text/css"
272
+ elif path.endswith(".wasm"):
273
+ content_type = "application/wasm"
274
+ elif path.endswith(".json"):
275
+ content_type = "application/json"
276
+ elif path.endswith(".png"):
277
+ content_type = "image/png"
278
+ elif path.endswith(".jpg") or path.endswith(".jpeg"):
279
+ content_type = "image/jpeg"
280
+ elif path.endswith(".gif"):
281
+ content_type = "image/gif"
282
+ elif path.endswith(".svg"):
283
+ content_type = "image/svg+xml"
284
+ elif path.endswith(".ico"):
285
+ content_type = "image/x-icon"
286
+ elif path.endswith(".html"):
287
+ content_type = "text/html"
288
+
289
+ if content_type:
290
+ logger.info(f"Setting Content-Type to {content_type}")
291
+ response.headers["Content-Type"] = content_type
292
+
293
+ # now also add headers to force no caching
294
+ response.headers["Cache-Control"] = (
295
+ "no-cache, no-store, must-revalidate"
296
+ )
297
+ response.headers["Pragma"] = "no-cache"
298
+ response.headers["Expires"] = "0"
299
+
300
+ logger.info(f"Response status: {response.status_code}")
301
+ logger.debug(f"Response headers: {dict(response.headers)}")
302
+
303
+ elapsed_time = time.time() - start_time
304
+ logger.info(f"Request completed in {elapsed_time:.3f} seconds")
305
+
306
+ return response
307
+
308
+ except Exception as e:
309
+ logger.error(f"Error serving local file {path}: {e}", exc_info=True)
310
+ # Check if this is a FileNotFoundError (which can happen with send_from_directory)
311
+ if isinstance(e, FileNotFoundError):
312
+ return Response(f"File not found: {path}", status=404)
313
+ return Response(f"Error serving file: {str(e)}", status=500)
314
+
315
+ @app.route("/<path:path>")
316
+ def serve_files(path: str):
317
+ logger.info(f"Received request for path: {path}")
318
+
319
+ try:
320
+ if path.startswith("drawfsource/"):
321
+ logger.info(f"Handling as drawfsource: {path}")
322
+ return handle_dwarfsource(path)
323
+ elif path.startswith("sourcefiles/"):
324
+ logger.info(f"Handling as sourcefiles: {path}")
325
+ return handle_sourcefile(path)
326
+ else:
327
+ logger.info(f"Handling as local file: {path}")
328
+ return handle_local_file_fetch(path)
329
+ except Exception as e:
330
+ logger.error(f"Error in serve_files for {path}: {e}", exc_info=True)
331
+ return Response(f"Server error: {str(e)}", status=500)
332
+
333
+ @app.errorhandler(Exception)
334
+ def handle_exception(e):
335
+ """Log any uncaught exceptions"""
336
+ logger.error(f"Unhandled exception: {e}", exc_info=True)
337
+ return Response(f"Server error: {str(e)}", status=500)
338
+
339
+ logger.info("Setting up livereload server")
340
+ server = Server(app.wsgi_app)
341
+ # Watch index.html for changes
342
+ server.watch(str(fastled_js / "index.html"))
343
+ # server.watch(str(fastled_js / "index.js"))
344
+ # server.watch(str(fastled_js / "index.css"))
345
+ # Start the server
346
+ logger.info(f"Starting server on port {port}")
347
+ server.serve(port=port, debug=True)
348
+ except KeyboardInterrupt:
349
+ logger.info("Server stopped by keyboard interrupt")
350
+ import _thread
351
+
352
+ _thread.interrupt_main()
353
+ except Exception as e:
354
+ logger.error(f"Failed to run Flask server: {e}", exc_info=True)
355
+ logger.info("Flask server thread running")
356
+ import _thread
357
+
358
+ _thread.interrupt_main()
359
+
360
+
361
+ def run_flask_in_thread(
362
+ port: int,
363
+ cwd: Path,
364
+ compile_server_port: int,
365
+ certfile: Path | None = None,
366
+ keyfile: Path | None = None,
367
+ ) -> None:
368
+ """Run the Flask server."""
369
+ try:
370
+ if _ENABLE_LOGGING:
371
+ logger.info(f"Starting Flask server thread on port {port}")
372
+ logger.info(f"Serving files from {cwd}")
373
+ logger.info(f"Compile server port: {compile_server_port}")
374
+ if certfile:
375
+ logger.info(f"Using SSL certificate: {certfile}")
376
+ if keyfile:
377
+ logger.info(f"Using SSL key: {keyfile}")
378
+
379
+ _run_flask_server(cwd, port, compile_server_port, certfile, keyfile)
380
+ except KeyboardInterrupt:
381
+ logger.info("Flask server thread stopped by keyboard interrupt")
382
+ import _thread
383
+
384
+ _thread.interrupt_main()
385
+ pass
386
+ except Exception as e:
387
+ logger.error(f"Error in Flask server thread: {e}", exc_info=True)
388
+
389
+
390
+ def parse_args() -> argparse.Namespace:
391
+ """Parse the command line arguments."""
392
+ parser = argparse.ArgumentParser(
393
+ description="Open a browser to the fastled_js directory"
394
+ )
395
+ parser.add_argument(
396
+ "fastled_js", type=Path, help="Path to the fastled_js directory"
397
+ )
398
+ parser.add_argument(
399
+ "--port",
400
+ "-p",
401
+ type=int,
402
+ required=True,
403
+ help="Port to run the server on (default: %(default)s)",
404
+ )
405
+ parser.add_argument(
406
+ "--certfile",
407
+ type=Path,
408
+ help="Path to the SSL certificate file for HTTPS",
409
+ )
410
+ parser.add_argument(
411
+ "--keyfile",
412
+ type=Path,
413
+ help="Path to the SSL key file for HTTPS",
414
+ )
415
+ return parser.parse_args()
416
+
417
+
418
+ def run_flask_server_process(
419
+ port: int,
420
+ cwd: Path,
421
+ compile_server_port: int,
422
+ certfile: Path | None = None,
423
+ keyfile: Path | None = None,
424
+ ) -> Process:
425
+ """Run the Flask server in a separate process."""
426
+ if _ENABLE_LOGGING:
427
+ logger.info(f"Starting Flask server process on port {port}")
428
+ logger.info(f"Serving files from {cwd}")
429
+ logger.info(f"Compile server port: {compile_server_port}")
430
+
431
+ process = Process(
432
+ target=run_flask_in_thread,
433
+ args=(port, cwd, compile_server_port, certfile, keyfile),
434
+ )
435
+ process.start()
436
+ if _ENABLE_LOGGING:
437
+ logger.info(f"Flask server process started with PID {process.pid}")
438
+ return process
439
+
440
+
441
+ def main() -> None:
442
+ """Main function."""
443
+ if _ENABLE_LOGGING:
444
+ logger.info("Starting main function")
445
+ args = parse_args()
446
+ if _ENABLE_LOGGING:
447
+ logger.info(f"Arguments: port={args.port}, fastled_js={args.fastled_js}")
448
+ if args.certfile:
449
+ logger.info(f"Using SSL certificate: {args.certfile}")
450
+ if args.keyfile:
451
+ logger.info(f"Using SSL key: {args.keyfile}")
452
+ logger.warning("Note: main() is missing compile_server_port parameter")
453
+
454
+ # Note: This call is missing the compile_server_port parameter
455
+ # This is a bug in the original code
456
+ run_flask_in_thread(args.port, args.fastled_js, 0, args.certfile, args.keyfile)
457
+ if _ENABLE_LOGGING:
458
+ logger.info("Main function completed")
459
+
460
+
461
+ if __name__ == "__main__":
462
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.83
3
+ Version: 1.2.84
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -9,6 +9,7 @@ README.md
9
9
  RELEASE.md
10
10
  TODO.md
11
11
  build_exe.py
12
+ build_local_docker.py
12
13
  build_site.py
13
14
  clean
14
15
  docker-compose.yml
@@ -11,6 +11,10 @@ if [ ! -d ".venv" ]; then
11
11
  fi
12
12
 
13
13
  . ./activate
14
+
15
+ echo "Rebuilding from local image"
16
+ uv run build_local_docker.py
17
+
14
18
  pytest -x -v -s --durations=0 tests/unit "$@"
15
19
  # uv run pytest -n auto tests -v
16
20
 
@@ -71,6 +71,9 @@ class FetchSourceFileTester(unittest.TestCase):
71
71
  )
72
72
  if resp.status_code != 200:
73
73
  raise Exception(f"Failed to fetch source file: {resp.status_code}")
74
+ content_length = int(resp.headers["Content-Length"])
75
+ if content_length == 0:
76
+ raise Exception("Content-Length is 0")
74
77
 
75
78
  # now get something similar at static/js/fastled/src/platforms/wasm/js.cpp
76
79
  url = f"http://localhost:{http_port}/drawfsource/js/fastled/src/FastLED.h"