eVera CI/CD #40
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: eVera CI/CD | |
| # ─── Triggers ──────────────────────────────────────────────────────────────── | |
| on: | |
| push: | |
| branches: [master, develop] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [master] | |
| schedule: | |
| - cron: "0 2 * * *" # nightly regression at 02:00 UTC | |
| workflow_dispatch: | |
| inputs: | |
| skip_tests: | |
| description: "Skip test suite (use for hotfix releases)" | |
| required: false | |
| default: "false" | |
| type: boolean | |
| # ─── Global settings ───────────────────────────────────────────────────────── | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| NODE_VERSION: "20" | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| concurrency: | |
| group: evera-${{ github.ref }} | |
| cancel-in-progress: true | |
| # ─── Jobs ──────────────────────────────────────────────────────────────────── | |
| jobs: | |
| # ── 1. Test ───────────────────────────────────────────────────────────────── | |
| test: | |
| name: "🧪 Test — Python ${{ matrix.python-version }} / ${{ matrix.os }}" | |
| runs-on: ${{ matrix.os }} | |
| if: ${{ github.event.inputs.skip_tests != 'true' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12"] | |
| os: [ubuntu-22.04, macos-13, windows-2022] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-cov ruff mypy | |
| - name: Lint (ruff) | |
| run: ruff check . --select=E,F,W --ignore=E501 | |
| continue-on-error: true | |
| - name: Type check (mypy) | |
| run: mypy . --ignore-missing-imports --no-strict-optional | |
| continue-on-error: true | |
| - name: Run unit tests | |
| run: | | |
| python -m pytest tests/unit/ -v --tb=short \ | |
| --cov=. --cov-report=xml --cov-report=term-missing | |
| - name: Run functional tests | |
| run: python -m pytest tests/functional/ -v --tb=short | |
| - name: Upload coverage | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: coverage.xml | |
| flags: py${{ matrix.python-version }}-${{ matrix.os }} | |
| continue-on-error: true | |
| # ── 2. Desktop — Windows ──────────────────────────────────────────────────── | |
| build-windows: | |
| name: "🪟 Desktop — Windows" | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: windows-2022 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt pyinstaller | |
| - name: Build Python backend (PyInstaller) | |
| run: python build_backend.py | |
| continue-on-error: false | |
| - name: Install Electron dependencies | |
| working-directory: electron | |
| run: npm install | |
| - name: Build Windows installer | |
| working-directory: electron | |
| run: npm run build:win | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload Windows artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-windows | |
| path: electron/dist/*.exe | |
| if-no-files-found: warn | |
| # ── 3. Desktop — macOS ────────────────────────────────────────────────────── | |
| build-macos: | |
| name: "🍎 Desktop — macOS" | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: macos-13 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt pyinstaller | |
| - name: Build Python backend (PyInstaller) | |
| run: python build_backend.py | |
| - name: Install Electron dependencies | |
| working-directory: electron | |
| run: npm install | |
| - name: Build macOS DMG | |
| working-directory: electron | |
| run: npm run build:mac | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload macOS artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-macos | |
| path: electron/dist/*.dmg | |
| if-no-files-found: warn | |
| # ── 4. Desktop — Linux ────────────────────────────────────────────────────── | |
| build-linux: | |
| name: "🐧 Desktop — Linux" | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ | |
| librsvg2-dev patchelf fakeroot rpm | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt pyinstaller | |
| - name: Build Python backend (PyInstaller) | |
| run: python build_backend.py | |
| - name: Install Electron dependencies | |
| working-directory: electron | |
| run: npm install | |
| - name: Build Linux packages (AppImage + deb) | |
| working-directory: electron | |
| run: npm run build:linux | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload Linux artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-linux | |
| path: | | |
| electron/dist/*.AppImage | |
| electron/dist/*.deb | |
| if-no-files-found: warn | |
| # ── 5. Android ────────────────────────────────────────────────────────────── | |
| build-android: | |
| name: "📱 Android (Phone + Auto + Wear)" | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Set up Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Build Android Phone APK | |
| working-directory: mobile/android | |
| run: | | |
| if [ -f "gradlew" ]; then | |
| chmod +x gradlew | |
| ./gradlew assembleRelease --no-daemon 2>/dev/null || true | |
| else | |
| echo "⚠️ No gradlew found — skipping Android phone build" | |
| fi | |
| - name: Build Android Auto APK | |
| working-directory: mobile/android | |
| run: | | |
| if [ -d "automotive" ] && [ -f "gradlew" ]; then | |
| ./gradlew :automotive:assembleRelease --no-daemon 2>/dev/null || true | |
| else | |
| echo "⚠️ No automotive module — skipping" | |
| fi | |
| - name: Build Wear OS APK | |
| working-directory: mobile/android | |
| run: | | |
| if [ -d "wear" ] && [ -f "gradlew" ]; then | |
| ./gradlew :wear:assembleRelease --no-daemon 2>/dev/null || true | |
| else | |
| echo "⚠️ No wear module — skipping" | |
| fi | |
| - name: Upload Android Phone APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-phone | |
| path: mobile/android/app/build/outputs/apk/release/*.apk | |
| if-no-files-found: warn | |
| - name: Upload Android Auto APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-auto | |
| path: mobile/android/automotive/build/outputs/apk/release/*.apk | |
| if-no-files-found: warn | |
| - name: Upload Wear OS APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-wear | |
| path: mobile/android/wear/build/outputs/apk/release/*.apk | |
| if-no-files-found: warn | |
| # ── 6. iOS ────────────────────────────────────────────────────────────────── | |
| build-ios: | |
| name: "🍏 iOS Archive" | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install React Native dependencies | |
| working-directory: mobile | |
| run: | | |
| if [ -f "package.json" ]; then | |
| npm install | |
| fi | |
| - name: Install CocoaPods | |
| working-directory: mobile/ios | |
| run: | | |
| if [ -f "Podfile" ]; then | |
| pod install --repo-update 2>/dev/null || true | |
| fi | |
| - name: Build iOS archive (unsigned) | |
| working-directory: mobile/ios | |
| run: | | |
| WORKSPACE=$(find . -name "*.xcworkspace" -maxdepth 1 | head -1) | |
| SCHEME=$(xcodebuild -workspace "$WORKSPACE" -list 2>/dev/null \ | |
| | grep -A 100 "Schemes:" | grep -v "Schemes:" | head -1 | xargs || echo "") | |
| if [ -z "$WORKSPACE" ] || [ -z "$SCHEME" ]; then | |
| echo "⚠️ No Xcode workspace or scheme found — skipping iOS build" | |
| exit 0 | |
| fi | |
| xcodebuild \ | |
| -workspace "$WORKSPACE" \ | |
| -scheme "$SCHEME" \ | |
| -configuration Release \ | |
| -sdk iphoneos \ | |
| -archivePath build/Vera.xcarchive \ | |
| archive \ | |
| CODE_SIGN_IDENTITY="" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO || true | |
| - name: Zip iOS archive | |
| run: | | |
| if [ -d mobile/ios/build/Vera.xcarchive ]; then | |
| cd mobile/ios/build | |
| zip -r Vera.xcarchive.zip Vera.xcarchive | |
| else | |
| echo "⚠️ No xcarchive — creating placeholder" | |
| mkdir -p mobile/ios/build | |
| touch mobile/ios/build/Vera.xcarchive.zip | |
| fi | |
| - name: Upload iOS archive | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-archive | |
| path: mobile/ios/build/Vera.xcarchive.zip | |
| if-no-files-found: warn | |
| # ── 7. VS Code Extension ────────────────────────────────────────────────── | |
| build-vscode: | |
| name: "🔌 VS Code Extension (.vsix)" | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/v') }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Install dependencies | |
| working-directory: vscode-extension | |
| run: npm install | |
| - name: Install vsce | |
| run: npm install -g @vscode/vsce typescript | |
| - name: Compile TypeScript | |
| working-directory: vscode-extension | |
| run: tsc -p ./ | |
| - name: Package extension | |
| working-directory: vscode-extension | |
| run: vsce package --no-dependencies -o evera-ai.vsix | |
| - name: Upload VSIX artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: vscode-extension | |
| path: vscode-extension/evera-ai.vsix | |
| if-no-files-found: warn | |
| # ── 8. GitHub Release ─────────────────────────────────────────────────────── | |
| release: | |
| name: "🚀 GitHub Release" | |
| needs: [build-windows, build-macos, build-linux, build-android, build-ios, build-vscode] | |
| if: ${{ startsWith(github.ref, 'refs/tags/v') && always() }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: find artifacts -type f | sort | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| CHANGES=$(git log --oneline --pretty=format:"- %s" HEAD) | |
| else | |
| CHANGES=$(git log --oneline --pretty=format:"- %s" "${PREV_TAG}..HEAD") | |
| fi | |
| echo "CHANGES<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$CHANGES" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| artifacts/**/*.exe | |
| artifacts/**/*.dmg | |
| artifacts/**/*.AppImage | |
| artifacts/**/*.deb | |
| artifacts/**/*.apk | |
| artifacts/**/*.zip | |
| artifacts/**/*.vsix | |
| body: | | |
| ## 🚀 eVera ${{ github.ref_name }} | |
| ### What's Changed | |
| ${{ steps.changelog.outputs.CHANGES }} | |
| --- | |
| ### 📥 Download & Install | |
| | Platform | File | How to install | | |
| |---|---|---| | |
| | **Windows** | `Vera-Setup-*.exe` | Run the installer — everything is bundled | | |
| | **macOS** | `Vera-*.dmg` | Open DMG → drag Vera to Applications | | |
| | **Linux (AppImage)** | `Vera-*.AppImage` | `chmod +x Vera-*.AppImage && ./Vera-*.AppImage` | | |
| | **Linux (deb)** | `Vera-*.deb` | `sudo dpkg -i Vera-*.deb` | | |
| | **Android** | `Vera-*.apk` | Enable unknown sources → tap APK | | |
| | **Android Auto** | `Vera-Auto-*.apk` | Install on phone → connect to car | | |
| | **Wear OS** | `Vera-Wear-*.apk` | Sideload or install via Play Store | | |
| | **iOS** | `Vera.xcarchive.zip` | Open in Xcode → distribute to device | | |
| | **Web (PWA)** | — | Open `http://localhost:8000` in any browser | | |
| | **VS Code** | `evera-ai.vsix` | `code --install-extension evera-ai.vsix` | | |
| --- | |
| ### ⚡ One-Command Install | |
| **Linux / macOS** | |
| ```bash | |
| curl -fsSL https://raw.githubusercontent.com/embeddedos-org/eVera/master/install.sh | bash | |
| ``` | |
| **Windows (PowerShell)** | |
| ```powershell | |
| irm https://raw.githubusercontent.com/embeddedos-org/eVera/master/setup.ps1 | iex | |
| ``` | |
| --- | |
| ### 🤖 Offline LLMs (no internet needed) | |
| ```bash | |
| ollama pull qwen3:8b # 5 GB — recommended | |
| ollama pull llama3.2:3b # 2 GB — fastest | |
| ollama pull deepseek-r1:7b # 5 GB — best reasoning | |
| ``` | |
| Or use **LM Studio**, **Jan AI**, or **llama.cpp** — eVera auto-detects all of them. | |
| generate_release_notes: false |