diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ee4a02c..d30c4d4 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Generate build variables + run: python scripts/generate_build_vars.py + - name: Load config from Makefile id: config run: make -s _ci-dump-config >> $GITHUB_OUTPUT diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py index bb06d0b..5b54fe8 100644 --- a/scripts/bootstrap.py +++ b/scripts/bootstrap.py @@ -1,8 +1,8 @@ -import re import secrets from pathlib import Path import tomllib +from config_loader import load_build_config, write_env_make def generate_secret(rule: str) -> str: @@ -31,6 +31,8 @@ def extract_python_version(version_string: str) -> tuple[str, str]: "~3.14.2" -> ("3.14.2", "3.14") "3.14.2" -> ("3.14.2", "3.14") """ + import re # noqa: PLC0415 + # Remove poetry version operators (==, ^, ~, >=, etc.) clean_version = re.sub(r"^[=^~><]+", "", version_string.strip()) @@ -148,7 +150,9 @@ def bootstrap(): # noqa: PLR0912, PLR0915 elif key == "ALFRED_VERSION": if existing_env.get(key) != alfred_version: new_lines.append(f"{key}={alfred_version}\n") - print(f" ↻ Updated Alfred version: {existing_env.get(key, 'N/A')} → {alfred_version}") + print( + f" ↻ Updated Alfred version: {existing_env.get(key, 'N/A')} → {alfred_version}" + ) else: new_lines.append(f"{key}={alfred_version}\n") print(f" ↻ Kept Alfred version: {alfred_version}") @@ -224,20 +228,10 @@ def bootstrap(): # noqa: PLR0912, PLR0915 f.writelines(new_lines) print(f"\n✅ {env_path.name} generated successfully.") - # Generate .env.make for Makefile - env_make_path = base_dir / ".env.make" - with open(env_make_path, "w", encoding="utf-8") as f: - f.write("# Auto-generated from pyproject.toml by bootstrap.py\n") - f.write(f"export ALFRED_VERSION={alfred_version}\n") - f.write(f"export PYTHON_VERSION={python_version_full}\n") - f.write(f"export PYTHON_VERSION_SHORT={python_version_short}\n") - f.write(f"export RUNNER={settings_keys['runner']}\n") - f.write(f"export IMAGE_NAME={settings_keys['image_name']}\n") - f.write(f"export SERVICE_NAME={settings_keys['service_name']}\n") - f.write(f"export LIBRECHAT_VERSION={settings_keys['librechat_version']}\n") - f.write(f"export RAG_VERSION={settings_keys['rag_version']}\n") - - print(f"✅ {env_make_path.name} generated for Makefile.") + # Generate .env.make for Makefile using shared config loader + config = load_build_config(base_dir) + write_env_make(config, base_dir) + print("✅ .env.make generated for Makefile.") print("\n⚠️ Reminder: Please manually add your API keys to the .env file.") diff --git a/scripts/config_loader.py b/scripts/config_loader.py new file mode 100644 index 0000000..e558e14 --- /dev/null +++ b/scripts/config_loader.py @@ -0,0 +1,89 @@ +"""Shared configuration loader for bootstrap and CI.""" + +import re +from pathlib import Path +from typing import NamedTuple + +import tomllib + + +class BuildConfig(NamedTuple): + """Build configuration extracted from pyproject.toml.""" + + alfred_version: str + python_version: str + python_version_short: str + runner: str + image_name: str + service_name: str + librechat_version: str + rag_version: str + + +def extract_python_version(version_string: str) -> tuple[str, str]: + """ + Extract Python version from poetry dependency string. + Examples: + "==3.14.2" -> ("3.14.2", "3.14") + "^3.14.2" -> ("3.14.2", "3.14") + "~3.14.2" -> ("3.14.2", "3.14") + "3.14.2" -> ("3.14.2", "3.14") + """ + clean_version = re.sub(r"^[=^~><]+", "", version_string.strip()) + parts = clean_version.split(".") + + if len(parts) >= 2: + full_version = clean_version + short_version = f"{parts[0]}.{parts[1]}" + return full_version, short_version + else: + raise ValueError(f"Invalid Python version format: {version_string}") + + +def load_build_config(base_dir: Path | None = None) -> BuildConfig: + """Load build configuration from pyproject.toml.""" + if base_dir is None: + base_dir = Path(__file__).resolve().parent.parent + + toml_path = base_dir / "pyproject.toml" + if not toml_path.exists(): + raise FileNotFoundError(f"pyproject.toml not found: {toml_path}") + + with open(toml_path, "rb") as f: + data = tomllib.load(f) + settings_keys = data["tool"]["alfred"]["settings"] + dependencies = data["tool"]["poetry"]["dependencies"] + alfred_version = data["tool"]["poetry"]["version"] + + python_version_full, python_version_short = extract_python_version( + dependencies["python"] + ) + + return BuildConfig( + alfred_version=alfred_version, + python_version=python_version_full, + python_version_short=python_version_short, + runner=settings_keys["runner"], + image_name=settings_keys["image_name"], + service_name=settings_keys["service_name"], + librechat_version=settings_keys["librechat_version"], + rag_version=settings_keys["rag_version"], + ) + + +def write_env_make(config: BuildConfig, base_dir: Path | None = None) -> None: + """Write .env.make file for Makefile.""" + if base_dir is None: + base_dir = Path(__file__).resolve().parent.parent + + env_make_path = base_dir / ".env.make" + with open(env_make_path, "w", encoding="utf-8") as f: + f.write("# Auto-generated from pyproject.toml\n") + f.write(f"export ALFRED_VERSION={config.alfred_version}\n") + f.write(f"export PYTHON_VERSION={config.python_version}\n") + f.write(f"export PYTHON_VERSION_SHORT={config.python_version_short}\n") + f.write(f"export RUNNER={config.runner}\n") + f.write(f"export IMAGE_NAME={config.image_name}\n") + f.write(f"export SERVICE_NAME={config.service_name}\n") + f.write(f"export LIBRECHAT_VERSION={config.librechat_version}\n") + f.write(f"export RAG_VERSION={config.rag_version}\n") diff --git a/scripts/generate_build_vars.py b/scripts/generate_build_vars.py new file mode 100644 index 0000000..f0602cc --- /dev/null +++ b/scripts/generate_build_vars.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Generate .env.make for CI/CD without generating secrets.""" + +import sys + +from config_loader import load_build_config, write_env_make + + +def main(): + """Generate .env.make from pyproject.toml.""" + try: + config = load_build_config() + write_env_make(config) + print("✅ .env.make generated successfully.") + return 0 + except Exception as e: + print(f"❌ Failed to generate .env.make: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit()