Ship MicroPython stubs from repo lib/ and seed workspace lib on startup

Move machine.py and neopixel.py into a tracked /lib/ at the repo root and
auto-copy them into WORKSPACE_ROOT/lib whenever files are missing, so empty
volumes and fresh per-user workspaces always have the read-only stubs
available to Jedi and Pyodide. Allow all users to browse lib/ in the UI
(writes still gated by the API), and add tests covering initial seeding
and re-population after the dir is wiped.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-10 02:59:34 +12:00
parent f7892dd31b
commit a2318f2244
14 changed files with 146 additions and 181 deletions

View File

@@ -15,7 +15,12 @@ def _workspace_root(workspace_root: Path | None = None) -> Path:
def _shared_lib_root() -> Path:
return (config.WORKSPACE_ROOT.resolve() / LIB_DIR_NAME).resolve()
"""Shared MicroPython stubs live under WORKSPACE_ROOT/lib; seed from bundle if missing (e.g. volume wiped)."""
lib = (config.WORKSPACE_ROOT.resolve() / LIB_DIR_NAME).resolve()
from editor_app.services.user_workspace import ensure_workspace_lib
ensure_workspace_lib()
return lib
def normalize_relative_path(relative_path: str) -> str:
@@ -213,25 +218,24 @@ def delete_folder(folder_path: str, workspace_root: Path | None = None) -> None:
def collect_python_sources(workspace_root: Path | None = None) -> dict[str, str]:
"""Return all UTF-8 .py files under the workspace for browser-side Pyodide sync."""
"""Return UTF-8 `.py` under the scoped workspace plus shared stubs from `WORKSPACE_ROOT/lib/` (bundled Micropython mocks for Jedi/completion and Pyodide)."""
result: dict[str, str] = {}
workspace = _workspace_root(workspace_root)
if not workspace.exists():
return result
for path in workspace.rglob("*.py"):
try:
rel = path.relative_to(workspace)
except ValueError:
continue
if any(part.startswith(".") for part in rel.parts):
continue
try:
key = str(rel).replace("\\", "/")
result[key] = path.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue
if workspace.exists():
for path in workspace.rglob("*.py"):
try:
rel = path.relative_to(workspace)
except ValueError:
continue
if any(part.startswith(".") for part in rel.parts):
continue
try:
key = str(rel).replace("\\", "/")
result[key] = path.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue
shared_lib = _shared_lib_root()
if shared_lib.exists() and shared_lib.is_dir() and shared_lib != (workspace / LIB_DIR_NAME).resolve():
if shared_lib.is_dir():
for path in shared_lib.rglob("*.py"):
try:
rel = path.relative_to(shared_lib)