Deploy example apps to GitHub Pages #3
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
| name: Deploy example apps to GitHub Pages | |
| # Manual-only trigger so forks never auto-publish. Forkers must (1) enable | |
| # Pages with source "GitHub Actions" in repo Settings, then (2) run this | |
| # workflow from the Actions tab. Every subdirectory under example-apps/ | |
| # that has a package.json with a "build" script is auto-discovered and | |
| # deployed to: | |
| # https://<owner>.github.io/<repo>/<app-name>/ | |
| # Adding a new app requires no workflow edits. | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: pages-example-apps | |
| cancel-in-progress: true | |
| jobs: | |
| discover: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set.outputs.matrix }} | |
| apps: ${{ steps.set.outputs.apps }} | |
| meta: ${{ steps.set.outputs.meta }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - id: set | |
| name: Discover apps | |
| run: | | |
| set -euo pipefail | |
| metas=() | |
| for pkg in example-apps/*/package.json; do | |
| [ -f "$pkg" ] || continue | |
| dir="$(dirname "$pkg")" | |
| name="$(basename "$dir")" | |
| if jq -e '.scripts.build' "$pkg" >/dev/null; then | |
| metas+=("$(jq --arg name "$name" -c \ | |
| '{name: $name, displayName: (.displayName // null), description: (.description // null)}' \ | |
| "$pkg")") | |
| fi | |
| done | |
| if [ "${#metas[@]}" -eq 0 ]; then | |
| echo "No deployable apps found under example-apps/." >&2 | |
| exit 1 | |
| fi | |
| meta_json="$(printf '%s\n' "${metas[@]}" | jq -sc .)" | |
| apps_json="$(echo "$meta_json" | jq -c '[.[].name]')" | |
| echo "matrix={\"app\":$apps_json}" >> "$GITHUB_OUTPUT" | |
| echo "apps=$apps_json" >> "$GITHUB_OUTPUT" | |
| echo "meta=$meta_json" >> "$GITHUB_OUTPUT" | |
| echo "Discovered: $apps_json" | |
| build: | |
| needs: discover | |
| runs-on: ubuntu-latest | |
| # An app's test or build failure is reported in the UI but doesn't fail | |
| # the job, so the deploy step can still publish whichever apps passed. | |
| continue-on-error: true | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.discover.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: example-apps/${{ matrix.app }}/package-lock.json | |
| - name: Install | |
| run: npm ci | |
| working-directory: example-apps/${{ matrix.app }} | |
| - name: Test | |
| run: | | |
| if jq -e '.scripts.test' package.json >/dev/null; then | |
| npm test | |
| else | |
| echo "No test script for ${{ matrix.app }} — skipping" | |
| fi | |
| working-directory: example-apps/${{ matrix.app }} | |
| - name: Build | |
| run: npm run build | |
| working-directory: example-apps/${{ matrix.app }} | |
| env: | |
| # Self-configuring base path: resolves to /<repo>/<app>/ on any fork. | |
| VITE_BASE_PATH: /${{ github.event.repository.name }}/${{ matrix.app }}/ | |
| - name: Upload built dist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: site-${{ matrix.app }} | |
| path: example-apps/${{ matrix.app }}/dist | |
| if-no-files-found: error | |
| retention-days: 1 | |
| deploy: | |
| needs: [discover, build] | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 20 | |
| - name: Download all built apps | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: site-* | |
| path: artifacts | |
| - name: Assemble combined site | |
| id: assemble | |
| env: | |
| APPS_JSON: ${{ needs.discover.outputs.apps }} | |
| META_JSON: ${{ needs.discover.outputs.meta }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p _site | |
| deployed=() | |
| mapfile -t apps < <(echo "$APPS_JSON" | jq -r '.[]') | |
| for app in "${apps[@]}"; do | |
| src="artifacts/site-$app" | |
| if [ ! -d "$src" ]; then | |
| echo "::warning::Skipping $app — no artifact (test or build failed)" | |
| continue | |
| fi | |
| mkdir -p "_site/$app" | |
| cp -r "$src/." "_site/$app/" | |
| deployed+=("$app") | |
| done | |
| if [ "${#deployed[@]}" -eq 0 ]; then | |
| echo "No apps passed tests + build; aborting deploy." >&2 | |
| exit 1 | |
| fi | |
| deployed_json=$(printf '%s\n' "${deployed[@]}" | jq -R . | jq -sc .) | |
| filtered_meta=$(echo "$META_JSON" | jq -c --argjson keep "$deployed_json" \ | |
| '[.[] | select(.name as $n | $keep | index($n))]') | |
| { | |
| echo "meta<<__META_EOF__" | |
| echo "$filtered_meta" | |
| echo "__META_EOF__" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Generate landing page | |
| env: | |
| META_JSON: ${{ steps.assemble.outputs.meta }} | |
| run: node .github/scripts/build-landing-page.mjs "$META_JSON" _site/index.html | |
| - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 | |
| with: | |
| path: _site | |
| - id: deployment | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 |