From 87c42fde0e3976e5f70c3c981f19c00552a15ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 13 Oct 2026 13:35:58 -0400 Subject: [PATCH] Site: read the newest version from Gitea; links follow releases/latest [skip ci] A worker answers /release.json from the Gitea API (Gitea sends no CORS headers, so the page cannot ask it directly). Co-Authored-By: Claude Opus 5.5 --- agents.md | 2 +- site/e2e/film.spec.ts | 2 +- site/e2e/fixtures.ts | 16 ++++++++++++++++ site/e2e/night.spec.ts | 2 +- site/e2e/play.spec.ts | 2 +- site/e2e/rhythm.spec.ts | 2 +- site/e2e/text.spec.ts | 18 ++++++++++++++++-- site/e2e/touch.spec.ts | 2 +- site/e2e/tour.spec.ts | 2 +- site/src/main.ts | 5 +++-- site/src/release.ts | 13 ++++++++++--- site/tsconfig.json | 2 +- site/worker/index.ts | 20 ++++++++++++++++++++ site/wrangler.jsonc | 4 +++- 14 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 site/e2e/fixtures.ts create mode 100644 site/worker/index.ts diff --git a/agents.md b/agents.md index bd2bb33..a51611a 100644 --- a/agents.md +++ b/agents.md @@ -173,7 +173,7 @@ tests/scripts/show-fullscreen.sh 真窗口 ElyShow:F5 放映的窗口盖住 tests/scripts/types.sh packaging/types.tsv 的文件类型在 Linux 桌面项、Windows 安装程序与 macOS 文档类型里一致,从不设为默认 tests/scripts/layout.sh 真窗口:六个应用在 640、800、1100 宽下无元素越出窗口左右,留截图(需辅助功能与屏幕录制权限) tests/scripts/ooxml.py <用例> <文件> 不经 LibreOffice 读 DOCX、XLSX、PPTX 并按自算的预期核对(往返用例调用) -cd site && pnpm deploy 官网构建并经 wrangler 发到 Cloudflare Workers(只有静态资源),地址 https://ely-office.zhangyanghaha0407.workers.dev +cd site && pnpm deploy 官网构建并经 wrangler 发到 Cloudflare Workers(静态资源,另有 `site/worker` 只答 `/release.json`:Gitea API 的最新版本,缓存 5 分钟;下载链接指向 `releases/latest`),地址 https://ely-office.zhangyanghaha0407.workers.dev cd site && pnpm test && pnpm e2e 官网单测(几何、道路连通)与 Playwright 端到端(悬停、聚焦、按压、命中归属) cd site/video/scenes && npm run verify 发布视频:群像与离场两个渲染器在切场处屏幕角点一致,离场全程各屏播自己的录屏帧(真页面渲染) cargo check --workspace diff --git a/site/e2e/film.spec.ts b/site/e2e/film.spec.ts index 4d80c07..4a1c9cc 100644 --- a/site/e2e/film.spec.ts +++ b/site/e2e/film.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const PAGE = "/"; diff --git a/site/e2e/fixtures.ts b/site/e2e/fixtures.ts new file mode 100644 index 0000000..2046269 --- /dev/null +++ b/site/e2e/fixtures.ts @@ -0,0 +1,16 @@ +import { test as base } from "@playwright/test"; + +export { expect, type Page } from "@playwright/test"; + +export const VERSION = "9.9.9"; + +// Every page reads this version. +export const test = base.extend<{ release: void }>({ + release: [ + async ({ context }, use) => { + await context.route("**/release.json", (r) => r.fulfill({ json: { version: VERSION } })); + await use(); + }, + { auto: true }, + ], +}); diff --git a/site/e2e/night.spec.ts b/site/e2e/night.spec.ts index 8dd3c1c..59f3a87 100644 --- a/site/e2e/night.spec.ts +++ b/site/e2e/night.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const PAGE = "/"; const DESK = { light: "rgb(230, 230, 226)", dark: "rgb(58, 58, 58)" }; diff --git a/site/e2e/play.spec.ts b/site/e2e/play.spec.ts index e470a1e..36fbf3c 100644 --- a/site/e2e/play.spec.ts +++ b/site/e2e/play.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const TREE = "Open a Markdown file's history in ElyTree."; diff --git a/site/e2e/rhythm.spec.ts b/site/e2e/rhythm.spec.ts index 2124ff2..13d62b3 100644 --- a/site/e2e/rhythm.spec.ts +++ b/site/e2e/rhythm.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const APPS = ["ElyDoc", "ElyTable", "ElyShow", "ElyPDF", "ElyTree", "ElyMark"]; diff --git a/site/e2e/text.spec.ts b/site/e2e/text.spec.ts index 9ff1a21..f6efaf5 100644 --- a/site/e2e/text.spec.ts +++ b/site/e2e/text.spec.ts @@ -1,9 +1,9 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, VERSION, type Page } from "./fixtures.ts"; import { TEXT, type Key, type Lang } from "../src/text.ts"; const REPO = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice"; const APPS = ["ElyDoc", "ElyTable", "ElyShow", "ElyPDF", "ElyTree", "ElyMark"]; -const RELEASES = `${REPO}/releases/tag/v0.1.4`; +const RELEASES = `${REPO}/releases/latest`; test.use({ reducedMotion: "reduce" }); @@ -93,6 +93,20 @@ test.describe("English locale", () => { expect(files).toEqual([]); }); + test("the download area names the newest version", async ({ page }) => { + await page.goto("/"); + await expect(page.locator("#download h2")).toHaveText(`Ely Office ${VERSION}`); + }); + + test("a failed version read shows as an error", async ({ page }) => { + await page.route("**/release.json", (r) => r.fulfill({ status: 502, body: "Gitea answered 500" })); + await page.goto("/"); + await expect.poll(() => errors.length).toBe(1); + expect(errors[0]).toContain("Gitea answered 500"); + await expect(page.locator("#download h2")).toHaveText("Ely Office"); + errors = []; + }); + test("every external link answers @net", async ({ page, request }) => { await page.goto("/"); const hrefs = await page.locator("a[href^='http']").evaluateAll((as) => as.map((a) => (a as HTMLAnchorElement).href)); diff --git a/site/e2e/touch.spec.ts b/site/e2e/touch.spec.ts index c25cf2d..7965fea 100644 --- a/site/e2e/touch.spec.ts +++ b/site/e2e/touch.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const CELL = 28; const APPS = ["ElyDoc", "ElyTable", "ElyShow", "ElyPDF", "ElyTree", "ElyMark"]; diff --git a/site/e2e/tour.spec.ts b/site/e2e/tour.spec.ts index c618259..9d8e6b5 100644 --- a/site/e2e/tour.spec.ts +++ b/site/e2e/tour.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page } from "./fixtures.ts"; const APPS = ["ElyDoc", "ElyTable", "ElyShow", "ElyPDF", "ElyTree", "ElyMark"]; diff --git a/site/src/main.ts b/site/src/main.ts index b20d2ae..1d42e73 100644 --- a/site/src/main.ts +++ b/site/src/main.ts @@ -8,7 +8,7 @@ import { rhythm } from "./rhythm.ts"; import { playground } from "./play.ts"; import { film } from "./film.ts"; import { said, speak, type Lang } from "./text.ts"; -import { RELEASES, VERSION } from "./release.ts"; +import { RELEASES, latest } from "./release.ts"; const REPO = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice"; @@ -35,7 +35,8 @@ const shelf = props(); const footer = document.createElement("footer"); footer.id = "download"; const version = document.createElement("h2"); -version.textContent = `Ely Office ${VERSION}`; +version.textContent = "Ely Office"; +void latest().then((v) => (version.textContent = `Ely Office ${v}`)); const get = said("a", "get"); get.href = RELEASES; footer.append(version, said("p", "platforms"), said("p", "needs"), said("p", "unsigned"), get); diff --git a/site/src/release.ts b/site/src/release.ts index 0c15cf9..446de32 100644 --- a/site/src/release.ts +++ b/site/src/release.ts @@ -1,3 +1,10 @@ -// This version's release page holds every download. -export const VERSION = "0.1.4"; -export const RELEASES = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice/releases/tag/v0.1.4"; +// Gitea redirects this to the newest release. +export const RELEASES = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice/releases/latest"; + +/** The newest release's version, from the worker. */ +export async function latest(): Promise { + const res = await fetch("/release.json"); + if (!res.ok) throw new Error(`release.json: ${res.status} ${await res.text()}`); + const { version } = (await res.json()) as { version: string }; + return version; +} diff --git a/site/tsconfig.json b/site/tsconfig.json index 1035108..ed1b6fd 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -16,5 +16,5 @@ "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "worker"] } diff --git a/site/worker/index.ts b/site/worker/index.ts new file mode 100644 index 0000000..1362c4e --- /dev/null +++ b/site/worker/index.ts @@ -0,0 +1,20 @@ +// Answers /release.json with the newest release. +const API = "https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/ElyOffice/releases/latest"; + +type Cached = RequestInit & { cf: { cacheTtl: number; cacheEverything: boolean } }; + +export default { + async fetch(): Promise { + const init: Cached = { + headers: { accept: "application/json", "user-agent": "ely-office-site" }, + cf: { cacheTtl: 300, cacheEverything: true }, + }; + const res = await fetch(API, init); + if (!res.ok) return new Response(`Gitea answered ${res.status}`, { status: 502 }); + const { tag_name: tag } = (await res.json()) as { tag_name: unknown }; + if (typeof tag !== "string" || !/^v\d+\.\d+\.\d+$/.test(tag)) { + return new Response(`Gitea's latest tag is ${JSON.stringify(tag)}`, { status: 502 }); + } + return Response.json({ version: tag.slice(1) }, { headers: { "cache-control": "public, max-age=300" } }); + }, +}; diff --git a/site/wrangler.jsonc b/site/wrangler.jsonc index 8d48d32..c3e6fee 100644 --- a/site/wrangler.jsonc +++ b/site/wrangler.jsonc @@ -1,8 +1,10 @@ { "name": "ely-office", + "main": "worker/index.ts", "compatibility_date": "2026-10-08", "assets": { "directory": "./dist", - "not_found_handling": "404-page" + "not_found_handling": "404-page", + "run_worker_first": ["/release.json"] } }