diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..45a8598 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,48 @@ +# kigicli.dev + +The Kigi landing site and the Worker that fronts it, deployed to Cloudflare as +the `kigi-frontend` Worker on `kigicli.dev` and `www.kigicli.dev`. + +## Layout + +| Path | What it is | +| --------------- | ----------------------------------------------------------------- | +| `worker/index.ts` | Worker entry point: installer proxy, `/health`, asset fallthrough | +| `wrangler.jsonc` | Deployment config (custom domains, assets binding, SPA fallback) | +| `dist/` | The built site served by the assets binding | + +> **`dist/` is build output, not source.** It was recovered by mirroring the +> live `kigicli.dev` deployment — the original Vite/React project that produced +> these bundles is not in this repo. Content and copy changes therefore have to +> be made against the hashed `dist/assets/index-*.js` and `index-*.css` files +> directly. If the upstream source ever turns up, drop it in beside `dist/` and +> switch `wrangler.jsonc` to point at its build directory. + +## Routes + +Assets are matched first and served directly. Requests with no matching file +fall through to the Worker, which owns: + +- `GET /install.sh` — proxies `install.sh` from the Kigi repo on Gitea +- `GET /install.ps1` — proxies `install.ps1` from the Kigi repo on Gitea +- `GET /health` — plain-text liveness check +- everything else — `env.ASSETS.fetch()`, which serves `index.html` for unknown + paths (single-page-application fallback) + +The installer routes are proxied rather than linked so the published install +command (`curl -fsSL https://kigicli.dev/install.sh | bash`) stays stable no +matter where the repo is hosted. Responses are cached for 10 minutes at the +edge, so a push to `main` takes up to that long to show up. + +Because assets win over the Worker, never add `install.sh` or `install.ps1` to +`dist/` — they would shadow the proxy routes. + +## Deploy + +```sh +npm install +npx wrangler deploy +``` + +Cloudflare Web Analytics injects its beacon ` + + + +
+ + diff --git a/frontend/dist/og.png b/frontend/dist/og.png new file mode 100644 index 0000000..ac89549 Binary files /dev/null and b/frontend/dist/og.png differ diff --git a/frontend/dist/video/graph-engineering-poster.jpg b/frontend/dist/video/graph-engineering-poster.jpg new file mode 100644 index 0000000..19779d9 Binary files /dev/null and b/frontend/dist/video/graph-engineering-poster.jpg differ diff --git a/frontend/dist/video/graph-engineering-sm.mp4 b/frontend/dist/video/graph-engineering-sm.mp4 new file mode 100644 index 0000000..6989ace Binary files /dev/null and b/frontend/dist/video/graph-engineering-sm.mp4 differ diff --git a/frontend/dist/video/graph-engineering.mp4 b/frontend/dist/video/graph-engineering.mp4 new file mode 100644 index 0000000..2a5c377 Binary files /dev/null and b/frontend/dist/video/graph-engineering.mp4 differ diff --git a/frontend/dist/video/main-poster.jpg b/frontend/dist/video/main-poster.jpg new file mode 100644 index 0000000..0ac55b0 Binary files /dev/null and b/frontend/dist/video/main-poster.jpg differ diff --git a/frontend/dist/video/main-sm.mp4 b/frontend/dist/video/main-sm.mp4 new file mode 100644 index 0000000..433476a Binary files /dev/null and b/frontend/dist/video/main-sm.mp4 differ diff --git a/frontend/dist/video/main.mp4 b/frontend/dist/video/main.mp4 new file mode 100644 index 0000000..89046ca Binary files /dev/null and b/frontend/dist/video/main.mp4 differ diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0cfe915 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,14 @@ +{ + "name": "kigi-frontend", + "version": "0.0.0", + "private": true, + "description": "kigicli.dev — static marketing site plus the installer proxy Worker.", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "tail": "wrangler tail" + }, + "devDependencies": { + "wrangler": "^4.120.0" + } +} diff --git a/frontend/worker/index.ts b/frontend/worker/index.ts new file mode 100644 index 0000000..edcfef6 --- /dev/null +++ b/frontend/worker/index.ts @@ -0,0 +1,114 @@ +/** + * kigicli.dev — static site + installer proxy. + * + * Static assets are served directly by the assets binding (SPA fallback). Only + * paths that do not match an asset reach this Worker, which is where the two + * installer routes live: + * + * GET https://kigicli.dev/install.sh → install.sh from the Kigi repo + * GET https://kigicli.dev/install.ps1 → install.ps1 from the Kigi repo + * + * Keeping these behind our own domain means the published install command never + * has to change when the source host does. + */ + +interface Env { + ASSETS: Fetcher; +} + +/** Raw-file base for the canonical Kigi repo (Gitea, `main` branch). */ +const UPSTREAM = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/raw/branch/main"; + +/** Human-facing repo URL, used in the installer-unavailable message. */ +const REPO_URL = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI"; + +interface Installer { + file: string; + contentType: string; + /** Emitted when upstream is unreachable — must be valid in the target shell. */ + unavailable: (status: number) => string; +} + +const INSTALLERS: Record = { + "/install.sh": { + file: "install.sh", + contentType: "text/x-shellscript; charset=utf-8", + unavailable: (status) => + `echo "kigicli.dev: installer unavailable (upstream ${status}). Try ${REPO_URL}" >&2; exit 1\n`, + }, + "/install.ps1": { + file: "install.ps1", + contentType: "text/plain; charset=utf-8", + unavailable: (status) => + `Write-Error "kigicli.dev: installer unavailable (upstream ${status}). Try ${REPO_URL}"; exit 1\n`, + }, +}; + +async function serveInstaller( + request: Request, + path: string, + ctx: ExecutionContext, +): Promise { + const { file, contentType, unavailable } = INSTALLERS[path]; + const cache = caches.default; + const cacheKey = new Request(`${UPSTREAM}/${file}`, { method: "GET" }); + + let upstream = await cache.match(cacheKey); + if (!upstream) { + upstream = await fetch(cacheKey, { + headers: { "User-Agent": "kigicli.dev-installer-proxy" }, + cf: { cacheTtl: 600, cacheEverything: true }, + }); + if (upstream.ok) { + const cacheable = new Response(upstream.clone().body, upstream); + cacheable.headers.set("Cache-Control", "public, max-age=600"); + ctx.waitUntil(cache.put(cacheKey, cacheable)); + } + } + + if (!upstream.ok) { + return new Response(unavailable(upstream.status), { + status: 502, + headers: { "Content-Type": contentType }, + }); + } + + const headers = new Headers({ + "Content-Type": contentType, + "Cache-Control": "public, max-age=600", + "X-Content-Type-Options": "nosniff", + }); + + console.log( + JSON.stringify({ + event: "install_script", + script: path, + ua: request.headers.get("User-Agent") ?? "", + ref: request.headers.get("Referer") ?? "", + }), + ); + + return new Response(upstream.body, { status: 200, headers }); +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const url = new URL(request.url); + + if (url.pathname in INSTALLERS) { + if (request.method !== "GET" && request.method !== "HEAD") { + return new Response("Method Not Allowed", { + status: 405, + headers: { Allow: "GET, HEAD" }, + }); + } + return serveInstaller(request, url.pathname, ctx); + } + + if (url.pathname === "/health") { + return new Response("ok\n", { headers: { "Content-Type": "text/plain" } }); + } + + return env.ASSETS.fetch(request); + }, +} satisfies ExportedHandler; diff --git a/frontend/wrangler.jsonc b/frontend/wrangler.jsonc new file mode 100644 index 0000000..5952a76 --- /dev/null +++ b/frontend/wrangler.jsonc @@ -0,0 +1,26 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "kigi-frontend", + "main": "worker/index.ts", + "compatibility_date": "2026-07-01", + + // Assets are matched first and served directly; anything without a matching + // file falls through to the Worker (that is how /install.sh and /install.ps1 + // reach it). The SPA fallback applies to the explicit env.ASSETS.fetch() call. + "assets": { + "directory": "./dist", + "binding": "ASSETS", + "not_found_handling": "single-page-application" + }, + + "workers_dev": false, + "routes": [ + { "pattern": "kigicli.dev", "custom_domain": true }, + { "pattern": "www.kigicli.dev", "custom_domain": true } + ], + + "observability": { + "enabled": true, + "head_sampling_rate": 1 + } +}