fastled 1.2.79__tar.gz → 1.2.81__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 (134) hide show
  1. {fastled-1.2.79 → fastled-1.2.81}/PKG-INFO +1 -1
  2. {fastled-1.2.79 → fastled-1.2.81}/compiler/compile.py +29 -51
  3. fastled-1.2.81/compiler/debug.sh +6 -0
  4. {fastled-1.2.79 → fastled-1.2.81}/compiler/init_runtime.py +3 -0
  5. {fastled-1.2.79 → fastled-1.2.81}/compiler/paths.py +3 -2
  6. {fastled-1.2.79 → fastled-1.2.81}/compiler/run.py +2 -2
  7. {fastled-1.2.79 → fastled-1.2.81}/compiler/server.py +70 -4
  8. {fastled-1.2.79 → fastled-1.2.81}/docker-compose.yml +2 -1
  9. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/__init__.py +9 -3
  10. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/client_server.py +12 -1
  11. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/compile_server.py +5 -1
  12. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/compile_server_impl.py +24 -1
  13. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/open_browser.py +5 -3
  14. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/server_flask.py +39 -5
  15. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/server_start.py +21 -8
  16. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/types.py +9 -0
  17. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/PKG-INFO +1 -1
  18. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/SOURCES.txt +3 -1
  19. fastled-1.2.81/tests/unit/test_fastled_source_get.py +32 -0
  20. fastled-1.2.81/tests/unit/test_version.py +27 -0
  21. fastled-1.2.79/compiler/init_runtime2.py +0 -79
  22. {fastled-1.2.79 → fastled-1.2.81}/.aiderignore +0 -0
  23. {fastled-1.2.79 → fastled-1.2.81}/.dockerignore +0 -0
  24. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/build_multi_docker_image.yml +0 -0
  25. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/build_webpage.yml +0 -0
  26. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/lint.yml +0 -0
  27. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/publish_release.yml +0 -0
  28. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/template_build_docker_image.yml +0 -0
  29. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/test_build_exe.yml +0 -0
  30. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/test_macos.yml +0 -0
  31. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/test_ubuntu.yml +0 -0
  32. {fastled-1.2.79 → fastled-1.2.81}/.github/workflows/test_win.yml +0 -0
  33. {fastled-1.2.79 → fastled-1.2.81}/.gitignore +0 -0
  34. {fastled-1.2.79 → fastled-1.2.81}/.pylintrc +0 -0
  35. {fastled-1.2.79 → fastled-1.2.81}/.vscode/launch.json +0 -0
  36. {fastled-1.2.79 → fastled-1.2.81}/.vscode/settings.json +0 -0
  37. {fastled-1.2.79 → fastled-1.2.81}/.vscode/tasks.json +0 -0
  38. {fastled-1.2.79 → fastled-1.2.81}/Dockerfile +0 -0
  39. {fastled-1.2.79 → fastled-1.2.81}/LICENSE +0 -0
  40. {fastled-1.2.79 → fastled-1.2.81}/MANIFEST.in +0 -0
  41. {fastled-1.2.79 → fastled-1.2.81}/README.md +0 -0
  42. {fastled-1.2.79 → fastled-1.2.81}/RELEASE.md +0 -0
  43. {fastled-1.2.79 → fastled-1.2.81}/TODO.md +0 -0
  44. {fastled-1.2.79 → fastled-1.2.81}/build_exe.py +0 -0
  45. {fastled-1.2.79 → fastled-1.2.81}/build_site.py +0 -0
  46. {fastled-1.2.79 → fastled-1.2.81}/clean +0 -0
  47. {fastled-1.2.79 → fastled-1.2.81}/compiler/CMakeLists.txt +0 -0
  48. {fastled-1.2.79 → fastled-1.2.81}/compiler/__init__.py +0 -0
  49. {fastled-1.2.79 → fastled-1.2.81}/compiler/arduino-pre-process.sh +0 -0
  50. {fastled-1.2.79 → fastled-1.2.81}/compiler/build.sh +0 -0
  51. {fastled-1.2.79 → fastled-1.2.81}/compiler/build_archive.sh +0 -0
  52. {fastled-1.2.79 → fastled-1.2.81}/compiler/build_fast.sh +0 -0
  53. {fastled-1.2.79 → fastled-1.2.81}/compiler/code_sync.py +0 -0
  54. {fastled-1.2.79 → fastled-1.2.81}/compiler/compile_lock.py +0 -0
  55. {fastled-1.2.79 → fastled-1.2.81}/compiler/entrypoint.sh +0 -0
  56. {fastled-1.2.79 → fastled-1.2.81}/compiler/extra/100dots.html +0 -0
  57. {fastled-1.2.79 → fastled-1.2.81}/compiler/extra/demo_threejs.html +0 -0
  58. {fastled-1.2.79 → fastled-1.2.81}/compiler/extra/micdemo.html +0 -0
  59. {fastled-1.2.79 → fastled-1.2.81}/compiler/extra/mp3upload.html +0 -0
  60. {fastled-1.2.79 → fastled-1.2.81}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
  61. {fastled-1.2.79 → fastled-1.2.81}/compiler/final_prewarm.sh +0 -0
  62. {fastled-1.2.79 → fastled-1.2.81}/compiler/install-arduino-cli.sh +0 -0
  63. {fastled-1.2.79 → fastled-1.2.81}/compiler/libcompile/CMakeLists.txt +0 -0
  64. {fastled-1.2.79 → fastled-1.2.81}/compiler/pre-process.sh +0 -0
  65. {fastled-1.2.79 → fastled-1.2.81}/compiler/prewarm.sh +0 -0
  66. {fastled-1.2.79 → fastled-1.2.81}/compiler/process-ino.py +0 -0
  67. {fastled-1.2.79 → fastled-1.2.81}/compiler/process_extended.py +0 -0
  68. {fastled-1.2.79 → fastled-1.2.81}/compiler/pyproject.toml +0 -0
  69. {fastled-1.2.79 → fastled-1.2.81}/compiler/sketch_hasher.py +0 -0
  70. {fastled-1.2.79 → fastled-1.2.81}/compiler/wasm_compiler_flags.py +0 -0
  71. {fastled-1.2.79 → fastled-1.2.81}/install +0 -0
  72. {fastled-1.2.79 → fastled-1.2.81}/install_linux.sh +0 -0
  73. {fastled-1.2.79 → fastled-1.2.81}/lint +0 -0
  74. {fastled-1.2.79 → fastled-1.2.81}/pyproject.toml +0 -0
  75. {fastled-1.2.79 → fastled-1.2.81}/requirements.testing.txt +0 -0
  76. {fastled-1.2.79 → fastled-1.2.81}/setup.cfg +0 -0
  77. {fastled-1.2.79 → fastled-1.2.81}/setup.py +0 -0
  78. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/app.py +0 -0
  79. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/assets/example.txt +0 -0
  80. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/assets/localhost-key.pem +0 -0
  81. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/assets/localhost.pem +0 -0
  82. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/cli.py +0 -0
  83. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/cli_test.py +0 -0
  84. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/cli_test_interactive.py +0 -0
  85. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/docker_manager.py +0 -0
  86. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/filewatcher.py +0 -0
  87. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/keyboard.py +0 -0
  88. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/keyz.py +0 -0
  89. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/live_client.py +0 -0
  90. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/parse_args.py +0 -0
  91. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/paths.py +0 -0
  92. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/print_filter.py +0 -0
  93. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/project_init.py +0 -0
  94. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/select_sketch_directory.py +0 -0
  95. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/server_fastapi.py +0 -0
  96. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/server_fastapi_cli.py +0 -0
  97. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/settings.py +0 -0
  98. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/site/build.py +0 -0
  99. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/site/examples.py +0 -0
  100. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/sketch.py +0 -0
  101. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/spinner.py +0 -0
  102. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/string_diff.py +0 -0
  103. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/test/can_run_local_docker_tests.py +0 -0
  104. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/test/examples.py +0 -0
  105. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/util.py +0 -0
  106. {fastled-1.2.79 → fastled-1.2.81}/src/fastled/web_compile.py +0 -0
  107. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/dependency_links.txt +0 -0
  108. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/entry_points.txt +0 -0
  109. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/requires.txt +0 -0
  110. {fastled-1.2.79 → fastled-1.2.81}/src/fastled.egg-info/top_level.txt +0 -0
  111. {fastled-1.2.79 → fastled-1.2.81}/test +0 -0
  112. {fastled-1.2.79 → fastled-1.2.81}/tests/integration/test_build_examples.py +0 -0
  113. {fastled-1.2.79 → fastled-1.2.81}/tests/integration/test_examples.py +0 -0
  114. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/html/index.html +0 -0
  115. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_api.py +0 -0
  116. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_bad_ino.py +0 -0
  117. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_cli.py +0 -0
  118. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_compile_server.py +0 -0
  119. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_docker_linux_on_windows.py +0 -0
  120. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_embedded_data.py +0 -0
  121. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_filechanger.py +0 -0
  122. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_http_server.py +0 -0
  123. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/bad/bad.ino +0 -0
  124. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
  125. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
  126. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
  127. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/embedded/wasm.ino +0 -0
  128. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_ino/wasm/wasm.ino +0 -0
  129. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_print_filter.py +0 -0
  130. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_project_init.py +0 -0
  131. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_server_and_client_seperatly.py +0 -0
  132. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_string_diff.py +0 -0
  133. {fastled-1.2.79 → fastled-1.2.81}/tests/unit/test_webcompile.py +0 -0
  134. {fastled-1.2.79 → fastled-1.2.81}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.79
3
+ Version: 1.2.81
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -21,6 +21,7 @@ import shutil # noqa: E402
21
21
  import subprocess # noqa: E402
22
22
  import sys # noqa: E402
23
23
  import traceback # noqa: E402
24
+ import warnings # noqa: E402
24
25
  from dataclasses import dataclass # noqa: E402
25
26
  from datetime import datetime # noqa: E402
26
27
  from enum import Enum # noqa: E402
@@ -51,10 +52,7 @@ _FILE_EXTENSIONS = [".ino", ".h", ".hpp", ".cpp"]
51
52
  _FASTLED_OUTPUT_DIR_NAME = "fastled_js"
52
53
 
53
54
 
54
- @dataclass
55
- class DateLine:
56
- dt: datetime
57
- line: str
55
+ # DateLine class removed as it's no longer needed with streaming timestamps
58
56
 
59
57
 
60
58
  class BuildMode(Enum):
@@ -87,6 +85,8 @@ def copy_files(src_dir: Path, js_src: Path) -> None:
87
85
  else:
88
86
  print(f"Copying file: {item}")
89
87
  shutil.copy2(item, js_src / item.name)
88
+ else:
89
+ warnings.warn(f"No files found in the mapped directory: {src_dir.absolute()}")
90
90
 
91
91
 
92
92
  def _banner(msg: str) -> str:
@@ -172,19 +172,23 @@ def compile(
172
172
  )
173
173
  return out
174
174
 
175
- output_lines = []
176
175
  for attempt in range(1, max_attempts + 1):
177
176
  try:
178
177
  print(f"Attempting compilation (attempt {attempt}/{max_attempts})...")
179
178
  process = _open_process()
180
179
  assert process.stdout is not None
180
+
181
+ # Create a new timestamper for this compilation attempt
182
+ timestamper = StreamingTimestamper()
183
+
184
+ # Process and print each line as it comes in with relative timestamp
181
185
  line: str
182
186
  for line in process.stdout:
183
- timestamped_line = _timestamp_output(line)
184
- output_lines.append(timestamped_line)
187
+ timestamped_line = timestamper.timestamp_line(line)
188
+ print(timestamped_line)
189
+
185
190
  process.wait()
186
- relative_output = _make_timestamps_relative("\n".join(output_lines))
187
- _chunked_print(relative_output)
191
+
188
192
  if process.returncode == 0:
189
193
  print(_banner(f"Compilation successful on attempt {attempt}"))
190
194
  return 0
@@ -264,51 +268,25 @@ def process_ino_files(src_dir: Path) -> None:
264
268
  print(_banner("Transform to cpp and insert header operations completed."))
265
269
 
266
270
 
267
- def _make_timestamps_relative(stdout: str) -> str:
268
- def parse(line: str) -> DateLine:
269
- parts = line.split(" ")
270
- if len(parts) < 2:
271
- raise ValueError(f"Invalid line: {line}")
272
-
273
- date_str, time_str = parts[:2]
274
- rest = " ".join(parts[2:])
275
- # Parse with microsecond precision
276
- dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M:%S.%f")
277
- return DateLine(dt, rest)
278
-
279
- lines = stdout.split("\n")
280
- if not lines:
281
- return stdout
282
- parsed: list[DateLine] = []
283
- for line in lines:
284
- if not line.strip(): # Skip empty lines
285
- continue
286
- try:
287
- parsed.append(parse(line))
288
- except ValueError:
289
- print(f"Failed to parse line: {line}")
290
- continue
291
-
292
- if not parsed:
293
- return stdout
271
+ class StreamingTimestamper:
272
+ """
273
+ A class that provides streaming relative timestamps for output lines.
274
+ Instead of processing all lines at the end, this timestamps each line
275
+ as it's received with a relative time from when the object was created.
276
+ """
294
277
 
295
- outlines: list[str] = []
296
- start_time = parsed[0].dt
278
+ def __init__(self):
279
+ self.start_time = datetime.now()
297
280
 
298
- # Calculate relative times with
299
- for p in parsed:
300
- delta = p.dt - start_time
281
+ def timestamp_line(self, line: str) -> str:
282
+ """
283
+ Add a relative timestamp to a line of text.
284
+ The timestamp shows seconds elapsed since the StreamingTimestamper was created.
285
+ """
286
+ now = datetime.now()
287
+ delta = now - self.start_time
301
288
  seconds = delta.total_seconds()
302
- line_str = f"{seconds:3.2f} {p.line}"
303
- outlines.append(line_str)
304
-
305
- return "\n".join(outlines)
306
-
307
-
308
- def _timestamp_output(line: str) -> str:
309
- now = datetime.now()
310
- timestamp = now.strftime("%Y-%m-%d %H:%M:%S.%f")
311
- return f"{timestamp} {line.rstrip()}"
289
+ return f"{seconds:3.2f} {line.rstrip()}"
312
290
 
313
291
 
314
292
  @dataclass
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ # Run this when you want to debug the compiler and you are in interactive
4
+ # mode at /js
5
+
6
+ python compile.py --debug --keep
@@ -9,10 +9,13 @@ COMPILER_DIR = COMPILER_ROOT / "compiler"
9
9
 
10
10
  SRC_MAPPED_HOST_COMPLER_DIR = Path("/host/fastled/src/platforms/wasm/compiler")
11
11
  if SRC_MAPPED_HOST_COMPLER_DIR.exists():
12
+ print(f"Using mapped host compiler directory: {SRC_MAPPED_HOST_COMPLER_DIR}")
12
13
  FASTLED_COMPILER_DIR = SRC_MAPPED_HOST_COMPLER_DIR
13
14
  else:
15
+ print(f"Using standard host compiler directory: {SRC_MAPPED_HOST_COMPLER_DIR}")
14
16
  FASTLED_COMPILER_DIR = COMPILER_ROOT / "fastled/src/platforms/wasm/compiler"
15
17
 
18
+
16
19
  HERE = Path(__file__).parent
17
20
 
18
21
 
@@ -4,11 +4,12 @@ UPLOAD_DIR = Path("/uploads")
4
4
  TEMP_DIR = Path("/tmp")
5
5
  OUTPUT_DIR = Path("/output")
6
6
 
7
-
8
7
  COMPILER_ROOT = Path("/js")
8
+ FASTLED_SRC = COMPILER_ROOT / "fastled" / "src"
9
+
10
+
9
11
  SKETCH_SRC = COMPILER_ROOT / "src"
10
12
  VOLUME_MAPPED_SRC = Path("/host/fastled/src")
11
- RSYNC_DEST = COMPILER_ROOT / "fastled" / "src"
12
13
  SKETCH_CACHE_FILE = OUTPUT_DIR / "compile_cache.db"
13
14
  LIVE_GIT_FASTLED_DIR = Path("/git/fastled")
14
15
 
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from typing import Tuple
8
8
 
9
9
  from code_sync import CodeSync
10
- from paths import RSYNC_DEST, VOLUME_MAPPED_SRC
10
+ from paths import FASTLED_SRC, VOLUME_MAPPED_SRC
11
11
 
12
12
  _PORT = os.environ.get("PORT", 80)
13
13
 
@@ -106,7 +106,7 @@ def main() -> int:
106
106
 
107
107
  code_sync = CodeSync(
108
108
  volume_mapped_src=VOLUME_MAPPED_SRC,
109
- rsync_dest=RSYNC_DEST,
109
+ rsync_dest=FASTLED_SRC,
110
110
  )
111
111
  code_sync.sync_source_directory_if_volume_is_mapped()
112
112
 
@@ -28,10 +28,10 @@ from fastapi import ( # type: ignore
28
28
  UploadFile,
29
29
  )
30
30
  from fastapi.responses import FileResponse, RedirectResponse, Response # type: ignore
31
+ from paths import FASTLED_SRC # The folder where the actual source code is located.
31
32
  from paths import (
32
33
  LIVE_GIT_FASTLED_DIR,
33
34
  OUTPUT_DIR,
34
- RSYNC_DEST,
35
35
  SKETCH_CACHE_FILE,
36
36
  TEMP_DIR,
37
37
  UPLOAD_DIR,
@@ -131,7 +131,7 @@ class UploadSizeMiddleware(BaseHTTPMiddleware):
131
131
 
132
132
  _CODE_SYNC = CodeSync(
133
133
  volume_mapped_src=VOLUME_MAPPED_SRC,
134
- rsync_dest=RSYNC_DEST,
134
+ rsync_dest=FASTLED_SRC,
135
135
  )
136
136
 
137
137
 
@@ -226,12 +226,12 @@ def sync_live_git_to_target() -> None:
226
226
 
227
227
  _CODE_SYNC.sync_src_to_target(
228
228
  volume_mapped_src=LIVE_GIT_FASTLED_DIR / "src",
229
- rsync_dest=RSYNC_DEST,
229
+ rsync_dest=FASTLED_SRC,
230
230
  callback=on_files_changed,
231
231
  )
232
232
  _CODE_SYNC.sync_src_to_target(
233
233
  volume_mapped_src=LIVE_GIT_FASTLED_DIR / "examples",
234
- rsync_dest=RSYNC_DEST.parent / "examples",
234
+ rsync_dest=FASTLED_SRC.parent / "examples",
235
235
  callback=on_files_changed,
236
236
  )
237
237
  # Basically a setTimeout() in JS.
@@ -598,6 +598,72 @@ def project_init_example(
598
598
  )
599
599
 
600
600
 
601
+ @app.get("/sourcefiles/{filepath:path}")
602
+ def source_file(filepath: str) -> FileResponse:
603
+ """Get the source file from the server."""
604
+ print(f"Endpoint accessed: /sourcefiles/{filepath}")
605
+ if ".." in filepath:
606
+ raise HTTPException(status_code=400, detail="Invalid file path.")
607
+ 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
633
+
634
+
635
+ @app.get("/static/{file_path:path}")
636
+ async def static_files(file_path: str) -> Response:
637
+ """Serve static files."""
638
+ print(f"Endpoint accessed: /static/{file_path}")
639
+
640
+ # Check if path matches the pattern js/fastled/src/...
641
+ if file_path.startswith("js/fastled/src/"):
642
+ # Extract the path after "js/fastled/src/"
643
+ relative_path = file_path[len("js/fastled/src/") :]
644
+ 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)
660
+
661
+ # If file not found or path doesn't match expected format
662
+ return Response(
663
+ content=f"File not found: {file_path}", media_type="text/plain", status_code=404
664
+ )
665
+
666
+
601
667
  @app.get("/info")
602
668
  def info_examples() -> dict:
603
669
  """Get a list of examples."""
@@ -4,6 +4,7 @@ services:
4
4
  build:
5
5
  context: .
6
6
  ports:
7
- - "80:80"
7
+ - "8234:80"
8
8
  environment:
9
9
  - ENVIRONMENT=dev
10
+ image: niteris/fastled-wasm:latest
@@ -9,12 +9,12 @@ from .compile_server import CompileServer
9
9
  from .live_client import LiveClient
10
10
  from .settings import DOCKER_FILE, IMAGE_NAME
11
11
  from .site.build import build
12
- from .types import BuildMode, CompileResult, CompileServerError
12
+ from .types import BuildMode, CompileResult, CompileServerError, FileResponse
13
13
 
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.79"
17
+ __version__ = "1.2.81"
18
18
 
19
19
 
20
20
  class Api:
@@ -194,14 +194,19 @@ class Test:
194
194
  def spawn_http_server(
195
195
  directory: Path | str = Path("."),
196
196
  port: int | None = None,
197
+ compile_server_port: int | None = None,
197
198
  open_browser: bool = True,
198
199
  ) -> Process:
199
200
  from fastled.open_browser import open_browser_process
200
201
 
202
+ compile_server_port = compile_server_port or -1
201
203
  if isinstance(directory, str):
202
204
  directory = Path(directory)
203
205
  proc: Process = open_browser_process(
204
- directory, port=port, open_browser=open_browser
206
+ directory,
207
+ port=port,
208
+ compile_server_port=compile_server_port,
209
+ open_browser=open_browser,
205
210
  )
206
211
  return proc
207
212
 
@@ -213,5 +218,6 @@ __all__ = [
213
218
  "CompileResult",
214
219
  "CompileServerError",
215
220
  "BuildMode",
221
+ "FileResponse",
216
222
  "DOCKER_FILE",
217
223
  ]
@@ -188,6 +188,15 @@ def run_client(
188
188
  return DEFAULT_URL
189
189
 
190
190
  url = get_url()
191
+ # parse out the port from the url
192
+ # use a standard host address parser to grab it
193
+ import urllib.parse
194
+
195
+ parsed_url = urllib.parse.urlparse(url)
196
+ if parsed_url.port is not None:
197
+ port = parsed_url.port
198
+ else:
199
+ port = 80
191
200
 
192
201
  try:
193
202
 
@@ -214,7 +223,9 @@ def run_client(
214
223
 
215
224
  browser_proc: Process | None = None
216
225
  if open_web_browser:
217
- browser_proc = open_browser_process(directory / "fastled_js")
226
+ browser_proc = open_browser_process(
227
+ directory / "fastled_js", compile_server_port=port
228
+ )
218
229
  else:
219
230
  print("\nCompilation successful.")
220
231
  if compile_server:
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from fastled.types import BuildMode, CompileResult, Platform
3
+ from fastled.types import BuildMode, CompileResult, FileResponse, Platform
4
4
 
5
5
 
6
6
  class CompileServer:
@@ -53,6 +53,10 @@ class CompileServer:
53
53
 
54
54
  project_init(example=example, outputdir=outputdir)
55
55
 
56
+ def fetch_source_file(self, filepath: str) -> FileResponse | Exception:
57
+ """Get the source file from the server."""
58
+ return self.impl.fetch_source_file(filepath)
59
+
56
60
  @property
57
61
  def name(self) -> str:
58
62
  return self.impl.container_name
@@ -17,7 +17,7 @@ from fastled.docker_manager import (
17
17
  )
18
18
  from fastled.settings import DEFAULT_CONTAINER_NAME, IMAGE_NAME, SERVER_PORT
19
19
  from fastled.sketch import looks_like_fastled_repo
20
- from fastled.types import BuildMode, CompileResult, CompileServerError
20
+ from fastled.types import BuildMode, CompileResult, CompileServerError, FileResponse
21
21
 
22
22
  SERVER_OPTIONS = [
23
23
  "--allow-shutdown", # Allow the server to be shut down without a force kill.
@@ -204,6 +204,29 @@ class CompileServerImpl:
204
204
  return False
205
205
  return False
206
206
 
207
+ def fetch_source_file(self, filepath: str) -> FileResponse | Exception:
208
+ """Get the source file from the server."""
209
+ if not self._port:
210
+ raise RuntimeError("Server has not been started yet")
211
+ try:
212
+ httpx_client = httpx.Client()
213
+ url = f"http://localhost:{self._port}/sourcefiles/{filepath}"
214
+ response = httpx_client.get(url, follow_redirects=True)
215
+ if response.status_code == 200:
216
+ content = response.text
217
+ mimetype: str = response.headers.get("Content-Type", "text/plain")
218
+ return FileResponse(
219
+ content=content,
220
+ mimetype=mimetype,
221
+ filename=filepath,
222
+ )
223
+ else:
224
+ return CompileServerError(
225
+ f"Error fetching file {filepath}: {response.status_code}"
226
+ )
227
+ except httpx.RequestError as e:
228
+ return CompileServerError(f"Error fetching file {filepath}: {e}")
229
+
207
230
  def _start(self) -> int:
208
231
  print("Compiling server starting")
209
232
 
@@ -14,8 +14,7 @@ PYTHON_EXE = sys.executable
14
14
 
15
15
 
16
16
  def _open_http_server_subprocess(
17
- fastled_js: Path,
18
- port: int,
17
+ fastled_js: Path, port: int, compile_server_port: int
19
18
  ) -> None:
20
19
  print("\n################################################################")
21
20
  print(f"# Opening browser to {fastled_js} on port {port}")
@@ -30,6 +29,8 @@ def _open_http_server_subprocess(
30
29
  str(fastled_js),
31
30
  "--port",
32
31
  str(port),
32
+ "--compile-server-port",
33
+ str(compile_server_port),
33
34
  ]
34
35
  # Pass SSL flags if available
35
36
  if ssl:
@@ -89,6 +90,7 @@ def wait_for_server(port: int, timeout: int = 10) -> None:
89
90
 
90
91
  def open_browser_process(
91
92
  fastled_js: Path,
93
+ compile_server_port: int,
92
94
  port: int | None = None,
93
95
  open_browser: bool = True,
94
96
  ) -> Process:
@@ -100,7 +102,7 @@ def open_browser_process(
100
102
 
101
103
  proc = Process(
102
104
  target=_open_http_server_subprocess,
103
- args=(fastled_js, port),
105
+ args=(fastled_js, port, compile_server_port),
104
106
  daemon=True,
105
107
  )
106
108
  proc.start()
@@ -2,12 +2,14 @@ import argparse
2
2
  from multiprocessing import Process
3
3
  from pathlib import Path
4
4
 
5
+ import requests
5
6
  from livereload import Server
6
7
 
7
8
 
8
9
  def _run_flask_server(
9
10
  fastled_js: Path,
10
11
  port: int,
12
+ compile_server_port: int,
11
13
  certfile: Path | None = None,
12
14
  keyfile: Path | None = None,
13
15
  ) -> None:
@@ -27,10 +29,38 @@ def _run_flask_server(
27
29
  # Must be a full path or flask will fail to find the file.
28
30
  fastled_js = fastled_js.resolve()
29
31
 
32
+ print(f"Compile server port is at {compile_server_port}")
33
+
30
34
  @app.route("/")
31
35
  def serve_index():
32
36
  return send_from_directory(fastled_js, "index.html")
33
37
 
38
+ @app.route("/static/<path:path>")
39
+ def proxy_static(path):
40
+ """Proxy requests to /static/* to the compile server"""
41
+ from flask import Response, request
42
+
43
+ # Forward the request to the compile server
44
+ target_url = f"http://localhost:{compile_server_port}/static/{path}"
45
+
46
+ # Forward the request with the same method, headers, and body
47
+ resp = requests.request(
48
+ method=request.method,
49
+ url=target_url,
50
+ headers={key: value for key, value in request.headers if key != "Host"},
51
+ data=request.get_data(),
52
+ cookies=request.cookies,
53
+ allow_redirects=True,
54
+ stream=False,
55
+ )
56
+
57
+ # Create a Flask Response object from the requests response
58
+ response = Response(
59
+ resp.raw.read(), status=resp.status_code, headers=dict(resp.headers)
60
+ )
61
+
62
+ return response
63
+
34
64
  @app.route("/<path:path>")
35
65
  def serve_files(path):
36
66
  response = send_from_directory(fastled_js, path)
@@ -83,11 +113,15 @@ def _run_flask_server(
83
113
 
84
114
 
85
115
  def run(
86
- port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
116
+ port: int,
117
+ cwd: Path,
118
+ compile_server_port: int,
119
+ certfile: Path | None = None,
120
+ keyfile: Path | None = None,
87
121
  ) -> None:
88
122
  """Run the Flask server."""
89
123
  try:
90
- _run_flask_server(cwd, port, certfile, keyfile)
124
+ _run_flask_server(cwd, port, compile_server_port, certfile, keyfile)
91
125
  import warnings
92
126
 
93
127
  warnings.warn("Flask server has stopped")
@@ -128,15 +162,15 @@ def parse_args() -> argparse.Namespace:
128
162
 
129
163
  def run_flask_server_process(
130
164
  port: int,
131
- cwd: Path | None = None,
165
+ cwd: Path,
166
+ compile_server_port: int,
132
167
  certfile: Path | None = None,
133
168
  keyfile: Path | None = None,
134
169
  ) -> Process:
135
170
  """Run the Flask server in a separate process."""
136
- cwd = cwd or Path(".")
137
171
  process = Process(
138
172
  target=run,
139
- args=(port, cwd, certfile, keyfile),
173
+ args=(port, cwd, compile_server_port, certfile, keyfile),
140
174
  )
141
175
  process.start()
142
176
  return process
@@ -9,7 +9,11 @@ from fastled.server_flask import run_flask_server_process
9
9
 
10
10
 
11
11
  def run_server_process(
12
- port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
12
+ port: int,
13
+ cwd: Path,
14
+ compile_server_port: int,
15
+ certfile: Path | None = None,
16
+ keyfile: Path | None = None,
13
17
  ) -> Process:
14
18
  """Run the server in a separate process."""
15
19
  if True:
@@ -17,6 +21,7 @@ def run_server_process(
17
21
  process = run_flask_server_process(
18
22
  port=port,
19
23
  cwd=cwd,
24
+ compile_server_port=compile_server_port,
20
25
  certfile=certfile,
21
26
  keyfile=keyfile,
22
27
  )
@@ -45,19 +50,18 @@ def get_asset_path(filename: str) -> Path | None:
45
50
  def start_process(
46
51
  path: Path,
47
52
  port: int,
48
- certfile: Path | None = None,
49
- keyfile: Path | None = None,
53
+ compile_server_port: int,
54
+ certfile: Path | None = None, # reserved for future use
55
+ keyfile: Path | None = None, # reserved for future use
50
56
  ) -> Process:
51
57
  """Run the server, using package assets if explicit paths are not provided"""
52
58
  # Use package resources if no explicit path
53
- if certfile is None:
54
- certfile = get_asset_path("localhost.pem")
55
- if keyfile is None:
56
- keyfile = get_asset_path("localhost-key.pem")
57
59
 
58
60
  # _run_flask_server(path, port, certfile, keyfile)
59
61
  # run_fastapi_server_process(port=port, path=path, certfile=certfile, keyfile=keyfile)
60
- proc = run_server_process(port=port, cwd=path)
62
+ proc = run_server_process(
63
+ port=port, cwd=path, compile_server_port=compile_server_port
64
+ )
61
65
  # try:
62
66
  # proc.join()
63
67
  # except KeyboardInterrupt:
@@ -71,6 +75,7 @@ def start_process(
71
75
  class Args:
72
76
  fastled_js: Path
73
77
  port: int
78
+ compile_server_port: int
74
79
  cert: Path | None
75
80
  key: Path | None
76
81
 
@@ -89,6 +94,12 @@ def parse_args() -> Args:
89
94
  default=5500,
90
95
  help="Port to run the server on (default: 5500)",
91
96
  )
97
+ parser.add_argument(
98
+ "--compile-server-port",
99
+ type=int,
100
+ required=True,
101
+ help="Used to forward requests to the compile server",
102
+ )
92
103
  parser.add_argument(
93
104
  "--cert", type=Path, help="(Optional) Path to SSL certificate (PEM format)"
94
105
  )
@@ -99,6 +110,7 @@ def parse_args() -> Args:
99
110
  out: Args = Args(
100
111
  fastled_js=args.fastled_js,
101
112
  port=args.port,
113
+ compile_server_port=args.compile_server_port,
102
114
  cert=args.cert,
103
115
  key=args.key,
104
116
  )
@@ -116,6 +128,7 @@ def main() -> None:
116
128
  proc = start_process(
117
129
  path=fastled_js,
118
130
  port=port,
131
+ compile_server_port=args.compile_server_port,
119
132
  certfile=cert,
120
133
  keyfile=key,
121
134
  )
@@ -151,3 +151,12 @@ class Platform(Enum):
151
151
  raise ValueError(
152
152
  f"Platform must be one of {valid_modes}, got {platform_str}"
153
153
  )
154
+
155
+
156
+ @dataclass
157
+ class FileResponse:
158
+ """File response from the server."""
159
+
160
+ filename: str
161
+ content: str
162
+ mimetype: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.79
3
+ Version: 1.2.81
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies