ci(deploy): switch from Cloudflare Pages to GitHub Pages

Migrate build and deployment pipeline from Cloudflare Pages to GitHub Pages.
Adds production-ready deploy workflow in deploy-github-pages.yml with proper
artifact handling. Removes Cloudflare-specific tooling: wrangler config, _headers,
_redirects, and CSP hash injection scripts (no longer needed with GitHub Pages
static hosting). Updates package.json build scripts and all project documentation
to reflect new deployment target and simplified architecture.
This commit is contained in:
tiennm99 committed 2026-05-09 23:25:06 +07:00
1 parent a06cbd2675
commit 8fc3ec6ebd
20 files changed
+385 -301

No files matched your search

+3 -3
View File
@@ -189,7 +189,7 @@ Set in `.env.local` (not committed).
### Build Targets
- **adapter-static**: Generates static HTML + JS in `build/`.
- **basePath**: Dual-mode: `""` (Cloudflare, dev) or `/loto` (GitHub Pages via `BUILD_PROFILE=gh`).
- **basePath**: `/loto` for production GitHub Pages (`BUILD_PROFILE=gh`); `""` for local dev / generic static preview.
Last reviewed: 2026-04-27
Last synced: 2026-04-27 (6-phase refactor)
Last reviewed: 2026-05-09
Last synced: 2026-05-09 (deploy target switched to GitHub Pages)
+5 -7
View File
@@ -49,15 +49,13 @@
|------|---------|
| `svelte.config.js` | adapter-static (HTML export), dual basePath via BUILD_PROFILE env, SvelteKit PWA plugin config. |
| `vite.config.js` | Tailwind + SvelteKit + PWA plugins. codeserver HMR config (port, allowedHosts, hmr). |
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite, @vite-pwa/sveltekit. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch, verify:build. |
| `scripts/verify-build-inline-scripts.mjs` | Post-build CSP guard. Counts inline `<script>` tags in `build/index.html` (fails if > EXPECTED_INLINE) and asserts `build/_headers` `script-src` has no `'unsafe-inline'`. Catches future SvelteKit upgrades that ship a new inline block the CSP isn't calibrated for. |
| `scripts/inject-csp-hashes.mjs` | Postbuild step chained into `npm run build` and `build:gh`. Reads inline `<script>` bodies from `build/index.html`, computes SHA-256, and rewrites `build/_headers` `script-src 'self' 'unsafe-inline'` → `script-src 'self' 'sha256-…'`. Hash regenerates per build; `_headers` is treated as a build artifact. |
| `.github/workflows/verify-build.yml` | CI: on push/PR to main runs `npm test && npm run build && npm run verify:build` to enforce the inline-script guard above. |
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite, @vite-pwa/sveltekit. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch. |
| `.github/workflows/deploy-github-pages.yml` | Canonical deploy. On push to `main`: `npm ci && npm run build:gh`, uploads `build/` as the GitHub Pages artifact, deploys to `https://tiennm99.github.io/loto/`. |
| `.github/workflows/verify-build.yml` | CI: on push/PR to main runs `npm test && npm run build` as a regression gate before the deploy job. |
| `eslint.config.mjs` | ESLint 9 flat config (@eslint/js + eslint-plugin-svelte). Declares Svelte 5 rune globals. |
| `jsconfig.json` | Path alias `$lib`, no checkJs. |
| `.gitignore` | Excludes node_modules, build, .env.local, etc. |
| `.env.example` | codeserver profile vars (CODESERVER_HOST, CODESERVER_PORT). |
| `static/_redirects` | Cloudflare Pages: `/* / 301` — every unknown path 301-redirects to homepage. |
| `static/manifest.webmanifest` | PWA manifest: name, short_name, icons (192/512px), theme colors, display: standalone, scope, start_url. |
| `static/icons/{192,512}.png` | PWA icons (auto-generated by @vite-pwa/sveltekit from source). |
| `static/audio/{voiceId}/*.mp3` | Pre-generated Vietnamese voice clips (1–90 + cho + kinh per voice). Generated by `scripts/generate-audio.py`. SW caches at runtime (CacheFirst). |
@@ -105,5 +103,5 @@ RootLayout
| `handleCellClick()` | PlayerBoard.svelte | Toggle crossed[row][col]. |
| `saveGrid()` / `loadGrid()` | game-logic.js | localStorage with prefix-based keys. |
Last reviewed: 2026-04-27
Last synced: 2026-04-27 (UI polish v2 + PWA: font, mode picker, color picker, empty state, PWA stack)
Last reviewed: 2026-05-09
Last synced: 2026-05-09 (deploy target switched from Cloudflare Pages to GitHub Pages; CSP-hash machinery and `_headers` / `_redirects` removed)
+30 -68
View File
@@ -4,8 +4,8 @@
| Script | basePath | Target |
|---|---|---|
| `npm run build` | `""` (root) | Cloudflare Pages → `https://loto.miti99.com` |
| `npm run build:gh` | `/loto` | GitHub Pages → `https://tiennm99.github.io/loto` (manual fallback) |
| `npm run build` | `""` (root) | Local preview / generic static host |
| `npm run build:gh` | `/loto` | GitHub Pages → `https://tiennm99.github.io/loto` (canonical) |
Implementation: `svelte.config.js` reads `BUILD_PROFILE` env. Default is empty
basePath; `BUILD_PROFILE=gh npm run build` switches to `/loto`.
@@ -13,61 +13,21 @@ basePath; `BUILD_PROFILE=gh npm run build` switches to `/loto`.
Internal links use `import { base } from '$app/paths'` so they survive
either profile without code changes.
## Production Deployment — Cloudflare Pages
## Production Deployment — GitHub Pages
Primary deploy. Set up via the Cloudflare dashboard once; subsequent
pushes to `main` trigger automatic builds + deploys.
Canonical deploy. Wired via `.github/workflows/deploy-github-pages.yml`:
on push to `main`, runs `npm run build:gh`, uploads `build/` as the
GitHub Pages artifact, and deploys.
1. dash.cloudflare.com → Workers & Pages → Create → Pages → Connect to Git
→ pick the repo
2. Build settings:
- Framework preset: SvelteKit
- Build command: `npm run build`
- Build output directory: `build`
- Production branch: `main`
3. After first deploy, add the custom domain:
Project → Custom domains → `loto.miti99.com`. Cloudflare gives DNS
records to add at your registrar (or auto-configures if `miti99.com` is
on Cloudflare DNS).
One-time setup (already done; documented for restoration):
1. Repo → Settings → Pages → Source: **GitHub Actions**.
2. Push to `main` triggers the workflow; the deploy job posts the
live URL on completion.
No GitHub Actions involved; no repo secrets needed.
URL: `https://tiennm99.github.io/loto/`
## GitHub Pages (redirect-only)
`.github/workflows/deploy-github-pages.yml` no longer builds the app.
It generates a tiny HTML page at `/loto/index.html` that immediately
redirects to the canonical URL on Cloudflare Pages, plus a copy at
`/loto/master/index.html` to catch legacy `/master` bookmarks (the app
itself is single-page now; Cloudflare's `_redirects` collapses unknown
paths back to `/`).
The redirect uses both `<meta http-equiv="refresh">` (no-JS fallback)
and a tiny inline script that preserves path / query / hash:
```js
location.replace("https://loto.miti99.com" + path + search + hash);
```
So `tiennm99.github.io/loto/` → `loto.miti99.com/` and any legacy
`tiennm99.github.io/loto/master` link → `loto.miti99.com/` (after the
intermediate `/master` redirect is collapsed by `_redirects`).
The redirect runs on every push to `main`. If you want full GH Pages
serving back (instead of the redirect), restore the prior version of
`.github/workflows/deploy-github-pages.yml` from git history — it ran
`npm run build:gh` and uploaded the `build/` artifact.
### Manual GH Pages Build (still available)
If you ever want to build a real GH Pages export by hand:
```bash
npm run build:gh
# upload build/ to the GH Pages target manually
```
`build:gh` script kept as an escape hatch — not used by the automated
redirect workflow.
No external secrets; the workflow uses GitHub's built-in `pages` and
`id-token` permissions (declared in the workflow YAML).
## Development Environment
@@ -129,11 +89,11 @@ This is normal in proxy environments.
### Build Command
```bash
npm run build
npm run build:gh
```
Generates:
- `build/` — Complete static HTML + JS export
- `build/` — Complete static HTML + JS export with `/loto` basePath
- `.svelte-kit/` — Build cache (not needed for deployment)
### Export Settings
@@ -142,9 +102,9 @@ Generates:
- All pages pre-rendered to HTML + JS bundles
### Asset Hosting
- `base` path matches deployment target (prod: `""`, GH: `/loto`, codeserver: `/absproxy/{port}`)
- `base` path matches deployment target (GH: `/loto`, root for local preview, codeserver: `/absproxy/{port}`)
- CSS, JS, fonts all prefixed correctly
- GitHub Pages serves from repository root, so `/loto` paths resolve correctly
- GitHub Pages serves the project at `/loto`, so `/loto/_app/*` paths resolve correctly
## Environment Variables
@@ -154,7 +114,9 @@ Generates:
- `CODESERVER_PORT` — port (default 3000)
### Build-Time
- `BUILD_PROFILE` — set to "gh" for GitHub Pages build (basePath `/loto`). Default empty (Cloudflare).
- `BUILD_PROFILE` — set to `gh` for GitHub Pages build (basePath `/loto`). The
deploy workflow sets this via `npm run build:gh`. Default empty (root
basePath) is for local preview / non-GH static hosts.
### Not Used at Runtime
- No database URL, API keys, or secrets (all client-side, localStorage)
@@ -164,7 +126,7 @@ Generates:
| Issue | Cause | Fix |
|-------|-------|-----|
| 404 on subpages after deploy | basePath mismatch | Verify `BUILD_PROFILE=gh` for GitHub Pages; default for Cloudflare |
| 404 on assets after deploy | basePath mismatch | Workflow runs `npm run build:gh` — check the deploy job log emits `/loto/_app/...` URLs |
| HMR not connecting (code-server) | CODESERVER_HOST not set | Add `CODESERVER_HOST=...` to `.env.local` |
| Assets 404 (code-server) | Wrong proxy URL | Use `/absproxy/{port}`, not `/proxy/{port}` |
| Page blank after refresh | State not persisted | Check browser localStorage is enabled |
@@ -172,12 +134,12 @@ Generates:
## CI/CD Pipeline
Two pipelines run on push to `main`:
- **Cloudflare Pages** (canonical) — wired via the CF dashboard, builds with
`npm run build`, publishes to `loto.miti99.com`.
- **GitHub Pages** (redirect-only) — wired via
`.github/workflows/deploy-github-pages.yml`, deploys static redirect HTML
that forwards `tiennm99.github.io/loto/*` to `loto.miti99.com/*`.
Two workflows on `main`:
- **`.github/workflows/deploy-github-pages.yml`** — canonical deploy. Builds
with `npm run build:gh` and publishes `build/` to GitHub Pages.
- **`.github/workflows/verify-build.yml`** — PR + push gate. Runs
`npm test && npm run build` to catch regressions before they reach the
deploy job.
## Performance Checklist
@@ -185,7 +147,7 @@ Two pipelines run on push to `main`:
- [x] Tailwind 4 purged for production size
- [x] localStorage reduces bundle—no API calls
- [x] Images minimal (mostly CSS gradients + emojis)
- [x] Fonts: Geist via Google Fonts CDN
- [x] Fonts: Roboto Condensed self-hosted via @fontsource
Bundle analysis: Run `npm run build && ls -lh build/` to inspect file sizes.
@@ -195,6 +157,6 @@ Bundle analysis: Run `npm run build && ls -lh build/` to inspect file sizes.
- `.env.local` is local-only, not committed
- localStorage scoped to origin
- No external API calls (offline-capable)
- GitHub Pages HTTPS by default
- GitHub Pages serves HTTPS by default
Last reviewed: 2026-04-26
Last reviewed: 2026-05-09
+1 -1
View File
@@ -80,7 +80,7 @@ European Bingo 90 patterns. **Out of scope** — Tân Tân uses single-line "Kin
## Decision Rationale
All decisions follow **YAGNI** (You Aren't Gonna Need It), **KISS** (Keep It Simple), **DRY** (Don't Repeat Yourself):
- No multiplayer sync → adds server dependency, breaks static export (Cloudflare Pages)
- No multiplayer sync → adds server dependency, breaks static export (GitHub Pages)
- No i18n → Vietnamese-only community, localizing adds complexity
- Unit tests implemented → critical paths (constraints, persistence, settings) now have automated coverage
- Component/E2E tests deferred → small codebase, manual testing sufficient for UI flows
+5 -5
View File
@@ -73,7 +73,7 @@ That format is intentionally out of scope.
- **Styling**: Tailwind CSS 4 (utility-first, animations)
- **Persistence**: localStorage (no backend)
- **Audio**: pre-generated MP3 clips bundled under `static/audio/{voiceId}/`. Built once by `scripts/generate-audio.py` using free `edge-tts` (Microsoft Neural). Runtime plays via plain HTML5 Audio — no TTS API at runtime.
- **Deploy**: Cloudflare Pages (root domain), GitHub Pages fallback (`/loto`)
- **Deploy**: GitHub Pages at `/loto` (canonical, via GitHub Actions)
- **Dev Profile**: code-server compatible via `/absproxy/{port}` basePath + HMR proxy config
## Architecture Overview
@@ -86,9 +86,9 @@ State is entirely client-side. Each card / panel instance uses a unique localSto
## Deployment
- **Production**: Cloudflare Pages at `loto.miti99.com` (canonical, CF dashboard, root basePath). GitHub Pages serves only a redirect to the canonical URL via `.github/workflows/deploy-github-pages.yml`.
- **Production**: GitHub Pages at `https://tiennm99.github.io/loto/` (canonical, basePath `/loto`). Deployed by `.github/workflows/deploy-github-pages.yml` on push to `main`.
- **Development**: `npm run dev` (local), `npm run dev:codeserver` (code-server via proxy).
- **Build**: `npm run build` generates static export to `build/` directory.
- **Build**: `npm run build:gh` generates static export to `build/` directory with the `/loto` basePath.
## Key Acceptance Criteria
@@ -143,5 +143,5 @@ State is entirely client-side. Each card / panel instance uses a unique localSto
- Multiplayer sync (real-time via WebSocket)
- i18n beyond Vietnamese
Last reviewed: 2026-04-27 (scope locked: Lô tô hội chợ Tân Tân + theme/master/auto-call)
Last synced: 2026-04-27 (6-phase refactor)
Last reviewed: 2026-05-09 (scope locked: Lô tô hội chợ Tân Tân + theme/master/auto-call)
Last synced: 2026-05-09 (deploy target switched to GitHub Pages)
+5 -12
View File
@@ -68,20 +68,13 @@ All keys are JSON stringified. Corruption is silent (returns null).
## basePath & Asset Resolution
### Production (Cloudflare Pages, default)
```
npm run build
basePath="" (root)
Output: build/index.html
Deploy: loto.miti99.com (root domain)
```
### Production (GitHub Pages, manual)
### Production (GitHub Pages, canonical)
```
npm run build:gh
basePath="/loto"
Output: build/index.html with /loto prefix
Deploy: https://tiennm99.github.io/loto
Deploy: https://tiennm99.github.io/loto/
Workflow: .github/workflows/deploy-github-pages.yml
```
### Development (Local)
@@ -199,5 +192,5 @@ Each draw creates a new object (even repeat numbers) to ensure reactive re-fire.
3. User installs → app appears on home screen, launches in standalone mode
4. Updates: SW checks for new version; if found, toast appears; user can reload
Last reviewed: 2026-04-27
Last synced: 2026-04-27 (UI polish v2 + PWA: font, mode picker, color picker, empty state, PWA stack)
Last reviewed: 2026-05-09
Last synced: 2026-05-09 (Cloudflare Pages removed; GitHub Pages is the sole production target)