fastled 1.4.2__py3-none-any.whl → 1.4.4__py3-none-any.whl
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.
- fastled/__init__.py +11 -0
- fastled/__version__.py +1 -1
- fastled/client_server.py +23 -2
- fastled/compile_server.py +2 -0
- fastled/compile_server_impl.py +13 -0
- fastled/find_good_connection.py +105 -0
- fastled/playwright_browser.py +77 -2
- fastled/web_compile.py +216 -242
- fastled/zip_files.py +76 -0
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/METADATA +1 -1
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/RECORD +15 -13
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/WHEEL +0 -0
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/entry_points.txt +0 -0
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.4.2.dist-info → fastled-1.4.4.dist-info}/top_level.txt +0 -0
fastled/__init__.py
CHANGED
@@ -41,18 +41,29 @@ class Api:
|
|
41
41
|
profile: bool = False, # When true then profile information will be enabled and included in the zip.
|
42
42
|
no_platformio: bool = False,
|
43
43
|
) -> CompileResult:
|
44
|
+
from fastled.sketch import looks_like_fastled_repo
|
44
45
|
from fastled.web_compile import web_compile
|
45
46
|
|
46
47
|
if isinstance(host, CompileServer):
|
47
48
|
host = host.url()
|
48
49
|
if isinstance(directory, str):
|
49
50
|
directory = Path(directory)
|
51
|
+
|
52
|
+
# Guard: libfastled compilation requires volume source mapping
|
53
|
+
# Only allow libcompile if we're in a FastLED repository
|
54
|
+
allow_libcompile = looks_like_fastled_repo(Path(".").resolve())
|
55
|
+
if not allow_libcompile:
|
56
|
+
print(
|
57
|
+
"⚠️ libfastled compilation disabled: not running in FastLED repository"
|
58
|
+
)
|
59
|
+
|
50
60
|
out: CompileResult = web_compile(
|
51
61
|
directory,
|
52
62
|
host,
|
53
63
|
build_mode=build_mode,
|
54
64
|
profile=profile,
|
55
65
|
no_platformio=no_platformio,
|
66
|
+
allow_libcompile=allow_libcompile,
|
56
67
|
)
|
57
68
|
return out
|
58
69
|
|
fastled/__version__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
2
2
|
# that has any other change in the repo. Please bump the version as the
|
3
3
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
4
|
-
__version__ = "1.4.
|
4
|
+
__version__ = "1.4.4"
|
5
5
|
|
6
6
|
__version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
|
fastled/client_server.py
CHANGED
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
11
11
|
from fastled.compile_server import CompileServer
|
12
12
|
from fastled.docker_manager import DockerManager
|
13
13
|
from fastled.filewatcher import DebouncedFileWatcherProcess, FileWatcherProcess
|
14
|
+
from fastled.find_good_connection import ConnectionResult
|
14
15
|
from fastled.keyboard import SpaceBarWatcher
|
15
16
|
from fastled.open_browser import spawn_http_server
|
16
17
|
from fastled.parse_args import Args
|
@@ -19,7 +20,6 @@ from fastled.sketch import looks_like_sketch_directory
|
|
19
20
|
from fastled.types import BuildMode, CompileResult, CompileServerError
|
20
21
|
from fastled.web_compile import (
|
21
22
|
SERVER_PORT,
|
22
|
-
ConnectionResult,
|
23
23
|
find_good_connection,
|
24
24
|
web_compile,
|
25
25
|
)
|
@@ -101,9 +101,17 @@ def _run_web_compiler(
|
|
101
101
|
profile: bool,
|
102
102
|
last_hash_value: str | None,
|
103
103
|
no_platformio: bool = False,
|
104
|
+
allow_libcompile: bool = False,
|
104
105
|
) -> CompileResult:
|
106
|
+
# Remove the import and libcompile detection logic from here
|
107
|
+
# since it will now be passed as a parameter
|
105
108
|
input_dir = Path(directory)
|
106
109
|
output_dir = input_dir / "fastled_js"
|
110
|
+
|
111
|
+
# Guard: libfastled compilation requires volume source mapping
|
112
|
+
if not allow_libcompile:
|
113
|
+
print("⚠️ libfastled compilation disabled: not running in FastLED repository")
|
114
|
+
|
107
115
|
start = time.time()
|
108
116
|
web_result = web_compile(
|
109
117
|
directory=input_dir,
|
@@ -111,6 +119,7 @@ def _run_web_compiler(
|
|
111
119
|
build_mode=build_mode,
|
112
120
|
profile=profile,
|
113
121
|
no_platformio=no_platformio,
|
122
|
+
allow_libcompile=allow_libcompile,
|
114
123
|
)
|
115
124
|
diff = time.time() - start
|
116
125
|
if not web_result.success:
|
@@ -310,6 +319,11 @@ def run_client(
|
|
310
319
|
# Assume default port for www
|
311
320
|
port = 80
|
312
321
|
|
322
|
+
# Auto-detect libcompile capability on first call
|
323
|
+
from fastled.sketch import looks_like_fastled_repo
|
324
|
+
|
325
|
+
allow_libcompile = looks_like_fastled_repo(Path(".").resolve())
|
326
|
+
|
313
327
|
try:
|
314
328
|
|
315
329
|
def compile_function(
|
@@ -318,6 +332,7 @@ def run_client(
|
|
318
332
|
profile: bool = profile,
|
319
333
|
last_hash_value: str | None = None,
|
320
334
|
no_platformio: bool = no_platformio,
|
335
|
+
allow_libcompile: bool = allow_libcompile,
|
321
336
|
) -> CompileResult:
|
322
337
|
TEST_BEFORE_COMPILE(url)
|
323
338
|
return _run_web_compiler(
|
@@ -327,6 +342,7 @@ def run_client(
|
|
327
342
|
profile=profile,
|
328
343
|
last_hash_value=last_hash_value,
|
329
344
|
no_platformio=no_platformio,
|
345
|
+
allow_libcompile=allow_libcompile,
|
330
346
|
)
|
331
347
|
|
332
348
|
result: CompileResult = compile_function(last_hash_value=None)
|
@@ -454,6 +470,10 @@ def run_client(
|
|
454
470
|
if changed_files:
|
455
471
|
print(f"\nChanges detected in FastLED source code: {changed_files}")
|
456
472
|
print("Press space bar to trigger compile.")
|
473
|
+
|
474
|
+
# Re-evaluate libcompile capability when source code changes
|
475
|
+
allow_libcompile = True
|
476
|
+
|
457
477
|
while True:
|
458
478
|
space_bar_pressed = SpaceBarWatcher.watch_space_bar_pressed(
|
459
479
|
timeout=1.0
|
@@ -476,8 +496,9 @@ def run_client(
|
|
476
496
|
f"Changes detected in {','.join(sketch_files_changed)}, triggering recompile..."
|
477
497
|
)
|
478
498
|
last_compiled_result = compile_function(
|
479
|
-
last_hash_value=None
|
499
|
+
last_hash_value=None, allow_libcompile=allow_libcompile
|
480
500
|
)
|
501
|
+
allow_libcompile = False
|
481
502
|
print("Finished recompile.")
|
482
503
|
# Drain the space bar queue
|
483
504
|
SpaceBarWatcher.watch_space_bar_pressed()
|
fastled/compile_server.py
CHANGED
@@ -16,6 +16,7 @@ class CompileServer:
|
|
16
16
|
platform: Platform = Platform.WASM,
|
17
17
|
remove_previous: bool = False,
|
18
18
|
no_platformio: bool = False,
|
19
|
+
allow_libcompile: bool = True,
|
19
20
|
) -> None:
|
20
21
|
from fastled.compile_server_impl import ( # avoid circular import
|
21
22
|
CompileServerImpl,
|
@@ -31,6 +32,7 @@ class CompileServer:
|
|
31
32
|
auto_start=auto_start,
|
32
33
|
remove_previous=remove_previous,
|
33
34
|
no_platformio=no_platformio,
|
35
|
+
allow_libcompile=allow_libcompile,
|
34
36
|
)
|
35
37
|
|
36
38
|
# May throw CompileServerError if server could not be started.
|
fastled/compile_server_impl.py
CHANGED
@@ -51,6 +51,7 @@ class CompileServerImpl:
|
|
51
51
|
container_name: str | None = None,
|
52
52
|
remove_previous: bool = False,
|
53
53
|
no_platformio: bool = False,
|
54
|
+
allow_libcompile: bool = True,
|
54
55
|
) -> None:
|
55
56
|
container_name = container_name or DEFAULT_CONTAINER_NAME
|
56
57
|
if interactive and not mapped_dir:
|
@@ -70,6 +71,17 @@ class CompileServerImpl:
|
|
70
71
|
self.auto_updates = auto_updates
|
71
72
|
self.remove_previous = remove_previous
|
72
73
|
self.no_platformio = no_platformio
|
74
|
+
|
75
|
+
# Guard: libfastled compilation requires volume source mapping
|
76
|
+
# If we don't have fastled_src_dir (not in FastLED repo), disable libcompile
|
77
|
+
if allow_libcompile and self.fastled_src_dir is None:
|
78
|
+
print(
|
79
|
+
"⚠️ libfastled compilation disabled: volume source mapping not available"
|
80
|
+
)
|
81
|
+
print(" (not running in FastLED repository)")
|
82
|
+
allow_libcompile = False
|
83
|
+
|
84
|
+
self.allow_libcompile = allow_libcompile
|
73
85
|
self._port = 0 # 0 until compile server is started
|
74
86
|
if auto_start:
|
75
87
|
self.start()
|
@@ -112,6 +124,7 @@ class CompileServerImpl:
|
|
112
124
|
build_mode=build_mode,
|
113
125
|
profile=profile,
|
114
126
|
no_platformio=self.no_platformio,
|
127
|
+
allow_libcompile=self.allow_libcompile,
|
115
128
|
)
|
116
129
|
return out
|
117
130
|
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import _thread
|
2
|
+
import time
|
3
|
+
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Dict, Tuple
|
6
|
+
|
7
|
+
import httpx
|
8
|
+
|
9
|
+
_TIMEOUT = 30.0
|
10
|
+
|
11
|
+
_EXECUTOR = ThreadPoolExecutor(max_workers=8)
|
12
|
+
|
13
|
+
# In-memory cache for connection results
|
14
|
+
# Key: (tuple of urls, filter_out_bad, use_ipv6)
|
15
|
+
# Value: (ConnectionResult | None, timestamp)
|
16
|
+
_CONNECTION_CACHE: Dict[
|
17
|
+
Tuple[tuple, bool, bool], Tuple["ConnectionResult | None", float]
|
18
|
+
] = {}
|
19
|
+
_CACHE_TTL = 60.0 * 60.0 # Cache results for 1 hour
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class ConnectionResult:
|
24
|
+
host: str
|
25
|
+
success: bool
|
26
|
+
ipv4: bool
|
27
|
+
|
28
|
+
|
29
|
+
def _sanitize_host(host: str) -> str:
|
30
|
+
if host.startswith("http"):
|
31
|
+
return host
|
32
|
+
is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
|
33
|
+
use_https = not is_local_host
|
34
|
+
if use_https:
|
35
|
+
return host if host.startswith("https://") else f"https://{host}"
|
36
|
+
return host if host.startswith("http://") else f"http://{host}"
|
37
|
+
|
38
|
+
|
39
|
+
def _test_connection(host: str, use_ipv4: bool) -> ConnectionResult:
|
40
|
+
# Function static cache
|
41
|
+
host = _sanitize_host(host)
|
42
|
+
transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
|
43
|
+
result: ConnectionResult | None = None
|
44
|
+
try:
|
45
|
+
with httpx.Client(
|
46
|
+
timeout=_TIMEOUT,
|
47
|
+
transport=transport,
|
48
|
+
) as test_client:
|
49
|
+
test_response = test_client.get(
|
50
|
+
f"{host}/healthz", timeout=3, follow_redirects=True
|
51
|
+
)
|
52
|
+
result = ConnectionResult(host, test_response.status_code == 200, use_ipv4)
|
53
|
+
except KeyboardInterrupt:
|
54
|
+
_thread.interrupt_main()
|
55
|
+
result = ConnectionResult(host, False, use_ipv4)
|
56
|
+
except TimeoutError:
|
57
|
+
result = ConnectionResult(host, False, use_ipv4)
|
58
|
+
except Exception:
|
59
|
+
result = ConnectionResult(host, False, use_ipv4)
|
60
|
+
return result
|
61
|
+
|
62
|
+
|
63
|
+
def find_good_connection(
|
64
|
+
urls: list[str], filter_out_bad=True, use_ipv6: bool = True
|
65
|
+
) -> ConnectionResult | None:
|
66
|
+
# Create cache key from parameters
|
67
|
+
cache_key = (tuple(sorted(urls)), filter_out_bad, use_ipv6)
|
68
|
+
current_time = time.time()
|
69
|
+
|
70
|
+
# Check if we have a cached result
|
71
|
+
if cache_key in _CONNECTION_CACHE:
|
72
|
+
cached_result, cached_time = _CONNECTION_CACHE[cache_key]
|
73
|
+
if current_time - cached_time < _CACHE_TTL:
|
74
|
+
return cached_result
|
75
|
+
else:
|
76
|
+
# Remove expired cache entry
|
77
|
+
del _CONNECTION_CACHE[cache_key]
|
78
|
+
|
79
|
+
# No valid cache entry, perform the actual connection test
|
80
|
+
futures: list[Future] = []
|
81
|
+
for url in urls:
|
82
|
+
|
83
|
+
f = _EXECUTOR.submit(_test_connection, url, use_ipv4=True)
|
84
|
+
futures.append(f)
|
85
|
+
if use_ipv6 and "localhost" not in url:
|
86
|
+
f_v6 = _EXECUTOR.submit(_test_connection, url, use_ipv4=False)
|
87
|
+
futures.append(f_v6)
|
88
|
+
|
89
|
+
result = None
|
90
|
+
try:
|
91
|
+
# Return first successful result
|
92
|
+
for future in as_completed(futures):
|
93
|
+
connection_result: ConnectionResult = future.result()
|
94
|
+
if connection_result.success or not filter_out_bad:
|
95
|
+
result = connection_result
|
96
|
+
break
|
97
|
+
finally:
|
98
|
+
# Cancel any remaining futures
|
99
|
+
for future in futures:
|
100
|
+
future.cancel()
|
101
|
+
|
102
|
+
# Cache the result (even if None)
|
103
|
+
_CONNECTION_CACHE[cache_key] = (result, current_time)
|
104
|
+
|
105
|
+
return result
|
fastled/playwright_browser.py
CHANGED
@@ -79,8 +79,83 @@ class PlaywrightBrowser:
|
|
79
79
|
)
|
80
80
|
|
81
81
|
if self.page is None and self.browser is not None:
|
82
|
-
#
|
83
|
-
|
82
|
+
# Detect system device scale factor to match normal browser behavior
|
83
|
+
import platform
|
84
|
+
|
85
|
+
device_scale_factor = 1.0
|
86
|
+
|
87
|
+
# Try to detect system display scaling
|
88
|
+
try:
|
89
|
+
if platform.system() == "Windows":
|
90
|
+
# On Windows, try to get the DPI scaling factor
|
91
|
+
import ctypes
|
92
|
+
|
93
|
+
try:
|
94
|
+
# Get DPI awareness and scale factor
|
95
|
+
user32 = ctypes.windll.user32
|
96
|
+
user32.SetProcessDPIAware()
|
97
|
+
dc = user32.GetDC(0)
|
98
|
+
dpi = ctypes.windll.gdi32.GetDeviceCaps(dc, 88) # LOGPIXELSX
|
99
|
+
user32.ReleaseDC(0, dc)
|
100
|
+
device_scale_factor = dpi / 96.0 # 96 DPI is 100% scaling
|
101
|
+
except Exception:
|
102
|
+
# Fallback: try alternative method
|
103
|
+
try:
|
104
|
+
import tkinter as tk
|
105
|
+
|
106
|
+
root = tk.Tk()
|
107
|
+
device_scale_factor = root.winfo_fpixels("1i") / 96.0
|
108
|
+
root.destroy()
|
109
|
+
except Exception:
|
110
|
+
pass
|
111
|
+
elif platform.system() == "Darwin": # macOS
|
112
|
+
# On macOS, try to get the display scaling
|
113
|
+
try:
|
114
|
+
import subprocess
|
115
|
+
|
116
|
+
result = subprocess.run(
|
117
|
+
["system_profiler", "SPDisplaysDataType"],
|
118
|
+
capture_output=True,
|
119
|
+
text=True,
|
120
|
+
timeout=5,
|
121
|
+
)
|
122
|
+
if "Retina" in result.stdout or "2x" in result.stdout:
|
123
|
+
device_scale_factor = 2.0
|
124
|
+
elif "3x" in result.stdout:
|
125
|
+
device_scale_factor = 3.0
|
126
|
+
except Exception:
|
127
|
+
pass
|
128
|
+
elif platform.system() == "Linux":
|
129
|
+
# On Linux, try to get display scaling from environment or system
|
130
|
+
try:
|
131
|
+
import os
|
132
|
+
|
133
|
+
# Try GDK scaling first
|
134
|
+
gdk_scale = os.environ.get("GDK_SCALE")
|
135
|
+
if gdk_scale:
|
136
|
+
device_scale_factor = float(gdk_scale)
|
137
|
+
else:
|
138
|
+
# Try QT scaling
|
139
|
+
qt_scale = os.environ.get("QT_SCALE_FACTOR")
|
140
|
+
if qt_scale:
|
141
|
+
device_scale_factor = float(qt_scale)
|
142
|
+
except Exception:
|
143
|
+
pass
|
144
|
+
except Exception:
|
145
|
+
# If all detection methods fail, default to 1.0
|
146
|
+
device_scale_factor = 1.0
|
147
|
+
|
148
|
+
# Ensure device scale factor is reasonable (between 0.5 and 4.0)
|
149
|
+
device_scale_factor = max(0.5, min(4.0, device_scale_factor))
|
150
|
+
|
151
|
+
print(f"[PYTHON] Detected device scale factor: {device_scale_factor}")
|
152
|
+
|
153
|
+
# Create a new browser context with proper device scale factor
|
154
|
+
context = await self.browser.new_context(
|
155
|
+
device_scale_factor=device_scale_factor,
|
156
|
+
# Also ensure viewport scaling is handled properly
|
157
|
+
viewport={"width": 1280, "height": 720} if not self.headless else None,
|
158
|
+
)
|
84
159
|
self.page = await context.new_page()
|
85
160
|
|
86
161
|
async def open_url(self, url: str) -> None:
|
fastled/web_compile.py
CHANGED
@@ -1,35 +1,22 @@
|
|
1
|
-
import _thread
|
2
1
|
import io
|
3
|
-
import json
|
4
2
|
import os
|
5
3
|
import shutil
|
6
4
|
import tempfile
|
7
5
|
import time
|
8
6
|
import zipfile
|
9
|
-
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
10
|
-
from dataclasses import dataclass
|
11
7
|
from pathlib import Path
|
12
8
|
|
13
9
|
import httpx
|
14
10
|
|
11
|
+
from fastled.find_good_connection import find_good_connection
|
15
12
|
from fastled.settings import SERVER_PORT
|
16
|
-
from fastled.sketch import get_sketch_files
|
17
13
|
from fastled.types import BuildMode, CompileResult
|
18
|
-
from fastled.
|
14
|
+
from fastled.zip_files import ZipResult, zip_files
|
19
15
|
|
20
16
|
DEFAULT_HOST = "https://fastled.onrender.com"
|
21
17
|
ENDPOINT_COMPILED_WASM = "compile/wasm"
|
22
18
|
_TIMEOUT = 60 * 4 # 2 mins timeout
|
23
19
|
_AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
|
24
|
-
ENABLE_EMBEDDED_DATA = True
|
25
|
-
_EXECUTOR = ThreadPoolExecutor(max_workers=8)
|
26
|
-
|
27
|
-
|
28
|
-
@dataclass
|
29
|
-
class ConnectionResult:
|
30
|
-
host: str
|
31
|
-
success: bool
|
32
|
-
ipv4: bool
|
33
20
|
|
34
21
|
|
35
22
|
def _sanitize_host(host: str) -> str:
|
@@ -42,122 +29,6 @@ def _sanitize_host(host: str) -> str:
|
|
42
29
|
return host if host.startswith("http://") else f"http://{host}"
|
43
30
|
|
44
31
|
|
45
|
-
def _test_connection(host: str, use_ipv4: bool) -> ConnectionResult:
|
46
|
-
# Function static cache
|
47
|
-
host = _sanitize_host(host)
|
48
|
-
transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
|
49
|
-
result: ConnectionResult | None = None
|
50
|
-
try:
|
51
|
-
with httpx.Client(
|
52
|
-
timeout=_TIMEOUT,
|
53
|
-
transport=transport,
|
54
|
-
) as test_client:
|
55
|
-
test_response = test_client.get(
|
56
|
-
f"{host}/healthz", timeout=3, follow_redirects=True
|
57
|
-
)
|
58
|
-
result = ConnectionResult(host, test_response.status_code == 200, use_ipv4)
|
59
|
-
except KeyboardInterrupt:
|
60
|
-
_thread.interrupt_main()
|
61
|
-
result = ConnectionResult(host, False, use_ipv4)
|
62
|
-
except TimeoutError:
|
63
|
-
result = ConnectionResult(host, False, use_ipv4)
|
64
|
-
except Exception:
|
65
|
-
result = ConnectionResult(host, False, use_ipv4)
|
66
|
-
return result
|
67
|
-
|
68
|
-
|
69
|
-
def _file_info(file_path: Path) -> str:
|
70
|
-
hash_txt = hash_file(file_path)
|
71
|
-
file_size = file_path.stat().st_size
|
72
|
-
json_str = json.dumps({"hash": hash_txt, "size": file_size})
|
73
|
-
return json_str
|
74
|
-
|
75
|
-
|
76
|
-
@dataclass
|
77
|
-
class ZipResult:
|
78
|
-
zip_bytes: bytes
|
79
|
-
zip_embedded_bytes: bytes | None
|
80
|
-
success: bool
|
81
|
-
error: str | None
|
82
|
-
|
83
|
-
|
84
|
-
def zip_files(directory: Path, build_mode: BuildMode) -> ZipResult | Exception:
|
85
|
-
print("Zipping files...")
|
86
|
-
try:
|
87
|
-
files = get_sketch_files(directory)
|
88
|
-
if not files:
|
89
|
-
raise FileNotFoundError(f"No files found in {directory}")
|
90
|
-
for f in files:
|
91
|
-
print(f"Adding file: {f}")
|
92
|
-
# Create in-memory zip file
|
93
|
-
has_embedded_zip = False
|
94
|
-
zip_embedded_buffer = io.BytesIO()
|
95
|
-
zip_buffer = io.BytesIO()
|
96
|
-
with zipfile.ZipFile(
|
97
|
-
zip_embedded_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
98
|
-
) as emebedded_zip_file:
|
99
|
-
with zipfile.ZipFile(
|
100
|
-
zip_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
101
|
-
) as zip_file:
|
102
|
-
for file_path in files:
|
103
|
-
if "fastled_js" in str(file_path):
|
104
|
-
# These can be huge, don't send the output files back to the server!
|
105
|
-
continue
|
106
|
-
relative_path = file_path.relative_to(directory)
|
107
|
-
achive_path = str(Path("wasm") / relative_path)
|
108
|
-
if str(relative_path).startswith("data") and ENABLE_EMBEDDED_DATA:
|
109
|
-
_file_info_str = _file_info(file_path)
|
110
|
-
zip_file.writestr(
|
111
|
-
achive_path + ".embedded.json", _file_info_str
|
112
|
-
)
|
113
|
-
emebedded_zip_file.write(file_path, relative_path)
|
114
|
-
has_embedded_zip = True
|
115
|
-
else:
|
116
|
-
zip_file.write(file_path, achive_path)
|
117
|
-
# write build mode into the file as build.txt so that sketches are fingerprinted
|
118
|
-
# based on the build mode. Otherwise the same sketch with different build modes
|
119
|
-
# will have the same fingerprint.
|
120
|
-
zip_file.writestr(
|
121
|
-
str(Path("wasm") / "build_mode.txt"), build_mode.value
|
122
|
-
)
|
123
|
-
result = ZipResult(
|
124
|
-
zip_bytes=zip_buffer.getvalue(),
|
125
|
-
zip_embedded_bytes=(
|
126
|
-
zip_embedded_buffer.getvalue() if has_embedded_zip else None
|
127
|
-
),
|
128
|
-
success=True,
|
129
|
-
error=None,
|
130
|
-
)
|
131
|
-
return result
|
132
|
-
except Exception as e:
|
133
|
-
return e
|
134
|
-
|
135
|
-
|
136
|
-
def find_good_connection(
|
137
|
-
urls: list[str], filter_out_bad=True, use_ipv6: bool = True
|
138
|
-
) -> ConnectionResult | None:
|
139
|
-
futures: list[Future] = []
|
140
|
-
for url in urls:
|
141
|
-
|
142
|
-
f = _EXECUTOR.submit(_test_connection, url, use_ipv4=True)
|
143
|
-
futures.append(f)
|
144
|
-
if use_ipv6 and "localhost" not in url:
|
145
|
-
f_v6 = _EXECUTOR.submit(_test_connection, url, use_ipv4=False)
|
146
|
-
futures.append(f_v6)
|
147
|
-
|
148
|
-
try:
|
149
|
-
# Return first successful result
|
150
|
-
for future in as_completed(futures):
|
151
|
-
result: ConnectionResult = future.result()
|
152
|
-
if result.success or not filter_out_bad:
|
153
|
-
return result
|
154
|
-
finally:
|
155
|
-
# Cancel any remaining futures
|
156
|
-
for future in futures:
|
157
|
-
future.cancel()
|
158
|
-
return None
|
159
|
-
|
160
|
-
|
161
32
|
def _banner(msg: str) -> str:
|
162
33
|
"""
|
163
34
|
Create a banner for the given message.
|
@@ -189,6 +60,179 @@ def _print_banner(msg: str) -> None:
|
|
189
60
|
print(_banner(msg))
|
190
61
|
|
191
62
|
|
63
|
+
def _compile_libfastled(
|
64
|
+
host: str,
|
65
|
+
auth_token: str,
|
66
|
+
build_mode: BuildMode,
|
67
|
+
) -> httpx.Response:
|
68
|
+
"""Compile the FastLED library separately."""
|
69
|
+
host = _sanitize_host(host)
|
70
|
+
urls = [host]
|
71
|
+
domain = host.split("://")[-1]
|
72
|
+
if ":" not in domain:
|
73
|
+
urls.append(f"{host}:{SERVER_PORT}")
|
74
|
+
|
75
|
+
connection_result = find_good_connection(urls)
|
76
|
+
if connection_result is None:
|
77
|
+
raise ConnectionError(
|
78
|
+
"Connection failed to all endpoints for libfastled compilation"
|
79
|
+
)
|
80
|
+
|
81
|
+
ipv4_stmt = "IPv4" if connection_result.ipv4 else "IPv6"
|
82
|
+
transport = (
|
83
|
+
httpx.HTTPTransport(local_address="0.0.0.0") if connection_result.ipv4 else None
|
84
|
+
)
|
85
|
+
|
86
|
+
with httpx.Client(
|
87
|
+
transport=transport,
|
88
|
+
timeout=_TIMEOUT * 2, # Give more time for library compilation
|
89
|
+
) as client:
|
90
|
+
headers = {
|
91
|
+
"accept": "application/json",
|
92
|
+
"authorization": auth_token,
|
93
|
+
"build": build_mode.value.lower(),
|
94
|
+
}
|
95
|
+
|
96
|
+
url = f"{connection_result.host}/compile/libfastled"
|
97
|
+
print(f"Compiling libfastled on {url} via {ipv4_stmt}")
|
98
|
+
response = client.post(
|
99
|
+
url,
|
100
|
+
headers=headers,
|
101
|
+
timeout=_TIMEOUT * 2,
|
102
|
+
)
|
103
|
+
|
104
|
+
return response
|
105
|
+
|
106
|
+
|
107
|
+
def _send_compile_request(
|
108
|
+
host: str,
|
109
|
+
zip_bytes: bytes,
|
110
|
+
auth_token: str,
|
111
|
+
build_mode: BuildMode,
|
112
|
+
profile: bool,
|
113
|
+
no_platformio: bool,
|
114
|
+
allow_libcompile: bool,
|
115
|
+
) -> httpx.Response:
|
116
|
+
"""Send the compile request to the server and return the response."""
|
117
|
+
host = _sanitize_host(host)
|
118
|
+
urls = [host]
|
119
|
+
domain = host.split("://")[-1]
|
120
|
+
if ":" not in domain:
|
121
|
+
urls.append(f"{host}:{SERVER_PORT}")
|
122
|
+
|
123
|
+
connection_result = find_good_connection(urls)
|
124
|
+
if connection_result is None:
|
125
|
+
raise ConnectionError("Connection failed to all endpoints")
|
126
|
+
|
127
|
+
ipv4_stmt = "IPv4" if connection_result.ipv4 else "IPv6"
|
128
|
+
transport = (
|
129
|
+
httpx.HTTPTransport(local_address="0.0.0.0") if connection_result.ipv4 else None
|
130
|
+
)
|
131
|
+
|
132
|
+
archive_size = len(zip_bytes)
|
133
|
+
|
134
|
+
with httpx.Client(
|
135
|
+
transport=transport,
|
136
|
+
timeout=_TIMEOUT,
|
137
|
+
) as client:
|
138
|
+
headers = {
|
139
|
+
"accept": "application/json",
|
140
|
+
"authorization": auth_token,
|
141
|
+
"build": (
|
142
|
+
build_mode.value.lower()
|
143
|
+
if build_mode
|
144
|
+
else BuildMode.QUICK.value.lower()
|
145
|
+
),
|
146
|
+
"profile": "true" if profile else "false",
|
147
|
+
"no-platformio": "true" if no_platformio else "false",
|
148
|
+
"allow-libcompile": "false", # Always false since we handle it manually
|
149
|
+
}
|
150
|
+
|
151
|
+
url = f"{connection_result.host}/{ENDPOINT_COMPILED_WASM}"
|
152
|
+
print(
|
153
|
+
f"Compiling sketch on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes"
|
154
|
+
)
|
155
|
+
files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
|
156
|
+
response = client.post(
|
157
|
+
url,
|
158
|
+
follow_redirects=True,
|
159
|
+
files=files,
|
160
|
+
headers=headers,
|
161
|
+
timeout=_TIMEOUT,
|
162
|
+
)
|
163
|
+
|
164
|
+
return response
|
165
|
+
|
166
|
+
|
167
|
+
def _process_compile_response(
|
168
|
+
response: httpx.Response,
|
169
|
+
zip_result: ZipResult,
|
170
|
+
start_time: float,
|
171
|
+
) -> CompileResult:
|
172
|
+
"""Process the compile response and return the final result."""
|
173
|
+
if response.status_code != 200:
|
174
|
+
json_response = response.json()
|
175
|
+
detail = json_response.get("detail", "Could not compile")
|
176
|
+
return CompileResult(
|
177
|
+
success=False, stdout=detail, hash_value=None, zip_bytes=b""
|
178
|
+
)
|
179
|
+
|
180
|
+
print(f"Response status code: {response}")
|
181
|
+
# Create a temporary directory to extract the zip
|
182
|
+
with tempfile.TemporaryDirectory() as extract_dir:
|
183
|
+
extract_path = Path(extract_dir)
|
184
|
+
|
185
|
+
# Write the response content to a temporary zip file
|
186
|
+
temp_zip = extract_path / "response.zip"
|
187
|
+
temp_zip.write_bytes(response.content)
|
188
|
+
|
189
|
+
# Extract the zip
|
190
|
+
shutil.unpack_archive(temp_zip, extract_path, "zip")
|
191
|
+
|
192
|
+
if zip_result.zip_embedded_bytes:
|
193
|
+
# extract the embedded bytes, which were not sent to the server
|
194
|
+
temp_zip.write_bytes(zip_result.zip_embedded_bytes)
|
195
|
+
shutil.unpack_archive(temp_zip, extract_path, "zip")
|
196
|
+
|
197
|
+
# we don't need the temp zip anymore
|
198
|
+
temp_zip.unlink()
|
199
|
+
|
200
|
+
# Read stdout from out.txt if it exists
|
201
|
+
stdout_file = extract_path / "out.txt"
|
202
|
+
hash_file = extract_path / "hash.txt"
|
203
|
+
stdout = (
|
204
|
+
stdout_file.read_text(encoding="utf-8", errors="replace")
|
205
|
+
if stdout_file.exists()
|
206
|
+
else ""
|
207
|
+
)
|
208
|
+
hash_value = (
|
209
|
+
hash_file.read_text(encoding="utf-8", errors="replace")
|
210
|
+
if hash_file.exists()
|
211
|
+
else None
|
212
|
+
)
|
213
|
+
|
214
|
+
# now rezip the extracted files since we added the embedded json files
|
215
|
+
out_buffer = io.BytesIO()
|
216
|
+
with zipfile.ZipFile(
|
217
|
+
out_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
218
|
+
) as out_zip:
|
219
|
+
for root, _, _files in os.walk(extract_path):
|
220
|
+
for file in _files:
|
221
|
+
file_path = Path(root) / file
|
222
|
+
relative_path = file_path.relative_to(extract_path)
|
223
|
+
out_zip.write(file_path, relative_path)
|
224
|
+
|
225
|
+
diff_time = time.time() - start_time
|
226
|
+
msg = f"Compilation success, took {diff_time:.2f} seconds"
|
227
|
+
_print_banner(msg)
|
228
|
+
return CompileResult(
|
229
|
+
success=True,
|
230
|
+
stdout=stdout,
|
231
|
+
hash_value=hash_value,
|
232
|
+
zip_bytes=out_buffer.getvalue(),
|
233
|
+
)
|
234
|
+
|
235
|
+
|
192
236
|
def web_compile(
|
193
237
|
directory: Path | str,
|
194
238
|
host: str | None = None,
|
@@ -196,6 +240,7 @@ def web_compile(
|
|
196
240
|
build_mode: BuildMode | None = None,
|
197
241
|
profile: bool = False,
|
198
242
|
no_platformio: bool = False,
|
243
|
+
allow_libcompile: bool = True,
|
199
244
|
) -> CompileResult:
|
200
245
|
start_time = time.time()
|
201
246
|
if isinstance(directory, str):
|
@@ -206,125 +251,54 @@ def web_compile(
|
|
206
251
|
auth_token = auth_token or _AUTH_TOKEN
|
207
252
|
if not directory.exists():
|
208
253
|
raise FileNotFoundError(f"Directory not found: {directory}")
|
209
|
-
zip_result = zip_files(directory, build_mode=build_mode)
|
254
|
+
zip_result: ZipResult | Exception = zip_files(directory, build_mode=build_mode)
|
210
255
|
if isinstance(zip_result, Exception):
|
211
256
|
return CompileResult(
|
212
257
|
success=False, stdout=str(zip_result), hash_value=None, zip_bytes=b""
|
213
258
|
)
|
214
259
|
zip_bytes = zip_result.zip_bytes
|
215
|
-
archive_size = len(zip_bytes)
|
216
260
|
print(f"Web compiling on {host}...")
|
217
261
|
try:
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
262
|
+
# Step 1: Compile libfastled if requested
|
263
|
+
if allow_libcompile:
|
264
|
+
print("Step 1: Compiling libfastled...")
|
265
|
+
try:
|
266
|
+
libfastled_response = _compile_libfastled(host, auth_token, build_mode)
|
267
|
+
if libfastled_response.status_code != 200:
|
268
|
+
print(
|
269
|
+
f"Warning: libfastled compilation failed with status {libfastled_response.status_code}"
|
270
|
+
)
|
271
|
+
# Continue with sketch compilation even if libfastled fails
|
272
|
+
else:
|
273
|
+
print("✅ libfastled compilation successful")
|
274
|
+
except Exception as e:
|
275
|
+
print(f"Warning: libfastled compilation failed: {e}")
|
276
|
+
# Continue with sketch compilation even if libfastled fails
|
277
|
+
else:
|
278
|
+
print("Step 1 (skipped): Compiling libfastled")
|
279
|
+
|
280
|
+
# Step 2: Compile the sketch
|
281
|
+
print("Step 2: Compiling sketch...")
|
282
|
+
response = _send_compile_request(
|
283
|
+
host,
|
284
|
+
zip_bytes,
|
285
|
+
auth_token,
|
286
|
+
build_mode,
|
287
|
+
profile,
|
288
|
+
no_platformio,
|
289
|
+
False, # allow_libcompile is always False since we handle it manually
|
290
|
+
)
|
291
|
+
|
292
|
+
return _process_compile_response(response, zip_result, start_time)
|
293
|
+
|
294
|
+
except ConnectionError as e:
|
295
|
+
_print_banner(str(e))
|
296
|
+
return CompileResult(
|
297
|
+
success=False,
|
298
|
+
stdout=str(e),
|
299
|
+
hash_value=None,
|
300
|
+
zip_bytes=b"",
|
239
301
|
)
|
240
|
-
with httpx.Client(
|
241
|
-
transport=transport,
|
242
|
-
timeout=_TIMEOUT,
|
243
|
-
) as client:
|
244
|
-
headers = {
|
245
|
-
"accept": "application/json",
|
246
|
-
"authorization": auth_token,
|
247
|
-
"build": (
|
248
|
-
build_mode.value.lower()
|
249
|
-
if build_mode
|
250
|
-
else BuildMode.QUICK.value.lower()
|
251
|
-
),
|
252
|
-
"profile": "true" if profile else "false",
|
253
|
-
"no-platformio": "true" if no_platformio else "false",
|
254
|
-
}
|
255
|
-
|
256
|
-
url = f"{connection_result.host}/{ENDPOINT_COMPILED_WASM}"
|
257
|
-
print(f"Compiling on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes")
|
258
|
-
files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
|
259
|
-
response = client.post(
|
260
|
-
url,
|
261
|
-
follow_redirects=True,
|
262
|
-
files=files,
|
263
|
-
headers=headers,
|
264
|
-
timeout=_TIMEOUT,
|
265
|
-
)
|
266
|
-
|
267
|
-
if response.status_code != 200:
|
268
|
-
json_response = response.json()
|
269
|
-
detail = json_response.get("detail", "Could not compile")
|
270
|
-
return CompileResult(
|
271
|
-
success=False, stdout=detail, hash_value=None, zip_bytes=b""
|
272
|
-
)
|
273
|
-
|
274
|
-
print(f"Response status code: {response}")
|
275
|
-
# Create a temporary directory to extract the zip
|
276
|
-
with tempfile.TemporaryDirectory() as extract_dir:
|
277
|
-
extract_path = Path(extract_dir)
|
278
|
-
|
279
|
-
# Write the response content to a temporary zip file
|
280
|
-
temp_zip = extract_path / "response.zip"
|
281
|
-
temp_zip.write_bytes(response.content)
|
282
|
-
|
283
|
-
# Extract the zip
|
284
|
-
shutil.unpack_archive(temp_zip, extract_path, "zip")
|
285
|
-
|
286
|
-
if zip_result.zip_embedded_bytes:
|
287
|
-
# extract the embedded bytes, which were not sent to the server
|
288
|
-
temp_zip.write_bytes(zip_result.zip_embedded_bytes)
|
289
|
-
shutil.unpack_archive(temp_zip, extract_path, "zip")
|
290
|
-
|
291
|
-
# we don't need the temp zip anymore
|
292
|
-
temp_zip.unlink()
|
293
|
-
|
294
|
-
# Read stdout from out.txt if it exists
|
295
|
-
stdout_file = extract_path / "out.txt"
|
296
|
-
hash_file = extract_path / "hash.txt"
|
297
|
-
stdout = (
|
298
|
-
stdout_file.read_text(encoding="utf-8", errors="replace")
|
299
|
-
if stdout_file.exists()
|
300
|
-
else ""
|
301
|
-
)
|
302
|
-
hash_value = (
|
303
|
-
hash_file.read_text(encoding="utf-8", errors="replace")
|
304
|
-
if hash_file.exists()
|
305
|
-
else None
|
306
|
-
)
|
307
|
-
|
308
|
-
# now rezip the extracted files since we added the embedded json files
|
309
|
-
out_buffer = io.BytesIO()
|
310
|
-
with zipfile.ZipFile(
|
311
|
-
out_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
312
|
-
) as out_zip:
|
313
|
-
for root, _, _files in os.walk(extract_path):
|
314
|
-
for file in _files:
|
315
|
-
file_path = Path(root) / file
|
316
|
-
relative_path = file_path.relative_to(extract_path)
|
317
|
-
out_zip.write(file_path, relative_path)
|
318
|
-
|
319
|
-
diff_time = time.time() - start_time
|
320
|
-
msg = f"Compilation success, took {diff_time:.2f} seconds"
|
321
|
-
_print_banner(msg)
|
322
|
-
return CompileResult(
|
323
|
-
success=True,
|
324
|
-
stdout=stdout,
|
325
|
-
hash_value=hash_value,
|
326
|
-
zip_bytes=out_buffer.getvalue(),
|
327
|
-
)
|
328
302
|
except KeyboardInterrupt:
|
329
303
|
print("Keyboard interrupt")
|
330
304
|
raise
|
fastled/zip_files.py
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
import io
|
2
|
+
import json
|
3
|
+
import zipfile
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from fastled.sketch import get_sketch_files
|
8
|
+
from fastled.types import BuildMode
|
9
|
+
from fastled.util import hash_file
|
10
|
+
|
11
|
+
|
12
|
+
def _file_info(file_path: Path) -> str:
|
13
|
+
hash_txt = hash_file(file_path)
|
14
|
+
file_size = file_path.stat().st_size
|
15
|
+
json_str = json.dumps({"hash": hash_txt, "size": file_size})
|
16
|
+
return json_str
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class ZipResult:
|
21
|
+
zip_bytes: bytes
|
22
|
+
zip_embedded_bytes: bytes | None
|
23
|
+
success: bool
|
24
|
+
error: str | None
|
25
|
+
|
26
|
+
|
27
|
+
def zip_files(directory: Path, build_mode: BuildMode) -> ZipResult | Exception:
|
28
|
+
print("Zipping files...")
|
29
|
+
try:
|
30
|
+
files = get_sketch_files(directory)
|
31
|
+
if not files:
|
32
|
+
raise FileNotFoundError(f"No files found in {directory}")
|
33
|
+
for f in files:
|
34
|
+
print(f"Adding file: {f}")
|
35
|
+
# Create in-memory zip file
|
36
|
+
has_embedded_zip = False
|
37
|
+
zip_embedded_buffer = io.BytesIO()
|
38
|
+
zip_buffer = io.BytesIO()
|
39
|
+
with zipfile.ZipFile(
|
40
|
+
zip_embedded_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
41
|
+
) as emebedded_zip_file:
|
42
|
+
with zipfile.ZipFile(
|
43
|
+
zip_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
|
44
|
+
) as zip_file:
|
45
|
+
for file_path in files:
|
46
|
+
if "fastled_js" in str(file_path):
|
47
|
+
# These can be huge, don't send the output files back to the server!
|
48
|
+
continue
|
49
|
+
relative_path = file_path.relative_to(directory)
|
50
|
+
achive_path = str(Path("wasm") / relative_path)
|
51
|
+
if str(relative_path).startswith("data"):
|
52
|
+
_file_info_str = _file_info(file_path)
|
53
|
+
zip_file.writestr(
|
54
|
+
achive_path + ".embedded.json", _file_info_str
|
55
|
+
)
|
56
|
+
emebedded_zip_file.write(file_path, relative_path)
|
57
|
+
has_embedded_zip = True
|
58
|
+
else:
|
59
|
+
zip_file.write(file_path, achive_path)
|
60
|
+
# write build mode into the file as build.txt so that sketches are fingerprinted
|
61
|
+
# based on the build mode. Otherwise the same sketch with different build modes
|
62
|
+
# will have the same fingerprint.
|
63
|
+
zip_file.writestr(
|
64
|
+
str(Path("wasm") / "build_mode.txt"), build_mode.value
|
65
|
+
)
|
66
|
+
result = ZipResult(
|
67
|
+
zip_bytes=zip_buffer.getvalue(),
|
68
|
+
zip_embedded_bytes=(
|
69
|
+
zip_embedded_buffer.getvalue() if has_embedded_zip else None
|
70
|
+
),
|
71
|
+
success=True,
|
72
|
+
error=None,
|
73
|
+
)
|
74
|
+
return result
|
75
|
+
except Exception as e:
|
76
|
+
return e
|
@@ -1,23 +1,24 @@
|
|
1
|
-
fastled/__init__.py,sha256=
|
1
|
+
fastled/__init__.py,sha256=l4uDkh_YOd24okNfn6eWjtTYaZ0woeYC7khv-vHMmTM,7775
|
2
2
|
fastled/__main__.py,sha256=OcKv2ER1_iQAsZzLIUb3C8hRC9L2clNOhCrjpshrlf4,336
|
3
|
-
fastled/__version__.py,sha256=
|
3
|
+
fastled/__version__.py,sha256=NeBy2hEASkJ8lb9Yhec88flAXoi8mapEcJBoqi2tBnA,372
|
4
4
|
fastled/app.py,sha256=6XOuObi72AUnZXASDOVbcSflr4He0xnIDk5P8nVmVus,6131
|
5
5
|
fastled/args.py,sha256=uCMyRIYM8gFE52O12YKUfA-rwJL8Zxwk_hsH3cusSac,3669
|
6
6
|
fastled/cli.py,sha256=drgR2AOxVrj3QEz58iiKscYAumbbin2vIV-k91VCOAA,561
|
7
7
|
fastled/cli_test.py,sha256=W-1nODZrip_JU6BEbYhxOa4ckxduOsiX8zIoRkTyxv4,550
|
8
8
|
fastled/cli_test_interactive.py,sha256=BjNhveZOk5aCffHbcrxPQQjWmAuj4ClVKKcKX5eY6yM,542
|
9
|
-
fastled/client_server.py,sha256=
|
10
|
-
fastled/compile_server.py,sha256=
|
11
|
-
fastled/compile_server_impl.py,sha256=
|
9
|
+
fastled/client_server.py,sha256=mxRgVtUwsFHGMkCY-rIWUQqiKY6ovUtrWApkhrobrfc,21662
|
10
|
+
fastled/compile_server.py,sha256=iGUjteXKp5Dlp7mxAE4eD4s0NWgApRIp4ZjtcAN2iZY,3124
|
11
|
+
fastled/compile_server_impl.py,sha256=iCwNCs7YxypUuVPmY4979mOgoH9OiuAJa1a1bmpG1cc,12567
|
12
12
|
fastled/docker_manager.py,sha256=rkq39ZKrU6NHIyDa3mzs0Unb6o9oMeAwxhqiuHJU_RY,40291
|
13
13
|
fastled/filewatcher.py,sha256=gEcJJHTDJ1X3gKJzltmEBhixWGbZj2eJD7a4vwSvITQ,10036
|
14
|
+
fastled/find_good_connection.py,sha256=xnrJjrbwNZUkvSQRn_ZTMoVh5GBWTbO-lEsr_L95xq8,3372
|
14
15
|
fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
|
15
16
|
fastled/keyz.py,sha256=LO-8m_7CpNDiZLM-FXhQ30f9gN1bUYz5lOsUPTIbI-c,4020
|
16
17
|
fastled/live_client.py,sha256=aDZqSWDMpqNaEsT3u1nrBcdeIOItv-L0Gk2A10difLA,3209
|
17
18
|
fastled/open_browser.py,sha256=mwjm65p2ydwmsaar7ooH4mhT5_qH_LZvXUpkRPPJ9eA,4881
|
18
19
|
fastled/parse_args.py,sha256=htjap9tWZDJXnJ5upDwcy8EhecJD1uLZwacHR_T5ySs,11518
|
19
20
|
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
20
|
-
fastled/playwright_browser.py,sha256=
|
21
|
+
fastled/playwright_browser.py,sha256=jV0ckpMLoapYSMGABzuR21EYSh_RWa_WgIEm4w3QxTs,23417
|
21
22
|
fastled/print_filter.py,sha256=nc_rqYYdCUPinFycaK7fiQF5PG1up51pmJptR__QyAs,1499
|
22
23
|
fastled/project_init.py,sha256=bBt4DwmW5hZkm9ICt9Qk-0Nr_0JQM7icCgH5Iv-bCQs,3984
|
23
24
|
fastled/select_sketch_directory.py,sha256=-eudwCns3AKj4HuHtSkZAFwbnf005SNL07pOzs9VxnE,1383
|
@@ -30,7 +31,8 @@ fastled/string_diff.py,sha256=oTncu0qYdLlLUtYLLDB4bzdQ2OfzegAR6XNAzwE9fIs,6002
|
|
30
31
|
fastled/types.py,sha256=ZDf1TbTT4XgA_pKIwr4JbkDB38_29ogSdDORjoT-zuY,1803
|
31
32
|
fastled/util.py,sha256=TjhXbUNh4p2BGhNAldSeL68B7BBOjsWAXji5gy-vDEQ,1440
|
32
33
|
fastled/version.py,sha256=TpBMiEVdO3_sUZEu6wmwN8Q4AgX2BiCxStCsnPKh6E0,1209
|
33
|
-
fastled/web_compile.py,sha256=
|
34
|
+
fastled/web_compile.py,sha256=Ql2DBRInZy7dOr1WZiUlhdg1ZVuU1nkbndRWiq7iENQ,10002
|
35
|
+
fastled/zip_files.py,sha256=BgHFjaLJ7wF6mnzjqOgn76VcKDwhwc_-w_qyUG_-aNs,2815
|
34
36
|
fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
35
37
|
fastled/assets/localhost-key.pem,sha256=Q-CNO_UoOd8fFNN4ljcnqwUeCMhzTplRjLO2x0pYRlU,1704
|
36
38
|
fastled/assets/localhost.pem,sha256=QTwUtTwjYWbm9m3pHW2IlK2nFZJ8b0pppxPjhgVZqQo,1619
|
@@ -38,9 +40,9 @@ fastled/site/build.py,sha256=2YKU_UWKlJdGnjdbAbaL0co6kceFMSTVYwH1KCmgPZA,13987
|
|
38
40
|
fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
|
39
41
|
fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
|
40
42
|
fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
|
41
|
-
fastled-1.4.
|
42
|
-
fastled-1.4.
|
43
|
-
fastled-1.4.
|
44
|
-
fastled-1.4.
|
45
|
-
fastled-1.4.
|
46
|
-
fastled-1.4.
|
43
|
+
fastled-1.4.4.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
44
|
+
fastled-1.4.4.dist-info/METADATA,sha256=TTvZsjGN8o5yMhMNOhn1_DzlxObwPsTgu0RxeNV4n8A,32369
|
45
|
+
fastled-1.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
46
|
+
fastled-1.4.4.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
47
|
+
fastled-1.4.4.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
|
48
|
+
fastled-1.4.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|