fastled 1.1.0__tar.gz → 1.1.3__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 (89) hide show
  1. {fastled-1.1.0 → fastled-1.1.3}/PKG-INFO +4 -2
  2. {fastled-1.1.0 → fastled-1.1.3}/README.md +3 -1
  3. {fastled-1.1.0 → fastled-1.1.3}/examples/wasm/wasm.ino +5 -9
  4. {fastled-1.1.0 → fastled-1.1.3}/pyproject.toml +1 -1
  5. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/app.py +74 -9
  6. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/compile_server.py +98 -41
  7. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/docker_manager.py +8 -6
  8. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/web_compile.py +220 -173
  9. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/PKG-INFO +4 -2
  10. {fastled-1.1.0 → fastled-1.1.3}/test +1 -1
  11. {fastled-1.1.0 → fastled-1.1.3}/tests/test_compile_server.py +1 -1
  12. {fastled-1.1.0 → fastled-1.1.3}/tests/test_webcompile.py +1 -1
  13. {fastled-1.1.0 → fastled-1.1.3}/.aiderignore +0 -0
  14. {fastled-1.1.0 → fastled-1.1.3}/.github/workflows/build_multi_docker_image.yml +0 -0
  15. {fastled-1.1.0 → fastled-1.1.3}/.github/workflows/lint.yml +0 -0
  16. {fastled-1.1.0 → fastled-1.1.3}/.github/workflows/test_macos.yml +0 -0
  17. {fastled-1.1.0 → fastled-1.1.3}/.github/workflows/test_ubuntu.yml +0 -0
  18. {fastled-1.1.0 → fastled-1.1.3}/.github/workflows/test_win.yml +0 -0
  19. {fastled-1.1.0 → fastled-1.1.3}/.gitignore +0 -0
  20. {fastled-1.1.0 → fastled-1.1.3}/.pylintrc +0 -0
  21. {fastled-1.1.0 → fastled-1.1.3}/.vscode/launch.json +0 -0
  22. {fastled-1.1.0 → fastled-1.1.3}/.vscode/settings.json +0 -0
  23. {fastled-1.1.0 → fastled-1.1.3}/.vscode/tasks.json +0 -0
  24. {fastled-1.1.0 → fastled-1.1.3}/LICENSE +0 -0
  25. {fastled-1.1.0 → fastled-1.1.3}/MANIFEST.in +0 -0
  26. {fastled-1.1.0 → fastled-1.1.3}/clean +0 -0
  27. {fastled-1.1.0 → fastled-1.1.3}/docs/fastled.js +0 -0
  28. {fastled-1.1.0 → fastled-1.1.3}/docs/fastled.wasm +0 -0
  29. {fastled-1.1.0 → fastled-1.1.3}/docs/index.css +0 -0
  30. {fastled-1.1.0 → fastled-1.1.3}/docs/index.html +0 -0
  31. {fastled-1.1.0 → fastled-1.1.3}/docs/index.js +0 -0
  32. {fastled-1.1.0 → fastled-1.1.3}/examples/Blink/Blink.ino +0 -0
  33. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/Chromancer.ino +0 -0
  34. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/detail.h +0 -0
  35. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/gary_woos_wled_port/gary_woos_wled_ledmap.h +0 -0
  36. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/gary_woos_wled_port/presets.json +0 -0
  37. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/gary_woos_wled_port/presets.min.json +0 -0
  38. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/gen.py +0 -0
  39. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/mapping.h +0 -0
  40. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/net.h +0 -0
  41. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/output.json +0 -0
  42. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/ripple.h +0 -0
  43. {fastled-1.1.0 → fastled-1.1.3}/examples/Chromancer/screenmap.json.h +0 -0
  44. {fastled-1.1.0 → fastled-1.1.3}/examples/ColorPalette/ColorPalette.ino +0 -0
  45. {fastled-1.1.0 → fastled-1.1.3}/examples/ColorTemperature/ColorTemperature.ino +0 -0
  46. {fastled-1.1.0 → fastled-1.1.3}/examples/Cylon/Cylon.ino +0 -0
  47. {fastled-1.1.0 → fastled-1.1.3}/examples/DemoReel100/DemoReel100.ino +0 -0
  48. {fastled-1.1.0 → fastled-1.1.3}/examples/Esp32Rmt51/Esp32Rmt51.ino +0 -0
  49. {fastled-1.1.0 → fastled-1.1.3}/examples/EspI2SDemo/EspI2SDemo.ino +0 -0
  50. {fastled-1.1.0 → fastled-1.1.3}/examples/Fire2012/Fire2012.ino +0 -0
  51. {fastled-1.1.0 → fastled-1.1.3}/examples/Fire2012WithPalette/Fire2012WithPalette.ino +0 -0
  52. {fastled-1.1.0 → fastled-1.1.3}/examples/FirstLight/FirstLight.ino +0 -0
  53. {fastled-1.1.0 → fastled-1.1.3}/examples/FxEngine/FxEngine.ino +0 -0
  54. {fastled-1.1.0 → fastled-1.1.3}/examples/Noise/Noise.ino +0 -0
  55. {fastled-1.1.0 → fastled-1.1.3}/examples/NoisePlayground/NoisePlayground.ino +0 -0
  56. {fastled-1.1.0 → fastled-1.1.3}/examples/NoisePlusPalette/NoisePlusPalette.ino +0 -0
  57. {fastled-1.1.0 → fastled-1.1.3}/examples/OctoWS2811/OctoWS2811.ino +0 -0
  58. {fastled-1.1.0 → fastled-1.1.3}/examples/Pacifica/Pacifica.ino +0 -0
  59. {fastled-1.1.0 → fastled-1.1.3}/examples/Pride2015/Pride2015.ino +0 -0
  60. {fastled-1.1.0 → fastled-1.1.3}/examples/SdCard/SdCard.ino +0 -0
  61. {fastled-1.1.0 → fastled-1.1.3}/examples/TwinkleFox/TwinkleFox.ino +0 -0
  62. {fastled-1.1.0 → fastled-1.1.3}/examples/Video/Gfx2Video/Gfx2Video.ino +0 -0
  63. {fastled-1.1.0 → fastled-1.1.3}/examples/WasmScreenCoords/WasmScreenCoords.ino +0 -0
  64. {fastled-1.1.0 → fastled-1.1.3}/examples/Water/Water.ino +0 -0
  65. {fastled-1.1.0 → fastled-1.1.3}/examples/XYMatrix/XYMatrix.ino +0 -0
  66. {fastled-1.1.0 → fastled-1.1.3}/install +0 -0
  67. {fastled-1.1.0 → fastled-1.1.3}/lint +0 -0
  68. {fastled-1.1.0 → fastled-1.1.3}/requirements.testing.txt +0 -0
  69. {fastled-1.1.0 → fastled-1.1.3}/setup.cfg +0 -0
  70. {fastled-1.1.0 → fastled-1.1.3}/setup.py +0 -0
  71. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/__init__.py +0 -0
  72. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/assets/example.txt +0 -0
  73. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/build_mode.py +0 -0
  74. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/check_cpp_syntax.py +0 -0
  75. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/cli.py +0 -0
  76. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/filewatcher.py +0 -0
  77. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/open_browser.py +0 -0
  78. {fastled-1.1.0 → fastled-1.1.3}/src/fastled/paths.py +0 -0
  79. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/SOURCES.txt +0 -0
  80. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/dependency_links.txt +0 -0
  81. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/entry_points.txt +0 -0
  82. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/requires.txt +0 -0
  83. {fastled-1.1.0 → fastled-1.1.3}/src/fastled.egg-info/top_level.txt +0 -0
  84. {fastled-1.1.0 → fastled-1.1.3}/tests/test_bad_ino.py +0 -0
  85. {fastled-1.1.0 → fastled-1.1.3}/tests/test_cli.py +0 -0
  86. {fastled-1.1.0 → fastled-1.1.3}/tests/test_filechanger.py +0 -0
  87. {fastled-1.1.0 → fastled-1.1.3}/tests/test_ino/bad/bad.ino +0 -0
  88. {fastled-1.1.0 → fastled-1.1.3}/tests/test_ino/wasm/wasm.ino +0 -0
  89. {fastled-1.1.0 → fastled-1.1.3}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.0
3
+ Version: 1.1.3
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -28,7 +28,6 @@ Compiles an Arduino/Platformio sketch into a wasm binary that can be run directl
28
28
  [![Win_Tests](https://github.com/zackees/fastled-wasm/actions/workflows/test_win.yml/badge.svg)](https://github.com/zackees/fastled-wasm/actions/workflows/test_win.yml)
29
29
 
30
30
 
31
-
32
31
  # About
33
32
 
34
33
  This python app will compile your FastLED style sketches into html/js/wasm output that runs directly in the browser.
@@ -92,6 +91,9 @@ provide shims for most of the common api points.
92
91
 
93
92
  # Revisions
94
93
 
94
+ * 1.1.3 - Live editing of *.h and *.cpp files is now possible. Sketch cache will be disabled in this mode.
95
+ * 1.1.2 - `--server` will now volume map fastled src directory if it detects this. This was also implemented on the docker side.
96
+ * 1.1.1 - `--interactive` is now supported to debug the container. Volume maps and better compatibilty with ipv4/v6 by concurrent connection finding.
95
97
  * 1.1.0 - Use `fastled` as the command for the wasm compiler.
96
98
  * 1.0.17 - Pulls updates when necessary. Removed dependency on keyring.
97
99
  * 1.0.16 - `fastled-wasm` package name has been changed to `fled`
@@ -9,7 +9,6 @@ Compiles an Arduino/Platformio sketch into a wasm binary that can be run directl
9
9
  [![Win_Tests](https://github.com/zackees/fastled-wasm/actions/workflows/test_win.yml/badge.svg)](https://github.com/zackees/fastled-wasm/actions/workflows/test_win.yml)
10
10
 
11
11
 
12
-
13
12
  # About
14
13
 
15
14
  This python app will compile your FastLED style sketches into html/js/wasm output that runs directly in the browser.
@@ -73,6 +72,9 @@ provide shims for most of the common api points.
73
72
 
74
73
  # Revisions
75
74
 
75
+ * 1.1.3 - Live editing of *.h and *.cpp files is now possible. Sketch cache will be disabled in this mode.
76
+ * 1.1.2 - `--server` will now volume map fastled src directory if it detects this. This was also implemented on the docker side.
77
+ * 1.1.1 - `--interactive` is now supported to debug the container. Volume maps and better compatibilty with ipv4/v6 by concurrent connection finding.
76
78
  * 1.1.0 - Use `fastled` as the command for the wasm compiler.
77
79
  * 1.0.17 - Pulls updates when necessary. Removed dependency on keyring.
78
80
  * 1.0.16 - `fastled-wasm` package name has been changed to `fled`
@@ -14,10 +14,9 @@
14
14
  #include "fx/fx_engine.h"
15
15
 
16
16
  #include "fx/2d/animartrix.hpp"
17
- #include "platforms/wasm/js.h"
18
-
19
17
  #include "ui.h"
20
18
 
19
+
21
20
  #define LED_PIN 3
22
21
  #define BRIGHTNESS 96
23
22
  #define COLOR_ORDER GRB
@@ -69,6 +68,9 @@ CRGB leds[NUM_LEDS];
69
68
  XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT);
70
69
  NoisePalette noisePalette = NoisePalette(xyMap);
71
70
 
71
+ Title title("FastLED Wasm Demo");
72
+ Description description("This example combines two features of FastLED to produce a remarkable range of effects from a relatively small amount of code. This example combines FastLED's color palette lookup functions with FastLED's Perlin noise generator, and the combination is extremely powerful.");
73
+
72
74
  Slider brightness("Brightness", 255, 0, 255);
73
75
  Checkbox isOff("Off", false);
74
76
  Slider speed("Noise - Speed", 15, 1, 50);
@@ -77,8 +79,7 @@ Slider changePalletTime("Noise - Time until next random Palette", 5, 1, 100);
77
79
  Slider scale( "Noise - Scale", 20, 1, 100);
78
80
  Button changePalette("Noise - Next Palette");
79
81
  Button changeFx("Switch between Noise & Animartrix");
80
- NumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS);
81
-
82
+ NumberField fxIndex("Animartrix - iex", 0, 0, NUM_ANIMATIONS);
82
83
 
83
84
  Animartrix animartrix(xyMap, POLAR_WAVES);
84
85
  FxEngine fxEngine(NUM_LEDS);
@@ -119,11 +120,6 @@ void loop() {
119
120
  animartrix.fxSet(fxIndex);
120
121
  }
121
122
 
122
- EVERY_N_MILLISECONDS(1000) {
123
- printf("fastled running\r\n");
124
- printf("Numberfield: %f\r\n", fxIndex.value());
125
- }
126
-
127
123
 
128
124
  fxEngine.draw(millis(), leds);
129
125
  FastLED.show();
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "filelock",
20
20
  ]
21
21
  # Change this with the version number bump.
22
- version = "1.1.0"
22
+ version = "1.1.3"
23
23
 
24
24
  [tool.setuptools]
25
25
  package-dir = {"" = "src"}
@@ -16,7 +16,7 @@ from dataclasses import dataclass
16
16
  from pathlib import Path
17
17
 
18
18
  from fastled.build_mode import BuildMode, get_build_mode
19
- from fastled.compile_server import CompileServer
19
+ from fastled.compile_server import CompileServer, looks_like_fastled_repo
20
20
  from fastled.docker_manager import DockerManager
21
21
  from fastled.filewatcher import FileChangedNotifier
22
22
  from fastled.open_browser import open_browser_thread
@@ -81,6 +81,12 @@ def parse_args() -> argparse.Namespace:
81
81
  nargs="+",
82
82
  help="Additional patterns to exclude from file watching (Not available with --web)",
83
83
  )
84
+ parser.add_argument(
85
+ "-i",
86
+ "--interactive",
87
+ action="store_true",
88
+ help="Run in interactive mode (Not available with --web)",
89
+ )
84
90
  parser.add_argument(
85
91
  "--profile",
86
92
  action="store_true",
@@ -97,6 +103,11 @@ def parse_args() -> argparse.Namespace:
97
103
  build_mode.add_argument(
98
104
  "--release", action="store_true", help="Build in release mode"
99
105
  )
106
+ build_mode.add_argument(
107
+ "--server",
108
+ action="store_true",
109
+ help="Run the server in the current directory, volume mapping fastled if we are in the repo",
110
+ )
100
111
 
101
112
  build_mode.add_argument(
102
113
  "--force-compile",
@@ -148,9 +159,7 @@ def run_web_compiler(
148
159
 
149
160
  # now check to see if the hash value is the same as the last hash value
150
161
  if last_hash_value is not None and last_hash_value == web_result.hash_value:
151
- print(
152
- "\nNo significant source code changes detected and data was the same, skipping recompilation."
153
- )
162
+ print("\nSkipping redeploy: No significant changes found.")
154
163
  print_results()
155
164
  return CompiledResult(
156
165
  success=True, fastled_js=str(output_dir), hash_value=web_result.hash_value
@@ -200,7 +209,24 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
200
209
  return DEFAULT_URL
201
210
 
202
211
 
212
+ def _lots_and_lots_of_files(directory: Path) -> bool:
213
+ count = 0
214
+ for root, dirs, files in os.walk(directory):
215
+ count += len(files)
216
+ if count > 100:
217
+ return True
218
+ return False
219
+
220
+
203
221
  def _looks_like_sketch_directory(directory: Path) -> bool:
222
+ if looks_like_fastled_repo(directory):
223
+ print("Directory looks like the FastLED repo")
224
+ return False
225
+
226
+ if _lots_and_lots_of_files(directory):
227
+ print("Too many files in the directory, bailing out")
228
+ return False
229
+
204
230
  # walk the path and if there are over 30 files, return False
205
231
  # at the root of the directory there should either be an ino file or a src directory
206
232
  # or some cpp files
@@ -217,11 +243,10 @@ def _looks_like_sketch_directory(directory: Path) -> bool:
217
243
  return False
218
244
 
219
245
 
220
- def main() -> int:
221
- args = parse_args()
246
+ def run_client(args: argparse.Namespace) -> int:
247
+ compile_server: CompileServer | None = None
222
248
  open_web_browser = not args.just_compile
223
249
  profile = args.profile
224
-
225
250
  if not args.force_compile and not _looks_like_sketch_directory(
226
251
  Path(args.directory)
227
252
  ):
@@ -237,16 +262,16 @@ def main() -> int:
237
262
  )
238
263
  args.web = True
239
264
 
240
- compile_server: CompileServer | None = None
241
265
  url: str
242
-
243
266
  try:
244
267
  try:
245
268
  url_or_server: str | CompileServer = _try_start_server_or_get_url(args)
246
269
  if isinstance(url_or_server, str):
270
+ print(f"Found URL: {url_or_server}")
247
271
  url = url_or_server
248
272
  else:
249
273
  compile_server = url_or_server
274
+ print(f"Server started at {compile_server.url()}")
250
275
  url = compile_server.url()
251
276
  except KeyboardInterrupt:
252
277
  print("\nExiting from first try...")
@@ -343,9 +368,49 @@ def main() -> int:
343
368
  browser_proc.kill()
344
369
 
345
370
 
371
+ def run_server(args: argparse.Namespace) -> int:
372
+ interactive = args.interactive
373
+ compile_server = CompileServer(
374
+ disable_auto_clean=args.no_auto_clean, interactive=interactive
375
+ )
376
+ print(f"Server started at {compile_server.url()}")
377
+ compile_server.start()
378
+ compile_server.wait_for_startup()
379
+ try:
380
+ while True:
381
+ if not compile_server.proceess_running():
382
+ print("Server process is not running. Exiting...")
383
+ return 1
384
+ time.sleep(1)
385
+ except KeyboardInterrupt:
386
+ print("\nExiting from server...")
387
+ return 1
388
+ finally:
389
+ compile_server.stop()
390
+ return 0
391
+
392
+
393
+ def main() -> int:
394
+ args = parse_args()
395
+ target_dir = Path(args.directory)
396
+ cwd_is_target_dir = target_dir == Path(os.getcwd())
397
+ force_server = cwd_is_target_dir and looks_like_fastled_repo(target_dir)
398
+ auto_server = (args.server or args.interactive or cwd_is_target_dir) and (
399
+ not args.web and not args.just_compile
400
+ )
401
+ if auto_server or force_server:
402
+ print("Running in server only mode.")
403
+ return run_server(args)
404
+ else:
405
+ print("Running in client/server mode.")
406
+ return run_client(args)
407
+
408
+
346
409
  if __name__ == "__main__":
347
410
  try:
348
411
  sys.argv.append("examples/wasm")
412
+ sys.argv.append("-w")
413
+ sys.argv.append("localhost")
349
414
  sys.exit(main())
350
415
  except KeyboardInterrupt:
351
416
  print("\nExiting from main...")
@@ -2,6 +2,7 @@ import socket
2
2
  import subprocess
3
3
  import threading
4
4
  import time
5
+ from pathlib import Path
5
6
  from typing import Optional
6
7
 
7
8
  import httpx
@@ -10,10 +11,10 @@ from fastled.docker_manager import DockerManager
10
11
 
11
12
  _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
12
13
 
13
- _DEFAULT_START_PORT = 9021
14
+ SERVER_PORT = 9021
14
15
 
15
16
 
16
- def _find_available_port(start_port: int = _DEFAULT_START_PORT) -> int:
17
+ def find_available_port(start_port: int = SERVER_PORT) -> int:
17
18
  """Find an available port starting from the given port."""
18
19
  port = start_port
19
20
  end_port = start_port + 1000
@@ -26,16 +27,38 @@ def _find_available_port(start_port: int = _DEFAULT_START_PORT) -> int:
26
27
  raise RuntimeError("No available ports found")
27
28
 
28
29
 
30
+ def looks_like_fastled_repo(directory: Path) -> bool:
31
+ libprops = directory / "library.properties"
32
+ if not libprops.exists():
33
+ return False
34
+ txt = libprops.read_text()
35
+ return "FastLED" in txt
36
+
37
+
29
38
  class CompileServer:
30
39
  def __init__(
31
- self, container_name=_DEFAULT_CONTAINER_NAME, disable_auto_clean: bool = False
40
+ self,
41
+ container_name=_DEFAULT_CONTAINER_NAME,
42
+ disable_auto_clean: bool = False,
43
+ interactive: bool = False,
32
44
  ) -> None:
45
+
46
+ cwd = Path(".").resolve()
47
+ fastled_src_dir: Path | None = None
48
+ if looks_like_fastled_repo(cwd):
49
+ print(
50
+ "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
51
+ )
52
+ fastled_src_dir = cwd / "src"
53
+
33
54
  self.container_name = container_name
34
55
  self.disable_auto_clean = disable_auto_clean
35
56
  self.docker = DockerManager(container_name=container_name)
36
57
  self.running = False
37
58
  self.thread: Optional[threading.Thread] = None
38
59
  self.running_process: subprocess.Popen | None = None
60
+ self.fastled_src_dir: Path | None = fastled_src_dir
61
+ self.interactive = interactive
39
62
  self._port = self.start()
40
63
 
41
64
  def port(self) -> int:
@@ -54,7 +77,9 @@ class CompileServer:
54
77
  # use httpx to ping the server
55
78
  # if successful, return True
56
79
  try:
57
- response = httpx.get(f"http://localhost:{self._port}")
80
+ response = httpx.get(
81
+ f"http://localhost:{self._port}", follow_redirects=True
82
+ )
58
83
  if response.status_code < 400:
59
84
  return True
60
85
  except KeyboardInterrupt:
@@ -98,7 +123,7 @@ class CompileServer:
98
123
  except Exception as e:
99
124
  print(f"Warning: Failed to remove existing container: {e}")
100
125
 
101
- print("Ensuring Docker image exists")
126
+ print("Ensuring Docker image exists at latest version")
102
127
  if not self.docker.ensure_image_exists():
103
128
  print("Failed to ensure Docker image exists.")
104
129
  raise RuntimeError("Failed to ensure Docker image exists")
@@ -109,14 +134,35 @@ class CompileServer:
109
134
  # subprocess.run(["docker", "rmi", "fastled-wasm"], capture_output=True)
110
135
  # print("All clean")
111
136
 
112
- port = _find_available_port()
137
+ port = find_available_port()
138
+ print(f"Found an available port: {port}")
113
139
  # server_command = ["python", "/js/run.py", "server", "--allow-shutdown"]
114
- server_command = ["python", "/js/run.py", "server"]
140
+ if self.interactive:
141
+ server_command = ["/bin/bash"]
142
+ else:
143
+ server_command = ["python", "/js/run.py", "server"]
115
144
  if self.disable_auto_clean:
116
145
  server_command.append("--disable-auto-clean")
117
146
  print(f"Started Docker container with command: {server_command}")
118
147
  ports = {port: 80}
119
- self.running_process = self.docker.run_container(server_command, ports=ports)
148
+ volumes = None
149
+ if self.fastled_src_dir:
150
+ print(
151
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /js/fastled/src"
152
+ )
153
+ volumes = {
154
+ str(self.fastled_src_dir): {"bind": "/js/fastled/src", "mode": "rw"}
155
+ }
156
+ # no auto-update because the source directory is mapped in.
157
+ server_command.append("--no-auto-update") # stop git repo updates.
158
+ if not self.interactive:
159
+ server_command.append(
160
+ "--no-sketch-cache"
161
+ ) # Remove sketch cache which assumes src is static.
162
+ self.running_process = self.docker.run_container(
163
+ server_command, ports=ports, volumes=volumes
164
+ )
165
+ print("Compile server starting")
120
166
  time.sleep(3)
121
167
  if self.running_process.poll() is not None:
122
168
  print("Server failed to start")
@@ -124,38 +170,47 @@ class CompileServer:
124
170
  raise RuntimeError("Server failed to start")
125
171
  self.thread = threading.Thread(target=self._server_loop, daemon=True)
126
172
  self.thread.start()
127
- print("Compile server started")
173
+
128
174
  return port
129
175
 
176
+ def proceess_running(self) -> bool:
177
+ if self.running_process is None:
178
+ return False
179
+ return self.running_process.poll() is None
180
+
130
181
  def stop(self) -> None:
131
182
  print(f"Stopping server on port {self._port}")
132
183
  # attempt to send a shutdown signal to the server
133
- # httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
134
- if self.running_process:
135
- try:
136
- # Stop the Docker container
137
- cp: subprocess.CompletedProcess
138
- cp = subprocess.run(
139
- ["docker", "stop", self.container_name],
140
- capture_output=True,
141
- text=True,
142
- check=True,
143
- )
144
- if cp.returncode != 0:
145
- print(f"Failed to stop Docker container: {cp.stderr}")
146
-
147
- cp = subprocess.run(
148
- ["docker", "rm", self.container_name],
149
- capture_output=True,
150
- text=True,
151
- check=True,
152
- )
153
- if cp.returncode != 0:
154
- print(f"Failed to remove Docker container: {cp.stderr}")
184
+ try:
185
+ httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
186
+ # except Exception:
187
+ except Exception as e:
188
+ print(f"Failed to send shutdown signal: {e}")
189
+ pass
190
+ try:
191
+ # Stop the Docker container
192
+ cp: subprocess.CompletedProcess
193
+ cp = subprocess.run(
194
+ ["docker", "stop", self.container_name],
195
+ capture_output=True,
196
+ text=True,
197
+ check=True,
198
+ )
199
+ if cp.returncode != 0:
200
+ print(f"Failed to stop Docker container: {cp.stderr}")
201
+
202
+ cp = subprocess.run(
203
+ ["docker", "rm", self.container_name],
204
+ capture_output=True,
205
+ text=True,
206
+ check=True,
207
+ )
208
+ if cp.returncode != 0:
209
+ print(f"Failed to remove Docker container: {cp.stderr}")
155
210
 
156
- # Close the stdout pipe
157
- if self.running_process.stdout:
158
- self.running_process.stdout.close()
211
+ # Close the stdout pipe
212
+ if self.running_process and self.running_process.stdout:
213
+ self.running_process.stdout.close()
159
214
 
160
215
  # Wait for the process to fully terminate with a timeout
161
216
  self.running_process.wait(timeout=10)
@@ -167,17 +222,19 @@ class CompileServer:
167
222
  f"Server stopped with return code {self.running_process.returncode}"
168
223
  )
169
224
 
170
- except subprocess.TimeoutExpired:
171
- # Force kill if it doesn't stop gracefully
225
+ except subprocess.TimeoutExpired:
226
+ # Force kill if it doesn't stop gracefully
227
+ if self.running_process:
172
228
  self.running_process.kill()
173
229
  self.running_process.wait()
174
- except KeyboardInterrupt:
230
+ except KeyboardInterrupt:
231
+ if self.running_process:
175
232
  self.running_process.kill()
176
233
  self.running_process.wait()
177
- except Exception as e:
178
- print(f"Error stopping Docker container: {e}")
179
- finally:
180
- self.running_process = None
234
+ except Exception as e:
235
+ print(f"Error stopping Docker container: {e}")
236
+ finally:
237
+ self.running_process = None
181
238
  # Signal the server thread to stop
182
239
  self.running = False
183
240
  if self.thread:
@@ -210,15 +210,15 @@ class DockerManager:
210
210
  def run_container(
211
211
  self,
212
212
  cmd: list[str],
213
- volumes: dict[str, str] | None = None,
213
+ volumes: dict[str, dict[str, str]] | None = None,
214
214
  ports: dict[int, int] | None = None,
215
215
  ) -> subprocess.Popen:
216
216
  """Run the Docker container with the specified volume.
217
217
 
218
218
  Args:
219
- volume_path: Path to the volume to mount
220
- base_name: Base name for the mounted volume
221
- build_mode: Build mode (DEBUG, QUICK, or RELEASE)
219
+ cmd: Command to run in the container
220
+ volumes: Dict mapping host paths to dicts with 'bind' and 'mode' keys
221
+ ports: Dict mapping host ports to container ports
222
222
  """
223
223
  volumes = volumes or {}
224
224
  ports = ports or {}
@@ -237,8 +237,10 @@ class DockerManager:
237
237
  for host_port, container_port in ports.items():
238
238
  docker_command.extend(["-p", f"{host_port}:{container_port}"])
239
239
  if volumes:
240
- for host_path, container_path in volumes.items():
241
- docker_command.extend(["-v", f"{host_path}:{container_path}"])
240
+ for host_path, mount_spec in volumes.items():
241
+ docker_command.extend(
242
+ ["-v", f"{host_path}:{mount_spec['bind']}:{mount_spec['mode']}"]
243
+ )
242
244
 
243
245
  docker_command.extend(
244
246
  [