Repository navigation
fix(edgeone): restore server-handler detection via committed handler placeholders - #113
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| 🔵 In progress View logs |
openlist-tsworkers | c16233f | Oct 10 2026, 03:34 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
openlist-work | c16233f | Oct 10 2026, 03:36 AM |
…dgeOne preview) Three stacked issues made /manifest.json the only failing request: 1. Frontend #162 (2025-08-08) repointed index.html from /static/manifest.json to /manifest.json to match the Go backend dynamic route (OpenList server/router.go:36), but the dist still only ships static/manifest.json - no root manifest.json exists. 2. The TS worker never aligned that route, so /manifest.json fell through to the SPA fallback (text/html on Cloudflare). 3. On cookie-gated platforms (EdgeOne Makers preview domains, eo_time), every other request carries the auth cookie while the PWA manifest fetch is made without any credentials by Chromium - so manifest.json alone got 401. Fix: - fetch-frontend.mjs: copy static/manifest.json -> manifest.json at build time (covers static-first platforms like EdgeOne Pages) and add crossorigin="use-credentials" to the manifest <link> so the browser sends cookies; both idempotent, applied to every dist source path. - assets.ts: new ensureManifestCredentials() applied to all HTML served by getIndexHtmlWithCdn (covers CDN-sourced index.html, path B), plus a /manifest.json -> /static/manifest.json 302 route aligned with the Go router for worker-served traffic. - assets.test.ts: 5 new cases for the attribute patch and the CDN path. Verified: server suite 184/184, tsc --noEmit clean, fetch-frontend end-to-end produces dist/manifest.json and a patched index.html.
…placeholders EdgeOne Pages decides whether a project has a Node backend by scanning the repository content, not the post-build filesystem: with the handler artifact gitignored (PR #93), detection fails ("No server-handler detected, generating routes.json for pure project") and /api/* falls into the static rewrites returning index.html - while functions/kv-* keep working. - scripts/build-edge.mjs: emit the bundled handler to the officially documented node-functions/[[default]].js in addition to the historical cloud-functions/[[default]].js; the deploy-time build overwrites both with the fresh ~2MB bundle, so runtime always executes the latest code. - cloud-functions/[[default]].js + node-functions/[[default]].js: track a tiny, never-changing placeholder (valid onRequest export returning 503). It exists only so platform detection sees a server handler; the deploy build overwrites it with the real bundle on the build filesystem. Merge conflicts on artifacts are structurally impossible and contributors never need to touch these files. This supersedes both PR #93's gitignored-artifact approach and an interim full-artifact-committed one. - middleware.js: export config ({ matcher: [{ source: "/:path*" }] }) as the platform expects, silencing "Could not find config in middleware file, using defaults". - .github/workflows/build-verify.yml: assert the tracked entries are still the placeholder so accidentally committing a full build fails CI with a clear message. - .gitignore: stop ignoring the two handler entry files. Verified on a live EdgeOne deployment: /api/public/settings returns JSON again after deploying this change.
dd4db95 to
c16233f
Compare
pikachuren
left a comment
There was a problem hiding this comment.
🙏 感谢贡献这个 PR!以下是对 #113 fix(edgeone): repair /manifest.json 401 + restore server-handler detection 的代码评审。
⚠️ 注意:本 PR 已在 2026-10-10 合并,本评审为事后回顾,供参考与后续维护用。
✅ 总体评价
建议:Approve(已合并)
⭐⭐⭐⭐⭐ — 两个问题的根因诊断精确,修复方案分层得当(构建期 + 运行期双保险),新增测试覆盖了核心路径,CI 3/3 通过。
🎯 问题一:/manifest.json 401 修复
正确性评估 ✅
修复思路完全正确:
- 根因:Chromium 对
<link rel="manifest">的请求默认以 no-credentials 模式发出,EdgeOne Pages 预览域名的 Cookie 鉴权拦截了该请求。 - 构建期补丁(
fetch-frontend.mjs: patchManifestCredentials()):在dist/index.html的 manifest link 上补crossorigin="use-credentials",EdgeOne 静态直出场景覆盖。 - 运行期兜底(
assets.ts: ensureManifestCredentials()):对从 CDN 拉取的index.html(路径 B)同样打补丁,两条路径都覆盖到了。 - 文件复制(
fetch-frontend.mjs: copyManifestToRoot()):把dist/static/manifest.json复制到dist/manifest.json,对齐前端 #162 的引用路径。 - 302 兜底路由(
assets.ts: assetsRouter.get("/manifest.json")):Worker 模式下静态层找不到文件时 302 到/static/manifest.json,注释已写明"正常情况不走到这条路由"。
代码实测 ✅
在真实 Node.js 22 下执行 ensureManifestCredentials 逻辑,5 个测试用例全部通过:
PASS self-close standard → crossorigin="use-credentials" />
PASS no self-close → crossorigin="use-credentials">
PASS already has crossorigin → 原样返回(不重复添加)
PASS not manifest link → 原样返回
PASS no manifest in html → 原样返回
💡 小建议(P2,已合并,供后续参考)
[P2] 302 fallback 的 PWA scope 边角场景
W3C App Manifest 规范 §7.3:若 manifest.json 没有显式声明 scope 字段,scope 默认取 manifest 的 response URL(经 redirect 后)对应目录。若实际走到了 302 路由(即 dist/manifest.json 未能在静态层命中),response URL = /static/manifest.json,默认 scope 为 /static/,PWA 会被限制在该子树内。
风险程度:低——copyManifestToRoot() 正常情况已把文件复制到根,302 路由理论上不会触发;且现代 PWA 的 manifest.json 通常都有 "scope": "/"。若确认前端产物的 manifest.json 含 "scope": "/",可忽略;否则建议在 assetsRouter 中把 302 改为代理/内联(fetch 内容后直接返回 200),彻底规避 response URL 变更。
[P2] ensureManifestCredentials 对含 > 的属性值理论漏打
正则 /<link\b([^>]*)>/gi 在属性值含 > 时(如 data-val="a>b")会在第一个 > 截断,导致 rel="manifest" 在捕获组内不可见,补丁不生效。
实测结果:
in: <link data-val="a>b" rel="manifest" href="/manifest.json">
out: <link data-val="a>b" rel="manifest" href="/manifest.json"> ← 未处理
风险程度:极低——Vite/Rollup 生成的 index.html 不会在 link 属性中出现 >;此边界情况在现实中几乎不存在,记录即可。
🎯 问题二:EdgeOne server-handler 检测修复
正确性评估 ✅
双目录策略(cloud-functions/ + node-functions/)非常务实:新旧 EdgeOne CLI 版本分别扫描不同目录,两个位置并存覆盖新旧识别逻辑,思路扎实。
- 占位文件头部
// [PLACEHOLDER]标记清晰,后续贡献者不会误以为是功能代码。 .gitignore从cloud-functions/整目录改为只排除生产产物的大段变更,.js占位入库,逻辑正确。build-edge.mjs在构建时copyFileSync覆盖占位,两处同步,无需手动维护。
CI 防护评估 ✅ 并附建议
build-verify.yml 中的断言:
git show "HEAD:$f" | head -1 | grep -q "PLACEHOLDER"能在误提交产物后推送时触发、失败 CI 拦截——这是事后检测,可以发现问题。
[P2] 可加强的防护方向(供参考)
CI 检测时文件已推送,需要额外的 revert 操作。更早期的防护选项:
# 方案一:pre-commit hook(本地拦截,无需网络)
# .git/hooks/pre-commit(或 husky)
for f in "cloud-functions/[[default]].js" "node-functions/[[default]].js"; do
if git diff --cached -- "$f" | wc -c | grep -qv "^0$"; then
if ! git show :0:"$f" | head -1 | grep -q "PLACEHOLDER"; then
echo "ERROR: $f 疑似误提交了构建产物!"
exit 1
fi
fi
done# 方案二:.gitattributes 大文件提醒(较宽松,不强制)
cloud-functions/[[default]].js filter=placeholder-guard
node-functions/[[default]].js filter=placeholder-guard
如果贡献者较多、容易出现误提交,方案一比 CI 更早拦截。
🧪 测试覆盖评估
assets.test.ts 新增 6 个测试(4 个单元 + 1 个集成):
| 测试 | 结论 |
|---|---|
| 补 crossorigin | ✅ |
| 已有 crossorigin 不重复添加 | ✅ |
| 无 manifest 链接时原样返回 | ✅ |
| rel 在前(属性顺序不固定) | ✅ |
| CDN HTML(路径 B)也被补上 | ✅ |
覆盖了主干路径。建议(P3):可以补一个 > 在属性值中的测试用例,明确记录该边界行为(即使不修复)。
📐 middleware.js 变更
新增 export const config = { matcher: [{ source: "/:path*" }] } 消除 EdgeOne CLI 的 Could not find config in middleware file, using defaults 警告,且显式声明的值与默认值一致,不影响任何现有行为。✅ 低风险、正确。
📋 文件变更汇总
| 文件 | 评估 |
|---|---|
scripts/fetch-frontend.mjs |
✅ 构建期双补丁,逻辑清晰 |
src/backend/server/assets.ts |
✅ ensureManifestCredentials 实现正确,302 兜底合理 |
src/backend/server/assets.test.ts |
✅ 6 个新增测试覆盖主干路径 |
scripts/build-edge.mjs |
✅ 双目录复制,简洁 |
cloud-functions/[[default]].js |
✅ 占位正确,注释详尽 |
node-functions/[[default]].js |
✅ 同上 |
.gitignore |
✅ 逻辑已更新匹配新策略 |
.github/workflows/build-verify.yml |
✅ CI 断言覆盖新策略 |
middleware.js |
✅ 低风险修复 |
总结
两个根因修复均正确,实现分层合理(构建期 + 运行期 + 静态兜底),新增测试覆盖核心路径,CI 通过。
唯一值得关注的后续点:
- 如果前端产物的
manifest.json没有显式"scope": "/",建议将 Worker 模式下的/manifest.json路由从 302 改为 200 代理(规避 response URL 变更对 PWA scope 的影响)。 - 可以考虑加 pre-commit hook 更早防止误提交占位文件的覆盖产物。
两条均非阻塞项。✅
🤖 AI 自动审核声明
本评论由 AI 辅助的自动化评审生成(基于 PR diff 静态分析 + Node.js 22 实测执行
ensureManifestCredentials逻辑),仅供参考。
如有疑问或需要进一步讨论,欢迎在此 PR 下回复。重要提醒:AI 审查不能替代人工审查,最终合并决策请由有权限的维护者负责。
PR: fix(edgeone): repair /manifest.json 401 + restore server-handler detection
问题一:/manifest.json 401(cookie-gated 部署)
部署在 EdgeOne Pages(Makers)预览域名后的站点,
/manifest.json返回 401 Authorization Required,而站点其它所有资源(HTML / JS / CSS / API)全部正常。DevTools 中表现为孤立的 manifest 401,PWA 安装与图标解析失败。根因(三个因素叠加)
1. 前端上游 #162 改了引用路径,dist 里文件没跟上
OpenList-Frontend 提交
414ab3d(#162,2025-08-08)把 index.html 的 manifest 引用从/static/manifest.json改为/manifest.json。该改动是配合 Go 后端的——Go 版有专门的动态路由(OpenList仓库server/router.go:36):但前端 dist 中文件仍位于
static/manifest.json,根路径不存在manifest.json。纯静态 / Worker 部署没有这条路由,链接落空。旧版部署(链接与文件同在/static/manifest.json)不受影响,前端更新后才暴露——这就是「之前好着、现在坏了」的原因。2. TS Worker 未对齐 Go 版路由
OpenList-Worker 没有实现
/manifest.json路由,请求落到 SPA 兜底:Cloudflare 上返回text/html(PWA 解析失败但不报错),EdgeOne 上被 rewrite 吞掉。3. 平台 Cookie 鉴权 × Chromium manifest 请求不带凭证
EdgeOne Makers 预览域名(
*.edgeone.cool)使用平台级访问控制(eo_time签名,Cookie 或 query 携带)。浏览器其它请求都带 Cookie,唯独 Chromium 对 PWA manifest 的 fetch 默认不带任何凭证(同域 Cookie 也不带),因此 manifest 请求在鉴权层被拦为 401——状态码恰好掩盖了「路径本身也不存在」的问题。问题二:/api/* 全部返回 index.html(server-handler 检测失败)
EdgeOne Pages 构建日志显示:
/api/*返回 200 + text/html(SPA 壳):云函数产物明明生成了(✓ Edge build complete),但 EdgeOne CLI 没有把cloud-functions/识别为 server-handler 目录,整个项目被当成「纯静态项目」,/api/*落入rewrites: /* → /index.html。实测(带 Cookie):/kv-get(functions/源码目录函数)正常返回 JSON,而/api/ping、/api/public/settings全部返回 HTML —— catch-all 后端整体缺席。根因:EdgeOne 平台检测 server-handler 依据的是 Git 仓库里的文件,而不是构建后文件系统上的产物。历史背景:
cloud-functions/[[default]].js曾是入库产物(PRe0923799还配了「artifact guard with auto-commit」CI 保证 Git 里始终有新鲜产物);PRbfb963ee(#93)把它改为不入库、部署时生成——其成立前提是「平台在 buildCommand 执行之后收集 cloud-functions/ 目录」。实测该假设不成立:产物被 gitignore 后,平台检测不到 server-handler(No server-handler detected, generating routes.json for pure project),/api/*落入静态 rewrite。「之前好着」的部署正处于产物入库时代。修复(占位方案,
c16233f):调试过程中曾尝试"直接入库完整产物",虽然能修复检测,但会复活 #93 要解决的问题:协作者本地构建弄脏文件、后端每次改动都要提交 2.1MB blob、PR 产物与 CI 产物冲突。最终方案:
cloud-functions/[[default]].js、node-functions/[[default]].jsonRequest导出,返回 503),基本永不变更 → 结构上不可能产生产物冲突scripts/build-edge.mjsdist/从不入库、完全靠构建生成的机制一致).github/workflows/build-verify.yml工作原理:EdgeOne 按仓库内容检测到两处入口 → 启用 server-handler;部署构建在同路径生成完整产物覆盖占位 → 运行时执行最新后端代码。协作者永远不需要碰这两个文件,产物冲突从结构上消失。
运行验证:已在 EdgeOne 部署实测通过 ——
/api/public/settings恢复返回 JSON。修复内容
scripts/fetch-frontend.mjsreplaceDist,覆盖全部 dist 来源路径):①copyManifestToRoot()把static/manifest.json复制到根,补齐 #162 指向的路径(EdgeOne Pages 静态直出不经过 Worker,必须构建期解决);②patchManifestCredentials()给<link rel="manifest">补crossorigin="use-credentials",让浏览器携带 Cookie。两者均幂等、失败不阻塞构建。src/backend/server/assets.tsensureManifestCredentials():运行期对getIndexHtmlWithCdn所有返回的 HTML(含从 CDN 拉取的 index.html,即路径 B——构建期补丁覆盖不到的来源)做同样的属性补齐;② 新增assetsRouter.get("/manifest.json")302 →/static/manifest.json,对齐 Go 版server/router.go:36的语义,兜住静态层未命中的 Worker 流量。src/backend/server/assets.test.ts验证
tsx --test src/backend/server/*.test.ts:184/184 通过(assets 单文件 31/31)tsc -p tsconfig.json --noEmit:无错误@openlist-frontend/openlist-frontend@4.2.6)跑fetch-frontend.mjs,产物dist/manifest.json存在,dist/index.html中为<link href="/manifest.json" rel="manifest" crossorigin="use-credentials">X-EOP-MSG: eo_time missing;浏览器地址栏直接访问/static/manifest.json返回 200(Cookie 随导航携带),仅 manifest fetch 被拦影响面 / 兼容性
crossorigin="use-credentials":同源请求无 CORS 副作用;在无鉴权平台(Cloudflare、自定义域名、Go 版)上无行为差异static/manifest.json存在且根路径缺失时执行,幂等提交信息(已写入 commit message)
待办
/manifest.json200