Skip to content

Copy multiple sources #37

Copy multiple sources

Copy multiple sources #37

Workflow file for this run

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")
# Only support multi dist directories; ignore legacy single distDir
dist_dirs_rel = meta.get("distDirs", []) if isinstance(meta.get("distDirs"), list) else []
if not app_id:
print(f"[WARN] {meta_path} missing 'id', skipping.")
continue
# If no distDirs provided, just register app for index but skip copy
if not dist_dirs_rel:
print(f"[INFO] {meta_path} has no 'distDirs'; will only list app on index (no files copied).")
project_root = dirpath
# Helper: merge-copy contents of src into dst (overwrite on conflicts)
def merge_copy(src: str, dst: str):
if not os.path.exists(src):
return
os.makedirs(dst, exist_ok=True)
for name in os.listdir(src):
s = os.path.join(src, name)
d = os.path.join(dst, name)
if os.path.isdir(s):
merge_copy(s, d)
else:
os.makedirs(os.path.dirname(d), exist_ok=True)
try:
shutil.copy2(s, d)
except Exception as e:
print(f"[WARN] Failed to copy {s} -> {d}: {e}")
# Copy contents of every configured distDir into site/<id>/ (no validation)
target_app_dir = os.path.join(SITE_DIR, app_id)
if os.path.exists(target_app_dir):
shutil.rmtree(target_app_dir)
os.makedirs(target_app_dir, exist_ok=True)
for rel in dist_dirs_rel:
src_dir = os.path.join(project_root, rel)
if not os.path.exists(src_dir):
print(f"[INFO] distDir does not exist (skipped): {src_dir}")
continue
print(f"[OK] Merging distDir for {app_id}: {src_dir} -> {target_app_dir}")
merge_copy(src_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