commit
Deploy production / deploy (push) Successful in 15s

This commit is contained in:
2026-08-14 10:05:48 -07:00
parent 034c1b653e
commit 69f9cfea41
3 changed files with 111 additions and 20 deletions
+8
View File
@@ -410,6 +410,14 @@ editor directs the user to copy the standalone `scripts/devlog_editor.py` into
the project checkout, initialize and author the devlog, publish the changes, and
recheck. It never edits, commits, or pushes an external project repository.
Project Editor requires the Lektor Python package because it uses the site's
authoritative importer directly. If the launching Python cannot import Lektor,
the editor automatically checks `LABYRICORN_LEKTOR_PYTHON`, a project-local
`.venv`, common pipx Lektor environments, and the environment containing a
`lektor` command. It relaunches itself with the first runtime that can import
both Lektor and Tkinter. Set `LABYRICORN_LEKTOR_PYTHON` to an explicit Python
executable when Lektor is installed somewhere else.
## Production filesystem and permissions
```text
@@ -1,19 +0,0 @@
_model: entry
---
title: This is a blog example, delete me later
---
date: 2026-08-13
---
author: Christopher Chambers
---
tags: ai, configuration, html, taggy
---
kicker: Blog
---
summary: Summary
---
published_urls:
---
body:
Body (markdown) woohoo!
+103 -1
View File
@@ -12,6 +12,7 @@ from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
import threading
from typing import Any
@@ -33,6 +34,104 @@ class ProjectEditorError(Exception):
"""An expected project-editor problem suitable for display to a user."""
def _python_has_lektor(executable: Path) -> bool:
if not executable.is_file():
return False
options: dict[str, Any] = {
"capture_output": True,
"text": True,
"timeout": 10,
"check": False,
}
if os.name == "nt":
options["creationflags"] = subprocess.CREATE_NO_WINDOW
try:
result = subprocess.run(
[str(executable), "-c", "import lektor, tkinter"],
**options,
)
except (OSError, subprocess.SubprocessError):
return False
return result.returncode == 0
def _find_lektor_python(site_root: Path) -> Path | None:
"""Locate a Python runtime that can import both Lektor and Tkinter."""
candidates: list[Path] = []
configured = os.environ.get("LABYRICORN_LEKTOR_PYTHON", "").strip()
if configured:
candidates.append(Path(configured).expanduser())
if os.name == "nt":
candidates.extend(
(
site_root / ".venv" / "Scripts" / "python.exe",
Path(os.environ.get("LOCALAPPDATA", ""))
/ "pipx"
/ "pipx"
/ "venvs"
/ "lektor"
/ "Scripts"
/ "python.exe",
Path.home()
/ ".local"
/ "pipx"
/ "venvs"
/ "lektor"
/ "Scripts"
/ "python.exe",
)
)
else:
candidates.extend(
(
site_root / ".venv" / "bin" / "python",
Path.home() / ".local" / "pipx" / "venvs" / "lektor" / "bin" / "python",
)
)
lektor_command = shutil.which("lektor")
if lektor_command:
candidates.append(
Path(lektor_command).resolve().parent
/ ("python.exe" if os.name == "nt" else "python")
)
seen: set[Path] = set()
for candidate in candidates:
candidate = candidate.resolve()
if candidate in seen:
continue
seen.add(candidate)
if _python_has_lektor(candidate):
return candidate
return None
def _relaunch_with_lektor(site_root: Path) -> bool:
try:
import lektor # noqa: F401
except ModuleNotFoundError:
pass
else:
return False
if os.environ.get("LABYRICORN_LEKTOR_RELAUNCHED") == "1":
return False
executable = _find_lektor_python(site_root)
if executable is None:
return False
gui_executable = executable
if os.name == "nt":
pythonw = executable.with_name("pythonw.exe")
if pythonw.is_file():
gui_executable = pythonw
child_environment = {**os.environ, "LABYRICORN_LEKTOR_RELAUNCHED": "1"}
subprocess.Popen(
[str(gui_executable), str(Path(__file__).resolve()), *sys.argv[1:]],
cwd=site_root,
env=child_environment,
)
return True
def _load_project_importer() -> Any:
try:
return importlib.import_module("project_sources")
@@ -691,8 +790,11 @@ def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--site-root", type=Path, default=Path(__file__).resolve().parents[1])
args = parser.parse_args()
site_root = args.site_root.resolve()
if _relaunch_with_lektor(site_root):
return 0
try:
run_gui(ProjectSourceRegistry(args.site_root))
run_gui(ProjectSourceRegistry(site_root))
except ProjectEditorError as exc:
parser.exit(1, f"project-editor: ERROR: {exc}\n")
return 0