refactor(composer): lift agent path containment into renderer path utils #19708
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: CI | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - main | |
| - v2 | |
| pull_request: | |
| branches: | |
| - main | |
| - develop | |
| - v2 | |
| types: [ready_for_review, synchronize, opened] | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false | |
| outputs: | |
| main: ${{ steps.filter.outputs.main }} | |
| renderer: ${{ steps.filter.outputs.renderer }} | |
| shared: ${{ steps.filter.outputs.shared }} | |
| ai-core: ${{ steps.filter.outputs.ai-core }} | |
| ui: ${{ steps.filter.outputs.ui }} | |
| provider-registry: ${{ steps.filter.outputs.provider-registry }} | |
| scripts: ${{ steps.filter.outputs.scripts }} | |
| extension-table-plus: ${{ steps.filter.outputs.extension-table-plus }} | |
| test-infra: ${{ steps.filter.outputs.test-infra }} | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| - name: Detect code changes | |
| id: filter | |
| uses: dorny/paths-filter@v4 | |
| with: | |
| filters: | | |
| main: | |
| - 'src/main/**' | |
| - 'src/preload/**' | |
| - 'tests/helpers/**' | |
| - 'tests/main.setup.ts' | |
| - 'tests/__mocks__/main/**' | |
| - 'tests/__mocks__/MainLoggerService.ts' | |
| - 'migrations/**' | |
| renderer: | |
| - 'src/renderer/**' | |
| - 'src/preload/**' | |
| - 'tests/renderer.setup.ts' | |
| - 'tests/__mocks__/renderer/**' | |
| - 'tests/__mocks__/RendererLoggerService.ts' | |
| - 'tests/__mocks__/requestAnimationFrame.ts' | |
| shared: | |
| - 'src/shared/**' | |
| ai-core: | |
| - 'packages/aiCore/**' | |
| - 'packages/ai-sdk-provider/**' | |
| ui: | |
| - 'packages/ui/**' | |
| provider-registry: | |
| - 'packages/provider-registry/**' | |
| scripts: | |
| - 'scripts/**' | |
| extension-table-plus: | |
| - 'packages/extension-table-plus/**' | |
| test-infra: | |
| - '.github/workflows/ci.yml' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - 'tsconfig*.json' | |
| - 'electron.vite.config.ts' | |
| - 'vitest.config.ts' | |
| - 'patches/**' | |
| basic-checks: | |
| runs-on: ubuntu-latest | |
| env: | |
| CI: true | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| with: | |
| # The PR merge commit and its parents are enough for changeset comparison. | |
| fetch-depth: ${{ github.event_name == 'pull_request' && '2' || '1' }} | |
| - name: Prepare base reference | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: git update-ref "refs/remotes/origin/$BASE_REF" "$(git rev-parse HEAD^1)" | |
| # provider-registry data/*.json are generated from src/creators + src/providers. | |
| # The daily catalog workflow regenerates data without source changes, so exempt only | |
| # its exact same-repository branch from the hand-edit guard. | |
| - name: Detect provider-registry changes | |
| if: github.event_name == 'pull_request' && !(github.head_ref == 'auto-update-catalog-daily' && github.event.pull_request.head.repo.full_name == github.repository) | |
| id: catalog-filter | |
| uses: dorny/paths-filter@v4 | |
| with: | |
| filters: | | |
| data: | |
| - 'packages/provider-registry/data/**' | |
| source: | |
| - 'packages/provider-registry/src/**' | |
| - 'packages/provider-registry/scripts/**' | |
| - name: Reject hand-edited catalog | |
| if: steps.catalog-filter.outputs.data == 'true' && steps.catalog-filter.outputs.source == 'false' | |
| run: | | |
| echo "::error::packages/provider-registry/data/*.json changed but no src/ or scripts/ change — that's a hand-edit. The data files are generated: edit src/creators|src/providers and run \`pnpm --filter @cherrystudio/provider-registry generate\`, then commit. Never hand-edit data/*.json." | |
| exit 1 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".node-version" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm dependencies | |
| if: github.event_name == 'pull_request' | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Cache pnpm dependencies | |
| if: github.event_name != 'pull_request' | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: Check changesets | |
| if: github.event_name == 'pull_request' && github.head_ref != 'changeset-release/main' | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: pnpm changeset status --since="origin/$BASE_REF" | |
| - name: Read-only Checks | |
| run: | | |
| # Share one checkout/install while keeping independent checks parallel. | |
| # Do not kill sibling checks on failure so every failure is reported. | |
| pnpm exec concurrently \ | |
| --names "Lint,Type and Hardcoded Strings,Format and Repository" \ | |
| --prefix-colors "blue,yellow,cyan" \ | |
| "pnpm test:lint" \ | |
| "pnpm typecheck && pnpm i18n:hardcoded:strict" \ | |
| "pnpm format:check && pnpm build:builtin-knowledge:check && pnpm i18n:check && pnpm skills:check" | |
| - name: DB Migrations Check | |
| run: | | |
| # 1) Snapshot chain integrity — exits 1 on duplicate id / forked prevId (e.g. #15438). | |
| pnpm db:migrations:check | |
| # 2) Drift — schema source must match committed migrations. Catches "renamed a migration | |
| # on merge instead of re-running generate" even when it does not collide an id. | |
| # Note: `generate` exits 0 even on a chain collision, so it can never replace step 1. | |
| pnpm db:migrations:generate | |
| if [ -n "$(git status --porcelain migrations/)" ]; then | |
| echo "::error::Schema and migrations are out of sync. Run \`pnpm db:migrations:generate\` and commit the result." | |
| git --no-pager status --porcelain migrations/ | |
| git --no-pager diff -- migrations/ | |
| exit 1 | |
| fi | |
| - name: System Provider IDs Check | |
| run: | | |
| # SystemProviderId is generated from packages/provider-registry — fail on drift. | |
| pnpm gen:system-provider-ids | |
| if [ -n "$(git status --porcelain src/shared/utils/systemProviderId.ts)" ]; then | |
| echo "::error::systemProviderId.ts is out of sync with provider-registry. Run \`pnpm gen:system-provider-ids\` and commit the result." | |
| git --no-pager diff -- src/shared/utils/systemProviderId.ts | |
| exit 1 | |
| fi | |
| main-test-shard: | |
| name: main-test (${{ matrix.os }} ${{ matrix.shard }}/${{ matrix.shards }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: changes | |
| if: >- | |
| needs.changes.outputs.main == 'true' || | |
| needs.changes.outputs.shared == 'true' || | |
| needs.changes.outputs.ai-core == 'true' || | |
| needs.changes.outputs.provider-registry == 'true' || | |
| needs.changes.outputs.test-infra == 'true' || | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest] | |
| shard: [1, 2, 3] | |
| include: | |
| - os: ubuntu-latest | |
| shards: 3 | |
| # macOS/Windows run only the platform-gated files (see the test step) — | |
| # a small enough set that sharding it would just add runner overhead. | |
| - os: macos-latest | |
| shard: 1 | |
| shards: 1 | |
| - os: windows-2022 | |
| shard: 1 | |
| shards: 1 | |
| env: | |
| CI: true | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".node-version" | |
| - name: macos-latest dependencies fix | |
| if: matrix.os == 'macos-latest' | |
| run: brew install python-setuptools | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm dependencies | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: Main and Package Tests | |
| shell: bash | |
| env: | |
| AI_CORE_CHANGED: ${{ needs.changes.outputs.ai-core }} | |
| UI_CHANGED: ${{ needs.changes.outputs.ui }} | |
| SHARED_CHANGED: ${{ needs.changes.outputs.shared }} | |
| PROVIDER_REGISTRY_CHANGED: ${{ needs.changes.outputs.provider-registry }} | |
| SCRIPTS_CHANGED: ${{ needs.changes.outputs.scripts }} | |
| TEST_INFRA_CHANGED: ${{ needs.changes.outputs.test-infra }} | |
| run: | | |
| # Non-Linux runners exist to execute the `process.platform` branches that Linux | |
| # skips (symlinks, path casing, Windows command resolution). The rest of the | |
| # suite hardcodes POSIX paths in its fixtures and carries no per-OS signal, so | |
| # only the gated files run there. | |
| if [[ "$RUNNER_OS" != "Linux" ]]; then | |
| gated=$(grep -rl 'process\.platform' src/main src/shared --include='*.test.ts') | |
| if [[ -z "$gated" ]]; then | |
| echo "::error::No platform-gated test files found — refusing to run the whole suite." | |
| exit 1 | |
| fi | |
| pnpm rebuild:node | |
| pnpm exec vitest run --project main --project shared $gated | |
| exit 0 | |
| fi | |
| projects=(--project main) | |
| if [[ "$AI_CORE_CHANGED" == "true" || "$TEST_INFRA_CHANGED" == "true" || "${{ github.event_name }}" != "pull_request" ]]; then | |
| projects+=(--project aiCore) | |
| fi | |
| if [[ "$UI_CHANGED" == "true" || "$TEST_INFRA_CHANGED" == "true" || "${{ github.event_name }}" != "pull_request" ]]; then | |
| projects+=(--project ui) | |
| fi | |
| if [[ "$SHARED_CHANGED" == "true" || "$PROVIDER_REGISTRY_CHANGED" == "true" || "$TEST_INFRA_CHANGED" == "true" || "${{ github.event_name }}" != "pull_request" ]]; then | |
| projects+=(--project shared) | |
| fi | |
| if [[ "$PROVIDER_REGISTRY_CHANGED" == "true" || "$SHARED_CHANGED" == "true" || "$TEST_INFRA_CHANGED" == "true" || "${{ github.event_name }}" != "pull_request" ]]; then | |
| projects+=(--project provider-registry) | |
| fi | |
| if [[ "$SCRIPTS_CHANGED" == "true" || "$PROVIDER_REGISTRY_CHANGED" == "true" || "$TEST_INFRA_CHANGED" == "true" || "${{ github.event_name }}" != "pull_request" ]]; then | |
| projects+=(--project scripts) | |
| fi | |
| pnpm rebuild:node | |
| pnpm exec vitest run "${projects[@]}" --shard=${{ matrix.shard }}/${{ matrix.shards }} | |
| general-test: | |
| runs-on: ubuntu-latest | |
| needs: [changes, main-test-shard, package-test] | |
| if: >- | |
| always() && | |
| (needs.changes.outputs.main == 'true' || | |
| needs.changes.outputs.shared == 'true' || | |
| needs.changes.outputs.ai-core == 'true' || | |
| needs.changes.outputs.ui == 'true' || | |
| needs.changes.outputs.provider-registry == 'true' || | |
| needs.changes.outputs.scripts == 'true' || | |
| needs.changes.outputs.test-infra == 'true' || | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch') | |
| steps: | |
| - name: Verify main and package tests | |
| env: | |
| MAIN_RESULT: ${{ needs.main-test-shard.result }} | |
| PACKAGE_RESULT: ${{ needs.package-test.result }} | |
| run: | | |
| test "$MAIN_RESULT" = "success" || test "$MAIN_RESULT" = "skipped" | |
| test "$PACKAGE_RESULT" = "success" || test "$PACKAGE_RESULT" = "skipped" | |
| package-test: | |
| runs-on: ubuntu-latest | |
| needs: changes | |
| if: >- | |
| (needs.changes.outputs.ui == 'true' || | |
| needs.changes.outputs.scripts == 'true') && | |
| needs.changes.outputs.main != 'true' && | |
| needs.changes.outputs.shared != 'true' && | |
| needs.changes.outputs.ai-core != 'true' && | |
| needs.changes.outputs.provider-registry != 'true' && | |
| needs.changes.outputs.test-infra != 'true' && | |
| github.event_name == 'pull_request' | |
| env: | |
| CI: true | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".node-version" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm dependencies | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: AI Core Test | |
| if: needs.changes.outputs.ai-core == 'true' || needs.changes.outputs.test-infra == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| run: pnpm test:aicore | |
| - name: UI Package Test | |
| if: needs.changes.outputs.ui == 'true' || needs.changes.outputs.test-infra == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| run: pnpm test:pkg:ui | |
| - name: Shared Package Test | |
| if: needs.changes.outputs.shared == 'true' || needs.changes.outputs.provider-registry == 'true' || needs.changes.outputs.test-infra == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| run: pnpm test:shared | |
| - name: Provider Registry Package Test | |
| if: needs.changes.outputs.provider-registry == 'true' || needs.changes.outputs.shared == 'true' || needs.changes.outputs.test-infra == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| run: pnpm test:provider-registry | |
| - name: Scripts Test | |
| if: needs.changes.outputs.scripts == 'true' || needs.changes.outputs.provider-registry == 'true' || needs.changes.outputs.test-infra == 'true' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| run: pnpm test:scripts | |
| renderer-test-shard: | |
| name: renderer-test (${{ matrix.shard }}/5) | |
| runs-on: ubuntu-latest | |
| needs: changes | |
| if: >- | |
| needs.changes.outputs.renderer == 'true' || | |
| needs.changes.outputs.shared == 'true' || | |
| needs.changes.outputs.ai-core == 'true' || | |
| needs.changes.outputs.ui == 'true' || | |
| needs.changes.outputs.provider-registry == 'true' || | |
| needs.changes.outputs.extension-table-plus == 'true' || | |
| needs.changes.outputs.test-infra == 'true' || | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5] | |
| env: | |
| CI: true | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".node-version" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm dependencies | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: Renderer Test | |
| run: pnpm test:renderer --shard=${{ matrix.shard }}/5 | |
| render-test: | |
| runs-on: ubuntu-latest | |
| needs: [changes, renderer-test-shard] | |
| if: >- | |
| always() && | |
| (needs.changes.outputs.renderer == 'true' || | |
| needs.changes.outputs.shared == 'true' || | |
| needs.changes.outputs.ai-core == 'true' || | |
| needs.changes.outputs.ui == 'true' || | |
| needs.changes.outputs.provider-registry == 'true' || | |
| needs.changes.outputs.extension-table-plus == 'true' || | |
| needs.changes.outputs.test-infra == 'true' || | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch') | |
| steps: | |
| - name: Verify renderer test shards | |
| env: | |
| SHARD_RESULT: ${{ needs.renderer-test-shard.result }} | |
| run: test "$SHARD_RESULT" = "success" | |
| notify: | |
| runs-on: ubuntu-latest | |
| needs: [basic-checks, general-test, package-test, render-test] | |
| if: always() && (needs.basic-checks.result == 'failure' || needs.general-test.result == 'failure' || needs.package-test.result == 'failure' || needs.render-test.result == 'failure') && github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Check out Git repository | |
| uses: actions/checkout@v6 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".node-version" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: Send failure notification to Feishu | |
| env: | |
| FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} | |
| FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| COMMIT_SHA: ${{ github.sha }} | |
| COMMIT_MSG: ${{ github.event.head_commit.message }} | |
| run: | | |
| DESCRIPTION=$(printf "**分支:** main\n\n**提交:** %.7s\n\n**信息:** %s\n\n**工作流:** [查看详情](%s)" "$COMMIT_SHA" "$COMMIT_MSG" "$RUN_URL") | |
| pnpm tsx scripts/feishu-notify.ts send \ | |
| -t "Main 分支 CI 失败" \ | |
| -d "$DESCRIPTION" \ | |
| -c "red" |