Skip to content

Deploy example apps to GitHub Pages #3

Deploy example apps to GitHub Pages

Deploy example apps to GitHub Pages #3

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