Files
2026-07-13 12:58:18 +08:00

263 lines
15 KiB
YAML

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<<BUILDARGS_EOF" >> $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 }}