Improved versioning across all builds #89
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Documentation | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'docs/**' | |
| - 'src/**' | |
| - 'assets/**' | |
| - '.github/workflows/deploy-docs.yml' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Install Dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install .[test] | |
| pip install Pillow | |
| - name: Run Tests (I18n Integrity) | |
| run: python -m pytest tests/test_i18n_integrity.py | |
| - name: Pre-install Addons (Web) | |
| run: | | |
| # Helper to unzip addons into source tree so they are bundled as python packages | |
| # Must ensure destination exists | |
| mkdir -p src/switchcraft/addons | |
| python -c "import zipfile, os, glob; from pathlib import Path; [zipfile.ZipFile(z).extractall(Path('src/switchcraft/addons') / Path(z).stem.replace('switchcraft_', '')) for z in glob.glob('src/switchcraft/assets/addons/*.zip')]" | |
| - name: Build Web Demo | |
| run: | | |
| # Bake translations for WASM | |
| python scripts/prepare_web_dist.py --bake | |
| # Create an isolated build directory | |
| mkdir -p build_web | |
| cp -r src/switchcraft build_web/ | |
| # Create entry point inside build_web | |
| cat > build_web/web_entry.py <<EOF | |
| import os | |
| import sys | |
| print("DEBUG: WEB ENTRY RELOADED") | |
| print(f"BUILD_TIME: $(date)") | |
| # ============================================================ | |
| # CRITICAL: Patch ssl module BEFORE any urllib3 import | |
| # Pyodide injects a MagicMock for ssl, breaking urllib3's version check | |
| # ============================================================ | |
| if sys.platform == "emscripten": | |
| import types | |
| # Force patch if 'ssl' is a MagicMock or missing version info | |
| should_patch_ssl = "ssl" not in sys.modules | |
| if not should_patch_ssl: | |
| ssl_mod = sys.modules["ssl"] | |
| # Check if it's a MagicMock | |
| if "Mock" in str(type(ssl_mod)) or not hasattr(ssl_mod, "OPENSSL_VERSION_INFO"): | |
| should_patch_ssl = True | |
| else: | |
| try: | |
| if not isinstance(ssl_mod.OPENSSL_VERSION_INFO, tuple): | |
| should_patch_ssl = True | |
| except (AttributeError, TypeError): | |
| should_patch_ssl = True | |
| if should_patch_ssl: | |
| ssl_mock = types.ModuleType("ssl") | |
| ssl_mock.SSLContext = type("SSLContext", (), {"__init__": lambda *a, **kw: None}) | |
| ssl_mock.PROTOCOL_TLS = 2 | |
| ssl_mock.PROTOCOL_TLS_CLIENT = 16 | |
| ssl_mock.create_default_context = lambda *a, **kw: None | |
| ssl_mock.HAS_SNI = True | |
| ssl_mock.CERT_NONE = 0 | |
| ssl_mock.CERT_OPTIONAL = 1 | |
| ssl_mock.CERT_REQUIRED = 2 | |
| ssl_mock.OPENSSL_VERSION_NUMBER = 0x101010CF | |
| ssl_mock.OPENSSL_VERSION = "OpenSSL 1.1.1 (Pyodide Mock)" | |
| ssl_mock.OPENSSL_VERSION_INFO = (1, 1, 1, 15, 15) | |
| ssl_mock.HAS_NEVER_CHECK_COMMON_NAME = True | |
| sys.modules["ssl"] = ssl_mock | |
| print("DEBUG: SSL module patched successfully") | |
| # Now safe to patch pyodide_http | |
| try: | |
| import pyodide_http | |
| pyodide_http.patch_all() | |
| print("DEBUG: pyodide_http patched successfully") | |
| except ImportError: | |
| pass | |
| # ============================================================ | |
| # GLOBAL THREADING MONKEYPATCH FOR WASM | |
| # ============================================================ | |
| import threading | |
| import asyncio | |
| class WASMThread(threading.Thread): | |
| """Bridge between standard threading and WASM-friendly asyncio tasks.""" | |
| def start(self): | |
| async def _bridge(): | |
| try: | |
| self.run() | |
| except Exception as e: | |
| print(f"ERROR in WASM background thread '{self.name}': {e}") | |
| try: | |
| asyncio.create_task(_bridge()) | |
| except RuntimeError: | |
| self.run() | |
| threading.Thread = WASMThread | |
| print("DEBUG: Global threading monkeypatch applied") | |
| # ============================================================ | |
| # Normal imports AFTER ssl patching | |
| # ============================================================ | |
| import flet as ft | |
| # Ensure current dir is in path | |
| sys.path.insert(0, os.getcwd()) | |
| import switchcraft | |
| switchcraft.IS_DEMO = True | |
| import switchcraft.main | |
| if __name__ == "__main__": | |
| # Use ft.run for modern Flet (0.80.0+) | |
| ft.run(switchcraft.main.main, assets_dir="assets") | |
| EOF | |
| # Generate dynamic splash screen | |
| VERSION=$(python -c "import src.switchcraft as s; print(s.__version__)") | |
| echo "Generating Splash for Version: $VERSION" | |
| python scripts/generate_splash.py --version "$VERSION" --output "build_web/switchcraft/assets/splash.png" | |
| # Manually create requirements.txt | |
| cat > build_web/requirements.txt <<EOF | |
| flet==0.80.3 | |
| pyodide-http | |
| pefile | |
| olefile | |
| requests | |
| click | |
| rich | |
| PyYAML | |
| defusedxml | |
| PyJWT | |
| anyio>=4.12.1 | |
| EOF | |
| # Publish | |
| cd build_web | |
| cp web_entry.py ../web_entry.py.patched | |
| flet publish web_entry.py --app-name "SwitchCraft Demo" --app-short-name "SwitchCraft" --app-description "SwitchCraft Web Demo" --base-url "/demo/" --distpath ../dist --assets switchcraft/assets | |
| # Overwrite with patched entry | |
| cd .. | |
| cp web_entry.py.patched dist/web_entry.py | |
| # Customize Flet loading screen and Overwrite Icons | |
| python scripts/prepare_web_dist.py --patch dist | |
| cp src/switchcraft/assets/icon-192.png dist/icons/icon-192.png | |
| cp src/switchcraft/assets/icon-512.png dist/icons/icon-512.png | |
| cp src/switchcraft/assets/favicon.png dist/favicon.png | |
| cp src/switchcraft/assets/apple-touch-icon.png dist/apple-touch-icon.png || true | |
| # Deploy to docs public | |
| rm -rf docs/public/demo | |
| mkdir -p docs/public/demo | |
| cp -r dist/* docs/public/demo/ | |
| # Clean up | |
| rm -rf dist | |
| rm -rf build_web | |
| rm -f web_entry.py.patched | |
| shell: bash | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.13.0 | |
| - name: Install dependencies | |
| run: | | |
| cd docs | |
| if [ -f package-lock.json ]; then | |
| npm ci | |
| else | |
| npm install vitepress vue | |
| fi | |
| - name: Build with VitePress | |
| run: | | |
| cd docs | |
| npx vitepress build | |
| env: | |
| BASE_URL: / | |
| - name: Upload to gh-pages | |
| uses: JamesIves/github-pages-deploy-action@v4 | |
| with: | |
| folder: docs/.vitepress/dist | |
| branch: gh-pages | |
| clean: true | |
| # Preserve the pr-preview directory | |
| clean-exclude: | | |
| pr-preview/ |