fix(release): embed inline release notes in appcast.xml #684
Workflow file for this run
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: UI E2E | |
| on: | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-ui-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| paths-filter: | |
| runs-on: arc-general | |
| outputs: | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| code: ${{ steps.filter.outputs.code }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 | |
| id: filter | |
| with: | |
| filters: | | |
| code: | |
| - .github/workflows/ci-ui.yml | |
| - "frontend/**" | |
| - "tests/ui/**" | |
| - "src/persona_forge/**" | |
| - requirements-dev.txt | |
| frontend: | |
| - .github/workflows/ci-ui.yml | |
| - "frontend/**" | |
| frontend-lint: | |
| name: frontend-lint | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.frontend == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) | |
| runs-on: arc-general | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Oxlint (warnings are errors) | |
| working-directory: frontend | |
| run: npm run lint | |
| e2e: | |
| name: e2e | |
| needs: paths-filter | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: arc-general | |
| timeout-minutes: 30 | |
| env: | |
| PYTHONPATH: src:src/export | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: No relevant changes | |
| if: needs.paths-filter.outputs.code != 'true' | |
| run: echo "No frontend/UI-relevant changes detected, skipping E2E run" | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 | |
| if: needs.paths-filter.outputs.code == 'true' | |
| with: | |
| python-version: "3.13" | |
| cache: pip | |
| cache-dependency-path: requirements-dev.txt | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| if: needs.paths-filter.outputs.code == 'true' | |
| with: | |
| node-version: "24" | |
| - name: Install Python test dependencies | |
| if: needs.paths-filter.outputs.code == 'true' | |
| run: python -m pip install -r requirements-dev.txt | |
| - name: Environment and import smoke test | |
| if: needs.paths-filter.outputs.code == 'true' | |
| run: | | |
| echo "=== Environment ===" | |
| python --version | |
| pip list --format=columns | head -30 | |
| echo "PYTHONPATH=$PYTHONPATH" | |
| echo "PWD=$(pwd)" | |
| echo "=== Import smoke test ===" | |
| python -c " | |
| import sys | |
| sys.path.insert(0, 'src') | |
| sys.path.insert(0, 'src/export') | |
| # Install fake model before importing app (no torch on runner) | |
| from tests.fixtures.fake_runtime import FakeModelRuntime | |
| rt = FakeModelRuntime() | |
| rt.install() | |
| errors = [] | |
| def try_import(name): | |
| try: | |
| __import__(name) | |
| except Exception as e: | |
| errors.append(f'{name}: {e}') | |
| try_import('flask') | |
| try_import('soundfile') | |
| try_import('numpy') | |
| try_import('persona_forge') | |
| try_import('persona_forge.app') | |
| if errors: | |
| print('IMPORT ERRORS:') | |
| for e in errors: | |
| print(' ', e) | |
| sys.exit(1) | |
| else: | |
| print('All critical imports OK') | |
| " | |
| - name: Install frontend dependencies | |
| if: needs.paths-filter.outputs.code == 'true' | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Build frontend | |
| if: needs.paths-filter.outputs.code == 'true' | |
| working-directory: frontend | |
| run: npm run build | |
| - name: Install Playwright test dependencies | |
| if: needs.paths-filter.outputs.code == 'true' | |
| working-directory: tests/ui | |
| run: npm ci | |
| - name: Cache Playwright browsers | |
| if: needs.paths-filter.outputs.code == 'true' | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('tests/ui/package-lock.json') }} | |
| - name: Install Playwright browsers | |
| if: needs.paths-filter.outputs.code == 'true' | |
| working-directory: tests/ui | |
| run: npx playwright install --with-deps chromium | |
| - name: Start fake_model_server | |
| if: needs.paths-filter.outputs.code == 'true' | |
| run: | | |
| # Seed the same disposable libraries used by local E2E, then launch the | |
| # fake-only Python server. This avoids relying on child-process environment | |
| # propagation from the Node harness on the self-hosted runner. | |
| E2E_DATA_DIR="$(mktemp -d)" | |
| mkdir -p "$E2E_DATA_DIR/voices" "$E2E_DATA_DIR/segments" | |
| cp -R tests/ui/fixtures/capture-data/voices/. "$E2E_DATA_DIR/voices/" | |
| cp -R tests/ui/fixtures/capture-data/segments/. "$E2E_DATA_DIR/segments/" | |
| export VOICE_LIBRARY_DIR="$E2E_DATA_DIR/voices" | |
| export SEGMENT_LIBRARY_DIR="$E2E_DATA_DIR/segments" | |
| export FRONTEND_DIST_DIR="$PWD/frontend/dist" | |
| export PERSONA_FORGE_TEST_PORT=8319 | |
| python tests/ui/fixtures/fake_model_server.py > fake_model_server.log 2>&1 & | |
| SERVER_PID=$! | |
| echo "fake_model_server PID=$SERVER_PID" | |
| echo "=== Waiting for fake_model_server to become healthy ===" | |
| deadline=$((SECONDS + 10)) | |
| while [ $SECONDS -lt $deadline ]; do | |
| sleep 1 | |
| set +e | |
| response=$(curl -s -w "\n%{http_code}" http://127.0.0.1:8319/health 2>&1 || true) | |
| code=$(echo "$response" | tail -1) | |
| set -e | |
| if [ "$code" = "200" ]; then | |
| echo "fake_model_server is healthy after $((SECONDS))s" | |
| echo "health response:" | |
| echo "$response" | |
| segment_count=$(curl -fsS http://127.0.0.1:8319/omnivoice/segments | python -c "import json,sys; print(len(json.load(sys.stdin).get('segments', [])))") | |
| echo "fake segment fixture count: $segment_count" | |
| test "$segment_count" -gt 0 | |
| exit 0 | |
| fi | |
| done | |
| echo "=== fake_model_server did not become healthy ===" | |
| echo "Server PID alive?" | |
| kill -0 "$SERVER_PID" 2>/dev/null && echo "YES" || echo "NO" | |
| echo "--- fake_model_server logs (full) ---" | |
| cat fake_model_server.log || echo "(empty or unreadable)" | |
| echo "--- end logs ---" | |
| exit 1 | |
| - name: Run Playwright E2E tests | |
| if: needs.paths-filter.outputs.code == 'true' | |
| working-directory: tests/ui | |
| env: | |
| PYTHONPATH: src:src/export | |
| PERSONA_FORGE_UI_URL: http://127.0.0.1:8319 | |
| run: npm run test:ci | |
| - name: Upload Playwright report | |
| if: failure() && needs.paths-filter.outputs.code == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: playwright-report | |
| path: tests/ui/playwright-report | |
| retention-days: 1 | |
| - name: Upload Playwright test results | |
| if: failure() && needs.paths-filter.outputs.code == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: playwright-test-results | |
| path: tests/ui/test-results | |
| if-no-files-found: warn | |
| retention-days: 1 | |
| - name: Upload fake_model_server log | |
| if: failure() && needs.paths-filter.outputs.code == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: fake_model_server-log | |
| path: fake_model_server.log | |
| retention-days: 1 |