name: "Showcase: Build Check (PR)" # Pre-merge Docker build check. Runs the same Depot Docker builds as the # post-merge showcase_build.yml pipeline but with push: false, so # Dockerfile-specific failures (missing deps that exist in the monorepo # but not in the isolated Docker context) are caught before merge. on: pull_request: paths: - "showcase/**" - ".github/workflows/showcase_build.yml" - ".github/workflows/showcase_build_check.yml" concurrency: group: showcase-build-check-${{ github.event.pull_request.number }} cancel-in-progress: true permissions: contents: read jobs: detect-changes: runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read outputs: matrix: ${{ steps.build-matrix.outputs.matrix }} has_changes: ${{ steps.build-matrix.outputs.has_changes }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: persist-credentials: false - name: Detect changed paths uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: filter with: filters: | workflow_config: - '.github/workflows/showcase_build.yml' - '.github/workflows/showcase_build_check.yml' shell: - 'showcase/shell/**' - 'showcase/shared/**' - 'showcase/scripts/**' - 'showcase/integrations/*/manifest.yaml' langgraph_python: - 'showcase/integrations/langgraph-python/**' mastra: - 'showcase/integrations/mastra/**' crewai_crews: - 'showcase/integrations/crewai-crews/**' pydantic_ai: - 'showcase/integrations/pydantic-ai/**' google_adk: - 'showcase/integrations/google-adk/**' ag2: - 'showcase/integrations/ag2/**' agno: - 'showcase/integrations/agno/**' llamaindex: - 'showcase/integrations/llamaindex/**' langgraph_fastapi: - 'showcase/integrations/langgraph-fastapi/**' langgraph_typescript: - 'showcase/integrations/langgraph-typescript/**' langroid: - 'showcase/integrations/langroid/**' spring_ai: - 'showcase/integrations/spring-ai/**' strands: - 'showcase/integrations/strands/**' strands_typescript: - 'showcase/integrations/strands-typescript/**' ms_agent_python: - 'showcase/integrations/ms-agent-python/**' claude_sdk_typescript: - 'showcase/integrations/claude-sdk-typescript/**' ms_agent_dotnet: - 'showcase/integrations/ms-agent-dotnet/**' ms_agent_harness_dotnet: - 'showcase/integrations/ms-agent-harness-dotnet/**' claude_sdk_python: - 'showcase/integrations/claude-sdk-python/**' built_in_agent: - 'showcase/integrations/built-in-agent/**' shell_dojo: - 'showcase/shell-dojo/**' - 'showcase/shared/**' - 'showcase/scripts/**' - 'showcase/integrations/*/manifest.yaml' shell_dashboard: - 'showcase/shell-dashboard/**' - 'showcase/shared/**' - 'showcase/scripts/**' - 'showcase/integrations/*/manifest.yaml' shell_docs: - 'showcase/shell-docs/**' - 'showcase/shared/**' - 'showcase/scripts/**' - 'showcase/integrations/*/manifest.yaml' - 'showcase/integrations/*/docs-links.json' - 'showcase/integrations/*/docs/setup/**' - 'showcase/integrations/*/src/**' showcase_harness: - 'showcase/harness/**' - 'showcase/shared/**' - 'showcase/scripts/**' - 'showcase/integrations/*/manifest.yaml' showcase_aimock: - 'showcase/aimock/**' - name: Build service matrix id: build-matrix env: PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} PR_HEAD_REF: ${{ github.head_ref }} FILTER_CHANGES: ${{ steps.filter.outputs.changes }} run: | # Mirror of the ALL_SERVICES definition from showcase_build.yml. # Keep in sync — the matrix here must match the production build # workflow so that every service buildable post-merge is also # checked pre-merge. # # Fields carried over: dispatch_name, filter_key, context, image, # timeout, lfs, build_args_*, dockerfile. # Fields omitted (not needed for build-only): railway_id, health_path. ALL_SERVICES='[ {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell/Dockerfile"}, {"dispatch_name":"langgraph-python","filter_key":"langgraph_python","context":"showcase/integrations/langgraph-python","image":"showcase-langgraph-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"mastra","filter_key":"mastra","context":"showcase/integrations/mastra","image":"showcase-mastra","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"crewai-crews","filter_key":"crewai_crews","context":"showcase/integrations/crewai-crews","image":"showcase-crewai-crews","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"pydantic-ai","filter_key":"pydantic_ai","context":"showcase/integrations/pydantic-ai","image":"showcase-pydantic-ai","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"google-adk","filter_key":"google_adk","context":"showcase/integrations/google-adk","image":"showcase-google-adk","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"ag2","filter_key":"ag2","context":"showcase/integrations/ag2","image":"showcase-ag2","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"agno","filter_key":"agno","context":"showcase/integrations/agno","image":"showcase-agno","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"llamaindex","filter_key":"llamaindex","context":"showcase/integrations/llamaindex","image":"showcase-llamaindex","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"langgraph-fastapi","filter_key":"langgraph_fastapi","context":"showcase/integrations/langgraph-fastapi","image":"showcase-langgraph-fastapi","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"langgraph-typescript","filter_key":"langgraph_typescript","context":"showcase/integrations/langgraph-typescript","image":"showcase-langgraph-typescript","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"langroid","filter_key":"langroid","context":"showcase/integrations/langroid","image":"showcase-langroid","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"spring-ai","filter_key":"spring_ai","context":"showcase/integrations/spring-ai","image":"showcase-spring-ai","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"strands","filter_key":"strands","context":"showcase/integrations/strands","image":"showcase-strands","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"strands-typescript","filter_key":"strands_typescript","context":"showcase/integrations/strands-typescript","image":"showcase-strands-typescript","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"ms-agent-python","filter_key":"ms_agent_python","context":"showcase/integrations/ms-agent-python","image":"showcase-ms-agent-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"claude-sdk-typescript","filter_key":"claude_sdk_typescript","context":"showcase/integrations/claude-sdk-typescript","image":"showcase-claude-sdk-typescript","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"ms-agent-dotnet","filter_key":"ms_agent_dotnet","context":"showcase/integrations/ms-agent-dotnet","image":"showcase-ms-agent-dotnet","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"ms-agent-harness-dotnet","filter_key":"ms_agent_harness_dotnet","context":"showcase/integrations/ms-agent-harness-dotnet","image":"showcase-ms-agent-harness-dotnet","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/integrations/claude-sdk-python","image":"showcase-claude-sdk-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"built-in-agent","filter_key":"built_in_agent","context":"showcase/integrations/built-in-agent","image":"showcase-built-in-agent","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"shell-dojo","filter_key":"shell_dojo","context":".","image":"showcase-shell-dojo","timeout":10,"lfs":false,"build_args":"","dockerfile":"showcase/shell-dojo/Dockerfile"}, {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-dashboard/Dockerfile"}, {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-docs/Dockerfile"}, {"dispatch_name":"showcase-harness","filter_key":"showcase_harness","context":".","image":"showcase-harness","timeout":20,"lfs":false,"build_args":"","dockerfile":"showcase/harness/Dockerfile"}, {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile"} ]' CHANGES="${FILTER_CHANGES:-[]}" # PR event only — filter to changed services. Use jq --arg for # untrusted PR metadata so branch names are encoded as JSON data, # not interpolated into shell source. MATRIX=$(echo "$ALL_SERVICES" | jq -c --argjson changes "$CHANGES" --arg sha "$PR_HEAD_SHA" --arg ref "$PR_HEAD_REF" ' [.[] | if .build_args_sha == "__GH_SHA__" then .build_args_sha = $sha else . end | if .build_args_branch == "__GH_REF_NAME__" then .build_args_branch = $ref else . end | (.filter_key as $fk | select( ($changes | index("workflow_config") != null) or ($changes | index($fk) != null) ))] ') echo "matrix=$MATRIX" >> $GITHUB_OUTPUT if [ "$MATRIX" = "[]" ]; then echo "has_changes=false" >> $GITHUB_OUTPUT else echo "has_changes=true" >> $GITHUB_OUTPUT fi build-check: needs: [detect-changes] if: needs.detect-changes.outputs.has_changes == 'true' runs-on: depot-ubuntu-24.04-4 timeout-minutes: ${{ fromJSON(matrix.service.timeout) }} permissions: id-token: write contents: read strategy: fail-fast: false matrix: service: ${{ fromJSON(needs.detect-changes.outputs.matrix) }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: lfs: ${{ matrix.service.lfs }} persist-credentials: false - name: Setup Depot uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1 - name: Prepare build args id: build-args env: BUILD_ARGS_SHA: ${{ matrix.service.build_args_sha }} BUILD_ARGS_BRANCH: ${{ matrix.service.build_args_branch }} run: | ARGS="" if [ -n "$BUILD_ARGS_SHA" ]; then ARGS="COMMIT_SHA=${BUILD_ARGS_SHA}" ARGS="${ARGS}"$'\n'"BRANCH=${BUILD_ARGS_BRANCH}" fi # Use delimiter to safely pass multiline value echo "args<> $GITHUB_OUTPUT echo "$ARGS" >> $GITHUB_OUTPUT echo "BUILDARGS_EOF" >> $GITHUB_OUTPUT - name: Copy shared modules into build context run: | set -euo pipefail CONTEXT="${{ matrix.service.context }}" if [ -d "showcase/shared/python" ] && [ -d "$CONTEXT" ]; then rm -rf "$CONTEXT/shared_python" cp -r showcase/shared/python "$CONTEXT/shared_python" fi if [ -d "showcase/shared/typescript/tools" ] && [ -d "$CONTEXT" ]; then rm -rf "$CONTEXT/shared_typescript" mkdir -p "$CONTEXT/shared_typescript" cp -r showcase/shared/typescript/tools "$CONTEXT/shared_typescript/tools" fi # Dereference tools/, shared-tools/, and _shared/ symlinks for the # Docker context. Integration directories use symlinks pointing # outside the build context (tools -> ../../shared/python/tools, # _shared -> ../_shared for the CVDIAG bootstrap modules). Docker # build contexts cannot follow a symlink whose target escapes the # context root — buildkit fails the checksum with "too many # symlinks: /_shared" — so each symlink is replaced with a real # copy of its target. Mirrors stage_shared() in # showcase/scripts/cli/_common.sh (the local bin/showcase path). for link_name in tools shared-tools _shared; do link_path="$CONTEXT/$link_name" if [ -L "$link_path" ]; then target="$(readlink -f "$link_path")" if [ -d "$target" ]; then rm "$link_path" cp -r "$target" "$link_path" fi fi done - name: Build Docker image (no push) uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0 with: project: m2kw2wmmcp context: ${{ matrix.service.context }} file: ${{ matrix.service.dockerfile != '' && matrix.service.dockerfile || format('{0}/Dockerfile', matrix.service.context) }} platforms: linux/amd64 push: false build-args: ${{ steps.build-args.outputs.args }}