docs(lessons): credit Prof Benjamin Abel #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ---------------------------------------------------------------------------- | |
| # Publish SolverForge uc-* apps to Hugging Face Spaces solverforge-*. | |
| # | |
| # Canonical release flow: | |
| # 1. Cut an app-scoped tag such as solverforge-hospital@1.0.2. | |
| # 2. This workflow validates the tag against uc-hospital/Cargo.toml, | |
| # uc-hospital/Cargo.lock, and uc-hospital/CHANGELOG.md. | |
| # 3. The matching uc-* subtree is pushed to the matching Hugging Face Space. | |
| # | |
| # Required secrets : HF_TOKEN (HF token with write role on the spaces) | |
| # Required variables: HF_ORGANIZATION (HF org or username, e.g. SolverForge) | |
| # ---------------------------------------------------------------------------- | |
| name: Sync SolverForge Use Cases | |
| on: | |
| push: | |
| tags: | |
| - "solverforge-deliveries@*" | |
| - "solverforge-fsr@*" | |
| - "solverforge-hospital@*" | |
| - "solverforge-lessons@*" | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: > | |
| Folder to sync (e.g., "uc-hospital") or "all" to sync everything. | |
| Manual syncs are for recovery; app tags are the release path. | |
| required: true | |
| default: "all" | |
| concurrency: | |
| group: hf-sync-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| detect: | |
| name: Resolve release target | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| empty: ${{ steps.build-matrix.outputs.empty }} | |
| release_tag: ${{ steps.release-tag.outputs.tag_name }} | |
| release_version: ${{ steps.release-tag.outputs.version }} | |
| release_folder: ${{ steps.release-tag.outputs.folder }} | |
| steps: | |
| - name: Checkout full history | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate app release tag | |
| id: release-tag | |
| if: github.event_name == 'push' | |
| run: node scripts/verify-usecase-release-tag.cjs "${GITHUB_REF_NAME}" | |
| - name: Build matrix | |
| id: build-matrix | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| RELEASE_FOLDER: ${{ steps.release-tag.outputs.folder }} | |
| TARGET: ${{ github.event.inputs.target }} | |
| run: | | |
| set -euo pipefail | |
| OPEN_SOURCE_FOLDERS=$(cat <<'FOLDERS' | |
| uc-deliveries | |
| uc-fsr | |
| uc-hospital | |
| uc-lessons | |
| FOLDERS | |
| ) | |
| filter_open_source() { | |
| while IFS= read -r folder; do | |
| [ -n "$folder" ] || continue | |
| if printf '%s\n' "$OPEN_SOURCE_FOLDERS" | grep -Fxq "$folder"; then | |
| printf '%s\n' "$folder" | |
| else | |
| echo "::error::Folder '$folder' is not an open-source SolverForge use case." | |
| exit 1 | |
| fi | |
| done | |
| } | |
| if [ "$EVENT_NAME" = "push" ]; then | |
| FOLDERS=$(printf '%s\n' "$RELEASE_FOLDER" | filter_open_source) | |
| elif [ "${TARGET:-all}" = "all" ]; then | |
| FOLDERS="$OPEN_SOURCE_FOLDERS" | |
| else | |
| [ -d "$TARGET" ] || { echo "::error::Folder '$TARGET' not found."; exit 1; } | |
| FOLDERS=$(printf '%s\n' "$TARGET" | filter_open_source) | |
| fi | |
| if [ -z "$FOLDERS" ]; then | |
| echo "::notice::No uc-* folder selected, nothing to sync." | |
| echo "matrix={\"folder\":[]}" >> "$GITHUB_OUTPUT" | |
| echo "empty=true" >> "$GITHUB_OUTPUT" | |
| else | |
| JSON=$(echo "$FOLDERS" | jq -R . | jq -sc '{"folder":.}') | |
| echo "Detected folders: $FOLDERS" | |
| echo "matrix=$JSON" >> "$GITHUB_OUTPUT" | |
| echo "empty=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| sync: | |
| name: Sync ${{ matrix.folder }} | |
| needs: detect | |
| if: needs.detect.outputs.empty == 'false' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: ${{ fromJson(needs.detect.outputs.matrix) }} | |
| fail-fast: false | |
| steps: | |
| - name: Checkout full history | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Configure git | |
| run: | | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git config user.name "github-actions[bot]" | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install Node dependencies | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| - name: Install browser test dependencies | |
| run: make install-e2e | |
| - name: Validate release tag | |
| if: github.event_name == 'push' | |
| run: make release-ci APP="${{ matrix.folder }}" RELEASE_VERSION="${{ needs.detect.outputs.release_version }}" | |
| - name: Validate manual sync target | |
| if: github.event_name != 'push' | |
| run: make release-ci APP="${{ matrix.folder }}" | |
| - name: git subtree split to temporary branch | |
| id: split | |
| run: | | |
| FOLDER="${{ matrix.folder }}" | |
| BRANCH="hf-push/${FOLDER}/${GITHUB_RUN_ID}" | |
| echo "Extracting $FOLDER history..." | |
| git subtree split --prefix="$FOLDER" -b "$BRANCH" | |
| echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" | |
| - name: Push to Hugging Face Space | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| HF_ORGANIZATION: ${{ vars.HF_ORGANIZATION }} | |
| run: | | |
| FOLDER="${{ matrix.folder }}" | |
| BRANCH="${{ steps.split.outputs.branch }}" | |
| SPACE_NAME=$(echo "$FOLDER" | sed 's/^uc/solverforge/') | |
| SPACE="${HF_ORGANIZATION}/${SPACE_NAME}" | |
| HF_REMOTE="https://user:${HF_TOKEN}@huggingface.co/spaces/${SPACE}" | |
| echo "Pushing $FOLDER to hf.co/spaces/$SPACE (main branch)..." | |
| git push "$HF_REMOTE" "${BRANCH}:main" --force | |
| echo "::notice title=Sync OK::$FOLDER synced to hf.co/spaces/$SPACE" | |
| - name: Push release tag to Hugging Face Space | |
| if: github.event_name == 'push' | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| HF_ORGANIZATION: ${{ vars.HF_ORGANIZATION }} | |
| RELEASE_TAG: ${{ needs.detect.outputs.release_tag }} | |
| run: | | |
| FOLDER="${{ matrix.folder }}" | |
| BRANCH="${{ steps.split.outputs.branch }}" | |
| SPACE_NAME=$(echo "$FOLDER" | sed 's/^uc/solverforge/') | |
| SPACE="${HF_ORGANIZATION}/${SPACE_NAME}" | |
| HF_REMOTE="https://user:${HF_TOKEN}@huggingface.co/spaces/${SPACE}" | |
| git tag -f "$RELEASE_TAG" "$BRANCH" | |
| git push "$HF_REMOTE" "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" --force | |
| echo "::notice title=Tag Sync OK::$RELEASE_TAG synced to hf.co/spaces/$SPACE" | |
| - name: Cleanup temporary branch | |
| if: always() | |
| run: | | |
| BRANCH="${{ steps.split.outputs.branch }}" | |
| git branch -D "$BRANCH" 2>/dev/null || true |