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 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-13 13:35:58 -04:00
1 parent 48306fcd16
commit 87c42fde0e
14 files changed
+76 -16

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -1,4 +1,4 @@
import { expect, test, type Page } from "@playwright/test";
import { expect, test, type Page } from "./fixtures.ts";
const PAGE = "/";
+16
View File
@@ -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 },
],
});
+1 -1
View File
@@ -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)" };
+1 -1
View File
@@ -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.";
+1 -1
View File
@@ -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"];
+16 -2
View File
@@ -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));
+1 -1
View File
@@ -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"];
+1 -1
View File
@@ -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"];
+3 -2
View File
@@ -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);
+10 -3
View File
@@ -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<string> {
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;
}
+1 -1
View File
@@ -16,5 +16,5 @@
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
"include": ["src", "worker"]
}
+20
View File
@@ -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<Response> {
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" } });
},
};
+3 -1
View File
@@ -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"]
}
}