From 43c0e84ea356bda38ea6c43d2581eb98e7b1773e Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Thu, 23 Jul 2026 10:30:28 +0700 Subject: [PATCH 1/2] ci: Cloudflare Pages deploy workflows (production tag + preview + PR) 3-tier deploy strategy: - git tag vX.Y.Z -> coflui.pages.dev (production, stable) - push develop -> coflui-preview.pages.dev (preview, always fresh) - open PR -> .pages.dev (per-PR preview, bot comments URL) Both workflows use flutter-action + wrangler-action. Build always passes --no-tree-shake-icons (IconResolver creates IconData at runtime). Added tool/build_web.sh for local build + LAN serve. Added doc/DEPLOY.md with full CF setup (project, token, secrets). .gitignore: exclude example/ios/Podfile artifacts. --- .github/workflows/deploy-pages-preview.yml | 80 ++++++++++++++ .github/workflows/deploy-pages-production.yml | 45 ++++++++ .gitignore | 4 + doc/DEPLOY.md | 103 ++++++++++++++++++ tool/build_web.sh | 34 ++++++ 5 files changed, 266 insertions(+) create mode 100644 .github/workflows/deploy-pages-preview.yml create mode 100644 .github/workflows/deploy-pages-production.yml create mode 100644 doc/DEPLOY.md create mode 100755 tool/build_web.sh diff --git a/.github/workflows/deploy-pages-preview.yml b/.github/workflows/deploy-pages-preview.yml new file mode 100644 index 0000000..24e1e1f --- /dev/null +++ b/.github/workflows/deploy-pages-preview.yml @@ -0,0 +1,80 @@ +# 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: 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..49aaf91 --- /dev/null +++ b/.github/workflows/deploy-pages-production.yml @@ -0,0 +1,45 @@ +# 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: 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..eb1484b --- /dev/null +++ b/doc/DEPLOY.md @@ -0,0 +1,103 @@ +# 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 Cloudflare Pages projects + +Run locally (you'll be prompted to authenticate once): + +```bash +# Production project +npx wrangler pages project create coflui --production-branch=main + +# (Optional) separate preview project +npx wrangler pages project create coflui-preview --production-branch=develop +``` + +> The workflows use `--project-name=coflui` for both. CF Pages isolates +> production vs preview deployments by `--branch` automatically, so a single +> project is fine. Create `coflui-preview` only if you want a fully separate +> domain. + +### 2. 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. + +### 3. Add repository secrets + +In **GitHub β†’ repo β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New secret**: + +| Secret name | Value | +|-------------|-------| +| `CLOUDFLARE_API_TOKEN` | (token from step 2) | +| `CLOUDFLARE_ACCOUNT_ID` | Your CF account ID (find it in the CF dashboard URL or Pages project) | + +--- + +## 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 From e0c5d87bc708610b3e153399ca9673f366369edb Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Mon, 27 Jul 2026 08:27:18 +0700 Subject: [PATCH 2/2] fix(ci): auto-create CF Pages project (idempotent) on first run The first deploy failed because project 'coflui' didn't exist yet in CF. Added a create-project step with continue-on-error so it no-ops on subsequent runs once the project exists. No manual project setup needed. --- .github/workflows/deploy-pages-preview.yml | 9 +++++++ .github/workflows/deploy-pages-production.yml | 9 +++++++ doc/DEPLOY.md | 26 +++++-------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deploy-pages-preview.yml b/.github/workflows/deploy-pages-preview.yml index 24e1e1f..2d8985a 100644 --- a/.github/workflows/deploy-pages-preview.yml +++ b/.github/workflows/deploy-pages-preview.yml @@ -41,6 +41,15 @@ jobs: 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 diff --git a/.github/workflows/deploy-pages-production.yml b/.github/workflows/deploy-pages-production.yml index 49aaf91..cb2a20a 100644 --- a/.github/workflows/deploy-pages-production.yml +++ b/.github/workflows/deploy-pages-production.yml @@ -37,6 +37,15 @@ jobs: 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: diff --git a/doc/DEPLOY.md b/doc/DEPLOY.md index eb1484b..43c4df6 100644 --- a/doc/DEPLOY.md +++ b/doc/DEPLOY.md @@ -15,39 +15,25 @@ This repo ships **two GitHub Actions workflows** for a 3-tier deploy strategy: ## One-time setup -### 1. Create the Cloudflare Pages projects - -Run locally (you'll be prompted to authenticate once): - -```bash -# Production project -npx wrangler pages project create coflui --production-branch=main - -# (Optional) separate preview project -npx wrangler pages project create coflui-preview --production-branch=develop -``` - -> The workflows use `--project-name=coflui` for both. CF Pages isolates -> production vs preview deployments by `--branch` automatically, so a single -> project is fine. Create `coflui-preview` only if you want a fully separate -> domain. - -### 2. Create the API token +### 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. -### 3. Add repository secrets +### 2. Add repository secrets In **GitHub β†’ repo β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New secret**: | Secret name | Value | |-------------|-------| -| `CLOUDFLARE_API_TOKEN` | (token from step 2) | +| `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