mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
347 lines
23 KiB
YAML
347 lines
23 KiB
YAML
name: "Showcase: Build & Deploy"
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "showcase/**"
|
|
workflow_dispatch:
|
|
inputs:
|
|
service:
|
|
description: "Service to deploy"
|
|
required: false
|
|
default: "all"
|
|
type: choice
|
|
options:
|
|
- all
|
|
- shell
|
|
- langgraph-python
|
|
- mastra
|
|
- crewai-crews
|
|
- pydantic-ai
|
|
- google-adk
|
|
- ag2
|
|
- agno
|
|
- llamaindex
|
|
- langgraph-fastapi
|
|
- langgraph-typescript
|
|
- langroid
|
|
- spring-ai
|
|
- strands
|
|
- ms-agent-python
|
|
- claude-sdk-typescript
|
|
- ms-agent-dotnet
|
|
- claude-sdk-python
|
|
- starter-ag2
|
|
- starter-agno
|
|
- starter-claude-sdk-python
|
|
- starter-claude-sdk-typescript
|
|
- starter-crewai-crews
|
|
- starter-google-adk
|
|
- starter-langgraph-fastapi
|
|
- starter-langgraph-python
|
|
- starter-langgraph-typescript
|
|
- starter-langroid
|
|
- starter-llamaindex
|
|
- starter-mastra
|
|
- starter-ms-agent-dotnet
|
|
- starter-ms-agent-python
|
|
- starter-pydantic-ai
|
|
- starter-spring-ai
|
|
- starter-strands
|
|
- aimock
|
|
- shell-dojolike
|
|
|
|
concurrency:
|
|
group: showcase-deploy-${{ github.ref }}-${{ github.event.inputs.service || 'auto' }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' }}
|
|
|
|
env:
|
|
RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f"
|
|
|
|
jobs:
|
|
detect-changes:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
matrix: ${{ steps.build-matrix.outputs.matrix }}
|
|
has_changes: ${{ steps.build-matrix.outputs.has_changes }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Detect changed paths
|
|
uses: dorny/paths-filter@v3
|
|
id: filter
|
|
with:
|
|
filters: |
|
|
shell:
|
|
- 'showcase/shell/**'
|
|
- 'showcase/shared/**'
|
|
- 'showcase/scripts/**'
|
|
- 'showcase/packages/*/manifest.yaml'
|
|
langgraph: 'showcase/packages/langgraph-python/**'
|
|
mastra: 'showcase/packages/mastra/**'
|
|
crewai_crews: 'showcase/packages/crewai-crews/**'
|
|
pydantic_ai: 'showcase/packages/pydantic-ai/**'
|
|
google_adk: 'showcase/packages/google-adk/**'
|
|
ag2: 'showcase/packages/ag2/**'
|
|
agno: 'showcase/packages/agno/**'
|
|
llamaindex: 'showcase/packages/llamaindex/**'
|
|
langgraph_fastapi: 'showcase/packages/langgraph-fastapi/**'
|
|
langgraph_typescript: 'showcase/packages/langgraph-typescript/**'
|
|
langroid: 'showcase/packages/langroid/**'
|
|
spring_ai: 'showcase/packages/spring-ai/**'
|
|
strands: 'showcase/packages/strands/**'
|
|
ms_agent_python: 'showcase/packages/ms-agent-python/**'
|
|
claude_sdk_typescript: 'showcase/packages/claude-sdk-typescript/**'
|
|
ms_agent_dotnet: 'showcase/packages/ms-agent-dotnet/**'
|
|
claude_sdk_python: 'showcase/packages/claude-sdk-python/**'
|
|
starter_ag2: 'showcase/starters/ag2/**'
|
|
starter_agno: 'showcase/starters/agno/**'
|
|
starter_claude_sdk_python: 'showcase/starters/claude-sdk-python/**'
|
|
starter_claude_sdk_typescript: 'showcase/starters/claude-sdk-typescript/**'
|
|
starter_crewai_crews: 'showcase/starters/crewai-crews/**'
|
|
starter_google_adk: 'showcase/starters/google-adk/**'
|
|
starter_langgraph_fastapi: 'showcase/starters/langgraph-fastapi/**'
|
|
starter_langgraph_python: 'showcase/starters/langgraph-python/**'
|
|
starter_langgraph_typescript: 'showcase/starters/langgraph-typescript/**'
|
|
starter_langroid: 'showcase/starters/langroid/**'
|
|
starter_llamaindex: 'showcase/starters/llamaindex/**'
|
|
starter_mastra: 'showcase/starters/mastra/**'
|
|
starter_ms_agent_dotnet: 'showcase/starters/ms-agent-dotnet/**'
|
|
starter_ms_agent_python: 'showcase/starters/ms-agent-python/**'
|
|
starter_pydantic_ai: 'showcase/starters/pydantic-ai/**'
|
|
starter_spring_ai: 'showcase/starters/spring-ai/**'
|
|
starter_strands: 'showcase/starters/strands/**'
|
|
aimock: 'showcase/aimock/**'
|
|
shell_dojolike: 'showcase/shell-dojolike/**'
|
|
|
|
- name: Build service matrix
|
|
id: build-matrix
|
|
run: |
|
|
# Full service config as JSON
|
|
# Fields: dispatch_name, filter_key, context, image, cache_scope, railway_id, timeout, lfs, build_args, build_args_sha, build_args_branch, dockerfile
|
|
ALL_SERVICES='[
|
|
{"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","cache_scope":"shell","railway_id":"40eea0da-6071-4ea8-bdb9-39afb19225ec","timeout":10,"lfs":true,"build_args_sha":"${{ github.sha }}","build_args_branch":"${{ github.ref_name }}","dockerfile":"showcase/shell/Dockerfile"},
|
|
{"dispatch_name":"langgraph-python","filter_key":"langgraph","context":"showcase/packages/langgraph-python","image":"showcase-langgraph-python","cache_scope":"langgraph","railway_id":"90d03214-4569-41b0-b4c1-6438a8a7b203","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"mastra","filter_key":"mastra","context":"showcase/packages/mastra","image":"showcase-mastra","cache_scope":"mastra","railway_id":"d7979eb7-2405-4aab-ad21-438f4a1b08af","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"crewai-crews","filter_key":"crewai_crews","context":"showcase/packages/crewai-crews","image":"showcase-crewai-crews","cache_scope":"crewai_crews","railway_id":"0e9c284d-8d87-4fcf-9f82-6b704d7e4bd4","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"pydantic-ai","filter_key":"pydantic_ai","context":"showcase/packages/pydantic-ai","image":"showcase-pydantic-ai","cache_scope":"pydantic_ai","railway_id":"0a106173-2282-4887-a994-0ca276a99d69","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"google-adk","filter_key":"google_adk","context":"showcase/packages/google-adk","image":"showcase-google-adk","cache_scope":"google_adk","railway_id":"87f60507-5a3d-4b8a-9e23-2b1de85d939c","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"ag2","filter_key":"ag2","context":"showcase/packages/ag2","image":"showcase-ag2","cache_scope":"ag2","railway_id":"4a37481b-f264-4eb7-a9cd-0a9ebb9ac05c","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"agno","filter_key":"agno","context":"showcase/packages/agno","image":"showcase-agno","cache_scope":"agno","railway_id":"32cab80b-e329-45bd-9c73-c4e1ddc94305","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"llamaindex","filter_key":"llamaindex","context":"showcase/packages/llamaindex","image":"showcase-llamaindex","cache_scope":"llamaindex","railway_id":"285386e8-492d-4cb8-b632-0a7d4607378f","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"langgraph-fastapi","filter_key":"langgraph_fastapi","context":"showcase/packages/langgraph-fastapi","image":"showcase-langgraph-fastapi","cache_scope":"langgraph_fastapi","railway_id":"06cccb5c-59f4-46b5-8adc-7113e77011a4","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"langgraph-typescript","filter_key":"langgraph_typescript","context":"showcase/packages/langgraph-typescript","image":"showcase-langgraph-typescript","cache_scope":"langgraph_typescript","railway_id":"66246d3b-a18e-46f0-be51-5f3ff7a36e5a","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"langroid","filter_key":"langroid","context":"showcase/packages/langroid","image":"showcase-langroid","cache_scope":"langroid","railway_id":"6dd9cb0a-66cc-46f1-972e-7cd74756157d","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"spring-ai","filter_key":"spring_ai","context":"showcase/packages/spring-ai","image":"showcase-spring-ai","cache_scope":"spring_ai","railway_id":"eed5d041-91be-4282-b414-beea00843401","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"strands","filter_key":"strands","context":"showcase/packages/strands","image":"showcase-strands","cache_scope":"strands","railway_id":"92e1cfad-ad53-403f-ab2b-5ab380832232","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"ms-agent-python","filter_key":"ms_agent_python","context":"showcase/packages/ms-agent-python","image":"showcase-ms-agent-python","cache_scope":"ms_agent_python","railway_id":"655db75a-af8d-427d-a4f9-441570ae5003","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"claude-sdk-typescript","filter_key":"claude_sdk_typescript","context":"showcase/packages/claude-sdk-typescript","image":"showcase-claude-sdk-typescript","cache_scope":"claude_sdk_typescript","railway_id":"18a98727-5700-44aa-b497-b60795dbbd6a","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"ms-agent-dotnet","filter_key":"ms_agent_dotnet","context":"showcase/packages/ms-agent-dotnet","image":"showcase-ms-agent-dotnet","cache_scope":"ms_agent_dotnet","railway_id":"beeb2dd6-87a4-4599-aa07-0578f7bd6519","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/packages/claude-sdk-python","image":"showcase-claude-sdk-python","cache_scope":"claude_sdk_python","railway_id":"b122ab65-9854-4cb2-a68e-b50ff13f7481","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-ag2","filter_key":"starter_ag2","context":"showcase/starters/ag2","image":"showcase-starter-ag2","cache_scope":"starter_ag2","railway_id":"0d7ce4ea-0ebe-4ba6-a408-503f7425c175","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-agno","filter_key":"starter_agno","context":"showcase/starters/agno","image":"showcase-starter-agno","cache_scope":"starter_agno","railway_id":"baf9f0db-1f62-462e-a603-2e1448652473","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-claude-sdk-python","filter_key":"starter_claude_sdk_python","context":"showcase/starters/claude-sdk-python","image":"showcase-starter-claude-sdk-python","cache_scope":"starter_claude_sdk_python","railway_id":"912b480d-ee38-4d8d-ab32-237bee146fed","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-claude-sdk-typescript","filter_key":"starter_claude_sdk_typescript","context":"showcase/starters/claude-sdk-typescript","image":"showcase-starter-claude-sdk-typescript","cache_scope":"starter_claude_sdk_typescript","railway_id":"fa61aabc-aba7-4611-8269-25f5454901ad","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-crewai-crews","filter_key":"starter_crewai_crews","context":"showcase/starters/crewai-crews","image":"showcase-starter-crewai-crews","cache_scope":"starter_crewai_crews","railway_id":"6c8f5514-2295-4d7c-8c95-2687b9e77558","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-google-adk","filter_key":"starter_google_adk","context":"showcase/starters/google-adk","image":"showcase-starter-google-adk","cache_scope":"starter_google_adk","railway_id":"0ae6bb33-b653-41d3-93b4-53482f4e2c31","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-langgraph-fastapi","filter_key":"starter_langgraph_fastapi","context":"showcase/starters/langgraph-fastapi","image":"showcase-starter-langgraph-fastapi","cache_scope":"starter_langgraph_fastapi","railway_id":"dc2070ba-2edb-4def-b7bf-c4c67a5b721b","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-langgraph-python","filter_key":"starter_langgraph_python","context":"showcase/starters/langgraph-python","image":"showcase-starter-langgraph-python","cache_scope":"starter_langgraph_python","railway_id":"58eaea00-00f9-4b66-bd1b-484b2679221b","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-langgraph-typescript","filter_key":"starter_langgraph_typescript","context":"showcase/starters/langgraph-typescript","image":"showcase-starter-langgraph-typescript","cache_scope":"starter_langgraph_typescript","railway_id":"56b73322-1553-402c-9fca-5c710e9d9eb6","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-langroid","filter_key":"starter_langroid","context":"showcase/starters/langroid","image":"showcase-starter-langroid","cache_scope":"starter_langroid","railway_id":"d2da2be5-db1f-48bd-93f9-7fe770d6a863","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-llamaindex","filter_key":"starter_llamaindex","context":"showcase/starters/llamaindex","image":"showcase-starter-llamaindex","cache_scope":"starter_llamaindex","railway_id":"147341c5-12c0-4de1-985a-20af45abea17","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-mastra","filter_key":"starter_mastra","context":"showcase/starters/mastra","image":"showcase-starter-mastra","cache_scope":"starter_mastra","railway_id":"315270a7-7b0e-4a1d-b1ed-319515baf265","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-ms-agent-dotnet","filter_key":"starter_ms_agent_dotnet","context":"showcase/starters/ms-agent-dotnet","image":"showcase-starter-ms-agent-dotnet","cache_scope":"starter_ms_agent_dotnet","railway_id":"986d55f6-4e01-4658-b7c9-cd33cb4df978","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-ms-agent-python","filter_key":"starter_ms_agent_python","context":"showcase/starters/ms-agent-python","image":"showcase-starter-ms-agent-python","cache_scope":"starter_ms_agent_python","railway_id":"bd8e9def-d92f-4c87-95c5-97761c1ea482","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-pydantic-ai","filter_key":"starter_pydantic_ai","context":"showcase/starters/pydantic-ai","image":"showcase-starter-pydantic-ai","cache_scope":"starter_pydantic_ai","railway_id":"f9e01966-ce8d-4e57-a336-315e41d92654","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-spring-ai","filter_key":"starter_spring_ai","context":"showcase/starters/spring-ai","image":"showcase-starter-spring-ai","cache_scope":"starter_spring_ai","railway_id":"3559ece3-7ba3-41ac-b24c-1f780133ec58","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"starter-strands","filter_key":"starter_strands","context":"showcase/starters/strands","image":"showcase-starter-strands","cache_scope":"starter_strands","railway_id":"06db2bb8-e15d-4c6a-97ad-e14777c92d9f","timeout":15,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"aimock","filter_key":"aimock","context":"showcase/aimock","image":"showcase-aimock","cache_scope":"aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","timeout":5,"lfs":false,"build_args":"","dockerfile":""},
|
|
{"dispatch_name":"shell-dojolike","filter_key":"shell_dojolike","context":"showcase/shell-dojolike","image":"showcase-shell-dojolike","cache_scope":"shell_dojolike","railway_id":"7ad1ece7-2228-49cd-8a78-bddf30322907","timeout":10,"lfs":false,"build_args":"","dockerfile":""}
|
|
]'
|
|
|
|
DISPATCH="${{ github.event.inputs.service }}"
|
|
CHANGES='${{ steps.filter.outputs.changes }}'
|
|
CHANGES="${CHANGES:-[]}"
|
|
|
|
# Filter services: on workflow_dispatch, include all (default) or specific service; on push, include only services whose paths changed
|
|
MATRIX=$(echo "$ALL_SERVICES" | jq -c --arg dispatch "$DISPATCH" --argjson changes "$CHANGES" '
|
|
[.[] | select(
|
|
$dispatch == "all" or
|
|
$dispatch == .dispatch_name or
|
|
(.filter_key as $fk | $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
|
|
|
|
check-lockfile:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: "10.13.1"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22.x
|
|
- run: pnpm install --frozen-lockfile --ignore-scripts
|
|
|
|
build:
|
|
needs: [detect-changes, check-lockfile]
|
|
if: needs.detect-changes.outputs.has_changes == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: ${{ fromJSON(matrix.service.timeout) }}
|
|
permissions:
|
|
packages: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
service: ${{ fromJSON(needs.detect-changes.outputs.matrix) }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
lfs: ${{ matrix.service.lfs }}
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Prepare build args
|
|
id: build-args
|
|
run: |
|
|
ARGS=""
|
|
if [ -n "${{ matrix.service.build_args_sha }}" ]; then
|
|
ARGS="COMMIT_SHA=${{ matrix.service.build_args_sha }}"
|
|
ARGS="${ARGS}"$'\n'"BRANCH=${{ matrix.service.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: |
|
|
CONTEXT="${{ matrix.service.context }}"
|
|
# Starters are self-contained — skip shared module copies
|
|
if [[ "$CONTEXT" == showcase/starters/* ]]; then
|
|
echo "Starter build — skipping shared module copies"
|
|
exit 0
|
|
fi
|
|
if [ -d "showcase/shared/python" ] && [ -d "$CONTEXT" ]; then
|
|
cp -r showcase/shared/python "$CONTEXT/shared_python"
|
|
fi
|
|
if [ -d "showcase/shared/typescript/tools" ] && [ -d "$CONTEXT" ]; then
|
|
mkdir -p "$CONTEXT/shared_typescript"
|
|
cp -r showcase/shared/typescript/tools "$CONTEXT/shared_typescript/tools"
|
|
fi
|
|
|
|
- name: Build and push
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: ${{ matrix.service.context }}
|
|
file: ${{ matrix.service.dockerfile != '' && matrix.service.dockerfile || format('{0}/Dockerfile', matrix.service.context) }}
|
|
push: true
|
|
tags: |
|
|
ghcr.io/copilotkit/${{ matrix.service.image }}:latest
|
|
ghcr.io/copilotkit/${{ matrix.service.image }}:${{ github.sha }}
|
|
cache-from: type=gha,scope=${{ matrix.service.cache_scope }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.service.cache_scope }}
|
|
build-args: ${{ steps.build-args.outputs.args }}
|
|
|
|
- name: Deploy to Railway
|
|
if: matrix.service.railway_id != ''
|
|
run: |
|
|
# All Railway services are configured to pull :latest from GHCR.
|
|
# serviceInstanceRedeploy triggers a fresh pull of the configured image.
|
|
curl -sf -X POST "https://backboard.railway.com/graphql/v2" \
|
|
-H "Authorization: Bearer ${{ secrets.RAILWAY_TOKEN }}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"query":"mutation { serviceInstanceRedeploy(serviceId: \"${{ matrix.service.railway_id }}\", environmentId: \"${{ env.RAILWAY_ENV_ID }}\") }"}' \
|
|
&& echo "Deploy triggered for ${{ matrix.service.dispatch_name }}"
|
|
|
|
- name: Verify deploy health
|
|
if: matrix.service.railway_id != ''
|
|
run: |
|
|
# Wait for Railway to pull and start the new image
|
|
sleep 30
|
|
|
|
# Get the service domain from Railway API or construct from naming pattern
|
|
IMAGE="${{ matrix.service.image }}"
|
|
# Try the standard domain pattern
|
|
HEALTH_URL="https://${IMAGE}-production.up.railway.app/api/health"
|
|
|
|
echo "Checking health: $HEALTH_URL"
|
|
for i in $(seq 1 6); do
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$HEALTH_URL" 2>/dev/null || echo "000")
|
|
echo "Attempt $i: HTTP $STATUS"
|
|
if [ "$STATUS" = "200" ]; then
|
|
echo "Service healthy"
|
|
exit 0
|
|
fi
|
|
sleep 10
|
|
done
|
|
echo "Service did not return 200 after 90s (may still be starting with sleep-on-idle)"
|
|
# Don't fail the job — sleep-on-idle services may take longer to wake
|
|
# But log it clearly for visibility
|
|
|
|
notify:
|
|
needs: [detect-changes, check-lockfile, build]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 2
|
|
steps:
|
|
- name: Build deploy summary
|
|
id: summary
|
|
if: needs.detect-changes.result == 'success' && needs.detect-changes.outputs.has_changes == 'true'
|
|
run: |
|
|
MATRIX='${{ needs.detect-changes.outputs.matrix }}'
|
|
SERVICES=$(echo "$MATRIX" | jq -r '[.[].dispatch_name] | join(", ")')
|
|
COUNT=$(echo "$MATRIX" | jq 'length')
|
|
echo "services=$SERVICES" >> $GITHUB_OUTPUT
|
|
echo "count=$COUNT" >> $GITHUB_OUTPUT
|
|
|
|
- name: Build Slack payload
|
|
if: always()
|
|
run: |
|
|
URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
DETECT="${{ needs.detect-changes.result }}"
|
|
LOCKFILE="${{ needs.check-lockfile.result }}"
|
|
BUILD="${{ needs.build.result }}"
|
|
COUNT="${{ steps.summary.outputs.count }}"
|
|
SERVICES=$(echo "${{ steps.summary.outputs.services }}" | cut -c1-200)
|
|
|
|
if [ "$DETECT" = "failure" ] || [ "$LOCKFILE" = "failure" ]; then
|
|
MSG=":x: *Showcase deploy*: FAILED (pre-build check)"
|
|
elif [ "$BUILD" = "success" ]; then
|
|
MSG=":white_check_mark: *Showcase deploy*: ${COUNT} service(s) deployed to Railway (${SERVICES})"
|
|
elif [ "$BUILD" = "skipped" ] && [ "$DETECT" = "success" ]; then
|
|
MSG=":white_check_mark: *Showcase deploy*: no changes detected, nothing to deploy"
|
|
else
|
|
MSG=":x: *Showcase deploy*: FAILED — ${COUNT} service(s) targeted (${SERVICES})"
|
|
fi
|
|
|
|
jq -n --arg text "$MSG | <$URL|View run>" '{text: $text}' > /tmp/slack-payload.json
|
|
|
|
- name: Post to Slack
|
|
if: always()
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: /tmp/slack-payload.json
|