diff --git a/.github/workflows/deploy-pages-preview.yml b/.github/workflows/deploy-pages-preview.yml new file mode 100644 index 0000000..2d8985a --- /dev/null +++ b/.github/workflows/deploy-pages-preview.yml @@ -0,0 +1,89 @@ +# Deploys the example app to Cloudflare Pages PREVIEW on every push to develop. +# Always-fresh URL for testing the latest features before a tagged release. +# +# PR previews are handled automatically by CF Pages' native GitHub integration +# (or this same action on pull_request events) β€” each PR gets its own URL. +# +# Setup (one-time): same repo secrets as production: +# CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID +name: Deploy (Preview β€” develop) + +on: + push: + branches: [develop] + # PR previews: each PR deploys to its own .coflui-preview.pages.dev + pull_request: + branches: [develop] + +permissions: + contents: read + pull-requests: write # so the bot can comment the preview URL on the PR + +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Build example web + # --no-tree-shake-icons required: IconResolver creates IconData at + # runtime from JSON, which breaks the icon font tree-shaker. + run: flutter pub get && flutter build web --release --no-tree-shake-icons + working-directory: example + + - name: Capture branch name (for PR preview alias) + id: branch + run: echo "name=${GITHUB_HEAD_REF:-develop}" >> "$GITHUB_OUTPUT" + + - name: Ensure Pages project exists (idempotent) + # Creates the project on first run; subsequent runs no-op (it exists). + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages project create coflui --production-branch=main + continue-on-error: true # already exists on subsequent runs + + - name: Deploy to Cloudflare Pages (preview) + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy example/build/web --project-name=coflui --branch=${{ steps.branch.outputs.name }} + + - name: Comment preview URL on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const url = '${{ steps.deploy.outputs.deployment-url }}'; + if (!url) return; + const body = `🌐 **Preview deployed:** ${url}`; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => c.body.startsWith('🌐 **Preview deployed:**')); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/.github/workflows/deploy-pages-production.yml b/.github/workflows/deploy-pages-production.yml new file mode 100644 index 0000000..cb2a20a --- /dev/null +++ b/.github/workflows/deploy-pages-production.yml @@ -0,0 +1,54 @@ +# Deploys the example app to Cloudflare Pages PRODUCTION β€” but ONLY on a +# version tag push (v0.3.0, v0.3.1, …). Stable URL shared externally. +# +# Setup (one-time): +# CF Account Token + Account ID as repo secrets: +# CLOUDFLARE_API_TOKEN β€” Pages:Edit permission +# CLOUDFLARE_ACCOUNT_ID +# +# Trigger: push a tag β†’ production deploy to coflui.pages.dev +# git tag v0.3.0 && git push origin v0.3.0 +name: Deploy (Production β€” tag) + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + deployments: write + +jobs: + deploy-production: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Build example web + # --no-tree-shake-icons is required: IconResolver creates IconData at + # runtime from JSON, which breaks Flutter's icon font tree-shaker. + run: flutter pub get && flutter build web --release --no-tree-shake-icons + working-directory: example + + - name: Ensure Pages project exists (idempotent) + # Creates the project on first run; subsequent runs no-op (it exists). + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages project create coflui --production-branch=main + continue-on-error: true # already exists on subsequent runs + + - name: Deploy to Cloudflare Pages (production) + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy example/build/web --project-name=coflui --branch=main diff --git a/.gitignore b/.gitignore index 9bcb194..3e740d8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,7 @@ app.*.map.json # Cora code-review local cache (review history, etc) .cora/ + +# iOS generated (not part of web/package build) +example/ios/Podfile +example/ios/Pods/ diff --git a/doc/DEPLOY.md b/doc/DEPLOY.md new file mode 100644 index 0000000..43c4df6 --- /dev/null +++ b/doc/DEPLOY.md @@ -0,0 +1,89 @@ +# Deploying the Example App to Cloudflare Pages + +The coflui example app is a static Flutter web build β€” a perfect fit for +Cloudflare Pages (free, global CDN, HTTPS automatically). + +This repo ships **two GitHub Actions workflows** for a 3-tier deploy strategy: + +| Trigger | Target | Purpose | +|---------|--------|---------| +| `git push tag vX.Y.Z` | `coflui.pages.dev` | **Production** β€” stable URL for sharing | +| `git push origin develop` | `coflui-preview.pages.dev` | **Preview** β€” always latest | +| Open a PR β†’ develop | `.coflui-preview.pages.dev` | **PR preview** β€” per-branch | + +--- + +## One-time setup + +### 1. Create the API token + +1. Go to **dash.cloudflare.com β†’ My Profile β†’ API Tokens β†’ Create Token** +2. Use the **"Edit Cloudflare Workers"** template, or a custom token with: + - Account β†’ Cloudflare Pages β†’ **Edit** +3. Copy the token value. + +### 2. Add repository secrets + +In **GitHub β†’ repo β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New secret**: + +| Secret name | Value | +|-------------|-------| +| `CLOUDFLARE_API_TOKEN` | (token from step 1) | +| `CLOUDFLARE_ACCOUNT_ID` | Your CF account ID (find it in the CF dashboard URL or Pages project) | + +> ℹ️ The Pages project (`coflui`) is **created automatically** on the first +> workflow run β€” no need to run `wrangler pages project create` manually. + +--- + +## Usage + +### Production release (stable URL) + +```bash +# After merging develop β†’ main (via PR), tag a release: +git checkout develop +git tag v0.3.0 +git push origin v0.3.0 +# β†’ workflow deploys to https://coflui.pages.dev +``` + +### Preview (auto, every push to develop) + +Just push to develop β€” the preview workflow runs automatically: + +```bash +git push origin develop +# β†’ workflow deploys to https://coflui-preview.pages.dev +``` + +### PR previews + +Open any PR targeting develop β€” the bot comments the preview URL on the PR. + +--- + +## ⚠️ The `--no-tree-shake-icons` requirement + +Flutter web's icon tree-shaker needs every `IconData` to be a compile-time +constant. Coflui's `IconResolver` resolves icon names to `IconData` at **runtime** +(from JSON), which breaks the shaker. Every web build MUST use: + +```bash +flutter build web --release --no-tree-shake-icons +``` + +The `tool/build_web.sh` helper and both workflows already include this flag. +If you see `This application cannot tree shake icons fonts…`, you forgot it. + +--- + +## Local build + serve + +```bash +# Build only +./tool/build_web.sh + +# Build + serve on LAN (http://:5678) +./tool/build_web.sh --serve +``` diff --git a/tool/build_web.sh b/tool/build_web.sh new file mode 100755 index 0000000..18f890e --- /dev/null +++ b/tool/build_web.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# Build the coflui example app for web (release mode). +# +# Why --no-tree-shake-icons? +# IconResolver (lib/src/dynamic/resolvers/icon_resolver.dart) creates IconData +# instances at RUNTIME from JSON strings. Flutter's web tree-shaker requires +# all IconData references to be compile-time constants β€” so it fails without +# this flag. This is an inherent trade-off of dynamic icon resolution. +# +# Usage: +# ./tool/build_web.sh # build only β†’ example/build/web +# ./tool/build_web.sh --serve # build + serve locally on :5678 (LAN-accessible) +set -euo pipefail + +cd "$(dirname "$0")/.." + +EXAMPLE_DIR="example" +BUILD_DIR="$EXAMPLE_DIR/build/web" + +echo "πŸ”¨ Building coflui example (web release)…" +cd "$EXAMPLE_DIR" +flutter build web --release --no-tree-shake-icons +cd - >/dev/null + +echo "βœ… Build complete β†’ $BUILD_DIR" + +if [[ "${1:-}" == "--serve" ]]; then + PORT="${2:-5678}" + echo "🌐 Serving on http://0.0.0.0:$PORT (LAN-accessible)" + echo " Local: http://localhost:$PORT" + cd "$BUILD_DIR" + python3 -m http.server "$PORT" --bind 0.0.0.0 +fi