|
15 | 15 | branches: [main] |
16 | 16 | paths: |
17 | 17 | - "showcase/**" |
| 18 | + - "examples/integrations/**" |
18 | 19 | - ".github/workflows/showcase_build.yml" |
19 | 20 | - ".github/workflows/showcase_build_check.yml" |
20 | 21 | workflow_dispatch: |
|
52 | 53 | - showcase-harness |
53 | 54 | - showcase-aimock |
54 | 55 | - webhooks |
| 56 | + # Per-starter image builds (model B, §b stage 1). These map to the |
| 57 | + # build-starters job below, NOT the showcase `build` job. "all" |
| 58 | + # builds the full showcase fleet AND all 12 starters; a specific |
| 59 | + # starter slug narrows to that one starter via STARTER_DISPATCH. |
| 60 | + - starter-langgraph-python |
| 61 | + - starter-mastra |
| 62 | + - starter-langgraph-js |
| 63 | + - starter-crewai-crews |
| 64 | + - starter-pydantic-ai |
| 65 | + - starter-adk |
| 66 | + - starter-agno |
| 67 | + - starter-llamaindex |
| 68 | + - starter-langgraph-fastapi |
| 69 | + - starter-strands-python |
| 70 | + - starter-ms-agent-framework-python |
| 71 | + - starter-ms-agent-framework-dotnet |
55 | 72 |
|
56 | 73 | # No top-level concurrency group. Every build run completes. This is the |
57 | 74 | # whole point of the decoupling: rapid pushes to main no longer cancel |
@@ -245,10 +262,23 @@ jobs: |
245 | 262 | # done — a common "did my dispatch deploy?" footgun. The `all` and |
246 | 263 | # empty (push-event) modes legitimately produce [] when there are |
247 | 264 | # no changes and must still succeed. |
248 | | - if [ "$MATRIX" = "[]" ] && [ -n "$DISPATCH" ] && [ "$DISPATCH" != "all" ]; then |
249 | | - echo "::error::workflow_dispatch service='$DISPATCH' did not match any entry in ALL_SERVICES — check the dispatch_name spelling" |
250 | | - exit 1 |
251 | | - fi |
| 265 | + # |
| 266 | + # The `starter-*` namespace is OWNED by the detect-starter-changes |
| 267 | + # job (which scopes its OWN fail-loud to `starter-*`). A |
| 268 | + # `service=starter-<slug>` dispatch legitimately yields [] here |
| 269 | + # (no showcase service is named `starter-*`), so it must SKIP — an |
| 270 | + # empty showcase matrix, NOT a fail-loud exit 1. Mirrors how the |
| 271 | + # starter job scopes its fail-loud to the `starter-*` namespace. |
| 272 | + case "$DISPATCH" in |
| 273 | + starter-*) ;; # starter namespace → empty showcase matrix + skip |
| 274 | + "" | "all") ;; # push / full-fleet → [] is legitimate |
| 275 | + *) |
| 276 | + if [ "$MATRIX" = "[]" ]; then |
| 277 | + echo "::error::workflow_dispatch service='$DISPATCH' did not match any entry in ALL_SERVICES — check the dispatch_name spelling" |
| 278 | + exit 1 |
| 279 | + fi |
| 280 | + ;; |
| 281 | + esac |
252 | 282 |
|
253 | 283 | echo "matrix=$MATRIX" >> $GITHUB_OUTPUT |
254 | 284 | if [ "$MATRIX" = "[]" ]; then |
@@ -436,6 +466,180 @@ jobs: |
436 | 466 | if-no-files-found: error |
437 | 467 | retention-days: 7 |
438 | 468 |
|
| 469 | + # ──────────────────────────────────────────────────────────────────── |
| 470 | + # Per-starter image publish (model B, §b stage 1 / Phase 1). |
| 471 | + # |
| 472 | + # Fully decoupled from the showcase `build`→`aggregate`→`redeploy` chain |
| 473 | + # above: starters are self-contained npm projects (no monorepo-source |
| 474 | + # build, no shared-module copy) and build from their own root Dockerfile |
| 475 | + # at examples/integrations/<slug>/Dockerfile (the single-image deployable: |
| 476 | + # Next.js frontend + agent, EXPOSE 3000, CMD entrypoint.sh — distinct from |
| 477 | + # the docker/Dockerfile.app + docker/Dockerfile.agent split stack used by |
| 478 | + # docker-compose.test.yml). Published to ghcr.io/copilotkit/starter-<slug> |
| 479 | + # (the `starter-` prefix is disjoint from `showcase-*`; S2's harness |
| 480 | + # discovery filters on namePrefix "starter-"). Railway deploy is the |
| 481 | + # gated S5 — NOT done here. |
| 482 | + # |
| 483 | + # The 12 starter slugs are the matrix source of truth (the smoke matrix in |
| 484 | + # test_smoke-starter.yml and STARTER_TO_COLUMN in |
| 485 | + # showcase/harness/src/probes/helpers/starter-mapping.ts). The dashboard |
| 486 | + # column remap lives in the harness (§a), so this layer uses raw starter |
| 487 | + # slugs. |
| 488 | + detect-starter-changes: |
| 489 | + runs-on: ubuntu-latest |
| 490 | + timeout-minutes: 5 |
| 491 | + permissions: |
| 492 | + contents: read |
| 493 | + outputs: |
| 494 | + matrix: ${{ steps.starter-matrix.outputs.matrix }} |
| 495 | + has_changes: ${{ steps.starter-matrix.outputs.has_changes }} |
| 496 | + steps: |
| 497 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 |
| 498 | + with: |
| 499 | + persist-credentials: false |
| 500 | + |
| 501 | + - name: Detect changed starter paths |
| 502 | + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 |
| 503 | + id: filter |
| 504 | + with: |
| 505 | + filters: | |
| 506 | + workflow_config: |
| 507 | + - '.github/workflows/showcase_build.yml' |
| 508 | + langgraph_python: |
| 509 | + - 'examples/integrations/langgraph-python/**' |
| 510 | + mastra: |
| 511 | + - 'examples/integrations/mastra/**' |
| 512 | + langgraph_js: |
| 513 | + - 'examples/integrations/langgraph-js/**' |
| 514 | + crewai_crews: |
| 515 | + - 'examples/integrations/crewai-crews/**' |
| 516 | + pydantic_ai: |
| 517 | + - 'examples/integrations/pydantic-ai/**' |
| 518 | + adk: |
| 519 | + - 'examples/integrations/adk/**' |
| 520 | + agno: |
| 521 | + - 'examples/integrations/agno/**' |
| 522 | + llamaindex: |
| 523 | + - 'examples/integrations/llamaindex/**' |
| 524 | + langgraph_fastapi: |
| 525 | + - 'examples/integrations/langgraph-fastapi/**' |
| 526 | + strands_python: |
| 527 | + - 'examples/integrations/strands-python/**' |
| 528 | + ms_agent_framework_python: |
| 529 | + - 'examples/integrations/ms-agent-framework-python/**' |
| 530 | + ms_agent_framework_dotnet: |
| 531 | + - 'examples/integrations/ms-agent-framework-dotnet/**' |
| 532 | +
|
| 533 | + - name: Build starter matrix |
| 534 | + id: starter-matrix |
| 535 | + env: |
| 536 | + DISPATCH_SERVICE: ${{ github.event.inputs.service }} |
| 537 | + FILTER_CHANGES: ${{ steps.filter.outputs.changes }} |
| 538 | + run: | |
| 539 | + set -euo pipefail |
| 540 | + # One slot per starter. `slug` is the examples/integrations/<slug> |
| 541 | + # directory name; `image` is the published GHCR repo (starter-<slug>); |
| 542 | + # `filter_key` matches the paths-filter key above. |
| 543 | + ALL_STARTERS='[ |
| 544 | + {"slug":"langgraph-python","image":"starter-langgraph-python","filter_key":"langgraph_python"}, |
| 545 | + {"slug":"mastra","image":"starter-mastra","filter_key":"mastra"}, |
| 546 | + {"slug":"langgraph-js","image":"starter-langgraph-js","filter_key":"langgraph_js"}, |
| 547 | + {"slug":"crewai-crews","image":"starter-crewai-crews","filter_key":"crewai_crews"}, |
| 548 | + {"slug":"pydantic-ai","image":"starter-pydantic-ai","filter_key":"pydantic_ai"}, |
| 549 | + {"slug":"adk","image":"starter-adk","filter_key":"adk"}, |
| 550 | + {"slug":"agno","image":"starter-agno","filter_key":"agno"}, |
| 551 | + {"slug":"llamaindex","image":"starter-llamaindex","filter_key":"llamaindex"}, |
| 552 | + {"slug":"langgraph-fastapi","image":"starter-langgraph-fastapi","filter_key":"langgraph_fastapi"}, |
| 553 | + {"slug":"strands-python","image":"starter-strands-python","filter_key":"strands_python"}, |
| 554 | + {"slug":"ms-agent-framework-python","image":"starter-ms-agent-framework-python","filter_key":"ms_agent_framework_python"}, |
| 555 | + {"slug":"ms-agent-framework-dotnet","image":"starter-ms-agent-framework-dotnet","filter_key":"ms_agent_framework_dotnet"} |
| 556 | + ]' |
| 557 | +
|
| 558 | + # Dispatch modes (mirror the showcase detect-changes job): |
| 559 | + # "all" → every starter (full-fleet rebuild). |
| 560 | + # "starter-<slug>" → that one starter (strip "starter-" prefix to match .image). |
| 561 | + # "<showcase service slug>" → no starters (this is a showcase-only dispatch). |
| 562 | + # "" (push event) → starters whose filter_key appears in CHANGES |
| 563 | + # (or workflow_config touched → rebuild all). |
| 564 | + DISPATCH="${DISPATCH_SERVICE:-}" |
| 565 | + CHANGES="${FILTER_CHANGES:-[]}" |
| 566 | +
|
| 567 | + # `.image` is exactly "starter-<slug>", which is also the |
| 568 | + # workflow_dispatch choice value, so a specific-starter dispatch |
| 569 | + # matches `$dispatch == .image` directly. |
| 570 | + MATRIX=$(echo "$ALL_STARTERS" | jq -c --arg dispatch "$DISPATCH" --argjson changes "$CHANGES" ' |
| 571 | + [.[] | |
| 572 | + (.filter_key as $fk | select( |
| 573 | + $dispatch == "all" or |
| 574 | + ($dispatch != "" and $dispatch != "all" and $dispatch == .image) or |
| 575 | + ($dispatch == "" and (($changes | index("workflow_config") != null) or ($changes | index($fk) != null))) |
| 576 | + ))] |
| 577 | + ') |
| 578 | +
|
| 579 | + # Fail loudly on a typo'd starter dispatch (mirrors showcase job). |
| 580 | + # Only applies to the starter-* dispatch namespace; a showcase |
| 581 | + # service slug or "all" legitimately yields [] here. |
| 582 | + case "$DISPATCH" in |
| 583 | + starter-*) |
| 584 | + if [ "$MATRIX" = "[]" ]; then |
| 585 | + echo "::error::workflow_dispatch service='$DISPATCH' did not match any starter — check the slug" |
| 586 | + exit 1 |
| 587 | + fi |
| 588 | + ;; |
| 589 | + esac |
| 590 | +
|
| 591 | + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" |
| 592 | + if [ "$MATRIX" = "[]" ]; then |
| 593 | + echo "has_changes=false" >> "$GITHUB_OUTPUT" |
| 594 | + else |
| 595 | + echo "has_changes=true" >> "$GITHUB_OUTPUT" |
| 596 | + fi |
| 597 | +
|
| 598 | + build-starters: |
| 599 | + needs: [detect-starter-changes] |
| 600 | + if: needs.detect-starter-changes.outputs.has_changes == 'true' |
| 601 | + runs-on: depot-ubuntu-24.04-4 |
| 602 | + timeout-minutes: 20 |
| 603 | + permissions: |
| 604 | + id-token: write |
| 605 | + contents: read |
| 606 | + packages: write |
| 607 | + strategy: |
| 608 | + fail-fast: false |
| 609 | + matrix: |
| 610 | + starter: ${{ fromJSON(needs.detect-starter-changes.outputs.matrix) }} |
| 611 | + |
| 612 | + steps: |
| 613 | + - name: Checkout |
| 614 | + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 |
| 615 | + with: |
| 616 | + persist-credentials: false |
| 617 | + |
| 618 | + - name: Setup Depot |
| 619 | + uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1 |
| 620 | + |
| 621 | + - name: Login to GHCR |
| 622 | + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 |
| 623 | + with: |
| 624 | + registry: ghcr.io |
| 625 | + username: ${{ github.actor }} |
| 626 | + password: ${{ secrets.GITHUB_TOKEN }} |
| 627 | + |
| 628 | + - name: Build and push starter image |
| 629 | + uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0 |
| 630 | + with: |
| 631 | + project: m2kw2wmmcp |
| 632 | + context: examples/integrations/${{ matrix.starter.slug }} |
| 633 | + file: examples/integrations/${{ matrix.starter.slug }}/Dockerfile |
| 634 | + # Pin amd64: Railway and GHCR serve x86 hosts. Depot defaults to |
| 635 | + # the runner's native arch, so an arm64-only image would crash on |
| 636 | + # pull with "does not have a linux/amd64 variant available". |
| 637 | + platforms: linux/amd64 |
| 638 | + push: true |
| 639 | + tags: | |
| 640 | + ghcr.io/copilotkit/${{ matrix.starter.image }}:latest |
| 641 | + ghcr.io/copilotkit/${{ matrix.starter.image }}:${{ github.sha }} |
| 642 | +
|
439 | 643 | aggregate-build-results: |
440 | 644 | name: Aggregate build results |
441 | 645 | needs: [detect-changes, build] |
|
0 commit comments