Skip to content

Sync Sample Catalog #59

Sync Sample Catalog

Sync Sample Catalog #59

name: Sync Sample Catalog
on:
workflow_dispatch:
inputs:
commit_sha:
description: "foundry-samples commit to pin to. Blank = latest main."
required: false
type: string
default: ""
# Default (both off): incremental — only fill blank displayName/description
# with AI; existing values are kept untouched.
refine_with_ai:
description: "Full refresh, keep existing values. AI reviews all entries but only rewrites ones that no longer fit."
required: false
type: boolean
default: false
ignore_existing_catalog:
description: "Full refresh, overwrite everything. AI regenerates ALL displayName/description from scratch. Use only when necessary."
required: false
type: boolean
default: false
# Least-privilege for the default GITHUB_TOKEN: only what `actions/checkout`
# needs. All writes (push branch + create PR) are performed via the GitHub
# App token generated below, not GITHUB_TOKEN.
permissions:
contents: read
# A single in-flight sync at a time — a re-trigger cancels any earlier run
# rather than racing it for the same PR branch.
concurrency:
group: sync-sample-catalog-${{ github.ref_name }}
cancel-in-progress: true
jobs:
sync:
runs-on: ubuntu-latest
env:
PR_LABEL_CANDIDATES: |
automated-pr
area:samples
area:hosted-agent
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.SYNC_APP_ID }}
private-key: ${{ secrets.SYNC_APP_PRIVATE_KEY }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Resolve commit SHA
id: resolve-sha
shell: bash
env:
INPUT_SHA: ${{ inputs.commit_sha }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [[ -n "$INPUT_SHA" ]]; then
sha="$INPUT_SHA"
source="user-supplied input"
else
# No SHA provided: pin to the current tip of foundry-samples@main
# so the dispatcher doesn't have to look it up by hand.
sha=$(gh api repos/microsoft-foundry/foundry-samples/commits/main --jq .sha)
source="latest microsoft-foundry/foundry-samples@main"
fi
if [[ ! "$sha" =~ ^[0-9a-f]{7,40}$ ]]; then
echo "Resolved SHA is not a valid git object: $sha" >&2
exit 1
fi
echo "Using SHA $sha (source: $source)"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
echo "- Pinned SHA: \`$sha\` ($source)" >> "$GITHUB_STEP_SUMMARY"
- name: Generate sample catalog
env:
REPO_ROOT: ${{ github.workspace }}
GITHUB_TOKEN: ${{ github.token }}
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }}
AI_REFINE: ${{ inputs.refine_with_ai }}
IGNORE_EXISTING: ${{ inputs.ignore_existing_catalog }}
run: node .github/scripts/generate_sample_catalog.mjs "${{ steps.resolve-sha.outputs.sha }}"
- name: Detect catalog changes
id: diff
shell: bash
run: |
set -euo pipefail
catalog_file="samples/hosted-agent/sample-catalog.json"
if [[ ! -f "$catalog_file" ]]; then
echo "Catalog file not generated."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Stage the file so git diff detects both new and modified files
git add "$catalog_file"
if git diff --cached --quiet -- "$catalog_file"; then
echo "No changes to sample catalog."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Catalog has changes:"
git diff --cached --stat -- "$catalog_file"
{
echo "## Sample Catalog Sync"
echo
echo "The sample catalog was regenerated from \`microsoft-foundry/foundry-samples\`."
echo
echo "### Changed files"
echo "- \`$catalog_file\`"
} >> "$GITHUB_STEP_SUMMARY"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Resolve pull request labels
if: steps.diff.outputs.has_changes == 'true'
id: labels
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
existing_labels=$(gh api --paginate repos/${{ github.repository }}/labels --jq '.[].name')
matched_labels=""
while IFS= read -r candidate; do
if [[ -z "$candidate" ]]; then
continue
fi
if grep -Fqx "$candidate" <<< "$existing_labels"; then
matched_labels+="$candidate"
matched_labels+=$'\n'
fi
done <<< "$PR_LABEL_CANDIDATES"
{
echo "labels<<EOF"
printf '%s' "$matched_labels"
echo
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Generate PR branch name
if: steps.diff.outputs.has_changes == 'true'
id: branch
shell: bash
run: |
date_suffix=$(date -u +%Y%m%d)
echo "name=ci/sync-sample-catalog-${{ github.ref_name }}-${date_suffix}" >> "$GITHUB_OUTPUT"
- name: Create draft pull request
if: steps.diff.outputs.has_changes == 'true'
id: cpr
uses: peter-evans/create-pull-request@v8
with:
token: ${{ steps.app-token.outputs.token }}
branch: ${{ steps.branch.outputs.name }}
base: ${{ github.ref_name }}
delete-branch: true
draft: always-true
commit-message: |
ci: sync sample catalog from foundry-samples (${{ github.ref_name }})
title: "ci: sync sample catalog from foundry-samples (${{ github.ref_name }})"
body: |
## Summary
This automated draft PR updates `samples/hosted-agent/sample-catalog.json` by scanning
the `microsoft-foundry/foundry-samples` repository for hosted-agent sample templates.
## What changed
- New templates added or removed based on the upstream repo structure.
- `commitSha` pinned to the upstream commit supplied via the `commit_sha` workflow input.
- `requiresModel` and `protocol` values re-derived from each sample's `agent.yaml`.
- `displayName` derived from each template's folder name; `description` LLM-generated when empty.
- PM-edited `displayName` and `description` values are preserved from the previous catalog.
## Reviewer Checks
- Confirm new templates have correct `language`, `framework`, and `protocol` values.
- Review the auto-generated `description` for any new templates.
- Confirm no PM-edited display fields were accidentally lost.
labels: ${{ steps.labels.outputs.labels }}
add-paths: |
samples/hosted-agent/sample-catalog.json
- name: Report PR result
if: steps.diff.outputs.has_changes == 'true'
shell: bash
env:
PR_OPERATION: ${{ steps.cpr.outputs.pull-request-operation }}
PR_URL: ${{ steps.cpr.outputs.pull-request-url }}
PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }}
run: |
echo "PR operation: $PR_OPERATION"
echo "PR URL: $PR_URL"
{
echo
echo "## Pull Request"
echo
if [[ -n "$PR_URL" ]]; then
echo "- Operation: \`$PR_OPERATION\`"
echo "- PR: [#${PR_NUMBER:-?}]($PR_URL)"
else
echo "- No pull request was created or updated (peter-evans/create-pull-request returned no URL)."
fi
} >> "$GITHUB_STEP_SUMMARY"