fastled 1.0.11__py2.py3-none-any.whl → 1.0.12__py2.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """FastLED Wasm Compiler package."""
2
2
 
3
- __version__ = "1.0.11"
3
+ __version__ = "1.0.12"
fastled/app.py CHANGED
@@ -20,8 +20,8 @@ from fastled.docker_manager import DockerManager
20
20
  from fastled.filewatcher import FileWatcherProcess
21
21
  from fastled.keyboard import SpaceBarWatcher
22
22
  from fastled.open_browser import open_browser_thread
23
- from fastled.sketch import looks_like_sketch_directory
24
- from fastled.web_compile import web_compile
23
+ from fastled.sketch import looks_like_fastled_repo, looks_like_sketch_directory
24
+ from fastled.web_compile import ConnectionResult, find_good_connection, web_compile
25
25
 
26
26
  machine = platform.machine().lower()
27
27
  IS_ARM: bool = "arm" in machine or "aarch64" in machine
@@ -108,13 +108,21 @@ def parse_args() -> argparse.Namespace:
108
108
  help="Skips the test to see if the current directory is a valid FastLED sketch directory",
109
109
  )
110
110
 
111
+ cwd_is_fastled = looks_like_fastled_repo(Path(os.getcwd()))
112
+
111
113
  args = parser.parse_args()
114
+ if not cwd_is_fastled and not args.localhost and not args.web and not args.server:
115
+ print(f"Using web compiler at {DEFAULT_URL}")
116
+ args.web = DEFAULT_URL
117
+ if cwd_is_fastled and not args.web:
118
+ args.localhost = True
112
119
  if args.localhost:
113
120
  args.web = "localhost"
114
- if args.web is not None:
115
- args.web = args.web if args.web == "" else args.web
116
121
  if args.server and args.web:
117
122
  parser.error("--server and --web are mutually exclusive")
123
+ if args.interactive and not args.server:
124
+ print("--interactive forces --server mode")
125
+ args.server = True
118
126
  if args.directory is None and not args.server:
119
127
  # does current directory look like a sketch?
120
128
  maybe_sketch_dir = Path(os.getcwd())
@@ -125,6 +133,7 @@ def parse_args() -> argparse.Namespace:
125
133
  "\nYou either need to specify a sketch directory or run in --server mode."
126
134
  )
127
135
  sys.exit(1)
136
+
128
137
  return args
129
138
 
130
139
 
@@ -188,7 +197,18 @@ def run_web_compiler(
188
197
 
189
198
 
190
199
  def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServer:
191
- if args.web:
200
+ is_local_host = "localhost" in args.web or "127.0.0.1" in args.web or args.localhost
201
+
202
+ # test to see if there is already a local host server
203
+ local_host_needs_server = False
204
+ if is_local_host:
205
+ addr = "localhost" if args.localhost else args.web
206
+ result: ConnectionResult | None = find_good_connection([addr])
207
+ if result is not None:
208
+ print(f"Found local server at {result.host}")
209
+ local_host_needs_server = True
210
+
211
+ if not local_host_needs_server and args.web:
192
212
  if isinstance(args.web, str):
193
213
  return args.web
194
214
  if isinstance(args.web, bool):
@@ -196,6 +216,7 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
196
216
  return args.web
197
217
  else:
198
218
  try:
219
+ print("No local server found, starting one...")
199
220
  compile_server = CompileServer()
200
221
  print("Waiting for the local compiler to start...")
201
222
  if not compile_server.wait_for_startup():
@@ -211,7 +232,7 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
211
232
 
212
233
  def run_client(args: argparse.Namespace) -> int:
213
234
  compile_server: CompileServer | None = None
214
- open_web_browser = not args.just_compile
235
+ open_web_browser = not args.just_compile and not args.interactive
215
236
  profile = args.profile
216
237
  if not args.force_compile and not looks_like_sketch_directory(Path(args.directory)):
217
238
  print(
fastled/compile_server.py CHANGED
@@ -48,6 +48,12 @@ class CompileServer:
48
48
  self.fastled_src_dir: Path | None = fastled_src_dir
49
49
  self.interactive = interactive
50
50
  self._port = self._start()
51
+ # fancy print
52
+ if not interactive:
53
+ msg = f"# FastLED Compile Server started at {self.url()} #"
54
+ print("\n" + "#" * len(msg))
55
+ print(msg)
56
+ print("#" * len(msg) + "\n")
51
57
 
52
58
  def using_fastled_src_dir_volume(self) -> bool:
53
59
  return self.fastled_src_dir is not None
fastled/web_compile.py CHANGED
@@ -1,277 +1,284 @@
1
- import io
2
- import json
3
- import os
4
- import shutil
5
- import tempfile
6
- import zipfile
7
- from concurrent.futures import ProcessPoolExecutor, as_completed
8
- from dataclasses import dataclass
9
- from pathlib import Path
10
-
11
- import httpx
12
-
13
- from fastled.build_mode import BuildMode
14
- from fastled.compile_server import SERVER_PORT
15
- from fastled.sketch import get_sketch_files
16
- from fastled.util import hash_file
17
-
18
- DEFAULT_HOST = "https://fastled.onrender.com"
19
- ENDPOINT_COMPILED_WASM = "compile/wasm"
20
- _TIMEOUT = 60 * 4 # 2 mins timeout
21
- _AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
22
- ENABLE_EMBEDDED_DATA = True
23
- _EXECUTOR = ProcessPoolExecutor(max_workers=8)
24
-
25
-
26
- @dataclass
27
- class ConnectionResult:
28
- host: str
29
- success: bool
30
- ipv4: bool
31
-
32
-
33
- @dataclass
34
- class WebCompileResult:
35
- success: bool
36
- stdout: str
37
- hash_value: str | None
38
- zip_bytes: bytes
39
-
40
- def __bool__(self) -> bool:
41
- return self.success
42
-
43
-
44
- def _sanitize_host(host: str) -> str:
45
- if host.startswith("http"):
46
- return host
47
- is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
48
- use_https = not is_local_host
49
- if use_https:
50
- return host if host.startswith("https://") else f"https://{host}"
51
- return host if host.startswith("http://") else f"http://{host}"
52
-
53
-
54
- def _test_connection(host: str, use_ipv4: bool) -> ConnectionResult:
55
- transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
56
- try:
57
- with httpx.Client(
58
- timeout=_TIMEOUT,
59
- transport=transport,
60
- ) as test_client:
61
- test_response = test_client.get(
62
- f"{host}/healthz", timeout=3, follow_redirects=True
63
- )
64
- result = ConnectionResult(host, test_response.status_code == 200, use_ipv4)
65
- except Exception:
66
- result = ConnectionResult(host, False, use_ipv4)
67
- return result
68
-
69
-
70
- def _file_info(file_path: Path) -> str:
71
- hash_txt = hash_file(file_path)
72
- file_size = file_path.stat().st_size
73
- json_str = json.dumps({"hash": hash_txt, "size": file_size})
74
- return json_str
75
-
76
-
77
- @dataclass
78
- class ZipResult:
79
- zip_bytes: bytes
80
- zip_embedded_bytes: bytes | None
81
- success: bool
82
- error: str | None
83
-
84
-
85
- def zip_files(directory: Path) -> ZipResult | Exception:
86
- print("Zipping files...")
87
- try:
88
- files = get_sketch_files(directory)
89
- if not files:
90
- raise FileNotFoundError(f"No files found in {directory}")
91
- for f in files:
92
- print(f"Adding file: {f}")
93
- # Create in-memory zip file
94
- has_embedded_zip = False
95
- zip_embedded_buffer = io.BytesIO()
96
- zip_buffer = io.BytesIO()
97
- with zipfile.ZipFile(
98
- zip_embedded_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
99
- ) as emebedded_zip_file:
100
- with zipfile.ZipFile(
101
- zip_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
102
- ) as zip_file:
103
- for file_path in files:
104
- relative_path = file_path.relative_to(directory)
105
- achive_path = str(Path("wasm") / relative_path)
106
- if str(relative_path).startswith("data") and ENABLE_EMBEDDED_DATA:
107
- _file_info_str = _file_info(file_path)
108
- zip_file.writestr(
109
- achive_path + ".embedded.json", _file_info_str
110
- )
111
- emebedded_zip_file.write(file_path, relative_path)
112
- has_embedded_zip = True
113
- else:
114
- zip_file.write(file_path, achive_path)
115
- result = ZipResult(
116
- zip_bytes=zip_buffer.getvalue(),
117
- zip_embedded_bytes=(
118
- zip_embedded_buffer.getvalue() if has_embedded_zip else None
119
- ),
120
- success=True,
121
- error=None,
122
- )
123
- return result
124
- except Exception as e:
125
- return e
126
-
127
-
128
- def web_compile(
129
- directory: Path,
130
- host: str | None = None,
131
- auth_token: str | None = None,
132
- build_mode: BuildMode | None = None,
133
- profile: bool = False,
134
- ) -> WebCompileResult:
135
- host = _sanitize_host(host or DEFAULT_HOST)
136
- print("Compiling on", host)
137
- auth_token = auth_token or _AUTH_TOKEN
138
-
139
- if not directory.exists():
140
- raise FileNotFoundError(f"Directory not found: {directory}")
141
-
142
- zip_result = zip_files(directory)
143
-
144
- if isinstance(zip_result, Exception):
145
- return WebCompileResult(
146
- success=False, stdout=str(zip_result), hash_value=None, zip_bytes=b""
147
- )
148
- zip_bytes = zip_result.zip_bytes
149
- archive_size = len(zip_bytes)
150
- print(f"Web compiling on {host}...")
151
- try:
152
-
153
- files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
154
- urls = [host]
155
- domain = host.split("://")[-1]
156
- if ":" not in domain:
157
- urls.append(f"{host}:{SERVER_PORT}")
158
- test_connection_result: ConnectionResult | None = None
159
-
160
- futures: list = []
161
- ip_versions = [True, False] if "localhost" not in host else [True]
162
- for ipv4 in ip_versions:
163
- for url in urls:
164
- f = _EXECUTOR.submit(_test_connection, url, ipv4)
165
- futures.append(f)
166
-
167
- succeeded = False
168
- for future in as_completed(futures):
169
- result: ConnectionResult = future.result()
170
-
171
- if result.success:
172
- print(f"Connection successful to {result.host}")
173
- succeeded = True
174
- # host = test_url
175
- test_connection_result = result
176
- break
177
- else:
178
- print(f"Ignoring {result.host} due to connection failure")
179
-
180
- if not succeeded:
181
- print("Connection failed to all endpoints")
182
- return WebCompileResult(
183
- success=False,
184
- stdout="Connection failed",
185
- hash_value=None,
186
- zip_bytes=b"",
187
- )
188
- assert test_connection_result is not None
189
- ipv4_stmt = "IPv4" if test_connection_result.ipv4 else "IPv6"
190
- transport = (
191
- httpx.HTTPTransport(local_address="0.0.0.0")
192
- if test_connection_result.ipv4
193
- else None
194
- )
195
- with httpx.Client(
196
- transport=transport,
197
- timeout=_TIMEOUT,
198
- ) as client:
199
- headers = {
200
- "accept": "application/json",
201
- "authorization": auth_token,
202
- "build": (
203
- build_mode.value.lower()
204
- if build_mode
205
- else BuildMode.QUICK.value.lower()
206
- ),
207
- "profile": "true" if profile else "false",
208
- }
209
-
210
- url = f"{test_connection_result.host}/{ENDPOINT_COMPILED_WASM}"
211
- print(f"Compiling on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes")
212
- response = client.post(
213
- url,
214
- follow_redirects=True,
215
- files=files,
216
- headers=headers,
217
- timeout=_TIMEOUT,
218
- )
219
-
220
- if response.status_code != 200:
221
- json_response = response.json()
222
- detail = json_response.get("detail", "Could not compile")
223
- return WebCompileResult(
224
- success=False, stdout=detail, hash_value=None, zip_bytes=b""
225
- )
226
-
227
- print(f"Response status code: {response}")
228
- # Create a temporary directory to extract the zip
229
- with tempfile.TemporaryDirectory() as extract_dir:
230
- extract_path = Path(extract_dir)
231
-
232
- # Write the response content to a temporary zip file
233
- temp_zip = extract_path / "response.zip"
234
- temp_zip.write_bytes(response.content)
235
-
236
- # Extract the zip
237
- shutil.unpack_archive(temp_zip, extract_path, "zip")
238
-
239
- if zip_result.zip_embedded_bytes:
240
- # extract the embedded bytes, which were not sent to the server
241
- temp_zip.write_bytes(zip_result.zip_embedded_bytes)
242
- shutil.unpack_archive(temp_zip, extract_path, "zip")
243
-
244
- # we don't need the temp zip anymore
245
- temp_zip.unlink()
246
-
247
- # Read stdout from out.txt if it exists
248
- stdout_file = extract_path / "out.txt"
249
- hash_file = extract_path / "hash.txt"
250
- stdout = stdout_file.read_text() if stdout_file.exists() else ""
251
- hash_value = hash_file.read_text() if hash_file.exists() else None
252
-
253
- # now rezip the extracted files since we added the embedded json files
254
- out_buffer = io.BytesIO()
255
- with zipfile.ZipFile(
256
- out_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
257
- ) as out_zip:
258
- for root, _, _files in os.walk(extract_path):
259
- for file in _files:
260
- file_path = Path(root) / file
261
- relative_path = file_path.relative_to(extract_path)
262
- out_zip.write(file_path, relative_path)
263
-
264
- return WebCompileResult(
265
- success=True,
266
- stdout=stdout,
267
- hash_value=hash_value,
268
- zip_bytes=out_buffer.getvalue(),
269
- )
270
- except KeyboardInterrupt:
271
- print("Keyboard interrupt")
272
- raise
273
- except httpx.HTTPError as e:
274
- print(f"Error: {e}")
275
- return WebCompileResult(
276
- success=False, stdout=str(e), hash_value=None, zip_bytes=b""
277
- )
1
+ import io
2
+ import json
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ import zipfile
7
+ from concurrent.futures import Future, ProcessPoolExecutor, as_completed
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ import httpx
12
+
13
+ from fastled.build_mode import BuildMode
14
+ from fastled.compile_server import SERVER_PORT
15
+ from fastled.sketch import get_sketch_files
16
+ from fastled.util import hash_file
17
+
18
+ DEFAULT_HOST = "https://fastled.onrender.com"
19
+ ENDPOINT_COMPILED_WASM = "compile/wasm"
20
+ _TIMEOUT = 60 * 4 # 2 mins timeout
21
+ _AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
22
+ ENABLE_EMBEDDED_DATA = True
23
+ _EXECUTOR = ProcessPoolExecutor(max_workers=8)
24
+
25
+
26
+ @dataclass
27
+ class ConnectionResult:
28
+ host: str
29
+ success: bool
30
+ ipv4: bool
31
+
32
+
33
+ @dataclass
34
+ class WebCompileResult:
35
+ success: bool
36
+ stdout: str
37
+ hash_value: str | None
38
+ zip_bytes: bytes
39
+
40
+ def __bool__(self) -> bool:
41
+ return self.success
42
+
43
+
44
+ def _sanitize_host(host: str) -> str:
45
+ if host.startswith("http"):
46
+ return host
47
+ is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
48
+ use_https = not is_local_host
49
+ if use_https:
50
+ return host if host.startswith("https://") else f"https://{host}"
51
+ return host if host.startswith("http://") else f"http://{host}"
52
+
53
+
54
+ def _test_connection(host: str, use_ipv4: bool) -> ConnectionResult:
55
+ # Function static cache
56
+ connection_cache = _test_connection.__dict__.setdefault("_CONNECTION_CACHE", {})
57
+ key = f"{host}_{use_ipv4}"
58
+ cached_result = connection_cache.get(key)
59
+ if cached_result is not None:
60
+ return cached_result
61
+ transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
62
+ try:
63
+ with httpx.Client(
64
+ timeout=_TIMEOUT,
65
+ transport=transport,
66
+ ) as test_client:
67
+ test_response = test_client.get(
68
+ f"{host}/healthz", timeout=3, follow_redirects=True
69
+ )
70
+ result = ConnectionResult(host, test_response.status_code == 200, use_ipv4)
71
+ except Exception:
72
+ result = ConnectionResult(host, False, use_ipv4)
73
+ connection_cache[key] = result
74
+ return result
75
+
76
+
77
+ def _file_info(file_path: Path) -> str:
78
+ hash_txt = hash_file(file_path)
79
+ file_size = file_path.stat().st_size
80
+ json_str = json.dumps({"hash": hash_txt, "size": file_size})
81
+ return json_str
82
+
83
+
84
+ @dataclass
85
+ class ZipResult:
86
+ zip_bytes: bytes
87
+ zip_embedded_bytes: bytes | None
88
+ success: bool
89
+ error: str | None
90
+
91
+
92
+ def zip_files(directory: Path) -> ZipResult | Exception:
93
+ print("Zipping files...")
94
+ try:
95
+ files = get_sketch_files(directory)
96
+ if not files:
97
+ raise FileNotFoundError(f"No files found in {directory}")
98
+ for f in files:
99
+ print(f"Adding file: {f}")
100
+ # Create in-memory zip file
101
+ has_embedded_zip = False
102
+ zip_embedded_buffer = io.BytesIO()
103
+ zip_buffer = io.BytesIO()
104
+ with zipfile.ZipFile(
105
+ zip_embedded_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
106
+ ) as emebedded_zip_file:
107
+ with zipfile.ZipFile(
108
+ zip_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
109
+ ) as zip_file:
110
+ for file_path in files:
111
+ relative_path = file_path.relative_to(directory)
112
+ achive_path = str(Path("wasm") / relative_path)
113
+ if str(relative_path).startswith("data") and ENABLE_EMBEDDED_DATA:
114
+ _file_info_str = _file_info(file_path)
115
+ zip_file.writestr(
116
+ achive_path + ".embedded.json", _file_info_str
117
+ )
118
+ emebedded_zip_file.write(file_path, relative_path)
119
+ has_embedded_zip = True
120
+ else:
121
+ zip_file.write(file_path, achive_path)
122
+ result = ZipResult(
123
+ zip_bytes=zip_buffer.getvalue(),
124
+ zip_embedded_bytes=(
125
+ zip_embedded_buffer.getvalue() if has_embedded_zip else None
126
+ ),
127
+ success=True,
128
+ error=None,
129
+ )
130
+ return result
131
+ except Exception as e:
132
+ return e
133
+
134
+
135
+ def find_good_connection(
136
+ urls: list[str], filter_out_bad=False
137
+ ) -> ConnectionResult | None:
138
+ futures: list[Future] = []
139
+ ip_versions = [True, False]
140
+ # Start all connection tests
141
+ for ipv4 in ip_versions:
142
+ for url in urls:
143
+ f = _EXECUTOR.submit(_test_connection, url, ipv4)
144
+ futures.append(f)
145
+
146
+ try:
147
+ # Return first successful result
148
+ for future in as_completed(futures):
149
+ result: ConnectionResult = future.result()
150
+ if result.success or not filter_out_bad:
151
+ return result
152
+ finally:
153
+ # Cancel any remaining futures
154
+ for future in futures:
155
+ future.cancel()
156
+ return None
157
+
158
+
159
+ def web_compile(
160
+ directory: Path,
161
+ host: str | None = None,
162
+ auth_token: str | None = None,
163
+ build_mode: BuildMode | None = None,
164
+ profile: bool = False,
165
+ ) -> WebCompileResult:
166
+ host = _sanitize_host(host or DEFAULT_HOST)
167
+ print("Compiling on", host)
168
+ auth_token = auth_token or _AUTH_TOKEN
169
+ if not directory.exists():
170
+ raise FileNotFoundError(f"Directory not found: {directory}")
171
+ zip_result = zip_files(directory)
172
+ if isinstance(zip_result, Exception):
173
+ return WebCompileResult(
174
+ success=False, stdout=str(zip_result), hash_value=None, zip_bytes=b""
175
+ )
176
+ zip_bytes = zip_result.zip_bytes
177
+ archive_size = len(zip_bytes)
178
+ print(f"Web compiling on {host}...")
179
+ try:
180
+ urls = [host]
181
+ domain = host.split("://")[-1]
182
+ if ":" not in domain:
183
+ urls.append(f"{host}:{SERVER_PORT}")
184
+
185
+ connection_result = find_good_connection(urls)
186
+ if connection_result is None:
187
+ print("Connection failed to all endpoints")
188
+ return WebCompileResult(
189
+ success=False,
190
+ stdout="Connection failed",
191
+ hash_value=None,
192
+ zip_bytes=b"",
193
+ )
194
+
195
+ ipv4_stmt = "IPv4" if connection_result.ipv4 else "IPv6"
196
+ transport = (
197
+ httpx.HTTPTransport(local_address="0.0.0.0")
198
+ if connection_result.ipv4
199
+ else None
200
+ )
201
+ with httpx.Client(
202
+ transport=transport,
203
+ timeout=_TIMEOUT,
204
+ ) as client:
205
+ headers = {
206
+ "accept": "application/json",
207
+ "authorization": auth_token,
208
+ "build": (
209
+ build_mode.value.lower()
210
+ if build_mode
211
+ else BuildMode.QUICK.value.lower()
212
+ ),
213
+ "profile": "true" if profile else "false",
214
+ }
215
+
216
+ url = f"{connection_result.host}/{ENDPOINT_COMPILED_WASM}"
217
+ print(f"Compiling on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes")
218
+ files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
219
+ response = client.post(
220
+ url,
221
+ follow_redirects=True,
222
+ files=files,
223
+ headers=headers,
224
+ timeout=_TIMEOUT,
225
+ )
226
+
227
+ if response.status_code != 200:
228
+ json_response = response.json()
229
+ detail = json_response.get("detail", "Could not compile")
230
+ return WebCompileResult(
231
+ success=False, stdout=detail, hash_value=None, zip_bytes=b""
232
+ )
233
+
234
+ print(f"Response status code: {response}")
235
+ # Create a temporary directory to extract the zip
236
+ with tempfile.TemporaryDirectory() as extract_dir:
237
+ extract_path = Path(extract_dir)
238
+
239
+ # Write the response content to a temporary zip file
240
+ temp_zip = extract_path / "response.zip"
241
+ temp_zip.write_bytes(response.content)
242
+
243
+ # Extract the zip
244
+ shutil.unpack_archive(temp_zip, extract_path, "zip")
245
+
246
+ if zip_result.zip_embedded_bytes:
247
+ # extract the embedded bytes, which were not sent to the server
248
+ temp_zip.write_bytes(zip_result.zip_embedded_bytes)
249
+ shutil.unpack_archive(temp_zip, extract_path, "zip")
250
+
251
+ # we don't need the temp zip anymore
252
+ temp_zip.unlink()
253
+
254
+ # Read stdout from out.txt if it exists
255
+ stdout_file = extract_path / "out.txt"
256
+ hash_file = extract_path / "hash.txt"
257
+ stdout = stdout_file.read_text() if stdout_file.exists() else ""
258
+ hash_value = hash_file.read_text() if hash_file.exists() else None
259
+
260
+ # now rezip the extracted files since we added the embedded json files
261
+ out_buffer = io.BytesIO()
262
+ with zipfile.ZipFile(
263
+ out_buffer, "w", zipfile.ZIP_DEFLATED, compresslevel=9
264
+ ) as out_zip:
265
+ for root, _, _files in os.walk(extract_path):
266
+ for file in _files:
267
+ file_path = Path(root) / file
268
+ relative_path = file_path.relative_to(extract_path)
269
+ out_zip.write(file_path, relative_path)
270
+
271
+ return WebCompileResult(
272
+ success=True,
273
+ stdout=stdout,
274
+ hash_value=hash_value,
275
+ zip_bytes=out_buffer.getvalue(),
276
+ )
277
+ except KeyboardInterrupt:
278
+ print("Keyboard interrupt")
279
+ raise
280
+ except httpx.HTTPError as e:
281
+ print(f"Error: {e}")
282
+ return WebCompileResult(
283
+ success=False, stdout=str(e), hash_value=None, zip_bytes=b""
284
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.0.11
3
+ Version: 1.0.12
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -16,7 +16,6 @@ Requires-Dist: watchdog
16
16
  Requires-Dist: livereload
17
17
  Requires-Dist: download
18
18
  Requires-Dist: filelock
19
- Requires-Dist: windows-curses; platform_system == "Windows"
20
19
 
21
20
  # FastLED wasm compiler
22
21
 
@@ -91,9 +90,9 @@ Pre-processing is done to your source files. A fake Arduino.h will be inserted i
91
90
  provide shims for most of the common api points.
92
91
 
93
92
 
94
-
95
93
  # Revisions
96
94
 
95
+ * 1.1.12 - By default, fastled will default to the web compiler. `--localhost` to either attach to an existing server launched with `--server` or else one will be created automatically and launched.
97
96
  * 1.1.11 - Dev improvement: FastLED src code volume mapped into docker will just in time update without having to manually trigger it.
98
97
  * 1.1.10 - Swap large assets with embedded placeholders. This helps video sketches upload and compile instantly. Assets are re-added on after compile artifacts are returned.
99
98
  * 1.1.9 - Remove auto server and instead tell the user corrective action to take.
@@ -1,8 +1,8 @@
1
- fastled/__init__.py,sha256=f3KhION06nm0tJFF8POOIXhKebdmWIMlx8mVEmVFUrw,64
2
- fastled/app.py,sha256=WpcXlNUJzMPmH6BZIK6S15SGiAWtQmXungxlw0uOooc,13955
1
+ fastled/__init__.py,sha256=XO4EYoMURe1bnLz9dzABggQ31idNxZwqUDWnM5CR1oY,64
2
+ fastled/app.py,sha256=VB_uJDrFDvPgJuPh8crntWztKteWmHp5porqNSCCXu4,14949
3
3
  fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
4
4
  fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
5
- fastled/compile_server.py,sha256=-Qd9x0l-KfiFRGqZYOi3BRD7iEqfJmJU6EgFY-QJodg,7919
5
+ fastled/compile_server.py,sha256=Om9fFpg9Yx4_WJDC_pG5-tYtAwreKrEAx-k8dJQBkJs,8146
6
6
  fastled/docker_manager.py,sha256=gaDZpFV7E-9JhYIn6ahkP--9dGT32-WR5wZaU-o--6g,9107
7
7
  fastled/filewatcher.py,sha256=fJNMQRDCpihSL4nQeYPqbD4m1Jzjcz_-YRAo-wlPW6k,6518
8
8
  fastled/keyboard.py,sha256=PiAUhga8R_crMqFQRwjKyg6bgVTMlpezWa0gaGmL_So,2542
@@ -10,11 +10,11 @@ fastled/open_browser.py,sha256=RRHcsZ5Vzsw1AuZUEYuSfjKmf_9j3NGMDUR-FndHmqs,1483
10
10
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
11
11
  fastled/sketch.py,sha256=KhhPFqlFVlBk8YrzFy7-ioe7zEzecgrVLhyFbLpBp7k,1845
12
12
  fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
13
- fastled/web_compile.py,sha256=jN9p-ODWm_EspFlsT4M7SfyI_wAdi17u7_JEIef5WVw,9645
13
+ fastled/web_compile.py,sha256=RqHGiG9ZV3siQVhnQ8Ff7p-5aUKCwQWdoxs-zk7ItDE,10149
14
14
  fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
15
- fastled-1.0.11.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
16
- fastled-1.0.11.dist-info/METADATA,sha256=vi_VLpN90hbv8hxpGib2nRtRTYj5GBnoakQccKh7iuY,6780
17
- fastled-1.0.11.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
18
- fastled-1.0.11.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
19
- fastled-1.0.11.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
20
- fastled-1.0.11.dist-info/RECORD,,
15
+ fastled-1.0.12.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
16
+ fastled-1.0.12.dist-info/METADATA,sha256=fpErTM2GXagl_3JPvnrw8b3SUIJ6jeTPa-QFkTilAfc,6919
17
+ fastled-1.0.12.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
18
+ fastled-1.0.12.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
19
+ fastled-1.0.12.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
20
+ fastled-1.0.12.dist-info/RECORD,,