feat(backend): let chat list and target any accessible workspace #792
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: PR Checks | |
| on: | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: pr-checks-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| changes: | |
| name: Detect changed areas | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| auth_changed: ${{ steps.detect.outputs.auth_changed }} | |
| backend_changed: ${{ steps.detect.outputs.backend_changed }} | |
| web_changed: ${{ steps.detect.outputs.web_changed }} | |
| admin_changed: ${{ steps.detect.outputs.admin_changed }} | |
| infra_changed: ${{ steps.detect.outputs.infra_changed }} | |
| node_changed: ${{ steps.detect.outputs.node_changed }} | |
| android_changed: ${{ steps.detect.outputs.android_changed }} | |
| android_data_local_changed: ${{ steps.detect.outputs.android_data_local_changed }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed files | |
| id: detect | |
| run: | | |
| set -euo pipefail | |
| changed_files_path="${RUNNER_TEMP}/changed-files.txt" | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| printf '%s\n' 'workflow_dispatch (forced full PR checks)' > "${changed_files_path}" | |
| else | |
| git fetch --no-tags origin "${GITHUB_BASE_REF}" | |
| git diff --name-only "origin/${GITHUB_BASE_REF}...HEAD" > "${changed_files_path}" | |
| fi | |
| auth_changed=false | |
| backend_changed=false | |
| web_changed=false | |
| admin_changed=false | |
| infra_changed=false | |
| android_changed=false | |
| android_data_local_changed=false | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| auth_changed=true | |
| backend_changed=true | |
| web_changed=true | |
| admin_changed=true | |
| infra_changed=true | |
| android_changed=true | |
| android_data_local_changed=true | |
| else | |
| if grep -Eq '^(apps/auth/|\.github/workflows/pr-checks\.yml$)' "${changed_files_path}"; then | |
| auth_changed=true | |
| fi | |
| if grep -Eq '^(apps/backend/|\.github/workflows/pr-checks\.yml$)' "${changed_files_path}"; then | |
| backend_changed=true | |
| fi | |
| if grep -Eq '^(apps/web/|apps/backend/src/scheduling/|\.github/workflows/pr-checks\.yml$)' "${changed_files_path}"; then | |
| web_changed=true | |
| fi | |
| if grep -Eq '^(apps/admin/|\.github/workflows/pr-checks\.yml$)' "${changed_files_path}"; then | |
| admin_changed=true | |
| fi | |
| if grep -Eq '^(infra/|\.github/workflows/pr-checks\.yml$)' "${changed_files_path}"; then | |
| infra_changed=true | |
| fi | |
| while IFS= read -r changed_file; do | |
| case "${changed_file}" in | |
| apps/android/README.md|apps/android/docs/*) | |
| ;; | |
| apps/android/*|cloudbuild.android.yaml|scripts/android/*|.github/workflows/android-ci.yml|.github/workflows/android-ci-reusable.yml|.github/workflows/android-release.yml|.github/workflows/pr-checks.yml) | |
| android_changed=true | |
| ;; | |
| esac | |
| case "${changed_file}" in | |
| apps/android/data/*|apps/android/core/observability/*|apps/android/build.gradle.kts|apps/android/settings.gradle.kts|apps/android/gradle.properties|apps/android/gradle/*|.github/workflows/android-ci-reusable.yml|.github/workflows/pr-checks.yml) | |
| android_data_local_changed=true | |
| ;; | |
| esac | |
| done < "${changed_files_path}" | |
| fi | |
| node_changed=false | |
| if [[ "${auth_changed}" == "true" || "${backend_changed}" == "true" \ | |
| || "${web_changed}" == "true" || "${admin_changed}" == "true" || "${infra_changed}" == "true" ]]; then | |
| node_changed=true | |
| fi | |
| { | |
| echo "auth_changed=${auth_changed}" | |
| echo "backend_changed=${backend_changed}" | |
| echo "web_changed=${web_changed}" | |
| echo "admin_changed=${admin_changed}" | |
| echo "infra_changed=${infra_changed}" | |
| echo "node_changed=${node_changed}" | |
| echo "android_changed=${android_changed}" | |
| echo "android_data_local_changed=${android_data_local_changed}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Summarize changed areas | |
| env: | |
| AUTH_CHANGED: ${{ steps.detect.outputs.auth_changed }} | |
| BACKEND_CHANGED: ${{ steps.detect.outputs.backend_changed }} | |
| WEB_CHANGED: ${{ steps.detect.outputs.web_changed }} | |
| ADMIN_CHANGED: ${{ steps.detect.outputs.admin_changed }} | |
| INFRA_CHANGED: ${{ steps.detect.outputs.infra_changed }} | |
| ANDROID_CHANGED: ${{ steps.detect.outputs.android_changed }} | |
| ANDROID_DATA_LOCAL_CHANGED: ${{ steps.detect.outputs.android_data_local_changed }} | |
| run: | | |
| set -euo pipefail | |
| changed_files_path="${RUNNER_TEMP}/changed-files.txt" | |
| { | |
| echo "## PR check scope" | |
| echo "" | |
| echo "- Auth changed: \`${AUTH_CHANGED}\`" | |
| echo "- Backend changed: \`${BACKEND_CHANGED}\`" | |
| echo "- Web changed: \`${WEB_CHANGED}\`" | |
| echo "- Admin changed: \`${ADMIN_CHANGED}\`" | |
| echo "- Infra changed: \`${INFRA_CHANGED}\`" | |
| echo "- Android changed: \`${ANDROID_CHANGED}\`" | |
| echo "- Android data:local changed: \`${ANDROID_DATA_LOCAL_CHANGED}\`" | |
| echo "" | |
| echo "### Changed files" | |
| echo "" | |
| if [[ -s "${changed_files_path}" ]]; then | |
| cat "${changed_files_path}" | |
| else | |
| echo "No changed files detected." | |
| fi | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| android_build: | |
| name: Android build, unit tests, and lint | |
| needs: changes | |
| if: ${{ needs.changes.outputs.android_changed == 'true' }} | |
| uses: ./.github/workflows/android-ci-reusable.yml | |
| with: | |
| target_ref: ${{ github.sha }} | |
| android_version_code: ${{ github.run_number }} | |
| run_data_local_instrumentation: false | |
| secrets: inherit | |
| android_data_local: | |
| name: Android data:local instrumentation | |
| needs: changes | |
| if: ${{ needs.changes.outputs.android_data_local_changed == 'true' }} | |
| uses: ./.github/workflows/android-ci-reusable.yml | |
| with: | |
| target_ref: ${{ github.sha }} | |
| android_version_code: ${{ github.run_number }} | |
| run_build: false | |
| run_data_local_instrumentation: true | |
| secrets: inherit | |
| checks: | |
| name: Type checks and builds | |
| needs: changes | |
| if: ${{ needs.changes.outputs.node_changed == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: | | |
| apps/auth/package-lock.json | |
| apps/backend/package-lock.json | |
| apps/web/package-lock.json | |
| apps/admin/package-lock.json | |
| infra/aws/package-lock.json | |
| - name: Install dependencies | |
| run: | | |
| npm ci --prefix apps/auth | |
| npm ci --prefix apps/backend | |
| npm ci --prefix apps/web | |
| npm ci --prefix apps/admin | |
| npm ci --prefix infra/aws | |
| - name: Build auth app | |
| if: ${{ needs.changes.outputs.auth_changed == 'true' }} | |
| run: npm run build --prefix apps/auth | |
| - name: Test auth app | |
| if: ${{ needs.changes.outputs.auth_changed == 'true' }} | |
| run: npm run test --prefix apps/auth | |
| - name: Lint backend | |
| if: ${{ needs.changes.outputs.backend_changed == 'true' }} | |
| run: npm run lint --prefix apps/backend | |
| - name: Build backend | |
| if: ${{ needs.changes.outputs.backend_changed == 'true' }} | |
| run: npm run build --prefix apps/backend | |
| - name: Build web app | |
| if: ${{ needs.changes.outputs.web_changed == 'true' }} | |
| run: npm run build --prefix apps/web | |
| - name: Build admin app | |
| if: ${{ needs.changes.outputs.admin_changed == 'true' }} | |
| run: npm run build --prefix apps/admin | |
| - name: Build infra | |
| if: ${{ needs.changes.outputs.infra_changed == 'true' }} | |
| run: npm run build --prefix infra/aws | |
| - name: Test infra | |
| if: ${{ needs.changes.outputs.infra_changed == 'true' }} | |
| run: npm run test --prefix infra/aws | |
| backend_tests: | |
| name: Backend test suites | |
| needs: changes | |
| if: ${{ needs.changes.outputs.backend_changed == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: | | |
| apps/backend/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci --prefix apps/backend | |
| - name: Test backend | |
| run: npm test --prefix apps/backend | |
| web_tests: | |
| name: Web test suite | |
| needs: changes | |
| if: ${{ needs.changes.outputs.web_changed == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: | | |
| apps/web/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci --prefix apps/web | |
| - name: Test web app | |
| run: npm test --prefix apps/web | |
| repository_static_validation: | |
| name: Repository static validation | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| - name: Fetch base ref | |
| run: | | |
| set -euo pipefail | |
| if [[ -n "${GITHUB_BASE_REF:-}" ]]; then | |
| git fetch --no-tags origin "${GITHUB_BASE_REF}" | |
| else | |
| echo "No base ref to fetch for event ${GITHUB_EVENT_NAME}." | |
| fi | |
| - name: Run repository static checks | |
| env: | |
| PR_BASE_REF: ${{ github.base_ref }} | |
| run: node scripts/checks/pr/run-all.mjs | |
| lint_scripts: | |
| name: Shell and workflow lint | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Check shell script syntax | |
| run: | | |
| set -euo pipefail | |
| git ls-files -z '*.sh' | xargs -0 -r -n1 bash -n | |
| - name: Run shellcheck | |
| run: | | |
| set -euo pipefail | |
| shellcheck --version | |
| git ls-files -z '*.sh' | xargs -0 -r shellcheck --severity=warning | |
| - name: Run actionlint | |
| env: | |
| ACTIONLINT_VERSION: 1.7.12 | |
| ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 | |
| run: | | |
| set -euo pipefail | |
| install_dir="${RUNNER_TEMP}/actionlint" | |
| archive_path="${install_dir}/actionlint.tar.gz" | |
| archive_url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | |
| mkdir -p "${install_dir}" | |
| # Retry transient release-CDN failures instead of turning an unrelated PR red. | |
| # --show-error reports every failed attempt, and the last error still fails the step. | |
| curl --silent --show-error --fail --location \ | |
| --retry 3 --retry-delay 2 --retry-all-errors \ | |
| --output "${archive_path}" \ | |
| "${archive_url}" | |
| printf '%s %s\n' "${ACTIONLINT_SHA256}" "${archive_path}" | sha256sum --check --strict - | |
| tar -xzf "${archive_path}" -C "${install_dir}" actionlint | |
| "${install_dir}/actionlint" --version | |
| "${install_dir}/actionlint" | |
| static_checks: | |
| name: Repository static checks | |
| needs: | |
| - changes | |
| - checks | |
| - backend_tests | |
| - web_tests | |
| - android_build | |
| - android_data_local | |
| - repository_static_validation | |
| - lint_scripts | |
| if: ${{ always() }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Require successful PR checks | |
| env: | |
| CHANGES_RESULT: ${{ needs.changes.result }} | |
| NODE_CHANGED: ${{ needs.changes.outputs.node_changed }} | |
| BACKEND_CHANGED: ${{ needs.changes.outputs.backend_changed }} | |
| WEB_CHANGED: ${{ needs.changes.outputs.web_changed }} | |
| ANDROID_CHANGED: ${{ needs.changes.outputs.android_changed }} | |
| ANDROID_DATA_LOCAL_CHANGED: ${{ needs.changes.outputs.android_data_local_changed }} | |
| TYPE_CHECKS_RESULT: ${{ needs.checks.result }} | |
| BACKEND_TESTS_RESULT: ${{ needs.backend_tests.result }} | |
| WEB_TESTS_RESULT: ${{ needs.web_tests.result }} | |
| ANDROID_BUILD_RESULT: ${{ needs.android_build.result }} | |
| ANDROID_DATA_LOCAL_RESULT: ${{ needs.android_data_local.result }} | |
| STATIC_VALIDATION_RESULT: ${{ needs.repository_static_validation.result }} | |
| SCRIPT_LINT_RESULT: ${{ needs.lint_scripts.result }} | |
| run: | | |
| set -euo pipefail | |
| require_success() { | |
| local check_name="$1" | |
| local check_result="$2" | |
| if [[ "${check_result}" != "success" ]]; then | |
| echo "Required PR check '${check_name}' did not succeed. Result: ${check_result}." >&2 | |
| exit 1 | |
| fi | |
| } | |
| require_conditional_success() { | |
| local check_name="$1" | |
| local check_expected="$2" | |
| local check_result="$3" | |
| if [[ "${check_expected}" == "true" ]]; then | |
| require_success "${check_name}" "${check_result}" | |
| return | |
| fi | |
| if [[ "${check_expected}" == "false" && "${check_result}" == "skipped" ]]; then | |
| return | |
| fi | |
| if [[ "${check_expected}" != "false" ]]; then | |
| echo "Conditional PR check '${check_name}' has invalid scope value: ${check_expected}." >&2 | |
| exit 1 | |
| fi | |
| echo "Conditional PR check '${check_name}' ran outside its detected scope. Result: ${check_result}." >&2 | |
| exit 1 | |
| } | |
| require_success "Detect changed areas" "${CHANGES_RESULT}" | |
| require_success "Repository static validation" "${STATIC_VALIDATION_RESULT}" | |
| require_success "Shell and workflow lint" "${SCRIPT_LINT_RESULT}" | |
| require_conditional_success "Type checks and builds" "${NODE_CHANGED}" "${TYPE_CHECKS_RESULT}" | |
| require_conditional_success "Backend test suites" "${BACKEND_CHANGED}" "${BACKEND_TESTS_RESULT}" | |
| require_conditional_success "Web test suite" "${WEB_CHANGED}" "${WEB_TESTS_RESULT}" | |
| require_conditional_success "Android build, unit tests, and lint" "${ANDROID_CHANGED}" "${ANDROID_BUILD_RESULT}" | |
| require_conditional_success "Android data:local instrumentation" "${ANDROID_DATA_LOCAL_CHANGED}" "${ANDROID_DATA_LOCAL_RESULT}" |