Phaser 项目一键发布:发布前自动构建与打包
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

- AGC:export_local_project_package 改为发布前构建——已有可玩入口直接打包;否则解析 game/(Phaser 4 + Vite 脚手架)或项目根的 npm build 脚本,缺 game/node_modules 时先跑 project.bootstrap(npm install),再走 project.verify 的受控 npm 运行器执行 npm run build,最后校验入口并打包;安装/构建失败返回带日志尾部的可操作错误。
- 新增 resolve_publish_build_plan 纯函数与用例:game 子工程优先并要求先装依赖、根 npm 工程回退、没有可构建工程时失败关闭。
- 后端:发行网关根路径(含尾斜杠)等价于 index.html,路由级用例覆盖 Cookie 拒绝门与根路径;生产仍由每游戏 origin 把根路径映射到该游戏入口。
- 脚本:check:game-distribution-media-e2e 支持 E2E_PACKAGE_ZIP 直接发布真实构建产物,断言从包内派生入口/资源,本地审核入口改为发行网关路径。
- 文档:玩法链路与实施计划记录「作者不构建、不打 ZIP」的 Phaser 发布口径与验证证据。
- 验证:真实 Phaser 4.2.1 + Vite 7 构建产物(相对引用)经发布链路后网关 index.html 200 / assets 1,388,719 B 200,播放页在 allow-scripts 沙箱 iframe 内渲染 PHASER-PUBLISH-OK 且点击交互生效。
This commit is contained in:
2026-09-22 20:38:00 +08:00
parent 944ae9a6eb
commit 57a72f652c
7 changed files with 426 additions and 15 deletions
@@ -224,10 +224,29 @@ pub fn router(state: AppState) -> Router<AppState> {
"/api/game-distribution/releases/{game_id}/{*asset_path}",
get(serve_release_asset),
)
// 根路径等价于入口页:生产由发行来源(每游戏 origin)把 `/` 映射到 index.html,
// 本地直连网关或入口直接填网关地址时也必须能打开游戏。
.route(
"/api/game-distribution/releases/{game_id}",
get(serve_release_entry),
)
.route(
"/api/game-distribution/releases/{game_id}/",
get(serve_release_entry),
)
.merge(protected)
.merge(admin)
}
/// 发行网关根路径:等价于请求该游戏的 `index.html`。
async fn serve_release_entry(
state: State<AppState>,
headers: HeaderMap,
Path(game_id): Path<String>,
) -> Result<Response, AppError> {
serve_release_asset(state, headers, Path((game_id, "index.html".to_string()))).await
}
/// 公开发行网关。
///
/// 只服务当前已公开版本的游戏文件,路径必须在白名单内容类型内;私有 ZIP 对象和
@@ -1902,6 +1921,7 @@ mod tests {
// 未在白名单内的扩展名直接 404,不进入 SpacetimeDB 与对象存储。
let unknown_extension = app
.clone()
.oneshot(
Request::builder()
.uri("/api/game-distribution/releases/game_1/payload.bin")
@@ -1911,6 +1931,29 @@ mod tests {
.await
.expect("路由响应");
assert_eq!(unknown_extension.status(), StatusCode::NOT_FOUND);
// 根路径(含尾斜杠)等价于入口页:生产由发行来源映射,直接连网关时也必须能开。
for uri in [
"/api/game-distribution/releases/game_1",
"/api/game-distribution/releases/game_1/",
] {
let with_cookie = app
.clone()
.oneshot(
Request::builder()
.uri(uri)
.header("cookie", "genarrative.refresh-token=1")
.body(Body::empty())
.expect("请求"),
)
.await
.expect("路由响应");
assert_eq!(
with_cookie.status(),
StatusCode::FORBIDDEN,
"{uri} 必须先过 Cookie 拒绝门,而不是 404"
);
}
}
#[tokio::test]