diff --git a/.github/workflows/agents-integration-tests.yml b/.github/workflows/agents-integration-tests.yml index 4353e622b0..86fe4c8369 100644 --- a/.github/workflows/agents-integration-tests.yml +++ b/.github/workflows/agents-integration-tests.yml @@ -11,9 +11,16 @@ on: - dev - dev-staging - release/* + # The suite builds and consumes data-provider and data-schemas and imports + # across packages/api (the build-cache keys below hash all three src trees), + # so it must re-run on any of them — not just src/agents. paths: - - 'packages/api/src/agents/**' + - 'packages/api/src/**' - 'packages/api/package.json' + - 'packages/data-provider/src/**' + - 'packages/data-provider/package.json' + - 'packages/data-schemas/src/**' + - 'packages/data-schemas/package.json' - '.github/workflows/agents-integration-tests.yml' - '!**.md' @@ -60,7 +67,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -71,7 +78,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas if: steps.cache-data-schemas.outputs.cache-hit != 'true' @@ -82,7 +89,7 @@ jobs: uses: actions/cache@v5 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 47e08d03fc..b67de66f7b 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -4,6 +4,8 @@ on: paths: - 'api/**' - 'packages/**' + - 'package.json' + - 'package-lock.json' - 'config/circular-deps.mjs' - '.github/workflows/backend-review.yml' - '!**.md' @@ -53,7 +55,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -64,7 +66,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas if: steps.cache-data-schemas.outputs.cache-hit != 'true' @@ -75,7 +77,7 @@ jobs: uses: actions/cache@v5 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/.github/workflows/cache-integration-tests.yml b/.github/workflows/cache-integration-tests.yml index f98cff0c2f..7fda84602a 100644 --- a/.github/workflows/cache-integration-tests.yml +++ b/.github/workflows/cache-integration-tests.yml @@ -7,11 +7,17 @@ on: - dev - dev-staging - release/* + # The tested modules import across packages/api (e.g. mcp/oauth pulls in + # flow/manager) and consume built data-provider and data-schemas, so the + # whole src trees must trigger — subdirectory filters silently skip + # regressions in imported files. paths: - - 'packages/api/src/cache/**' - - 'packages/api/src/cluster/**' - - 'packages/api/src/mcp/**' - - 'packages/api/src/stream/**' + - 'packages/api/src/**' + - 'packages/api/package.json' + - 'packages/data-provider/src/**' + - 'packages/data-provider/package.json' + - 'packages/data-schemas/src/**' + - 'packages/data-schemas/package.json' - 'redis-config/**' - '.github/workflows/cache-integration-tests.yml' - '!**.md' @@ -82,7 +88,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -93,7 +99,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas if: steps.cache-data-schemas.outputs.cache-hit != 'true' @@ -104,7 +110,7 @@ jobs: uses: actions/cache@v5 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' diff --git a/.github/workflows/dev-branch-images.yml b/.github/workflows/dev-branch-images.yml index df66cb67c0..5a85779b11 100644 --- a/.github/workflows/dev-branch-images.yml +++ b/.github/workflows/dev-branch-images.yml @@ -8,12 +8,21 @@ on: paths: - 'api/**' - 'client/**' + - 'config/**' + - 'skill/**' - 'packages/**' - 'package.json' - 'package-lock.json' - 'Dockerfile' - 'Dockerfile.multi' + - '.dockerignore' - '!**.md' + # Deployment skills are Markdown the image ships and reads at runtime + # (skill//SKILL.md plus md resources); re-include them after the + # !**.md exclusion (later patterns win), keeping the top-level README + # documentation-only. + - 'skill/**/*.md' + - '!skill/README.md' permissions: contents: read diff --git a/.github/workflows/dev-images.yml b/.github/workflows/dev-images.yml index d936b09627..616f5a6af4 100644 --- a/.github/workflows/dev-images.yml +++ b/.github/workflows/dev-images.yml @@ -8,12 +8,21 @@ on: paths: - 'api/**' - 'client/**' + - 'config/**' + - 'skill/**' - 'packages/**' - 'package.json' - 'package-lock.json' - 'Dockerfile' - 'Dockerfile.multi' + - '.dockerignore' - '!**.md' + # Deployment skills are Markdown the image ships and reads at runtime + # (skill//SKILL.md plus md resources); re-include them after the + # !**.md exclusion (later patterns win), keeping the top-level README + # documentation-only. + - 'skill/**/*.md' + - '!skill/README.md' permissions: contents: read diff --git a/.github/workflows/docker-smoke.yml b/.github/workflows/docker-smoke.yml index 0c24150764..dd576291cb 100644 --- a/.github/workflows/docker-smoke.yml +++ b/.github/workflows/docker-smoke.yml @@ -6,6 +6,7 @@ on: paths: - '.github/workflows/docker-smoke.yml' - '.dockerignore' + - 'Dockerfile' - 'Dockerfile.multi' - 'package.json' - 'package-lock.json' @@ -21,6 +22,7 @@ on: permissions: contents: read + pull-requests: read concurrency: group: docker-smoke-${{ github.workflow }}-${{ github.ref }} @@ -46,6 +48,44 @@ jobs: push: false target: client-package-build + # The plain single-stage Dockerfile ships via dev-images/tag-images but had no + # PR-time validation. The npm build pipeline itself is already smoked on every + # matching PR by the Dockerfile.multi jobs above, so the full build here is + # gated to changes of the Dockerfile or the build-context definition. + node-image-smoke: + name: Node image smoke (plain Dockerfile builds) + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@v5 + + - name: Detect plain Dockerfile changes + id: paths + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@v4 + with: + filters: | + dockerfile: + - 'Dockerfile' + - '.dockerignore' + - '.github/workflows/docker-smoke.yml' + + - name: Set up Docker Buildx + if: github.event_name == 'workflow_dispatch' || steps.paths.outputs.dockerfile == 'true' + uses: docker/setup-buildx-action@v4 + + - name: Build node image + if: github.event_name == 'workflow_dispatch' || steps.paths.outputs.dockerfile == 'true' + uses: docker/build-push-action@v7 + with: + context: . + file: Dockerfile + platforms: linux/amd64 + push: false + target: node + cache-from: type=gha,scope=docker-smoke-node + cache-to: type=gha,mode=max,scope=docker-smoke-node + api-runtime-smoke: name: API runtime smoke (production image boots) runs-on: ubuntu-latest diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml index d3bd40ae47..2f336dc8f2 100644 --- a/.github/workflows/frontend-review.yml +++ b/.github/workflows/frontend-review.yml @@ -6,6 +6,8 @@ on: - 'client/**' - 'packages/client/**' - 'packages/data-provider/**' + - 'package.json' + - 'package-lock.json' - '.github/workflows/frontend-review.yml' - '!**.md' @@ -52,7 +54,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -63,7 +65,7 @@ jobs: uses: actions/cache@v5 with: path: packages/client/dist - key: build-client-package-${{ runner.os }}-${{ hashFiles('packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client-package if: steps.cache-client-package.outputs.cache-hit != 'true' diff --git a/.github/workflows/frontend-windows-nightly.yml b/.github/workflows/frontend-windows-nightly.yml index d8da19e7cd..28c1ea4605 100644 --- a/.github/workflows/frontend-windows-nightly.yml +++ b/.github/workflows/frontend-windows-nightly.yml @@ -57,7 +57,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -68,7 +68,7 @@ jobs: uses: actions/cache@v5 with: path: packages/client/dist - key: build-client-package-${{ runner.os }}-${{ hashFiles('packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client-package if: steps.cache-client-package.outputs.cache-hit != 'true' diff --git a/.github/workflows/generate_embeddings.yml b/.github/workflows/generate_embeddings.yml deleted file mode 100644 index 8b75c11445..0000000000 --- a/.github/workflows/generate_embeddings.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: 'generate_embeddings' -on: - workflow_dispatch: - push: - branches: - - main - paths: - - 'docs/**' - -permissions: - contents: read - -jobs: - generate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: supabase/embeddings-generator@v0.0.5 - with: - supabase-url: ${{ secrets.SUPABASE_URL }} - supabase-service-role-key: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} - openai-key: ${{ secrets.OPENAI_DOC_EMBEDDINGS_KEY }} - docs-root-path: 'docs' diff --git a/.github/workflows/playwright-bombadil.yml b/.github/workflows/playwright-bombadil.yml index 16df9583f5..58d28b9884 100644 --- a/.github/workflows/playwright-bombadil.yml +++ b/.github/workflows/playwright-bombadil.yml @@ -69,7 +69,7 @@ jobs: uses: actions/cache@v4 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -80,7 +80,7 @@ jobs: uses: actions/cache@v4 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas if: steps.cache-data-schemas.outputs.cache-hit != 'true' @@ -91,7 +91,7 @@ jobs: uses: actions/cache@v4 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' @@ -102,7 +102,7 @@ jobs: uses: actions/cache@v4 with: path: packages/client/dist - key: build-client-package-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client-package if: steps.cache-client-package.outputs.cache-hit != 'true' @@ -113,7 +113,7 @@ jobs: uses: actions/cache@v4 with: path: client/dist - key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client app if: steps.cache-client-app.outputs.cache-hit != 'true' diff --git a/.github/workflows/playwright-mock.yml b/.github/workflows/playwright-mock.yml index f02fc3b6de..1b677dcc76 100644 --- a/.github/workflows/playwright-mock.yml +++ b/.github/workflows/playwright-mock.yml @@ -85,7 +85,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider if: steps.cache-data-provider.outputs.cache-hit != 'true' @@ -96,7 +96,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas if: steps.cache-data-schemas.outputs.cache-hit != 'true' @@ -107,7 +107,7 @@ jobs: uses: actions/cache@v5 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api if: steps.cache-api.outputs.cache-hit != 'true' @@ -118,7 +118,7 @@ jobs: uses: actions/cache@v5 with: path: packages/client/dist - key: build-client-package-${{ runner.os }}-${{ hashFiles('package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client-package if: steps.cache-client-package.outputs.cache-hit != 'true' @@ -129,7 +129,7 @@ jobs: uses: actions/cache@v5 with: path: client/dist - key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build client app if: steps.cache-client-app.outputs.cache-hit != 'true' diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index fff4b8d0f3..cfa43bc2ef 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -9,6 +9,7 @@ on: - 'packages/**' - 'package.json' - 'package-lock.json' + - 'eslint.config.mjs' - '.github/workflows/static-checks.yml' - '!**.md' @@ -34,6 +35,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 with: + # Full history is load-bearing: changed-file steps diff against the + # PR base and the ESLint regression gate reads the base ref's + # config via git show — a shallow checkout breaks both. fetch-depth: 0 - name: Detect affected checks @@ -46,8 +50,12 @@ jobs: - 'api/**' - 'client/**' - 'packages/**' + - 'eslint.config.mjs' - '.github/workflows/static-checks.yml' - '!**.md' + eslint_config: + - 'eslint.config.mjs' + - '.github/workflows/static-checks.yml' config: - 'api/**' - 'config/**' @@ -186,6 +194,98 @@ jobs: exit 1 fi + # The changed-file lint above never loads a changed root config: a + # config-only PR matches no lintable files, so even a malformed + # eslint.config.mjs would pass. When the config changes, gate on it + # loading and applying cleanly to representative sources, then run the + # full-tree regression gate below. + # Directory args, not `npm run lint`: the root brace-expansion@^5 + # override breaks minimatch@3's brace expansion, so that script's + # braced glob crashes on a clean install; dir args never brace-expand. + - name: Validate ESLint config on config changes + id: eslint_config + if: always() && steps.paths.outputs.eslint_config == 'true' + continue-on-error: true + run: | + npx eslint --config eslint.config.mjs \ + api/server/index.js client/src/main.jsx packages/api/src/index.ts + + # Full-tree sweep that gates on regression, not cleanliness: the tree + # carries a pre-existing lint backlog (70 errors at time of wiring), so + # requiring a clean sweep would fail config PRs on unrelated debt. + # Instead, lint the same tree under the PR's config and under the base + # ref's config and fail when the PR's config (a) stops linting files + # the base config covered — the signature of a mis-scoped ignores — or + # (b) produces more diagnostics for some (file, rule, severity) triple. + # On an identical tree, any delta is attributable to the config change + # alone. Severity is part of the key so a warn->error escalation must + # land with the tree clean for that rule; downgrades and fixes are + # never penalized. + - name: ESLint full-sweep regression gate on config changes + id: eslint_sweep + if: always() && steps.paths.outputs.eslint_config == 'true' + continue-on-error: true + run: | + npx eslint --config eslint.config.mjs api client packages -f json -o "$RUNNER_TEMP/eslint-head.json" || true + if [ ! -s "$RUNNER_TEMP/eslint-head.json" ]; then + echo "::error title=ESLint sweep::Head-config sweep produced no report — ESLint likely crashed under the new config." + exit 1 + fi + + BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH") + if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then + echo "::error title=ESLint sweep::Base commit is unavailable — this gate requires the checkout above to keep fetch-depth: 0." + exit 1 + fi + # The base config is written to the repo root, not $RUNNER_TEMP: + # flat-config files/ignores patterns and plugin imports resolve + # relative to the config's own directory, so a temp-dir copy would + # scope to nothing and the comparison would pass vacuously. + trap 'rm -f eslint.config.base.mjs' EXIT + if ! git show "$BASE_SHA:eslint.config.mjs" > eslint.config.base.mjs 2>/dev/null; then + echo "::notice title=ESLint sweep::No eslint.config.mjs at base ref; skipping regression comparison." + exit 0 + fi + npx eslint --config eslint.config.base.mjs api client packages -f json -o "$RUNNER_TEMP/eslint-base.json" || true + if [ ! -s "$RUNNER_TEMP/eslint-base.json" ]; then + echo "::notice title=ESLint sweep::Base config cannot run against this tree; skipping regression comparison." + exit 0 + fi + + jq -r '.[].filePath' "$RUNNER_TEMP/eslint-head.json" | sort > "$RUNNER_TEMP/head.files" + jq -r '.[].filePath' "$RUNNER_TEMP/eslint-base.json" | sort > "$RUNNER_TEMP/base.files" + LOST=$(comm -23 "$RUNNER_TEMP/base.files" "$RUNNER_TEMP/head.files") + if [ -n "$LOST" ]; then + LOST_COUNT=$(printf '%s\n' "$LOST" | wc -l) + echo "::error title=ESLint coverage regression::The config change stops linting $LOST_COUNT file(s) the base config covered (showing up to 20):" + printf '%s\n' "$LOST" | head -20 + exit 1 + fi + + fingerprints() { + jq -r '.[] | .filePath as $f | .messages[] | "\($f)\t\(.ruleId // "parse-error")\t\(.severity)"' "$1" | + sort | uniq -c | sed -E 's/^ *([0-9]+) /\1\t/' + } + fingerprints "$RUNNER_TEMP/eslint-head.json" > "$RUNNER_TEMP/head.fp" + fingerprints "$RUNNER_TEMP/eslint-base.json" > "$RUNNER_TEMP/base.fp" + + REGRESSIONS=$(awk -F'\t' ' + NR==FNR { base[$2 FS $3 FS $4] = $1; next } + { + if ($1 > base[$2 FS $3 FS $4] + 0) { + sev = ($4 == 2) ? "error" : "warn" + printf "%s %s (%s): %d -> %d\n", $2, $3, sev, base[$2 FS $3 FS $4] + 0, $1 + } + } + ' "$RUNNER_TEMP/base.fp" "$RUNNER_TEMP/head.fp") + + if [ -n "$REGRESSIONS" ]; then + echo "::error title=ESLint config regression::The config change introduces new diagnostics (file rule (severity): base -> head):" + echo "$REGRESSIONS" + exit 1 + fi + echo "No coverage loss and no new diagnostics versus the base config." + - name: Restore data-provider build cache if: always() && steps.paths.outputs.config == 'true' id: cache-data-provider @@ -193,7 +293,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-provider id: config_data_provider @@ -208,7 +308,7 @@ jobs: uses: actions/cache@v5 with: path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} + key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} - name: Build data-schemas id: config_data_schemas @@ -223,7 +323,7 @@ jobs: uses: actions/cache@v5 with: path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} + key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} - name: Build api id: config_api @@ -615,6 +715,8 @@ jobs: env: INSTALL_DEPENDENCIES_OUTCOME: ${{ steps.install_dependencies.outcome }} ESLINT_OUTCOME: ${{ steps.eslint.outcome }} + ESLINT_CONFIG_OUTCOME: ${{ steps.eslint_config.outcome }} + ESLINT_SWEEP_OUTCOME: ${{ steps.eslint_sweep.outcome }} PRETTIER_OUTCOME: ${{ steps.prettier.outcome }} IMPORT_SORT_OUTCOME: ${{ steps.import_sort.outcome }} CACHE_DATA_PROVIDER_OUTCOME: ${{ steps.cache-data-provider.outcome }} @@ -650,6 +752,8 @@ jobs: record_failure "Dependency installation" "$INSTALL_DEPENDENCIES_OUTCOME" record_failure "ESLint" "$ESLINT_OUTCOME" + record_failure "ESLint config validation" "$ESLINT_CONFIG_OUTCOME" + record_failure "ESLint config regression sweep" "$ESLINT_SWEEP_OUTCOME" record_failure "Prettier" "$PRETTIER_OUTCOME" record_failure "Import sorting" "$IMPORT_SORT_OUTCOME" record_failure "Config data-provider cache" "$CACHE_DATA_PROVIDER_OUTCOME"