Publish releases only. #36
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
| name: Build & Deploy Web Apps to GitHub Pages | |
| on: | |
| push: | |
| tags: | |
| - '*' # Push events to matching *, i.e. 1.0, 20.15.10 | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: "pages" | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "22" | |
| - name: Make gradlew executable (if present) | |
| run: | | |
| if [ -f "./gradlew" ]; then | |
| chmod +x ./gradlew | |
| fi | |
| - name: Build all web apps (per subproject) | |
| run: | | |
| set -e | |
| # Optional: ensure jq exists (Ubuntu images usually have it already) | |
| if ! command -v jq >/dev/null 2>&1; then | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| fi | |
| # Find all webapp.json files | |
| while IFS= read -r meta; do | |
| app_dir="$(dirname "$meta")" | |
| build_cmd="$(jq -r '.build.command // empty' "$meta")" | |
| if [ -z "$build_cmd" ]; then | |
| echo "[INFO] $meta has no build.command, skipping build step." | |
| continue | |
| fi | |
| echo "[INFO] Building app in $app_dir with: $build_cmd" | |
| ( | |
| cd "$app_dir" | |
| # Execute the command as defined in webapp.json | |
| set -e | |
| eval "$build_cmd" | |
| ) | |
| done < <(find . -name webapp.json -print) | |
| - name: Prepare static site folder (scan apps & generate index) | |
| run: | | |
| set -e | |
| SITE_DIR="site" | |
| mkdir -p "$SITE_DIR" | |
| python << 'PYTHON_EOF' | |
| import json, os, shutil, html | |
| ROOT = os.getcwd() | |
| SITE_DIR = os.path.join(ROOT, "site") | |
| os.makedirs(SITE_DIR, exist_ok=True) | |
| release_tag = os.environ.get("GITHUB_REF_NAME", "") | |
| apps = [] | |
| # Walk repo and look for webapp.json | |
| for dirpath, dirnames, filenames in os.walk(ROOT): | |
| if "webapp.json" not in filenames: | |
| continue | |
| meta_path = os.path.join(dirpath, "webapp.json") | |
| with open(meta_path, "r", encoding="utf-8") as f: | |
| try: | |
| meta = json.load(f) | |
| except Exception as e: | |
| print(f"[WARN] Cannot parse {meta_path}: {e}") | |
| continue | |
| app_id = meta.get("id") | |
| name = meta.get("name", app_id) | |
| description = meta.get("description", "") | |
| screenshot_rel = meta.get("screenshot") | |
| # Allow distDirs list, fallback to single distDir | |
| dist_dirs_rel = [] | |
| if isinstance(meta.get("distDirs"), list): | |
| dist_dirs_rel = meta["distDirs"] | |
| elif isinstance(meta.get("distDir"), str): | |
| dist_dirs_rel = [meta["distDir"]] | |
| if not app_id: | |
| print(f"[WARN] {meta_path} missing 'id', skipping.") | |
| continue | |
| if not dist_dirs_rel: | |
| print(f"[WARN] {meta_path} missing 'distDirs'/'distDir', skipping.") | |
| continue | |
| project_root = dirpath | |
| # Choose first distDir that contains index.html | |
| chosen_dist_dir = None | |
| for rel in dist_dirs_rel: | |
| cand_dist = os.path.join(project_root, rel) | |
| cand_index = os.path.join(cand_dist, "index.html") | |
| if os.path.exists(cand_index): | |
| chosen_dist_dir = cand_dist | |
| break | |
| if not chosen_dist_dir: | |
| print(f"[INFO] No index.html in any distDir for {app_id}, skipping.") | |
| continue | |
| print(f"[OK] Using dist dir {chosen_dist_dir} for app {app_id}") | |
| # Copy dist folder into site/<id>/ | |
| target_app_dir = os.path.join(SITE_DIR, app_id) | |
| if os.path.exists(target_app_dir): | |
| shutil.rmtree(target_app_dir) | |
| shutil.copytree(chosen_dist_dir, target_app_dir) | |
| screenshot_target = None | |
| if screenshot_rel: | |
| screenshot_src = os.path.join(project_root, screenshot_rel) | |
| if os.path.exists(screenshot_src): | |
| screenshot_name = os.path.basename(screenshot_src) | |
| screenshot_target = f"{app_id}/{screenshot_name}" | |
| shutil.copy2(screenshot_src, os.path.join(target_app_dir, screenshot_name)) | |
| else: | |
| print(f"[WARN] Screenshot configured but not found: {screenshot_src}") | |
| apps.append({ | |
| "id": app_id, | |
| "name": name or app_id, | |
| "description": description, | |
| "screenshot": screenshot_target | |
| }) | |
| # Generate root index.html | |
| apps.sort(key=lambda a: a["id"]) | |
| index_html_path = os.path.join(SITE_DIR, "index.html") | |
| with open(index_html_path, "w", encoding="utf-8") as f: | |
| f.write("<!DOCTYPE html>\n<html lang='en'>\n<head>\n") | |
| f.write(" <meta charset='UTF-8'>\n") | |
| f.write(" <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n") | |
| f.write(" <title>KMP Web Apps</title>\n") | |
| f.write(" <style>\n") | |
| f.write(" body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; }\n") | |
| f.write(" h1 { margin-bottom: 0.25rem; }\n") | |
| f.write(" .tag { color: #666; font-size: 0.9rem; margin-bottom: 1.5rem; }\n") | |
| f.write(" .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; }\n") | |
| f.write(" .card { border: 1px solid #ddd; border-radius: 0.75rem; padding: 1rem; text-decoration: none; color: inherit; background: #fafafa; transition: box-shadow 0.15s, transform 0.15s; }\n") | |
| f.write(" .card:hover { box-shadow: 0 6px 18px rgba(0,0,0,0.08); transform: translateY(-2px); }\n") | |
| f.write(" .card h2 { margin-top: 0; margin-bottom: 0.25rem; font-size: 1.1rem; }\n") | |
| f.write(" .card p { margin: 0.25rem 0 0.5rem 0; font-size: 0.9rem; color: #555; }\n") | |
| f.write(" .badge { display: inline-block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: #555; background: #eaeaea; padding: 0.1rem 0.45rem; border-radius: 999px; margin-bottom: 0.3rem; }\n") | |
| f.write(" .screenshot { width: 100%; border-radius: 0.5rem; margin-top: 0.5rem; border: 1px solid #e0e0e0; object-fit: cover; max-height: 180px; }\n") | |
| f.write(" .empty { color: #777; font-style: italic; }\n") | |
| f.write(" </style>\n") | |
| f.write("</head>\n<body>\n") | |
| f.write(" <h1>KMP Web Apps</h1>\n") | |
| if release_tag: | |
| f.write(f" <div class='tag'>Deployed from release <strong>{html.escape(release_tag)}</strong></div>\n") | |
| if not apps: | |
| f.write(" <p class='empty'>No web apps detected for this release (no valid <code>webapp.json</code> with a working <code>index.html</code>).</p>\n") | |
| else: | |
| f.write(" <div class='grid'>\n") | |
| for app in apps: | |
| href = f"./{app['id']}/" | |
| name = html.escape(app["name"]) | |
| desc = html.escape(app["description"] or "") | |
| f.write(f" <a class='card' href='{href}'>\n") | |
| f.write(f" <div class='badge'>/{html.escape(app['id'])}</div>\n") | |
| f.write(f" <h2>{name}</h2>\n") | |
| if desc: | |
| f.write(f" <p>{desc}</p>\n") | |
| if app["screenshot"]: | |
| f.write(f" <img class='screenshot' src='{html.escape(app['screenshot'])}' alt='Screenshot of {name}' />\n") | |
| f.write(" </a>\n") | |
| f.write(" </div>\n") | |
| f.write("</body>\n</html>\n") | |
| print(f"[OK] Root index generated at {index_html_path}") | |
| PYTHON_EOF | |
| - name: Upload artifact for GitHub Pages | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: site | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 |