fastled 1.4.30__py3-none-any.whl → 1.4.32__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/__version__.py +1 -1
- fastled/app.py +10 -0
- fastled/args.py +15 -0
- fastled/install/__init__.py +1 -0
- fastled/install/examples_manager.py +62 -0
- fastled/install/extension_manager.py +113 -0
- fastled/install/main.py +156 -0
- fastled/install/project_detection.py +167 -0
- fastled/install/test_install.py +373 -0
- fastled/install/vscode_config.py +167 -0
- fastled/open_browser.py +7 -4
- fastled/parse_args.py +45 -0
- fastled/playwright/playwright_browser.py +75 -120
- fastled/playwright/resize_tracking.py +127 -0
- fastled/print_filter.py +52 -52
- fastled/string_diff.py +165 -165
- fastled/version.py +41 -41
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/METADATA +531 -531
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/RECORD +23 -15
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/WHEEL +0 -0
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/entry_points.txt +0 -0
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.4.30.dist-info → fastled-1.4.32.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
"""Comprehensive test suite for FastLED install feature."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import sys
|
7
|
+
import tempfile
|
8
|
+
import unittest
|
9
|
+
from pathlib import Path
|
10
|
+
from unittest.mock import MagicMock, patch
|
11
|
+
|
12
|
+
# Add parent directory to path for imports
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
14
|
+
|
15
|
+
from fastled.install.main import auto_execute_fastled, fastled_install
|
16
|
+
from fastled.install.project_detection import (
|
17
|
+
check_existing_arduino_content,
|
18
|
+
)
|
19
|
+
from fastled.install.vscode_config import (
|
20
|
+
generate_fastled_tasks,
|
21
|
+
update_launch_json_for_arduino,
|
22
|
+
update_vscode_settings_for_fastled,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class TestFastLEDInstall(unittest.TestCase):
|
27
|
+
"""Test FastLED installation functionality."""
|
28
|
+
|
29
|
+
def setUp(self):
|
30
|
+
"""Set up test environment."""
|
31
|
+
self.test_dir = tempfile.mkdtemp()
|
32
|
+
self.original_cwd = os.getcwd()
|
33
|
+
os.chdir(self.test_dir)
|
34
|
+
|
35
|
+
def tearDown(self):
|
36
|
+
"""Clean up test environment."""
|
37
|
+
os.chdir(self.original_cwd)
|
38
|
+
shutil.rmtree(self.test_dir)
|
39
|
+
|
40
|
+
@patch("fastled.install.project_detection.shutil.which")
|
41
|
+
@patch("builtins.input")
|
42
|
+
def test_dry_run_basic_project(self, mock_input, mock_which):
|
43
|
+
"""Test 1: Dry-run in basic project."""
|
44
|
+
# Setup
|
45
|
+
mock_which.return_value = "/usr/bin/code"
|
46
|
+
mock_input.return_value = "y"
|
47
|
+
os.makedirs(".vscode")
|
48
|
+
|
49
|
+
# Run
|
50
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
51
|
+
|
52
|
+
# Verify
|
53
|
+
self.assertTrue(result)
|
54
|
+
self.assertTrue(Path(".vscode/launch.json").exists())
|
55
|
+
self.assertTrue(Path(".vscode/tasks.json").exists())
|
56
|
+
|
57
|
+
@patch("fastled.install.project_detection.is_fastled_repository")
|
58
|
+
@patch("fastled.install.project_detection.detect_fastled_project")
|
59
|
+
@patch("fastled.install.project_detection.shutil.which")
|
60
|
+
@patch("builtins.input")
|
61
|
+
def test_dry_run_fastled_external(
|
62
|
+
self, mock_input, mock_which, mock_detect, mock_repo
|
63
|
+
):
|
64
|
+
"""Test 2: Dry-run in external FastLED project."""
|
65
|
+
# Setup
|
66
|
+
mock_which.return_value = "/usr/bin/code"
|
67
|
+
mock_input.return_value = "y"
|
68
|
+
mock_detect.return_value = True
|
69
|
+
mock_repo.return_value = False
|
70
|
+
os.makedirs(".vscode")
|
71
|
+
|
72
|
+
# Create library.json
|
73
|
+
with open("library.json", "w") as f:
|
74
|
+
json.dump({"name": "FastLED"}, f)
|
75
|
+
|
76
|
+
# Run
|
77
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
78
|
+
|
79
|
+
# Verify
|
80
|
+
self.assertTrue(result)
|
81
|
+
self.assertFalse(Path(".vscode/settings.json").exists()) # No clangd settings
|
82
|
+
|
83
|
+
@patch("fastled.install.project_detection.is_fastled_repository")
|
84
|
+
@patch("fastled.install.project_detection.detect_fastled_project")
|
85
|
+
@patch("fastled.install.project_detection.shutil.which")
|
86
|
+
@patch("builtins.input")
|
87
|
+
def test_dry_run_fastled_repository(
|
88
|
+
self, mock_input, mock_which, mock_detect, mock_repo
|
89
|
+
):
|
90
|
+
"""Test 3: Dry-run in actual FastLED repository."""
|
91
|
+
# Setup
|
92
|
+
mock_which.return_value = "/usr/bin/code"
|
93
|
+
mock_input.return_value = "y"
|
94
|
+
mock_detect.return_value = True
|
95
|
+
mock_repo.return_value = True
|
96
|
+
os.makedirs(".vscode")
|
97
|
+
|
98
|
+
# Run
|
99
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
100
|
+
|
101
|
+
# Verify
|
102
|
+
self.assertTrue(result)
|
103
|
+
self.assertTrue(Path(".vscode/settings.json").exists()) # Has clangd settings
|
104
|
+
|
105
|
+
@patch("fastled.install.project_detection.shutil.which")
|
106
|
+
@patch("builtins.input")
|
107
|
+
def test_existing_vscode_project(self, mock_input, mock_which):
|
108
|
+
"""Test 4: Merge with existing .vscode configs."""
|
109
|
+
# Setup
|
110
|
+
mock_which.return_value = "/usr/bin/code"
|
111
|
+
mock_input.return_value = "y"
|
112
|
+
os.makedirs(".vscode")
|
113
|
+
|
114
|
+
# Create existing launch.json
|
115
|
+
existing_config = {
|
116
|
+
"version": "0.2.0",
|
117
|
+
"configurations": [{"name": "Existing", "type": "node"}],
|
118
|
+
}
|
119
|
+
with open(".vscode/launch.json", "w") as f:
|
120
|
+
json.dump(existing_config, f)
|
121
|
+
|
122
|
+
# Run
|
123
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
124
|
+
|
125
|
+
# Verify
|
126
|
+
self.assertTrue(result)
|
127
|
+
with open(".vscode/launch.json") as f:
|
128
|
+
data = json.load(f)
|
129
|
+
self.assertEqual(len(data["configurations"]), 2)
|
130
|
+
self.assertEqual(
|
131
|
+
data["configurations"][0]["name"],
|
132
|
+
"🎯 Auto Debug (Smart File Detection)",
|
133
|
+
)
|
134
|
+
|
135
|
+
@patch("fastled.install.project_detection.shutil.which")
|
136
|
+
@patch("builtins.input")
|
137
|
+
def test_parent_directory_detection(self, mock_input, mock_which):
|
138
|
+
"""Test 5: Find .vscode in parent directories."""
|
139
|
+
# Setup
|
140
|
+
mock_which.return_value = "/usr/bin/code"
|
141
|
+
parent_dir = Path(self.test_dir)
|
142
|
+
child_dir = parent_dir / "child" / "grandchild"
|
143
|
+
child_dir.mkdir(parents=True)
|
144
|
+
(parent_dir / ".vscode").mkdir()
|
145
|
+
os.chdir(child_dir)
|
146
|
+
|
147
|
+
# Test non-interactive mode - should fail
|
148
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
149
|
+
self.assertFalse(result) # Should fail in non-interactive
|
150
|
+
|
151
|
+
# Test interactive mode
|
152
|
+
mock_input.side_effect = ["y", "y"] # Yes to parent, yes to extension
|
153
|
+
result = fastled_install(dry_run=True, no_interactive=False)
|
154
|
+
|
155
|
+
# Verify - we should be in parent directory now
|
156
|
+
self.assertTrue(result)
|
157
|
+
self.assertEqual(Path.cwd(), parent_dir)
|
158
|
+
|
159
|
+
@patch("fastled.install.project_detection.shutil.which")
|
160
|
+
@patch("builtins.input")
|
161
|
+
def test_project_generation(self, mock_input, mock_which):
|
162
|
+
"""Test 6: Generate new VSCode project."""
|
163
|
+
# Setup
|
164
|
+
mock_which.return_value = "/usr/bin/code"
|
165
|
+
|
166
|
+
# Test non-interactive mode - should fail
|
167
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
168
|
+
self.assertFalse(result) # Should fail without .vscode
|
169
|
+
|
170
|
+
# Test interactive mode
|
171
|
+
mock_input.side_effect = ["y", "y"] # Yes to generate, yes to extension
|
172
|
+
result = fastled_install(dry_run=True, no_interactive=False)
|
173
|
+
|
174
|
+
# Verify
|
175
|
+
self.assertTrue(result)
|
176
|
+
self.assertTrue(Path(".vscode").exists())
|
177
|
+
self.assertTrue(Path(".vscode/launch.json").exists())
|
178
|
+
self.assertTrue(Path(".vscode/tasks.json").exists())
|
179
|
+
|
180
|
+
def test_arduino_content_detection(self):
|
181
|
+
"""Test 7: Detect existing .ino files."""
|
182
|
+
# Create .ino file
|
183
|
+
with open("test.ino", "w") as f:
|
184
|
+
f.write("void setup() {}")
|
185
|
+
|
186
|
+
# Test detection
|
187
|
+
self.assertTrue(check_existing_arduino_content())
|
188
|
+
|
189
|
+
# Remove and test examples folder
|
190
|
+
os.unlink("test.ino")
|
191
|
+
os.makedirs("examples")
|
192
|
+
self.assertTrue(check_existing_arduino_content())
|
193
|
+
|
194
|
+
@patch("fastled.install.project_detection.shutil.which")
|
195
|
+
@patch("builtins.input")
|
196
|
+
def test_tasks_json_merging(self, mock_input, mock_which):
|
197
|
+
"""Test 8: Merge FastLED tasks with existing."""
|
198
|
+
# Setup
|
199
|
+
mock_which.return_value = "/usr/bin/code"
|
200
|
+
mock_input.return_value = "y"
|
201
|
+
os.makedirs(".vscode")
|
202
|
+
|
203
|
+
# Create existing tasks.json
|
204
|
+
existing_tasks = {
|
205
|
+
"version": "2.0.0",
|
206
|
+
"tasks": [{"label": "Existing Task", "command": "echo"}],
|
207
|
+
}
|
208
|
+
with open(".vscode/tasks.json", "w") as f:
|
209
|
+
json.dump(existing_tasks, f)
|
210
|
+
|
211
|
+
# Run
|
212
|
+
generate_fastled_tasks()
|
213
|
+
|
214
|
+
# Verify
|
215
|
+
with open(".vscode/tasks.json") as f:
|
216
|
+
data = json.load(f)
|
217
|
+
self.assertEqual(len(data["tasks"]), 3) # 1 existing + 2 FastLED
|
218
|
+
labels = [task["label"] for task in data["tasks"]]
|
219
|
+
self.assertIn("Run FastLED (Debug)", labels)
|
220
|
+
self.assertIn("Run FastLED (Quick)", labels)
|
221
|
+
|
222
|
+
def test_launch_json_updates(self):
|
223
|
+
"""Test 9: Update launch.json configurations."""
|
224
|
+
# Setup
|
225
|
+
os.makedirs(".vscode")
|
226
|
+
|
227
|
+
# Run
|
228
|
+
update_launch_json_for_arduino()
|
229
|
+
|
230
|
+
# Verify
|
231
|
+
with open(".vscode/launch.json") as f:
|
232
|
+
data = json.load(f)
|
233
|
+
self.assertEqual(len(data["configurations"]), 1)
|
234
|
+
config = data["configurations"][0]
|
235
|
+
self.assertEqual(config["name"], "🎯 Auto Debug (Smart File Detection)")
|
236
|
+
self.assertEqual(config["type"], "auto-debug")
|
237
|
+
self.assertIn("*.ino", config["map"])
|
238
|
+
|
239
|
+
@patch("fastled.install.project_detection.is_fastled_repository")
|
240
|
+
def test_safety_clangd_protection(self, mock_repo):
|
241
|
+
"""Test 10: 🚨 CRITICAL - clangd safety protection."""
|
242
|
+
# Setup
|
243
|
+
os.makedirs(".vscode")
|
244
|
+
|
245
|
+
# Test non-repository
|
246
|
+
mock_repo.return_value = False
|
247
|
+
update_vscode_settings_for_fastled()
|
248
|
+
self.assertFalse(Path(".vscode/settings.json").exists())
|
249
|
+
|
250
|
+
# Test repository
|
251
|
+
mock_repo.return_value = True
|
252
|
+
update_vscode_settings_for_fastled()
|
253
|
+
self.assertTrue(Path(".vscode/settings.json").exists())
|
254
|
+
|
255
|
+
with open(".vscode/settings.json") as f:
|
256
|
+
data = json.load(f)
|
257
|
+
self.assertIn("clangd.arguments", data)
|
258
|
+
|
259
|
+
@patch("fastled.install.main.check_existing_arduino_content")
|
260
|
+
def test_auto_execution_trigger(self, mock_check):
|
261
|
+
"""Test 11: Post-installation auto-execution."""
|
262
|
+
# Setup
|
263
|
+
mock_check.return_value = True
|
264
|
+
original_argv = sys.argv.copy()
|
265
|
+
sys.argv = ["fastled", "--install"]
|
266
|
+
|
267
|
+
try:
|
268
|
+
# We'll test that auto_execute_fastled modifies sys.argv correctly
|
269
|
+
# without actually calling main()
|
270
|
+
with patch("fastled.app.main") as mock_main:
|
271
|
+
mock_main.return_value = 0
|
272
|
+
auto_execute_fastled()
|
273
|
+
|
274
|
+
# Verify
|
275
|
+
mock_main.assert_called_once()
|
276
|
+
# Check that argv was filtered before calling main
|
277
|
+
# The function should have set sys.argv to ['fastled', '.']
|
278
|
+
finally:
|
279
|
+
sys.argv = original_argv
|
280
|
+
|
281
|
+
@patch("fastled.install.project_detection.shutil.which")
|
282
|
+
def test_no_ide_error_handling(self, mock_which):
|
283
|
+
"""Test 12: Error when no IDE available."""
|
284
|
+
# Setup
|
285
|
+
mock_which.return_value = None
|
286
|
+
|
287
|
+
# Run
|
288
|
+
from fastled.install.project_detection import validate_vscode_project
|
289
|
+
|
290
|
+
result = validate_vscode_project(no_interactive=True)
|
291
|
+
|
292
|
+
# Verify
|
293
|
+
self.assertFalse(result)
|
294
|
+
|
295
|
+
@patch("subprocess.run")
|
296
|
+
@patch("builtins.input")
|
297
|
+
def test_examples_installation(self, mock_input, mock_run):
|
298
|
+
"""Test 13: --project-init examples installation."""
|
299
|
+
# Setup
|
300
|
+
mock_input.return_value = "y"
|
301
|
+
mock_run.return_value = MagicMock(returncode=0)
|
302
|
+
|
303
|
+
# Run
|
304
|
+
from fastled.install.examples_manager import (
|
305
|
+
install_fastled_examples_via_project_init,
|
306
|
+
)
|
307
|
+
|
308
|
+
# Test non-interactive mode - should skip
|
309
|
+
result = install_fastled_examples_via_project_init(no_interactive=True)
|
310
|
+
self.assertFalse(result)
|
311
|
+
|
312
|
+
# Test interactive mode
|
313
|
+
result = install_fastled_examples_via_project_init(no_interactive=False)
|
314
|
+
self.assertTrue(result)
|
315
|
+
mock_run.assert_called_once()
|
316
|
+
call_args = mock_run.call_args[0][0]
|
317
|
+
self.assertEqual(call_args, ["fastled", "--project-init"])
|
318
|
+
|
319
|
+
@patch("fastled.install.extension_manager.download_auto_debug_extension")
|
320
|
+
@patch("fastled.install.extension_manager.install_vscode_extensions")
|
321
|
+
def test_extension_installation_flow(self, mock_install, mock_download):
|
322
|
+
"""Test 14: Auto Debug extension prompt/install."""
|
323
|
+
# Setup
|
324
|
+
mock_download.return_value = Path("test.vsix")
|
325
|
+
mock_install.return_value = True
|
326
|
+
|
327
|
+
# Test dry run
|
328
|
+
from fastled.install.extension_manager import install_auto_debug_extension
|
329
|
+
|
330
|
+
result = install_auto_debug_extension(dry_run=True)
|
331
|
+
self.assertTrue(result)
|
332
|
+
mock_download.assert_not_called()
|
333
|
+
|
334
|
+
# Test real install
|
335
|
+
result = install_auto_debug_extension(dry_run=False)
|
336
|
+
self.assertTrue(result)
|
337
|
+
mock_download.assert_called_once()
|
338
|
+
mock_install.assert_called_once()
|
339
|
+
|
340
|
+
@patch("fastled.install.project_detection.is_fastled_repository")
|
341
|
+
@patch("fastled.install.project_detection.shutil.which")
|
342
|
+
@patch("builtins.input")
|
343
|
+
def test_comprehensive_integration(self, mock_input, mock_which, mock_repo):
|
344
|
+
"""Test 15: End-to-end integration test."""
|
345
|
+
# Setup
|
346
|
+
mock_which.return_value = "/usr/bin/code"
|
347
|
+
mock_input.side_effect = ["y", "y", "y"] # Yes to project, extension, examples
|
348
|
+
mock_repo.return_value = False
|
349
|
+
|
350
|
+
# Create .vscode first for non-interactive test
|
351
|
+
os.makedirs(".vscode")
|
352
|
+
|
353
|
+
# Run full installation in non-interactive mode
|
354
|
+
result = fastled_install(dry_run=True, no_interactive=True)
|
355
|
+
|
356
|
+
# Verify all components
|
357
|
+
self.assertTrue(result)
|
358
|
+
self.assertTrue(Path(".vscode").exists())
|
359
|
+
self.assertTrue(Path(".vscode/launch.json").exists())
|
360
|
+
self.assertTrue(Path(".vscode/tasks.json").exists())
|
361
|
+
|
362
|
+
# Verify tasks have correct content
|
363
|
+
with open(".vscode/tasks.json") as f:
|
364
|
+
data = json.load(f)
|
365
|
+
debug_task = next(
|
366
|
+
t for t in data["tasks"] if t["label"] == "Run FastLED (Debug)"
|
367
|
+
)
|
368
|
+
self.assertIn("--debug", debug_task["args"])
|
369
|
+
self.assertIn("--app", debug_task["args"])
|
370
|
+
|
371
|
+
|
372
|
+
if __name__ == "__main__":
|
373
|
+
unittest.main()
|
@@ -0,0 +1,167 @@
|
|
1
|
+
"""VSCode configuration generation for FastLED projects."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
|
7
|
+
def update_launch_json_for_arduino() -> None:
|
8
|
+
"""Update launch.json with Arduino debugging configuration."""
|
9
|
+
launch_json_path = Path.cwd() / ".vscode" / "launch.json"
|
10
|
+
|
11
|
+
# Default launch configuration
|
12
|
+
arduino_config = {
|
13
|
+
"name": "🎯 Auto Debug (Smart File Detection)",
|
14
|
+
"type": "auto-debug",
|
15
|
+
"request": "launch",
|
16
|
+
"map": {
|
17
|
+
"*.ino": "Arduino: Run .ino with FastLED",
|
18
|
+
"*.py": "Python: Current File (UV)",
|
19
|
+
},
|
20
|
+
}
|
21
|
+
|
22
|
+
if launch_json_path.exists():
|
23
|
+
# Merge with existing
|
24
|
+
try:
|
25
|
+
with open(launch_json_path, "r") as f:
|
26
|
+
data = json.load(f)
|
27
|
+
except json.JSONDecodeError:
|
28
|
+
data = {"version": "0.2.0", "configurations": []}
|
29
|
+
else:
|
30
|
+
data = {"version": "0.2.0", "configurations": []}
|
31
|
+
|
32
|
+
# Check if configuration already exists
|
33
|
+
configs = data.get("configurations", [])
|
34
|
+
exists = any(cfg.get("name") == arduino_config["name"] for cfg in configs)
|
35
|
+
|
36
|
+
if not exists:
|
37
|
+
configs.insert(0, arduino_config) # Add at the beginning
|
38
|
+
data["configurations"] = configs
|
39
|
+
|
40
|
+
# Write back
|
41
|
+
launch_json_path.parent.mkdir(exist_ok=True)
|
42
|
+
with open(launch_json_path, "w") as f:
|
43
|
+
json.dump(data, f, indent=4)
|
44
|
+
|
45
|
+
print(f"✅ Updated {launch_json_path}")
|
46
|
+
|
47
|
+
|
48
|
+
def generate_fastled_tasks() -> None:
|
49
|
+
"""Generate/update tasks.json with FastLED build tasks."""
|
50
|
+
tasks_json_path = Path.cwd() / ".vscode" / "tasks.json"
|
51
|
+
|
52
|
+
# FastLED tasks
|
53
|
+
fastled_tasks = [
|
54
|
+
{
|
55
|
+
"type": "shell",
|
56
|
+
"label": "Run FastLED (Debug)",
|
57
|
+
"command": "fastled",
|
58
|
+
"args": ["${file}", "--debug", "--app"],
|
59
|
+
"options": {"cwd": "${workspaceFolder}"},
|
60
|
+
"group": {"kind": "build", "isDefault": True},
|
61
|
+
"presentation": {
|
62
|
+
"echo": True,
|
63
|
+
"reveal": "always",
|
64
|
+
"focus": True,
|
65
|
+
"panel": "new",
|
66
|
+
"showReuseMessage": False,
|
67
|
+
"clear": True,
|
68
|
+
},
|
69
|
+
"detail": "Run FastLED with debug mode and app visualization",
|
70
|
+
"problemMatcher": [],
|
71
|
+
},
|
72
|
+
{
|
73
|
+
"type": "shell",
|
74
|
+
"label": "Run FastLED (Quick)",
|
75
|
+
"command": "fastled",
|
76
|
+
"args": ["${file}", "--background-update"],
|
77
|
+
"options": {"cwd": "${workspaceFolder}"},
|
78
|
+
"group": "build",
|
79
|
+
"presentation": {
|
80
|
+
"echo": True,
|
81
|
+
"reveal": "always",
|
82
|
+
"focus": True,
|
83
|
+
"panel": "new",
|
84
|
+
"showReuseMessage": False,
|
85
|
+
"clear": True,
|
86
|
+
},
|
87
|
+
"detail": "Run FastLED with quick background update mode",
|
88
|
+
"problemMatcher": [],
|
89
|
+
},
|
90
|
+
]
|
91
|
+
|
92
|
+
if tasks_json_path.exists():
|
93
|
+
# Merge with existing
|
94
|
+
try:
|
95
|
+
with open(tasks_json_path, "r") as f:
|
96
|
+
data = json.load(f)
|
97
|
+
except json.JSONDecodeError:
|
98
|
+
data = {"version": "2.0.0", "tasks": []}
|
99
|
+
else:
|
100
|
+
data = {"version": "2.0.0", "tasks": []}
|
101
|
+
|
102
|
+
# Get existing tasks
|
103
|
+
existing_tasks = data.get("tasks", [])
|
104
|
+
existing_labels = {task.get("label") for task in existing_tasks}
|
105
|
+
|
106
|
+
# Add new tasks if they don't exist
|
107
|
+
for task in fastled_tasks:
|
108
|
+
if task["label"] not in existing_labels:
|
109
|
+
existing_tasks.append(task)
|
110
|
+
|
111
|
+
data["tasks"] = existing_tasks
|
112
|
+
|
113
|
+
# Write back
|
114
|
+
tasks_json_path.parent.mkdir(exist_ok=True)
|
115
|
+
with open(tasks_json_path, "w") as f:
|
116
|
+
json.dump(data, f, indent=4)
|
117
|
+
|
118
|
+
print(f"✅ Updated {tasks_json_path}")
|
119
|
+
|
120
|
+
|
121
|
+
def update_vscode_settings_for_fastled() -> None:
|
122
|
+
"""
|
123
|
+
🚨 Repository-only: Apply clangd settings and IntelliSense overrides.
|
124
|
+
This should ONLY be called for the actual FastLED repository.
|
125
|
+
"""
|
126
|
+
from .project_detection import is_fastled_repository
|
127
|
+
|
128
|
+
# Safety check - only apply in actual repository
|
129
|
+
if not is_fastled_repository():
|
130
|
+
return
|
131
|
+
settings_json_path = Path.cwd() / ".vscode" / "settings.json"
|
132
|
+
|
133
|
+
# FastLED repository-specific settings
|
134
|
+
fastled_settings = {
|
135
|
+
"clangd.arguments": [
|
136
|
+
"--compile-commands-dir=${workspaceFolder}/compile_commands",
|
137
|
+
"--header-insertion=never",
|
138
|
+
"--clang-tidy",
|
139
|
+
"--background-index",
|
140
|
+
],
|
141
|
+
"C_Cpp.intelliSenseEngine": "disabled",
|
142
|
+
"files.associations": {"*.ino": "cpp", "*.h": "cpp", "*.cpp": "cpp"},
|
143
|
+
"editor.formatOnSave": True,
|
144
|
+
"editor.formatOnType": True,
|
145
|
+
"editor.tabSize": 4,
|
146
|
+
"editor.insertSpaces": True,
|
147
|
+
}
|
148
|
+
|
149
|
+
if settings_json_path.exists():
|
150
|
+
# Merge with existing
|
151
|
+
try:
|
152
|
+
with open(settings_json_path, "r") as f:
|
153
|
+
data = json.load(f)
|
154
|
+
except json.JSONDecodeError:
|
155
|
+
data = {}
|
156
|
+
else:
|
157
|
+
data = {}
|
158
|
+
|
159
|
+
# Update settings
|
160
|
+
data.update(fastled_settings)
|
161
|
+
|
162
|
+
# Write back
|
163
|
+
settings_json_path.parent.mkdir(exist_ok=True)
|
164
|
+
with open(settings_json_path, "w") as f:
|
165
|
+
json.dump(data, f, indent=4)
|
166
|
+
|
167
|
+
print(f"✅ Updated {settings_json_path} with FastLED development settings")
|
fastled/open_browser.py
CHANGED
@@ -15,10 +15,13 @@ _playwright_browser_proxy = None
|
|
15
15
|
|
16
16
|
def cleanup_playwright_browser() -> None:
|
17
17
|
"""Clean up the Playwright browser on exit."""
|
18
|
-
|
19
|
-
|
20
|
-
_playwright_browser_proxy
|
21
|
-
|
18
|
+
try:
|
19
|
+
global _playwright_browser_proxy
|
20
|
+
if _playwright_browser_proxy:
|
21
|
+
_playwright_browser_proxy.close()
|
22
|
+
_playwright_browser_proxy = None
|
23
|
+
except Exception:
|
24
|
+
pass
|
22
25
|
|
23
26
|
|
24
27
|
# Register cleanup function
|
fastled/parse_args.py
CHANGED
@@ -170,6 +170,24 @@ def parse_args() -> Args:
|
|
170
170
|
help="Remove all FastLED containers and images",
|
171
171
|
)
|
172
172
|
|
173
|
+
parser.add_argument(
|
174
|
+
"--install",
|
175
|
+
action="store_true",
|
176
|
+
help="Install FastLED development environment with VSCode configuration and Auto Debug extension",
|
177
|
+
)
|
178
|
+
|
179
|
+
parser.add_argument(
|
180
|
+
"--dry-run",
|
181
|
+
action="store_true",
|
182
|
+
help="Run in dry-run mode (simulate actions without making changes)",
|
183
|
+
)
|
184
|
+
|
185
|
+
parser.add_argument(
|
186
|
+
"--no-interactive",
|
187
|
+
action="store_true",
|
188
|
+
help="Run in non-interactive mode (fail instead of prompting for input)",
|
189
|
+
)
|
190
|
+
|
173
191
|
build_mode = parser.add_mutually_exclusive_group()
|
174
192
|
build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
|
175
193
|
build_mode.add_argument(
|
@@ -185,6 +203,28 @@ def parse_args() -> Args:
|
|
185
203
|
|
186
204
|
args = parser.parse_args()
|
187
205
|
|
206
|
+
# Auto-enable app mode if debug is used and Playwright cache exists
|
207
|
+
if args.debug and not args.app:
|
208
|
+
playwright_dir = Path.home() / ".fastled" / "playwright"
|
209
|
+
if playwright_dir.exists() and any(playwright_dir.iterdir()):
|
210
|
+
print(
|
211
|
+
"🎭 Detected Playwright cache - automatically enabling app mode for debug"
|
212
|
+
)
|
213
|
+
args.app = True
|
214
|
+
elif not args.no_interactive:
|
215
|
+
# Prompt user to install Playwright only if not in no-interactive mode
|
216
|
+
answer = (
|
217
|
+
input("Would you like to install the FastLED debugger? [y/n] ")
|
218
|
+
.strip()
|
219
|
+
.lower()
|
220
|
+
)
|
221
|
+
if answer in ["y", "yes"]:
|
222
|
+
print(
|
223
|
+
"📦 To install Playwright, run: pip install playwright && python -m playwright install"
|
224
|
+
)
|
225
|
+
print("Then run your command again with --app flag")
|
226
|
+
sys.exit(0)
|
227
|
+
|
188
228
|
# TODO: propagate the library.
|
189
229
|
# from fastled.docker_manager import force_remove_previous
|
190
230
|
|
@@ -204,6 +244,11 @@ def parse_args() -> Args:
|
|
204
244
|
# print(msg)
|
205
245
|
# set_ramdisk_size(args.ram_disk_size)
|
206
246
|
|
247
|
+
# Handle --install early before other processing
|
248
|
+
if args.install:
|
249
|
+
# Don't process other arguments when --install is used
|
250
|
+
return Args.from_namespace(args)
|
251
|
+
|
207
252
|
if args.purge:
|
208
253
|
from fastled.docker_manager import DockerManager
|
209
254
|
|