fix(manual-entry): restore single-client lock by dropping force_selectable #431
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: Comprehensive CI Pipeline | |
| # This workflow runs comprehensive tests on pull requests | |
| # | |
| # Test Strategy: | |
| # - Smoke tests (fast, critical) run first | |
| # - Unit, integration, security, and code quality tests run in parallel | |
| # - Full test suite with PostgreSQL runs for PRs to main/master and RC branches | |
| # - Docker build test ensures the image builds correctly | |
| # - Test summary posted as PR comment | |
| # | |
| # All tests must pass before a PR can be merged | |
| # | |
| # Workflow triggers: | |
| # - PRs to RC branches (from develop) - validates code before RC build | |
| # - PRs to main/master (from RC) - validates code before release | |
| # - workflow_dispatch - manual run from GitHub Actions (branch selectable in UI) | |
| on: | |
| pull_request: | |
| branches: [ main, master, 'rc', 'rc/**' ] | |
| workflow_dispatch: | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| POSTGRES_VERSION: '16' | |
| jobs: | |
| # ============================================================================ | |
| # Smoke Tests - Fast, critical tests that run first | |
| # ============================================================================ | |
| smoke-tests: | |
| name: Smoke Tests (Quick) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Run smoke tests | |
| env: | |
| PYTHONPATH: ${{ github.workspace }} | |
| run: | | |
| pytest -m smoke -v --tb=short --no-cov -n auto | |
| - name: Upload smoke test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: smoke-test-results | |
| path: | | |
| .pytest_cache/ | |
| test-results/ | |
| # ============================================================================ | |
| # Unit Tests - Fast, isolated tests | |
| # ============================================================================ | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| needs: smoke-tests | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| test-group: [models, routes, api, utils] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Run unit tests - ${{ matrix.test-group }} | |
| env: | |
| PYTHONPATH: ${{ github.workspace }} | |
| FLASK_ENV: testing | |
| SECRET_KEY: test-secret-key-for-ci | |
| run: | | |
| if [ "${{ matrix.test-group }}" == "api" ]; then | |
| pytest -m "api and integration" -v -n auto --cov=app --cov-report=xml --cov-report=html --cov-report=term-missing | |
| elif [ "${{ matrix.test-group }}" == "routes" ]; then | |
| pytest -m "unit and routes" -v -n 0 --cov=app --cov-report=xml --cov-report=html --cov-report=term-missing | |
| else | |
| pytest -m "unit and ${{ matrix.test-group }}" -v -n auto --cov=app --cov-report=xml --cov-report=html --cov-report=term-missing | |
| fi | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| files: ./coverage.xml | |
| flags: unit-${{ matrix.test-group }} | |
| name: unit-${{ matrix.test-group }} | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-test-results-${{ matrix.test-group }} | |
| path: | | |
| htmlcov/ | |
| coverage.xml | |
| # ============================================================================ | |
| # Integration Tests - Medium speed, component interaction tests | |
| # ============================================================================ | |
| integration-tests: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: smoke-tests | |
| timeout-minutes: 15 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_PASSWORD: test_password | |
| POSTGRES_USER: test_user | |
| POSTGRES_DB: test_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Run integration tests | |
| env: | |
| DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db | |
| FLASK_APP: app.py | |
| FLASK_ENV: testing | |
| PYTHONPATH: ${{ github.workspace }} | |
| INSTALLATION_CONFIG_DIR: ${{ github.workspace }}/.test_installation_config | |
| run: | | |
| pytest -m integration -v -n auto --cov=app --cov-report=xml --cov-report=html --cov-report=term-missing | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| files: ./coverage.xml | |
| flags: integration | |
| name: integration-tests | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: integration-test-results | |
| path: | | |
| htmlcov/ | |
| coverage.xml | |
| # ============================================================================ | |
| # Security Tests | |
| # ============================================================================ | |
| security-tests: | |
| name: Security Tests | |
| runs-on: ubuntu-latest | |
| needs: smoke-tests | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Run security tests | |
| env: | |
| PYTHONPATH: ${{ github.workspace }} | |
| run: bash scripts/ci/security-pytest.sh | |
| - name: Run Safety dependency check | |
| run: | | |
| mkdir -p "${{ github.workspace }}/.test_installation_config" | |
| REPORT="${{ github.workspace }}/.test_installation_config/safety-report.json" | |
| EC=0 | |
| python -m safety check --file requirements.txt --save-json "${REPORT}" >/dev/null || EC=$? | |
| if [ -f "${REPORT}" ]; then | |
| python "${{ github.workspace }}/scripts/ci/sanitize_safety_report.py" "${REPORT}" "${{ github.workspace }}" | |
| fi | |
| exit "${EC}" | |
| - name: Upload security reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: security-reports | |
| path: | | |
| .test_installation_config/safety-report.json | |
| # ============================================================================ | |
| # Code Quality | |
| # ============================================================================ | |
| code-quality: | |
| name: Code Quality Checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Run flake8 | |
| run: | | |
| flake8 app/ --count --select=E9,F63,F7,F82 --show-source --statistics | |
| flake8 app/ --count --max-complexity=10 --max-line-length=120 --statistics | |
| - name: Run black (format check) | |
| run: black --check app/ | |
| - name: Run isort (import check) | |
| run: isort --check-only app/ | |
| - name: Run mypy | |
| run: mypy app/ || true | |
| # Ratchets: these may improve but must never regress. | |
| - name: Check silent exception handlers | |
| run: python scripts/check_silent_excepts.py | |
| - name: Check translation coverage | |
| run: python scripts/check_translation_coverage.py | |
| # ============================================================================ | |
| # Frontend Assets - build pipeline and self-hosting guarantees | |
| # ============================================================================ | |
| frontend-assets: | |
| name: Frontend Assets & Offline Guarantee | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '18' | |
| - name: Install frontend dependencies | |
| run: npm install | |
| # Runs Tailwind, vendors third-party libraries into app/static/vendor, | |
| # and produces the content-hashed JS bundles + manifest.json. | |
| - name: Build assets | |
| run: npm run build:docker | |
| - name: Verify build output | |
| run: | | |
| test -f app/static/dist/output.css | |
| test -f app/static/dist/manifest.json | |
| test -d app/static/vendor/fontawesome | |
| echo "Bundles built:" | |
| python -c "import json;print('\n'.join(f' {k} -> {v}' for k,v in json.load(open('app/static/dist/manifest.json')).items()))" | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install Python dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| # Fails if any template, stylesheet or script reaches out to a CDN, if Font | |
| # Awesome is double-loaded, if the CSP allowlists a third party, or if an | |
| # inline <script> is missing its nonce. | |
| - name: Enforce no external assets | |
| run: pytest tests/test_no_external_assets.py -v | |
| # ============================================================================ | |
| # End-to-end: CSP, offline rendering, accessibility, page-weight budget | |
| # ============================================================================ | |
| e2e: | |
| name: E2E, CSP & Accessibility | |
| runs-on: ubuntu-latest | |
| needs: [docker-build] | |
| timeout-minutes: 25 | |
| services: | |
| postgres: | |
| image: postgres:16 | |
| env: | |
| POSTGRES_USER: timetracker | |
| POSTGRES_PASSWORD: timetracker | |
| POSTGRES_DB: timetracker | |
| ports: ['5432:5432'] | |
| options: >- | |
| --health-cmd pg_isready --health-interval 10s | |
| --health-timeout 5s --health-retries 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '18' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Install Playwright browser | |
| run: npx playwright install --with-deps chromium | |
| # Build and run the real image so the assets under test are the ones that ship. | |
| - name: Build image | |
| run: docker build -t timetracker-e2e:ci . | |
| - name: Start application | |
| env: | |
| E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD || 'e2e-local-password-123' }} | |
| run: | | |
| docker run -d --name timetracker-e2e --network host \ | |
| -e SECRET_KEY=ci-e2e-secret-key-not-for-production \ | |
| -e DATABASE_URL=postgresql://timetracker:timetracker@localhost:5432/timetracker \ | |
| -e ADMIN_USERNAME=e2eadmin \ | |
| -e ADMIN_PASSWORD="$E2E_ADMIN_PASSWORD" \ | |
| -e TZ=UTC \ | |
| timetracker-e2e:ci | |
| echo "Waiting for the app to become healthy..." | |
| for i in $(seq 1 60); do | |
| if curl -fsS http://localhost:8080/login >/dev/null 2>&1; then | |
| echo "Ready after ${i}s"; exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| echo "App did not start; container logs:" >&2 | |
| docker logs timetracker-e2e >&2 | |
| exit 1 | |
| - name: Run Playwright suite | |
| env: | |
| E2E_BASE_URL: http://localhost:8080 | |
| E2E_USERNAME: e2eadmin | |
| E2E_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD || 'e2e-local-password-123' }} | |
| run: npx playwright test | |
| - name: Container logs on failure | |
| if: failure() | |
| run: docker logs timetracker-e2e | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 7 | |
| # ============================================================================ | |
| # Docker Build Test | |
| # ============================================================================ | |
| docker-build: | |
| name: Docker Build Test | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, integration-tests] | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build Docker image | |
| run: | | |
| docker build -t timetracker-test:pr-${{ github.event.pull_request.number || 'dev' }} . | |
| - name: Test Docker container startup | |
| run: | | |
| # Start container | |
| CONTAINER_ID=$(docker run -d --name test-container \ | |
| -p 8080:8080 \ | |
| -e DATABASE_URL="sqlite:////app/test.db" \ | |
| -e SECRET_KEY="test-secret-key-for-ci-only-$(openssl rand -hex 32)" \ | |
| -e FLASK_ENV="development" \ | |
| timetracker-test:pr-${{ github.event.pull_request.number || 'dev' }}) | |
| echo "🐳 Started container: $CONTAINER_ID" | |
| # Wait for container to be ready (increased timeout for migrations) | |
| HEALTH_CHECK_PASSED=false | |
| for i in {1..60}; do | |
| # Check if container is still running | |
| if ! docker ps -q --filter "name=test-container" | grep -q .; then | |
| echo "❌ Container exited unexpectedly!" | |
| echo "" | |
| echo "📋 Container logs:" | |
| docker logs test-container | |
| echo "" | |
| echo "🔍 Container status:" | |
| docker ps -a --filter "name=test-container" | |
| exit 1 | |
| fi | |
| # Try health check | |
| if curl -f http://localhost:8080/_health >/dev/null 2>&1; then | |
| echo "✅ Container health check passed (attempt $i/60)" | |
| HEALTH_CHECK_PASSED=true | |
| break | |
| fi | |
| # Show progress | |
| if [ $((i % 10)) -eq 0 ]; then | |
| echo "⏳ Still waiting for container... ($i/60)" | |
| echo "📊 Last 10 log lines:" | |
| docker logs --tail 10 test-container | |
| else | |
| echo "⏳ Waiting for container... ($i/60)" | |
| fi | |
| sleep 2 | |
| done | |
| # Show full logs for debugging | |
| echo "" | |
| echo "📋 Full container logs:" | |
| docker logs test-container | |
| echo "" | |
| # Check if health check passed | |
| if [ "$HEALTH_CHECK_PASSED" = false ]; then | |
| echo "❌ Health check never passed after 120 seconds" | |
| echo "" | |
| echo "🔍 Container inspect:" | |
| docker inspect test-container | |
| echo "" | |
| echo "🔍 Container status:" | |
| docker ps -a --filter "name=test-container" | |
| exit 1 | |
| fi | |
| # Final health check with detailed output | |
| echo "🔍 Final health check:" | |
| curl -v http://localhost:8080/_health || { | |
| echo "❌ Final health check failed" | |
| echo "📋 Latest logs:" | |
| docker logs --tail 50 test-container | |
| exit 1 | |
| } | |
| echo "✅ Docker container test completed successfully" | |
| # Cleanup | |
| docker stop test-container | |
| docker rm test-container | |
| # ============================================================================ | |
| # Full Test Suite (runs on all PRs to main/master/rc) | |
| # ============================================================================ | |
| full-test-suite: | |
| name: Full Test Suite with PostgreSQL | |
| runs-on: ubuntu-latest | |
| needs: [smoke-tests, unit-tests, integration-tests] | |
| if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && (github.base_ref == 'main' || github.base_ref == 'master' || startsWith(github.base_ref, 'rc'))) | |
| timeout-minutes: 30 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_PASSWORD: test_password | |
| POSTGRES_USER: test_user | |
| POSTGRES_DB: test_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| pip install -r requirements-test.txt | |
| pip install -e . | |
| - name: Validate database migrations | |
| env: | |
| DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db | |
| FLASK_APP: app.py | |
| FLASK_ENV: testing | |
| run: | | |
| echo "🔍 Validating database migrations..." | |
| # Check if there are migration-related changes | |
| if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E "(app/models/|migrations/)" > /dev/null; then | |
| echo "📋 Migration-related changes detected" | |
| # Initialize fresh database | |
| flask db upgrade | |
| # Test migration rollback | |
| CURRENT_MIGRATION=$(flask db current) | |
| echo "Current migration: $CURRENT_MIGRATION" | |
| if [ -n "$CURRENT_MIGRATION" ] && [ "$CURRENT_MIGRATION" != "None" ]; then | |
| echo "Testing migration operations..." | |
| flask db upgrade head | |
| echo "✅ Migration validation passed" | |
| fi | |
| # Test with sample data | |
| python -c " | |
| from app import create_app, db | |
| from app.models.user import User | |
| from app.models.project import Project | |
| from app.models.client import Client | |
| app = create_app() | |
| with app.app_context(): | |
| user = User(username='test_user', role='user') | |
| db.session.add(user) | |
| db.session.commit() | |
| client = Client(name='Test Client', description='Test client') | |
| db.session.add(client) | |
| db.session.commit() | |
| project = Project(name='Test Project', client_id=client.id, description='Test project') | |
| db.session.add(project) | |
| db.session.commit() | |
| print('✅ Sample data created and validated successfully') | |
| " | |
| else | |
| echo "ℹ️ No migration-related changes detected" | |
| fi | |
| - name: Run full test suite | |
| env: | |
| DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db | |
| FLASK_APP: app.py | |
| FLASK_ENV: testing | |
| PYTHONPATH: ${{ github.workspace }} | |
| INSTALLATION_CONFIG_DIR: ${{ github.workspace }}/.test_installation_config | |
| run: | | |
| pytest -v -n auto --cov=app --cov-report=xml --cov-report=html --cov-report=term-missing \ | |
| --cov-fail-under=35 --junitxml=junit.xml --maxfail=5 | |
| - name: Upload full coverage | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| files: ./coverage.xml | |
| flags: full-suite | |
| name: full-test-suite | |
| - name: Upload full test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: full-test-results | |
| path: | | |
| htmlcov/ | |
| coverage.xml | |
| junit.xml | |
| - name: Publish full test results | |
| uses: EnricoMi/publish-unit-test-result-action@v2 | |
| if: always() | |
| with: | |
| files: junit.xml | |
| check_name: Full Test Suite Results | |
| # ============================================================================ | |
| # Test Summary and PR Comment | |
| # ============================================================================ | |
| test-summary: | |
| name: Test Summary | |
| runs-on: ubuntu-latest | |
| needs: [smoke-tests, unit-tests, integration-tests, security-tests, code-quality, frontend-assets, e2e, docker-build, full-test-suite] | |
| if: always() && github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Generate test summary | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const jobs = [ | |
| { name: 'Smoke Tests', result: '${{ needs.smoke-tests.result }}' }, | |
| { name: 'Unit Tests', result: '${{ needs.unit-tests.result }}' }, | |
| { name: 'Integration Tests', result: '${{ needs.integration-tests.result }}' }, | |
| { name: 'Security Tests', result: '${{ needs.security-tests.result }}' }, | |
| { name: 'Code Quality', result: '${{ needs.code-quality.result }}' }, | |
| { name: 'Frontend Assets', result: '${{ needs.frontend-assets.result }}' }, | |
| { name: 'E2E, CSP & Accessibility', result: '${{ needs.e2e.result }}' }, | |
| { name: 'Docker Build', result: '${{ needs.docker-build.result }}' }, | |
| { name: 'Full Test Suite', result: '${{ needs.full-test-suite.result }}' } | |
| ]; | |
| const passed = jobs.filter(j => j.result === 'success').length; | |
| const failed = jobs.filter(j => j.result === 'failure').length; | |
| const total = jobs.length; | |
| let emoji = failed === 0 ? '✅' : '❌'; | |
| let status = failed === 0 ? 'All tests passed!' : `${failed} test suite(s) failed`; | |
| let commentBody = `## ${emoji} CI Test Results\n\n`; | |
| commentBody += `**Overall Status:** ${status}\n\n`; | |
| commentBody += `**Test Results:** ${passed}/${total} passed\n\n`; | |
| commentBody += `### Test Suites:\n\n`; | |
| for (const job of jobs) { | |
| const icon = job.result === 'success' ? '✅' : | |
| job.result === 'failure' ? '❌' : | |
| job.result === 'skipped' ? '⏭️' : '⏸️'; | |
| commentBody += `- ${icon} ${job.name}: **${job.result}**\n`; | |
| } | |
| commentBody += `\n---\n`; | |
| commentBody += `*Commit: ${context.sha.substring(0, 7)}*\n`; | |
| commentBody += `*Workflow: [${context.runId}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})*`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('CI Test Results') | |
| ); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| comment_id: botComment.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: commentBody, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: commentBody, | |
| }); | |
| } | |