Merge remote-tracking branch 'origin/master' into feat/pricing-plan
Project CI / Backend tests (pull_request) Failing after 48s
Project CI / Native shell tests (pull_request) Failing after 3m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m21s
Project CI / Frontend tests (pull_request) Failing after 47s
Project CI / AI game creator shell web tests (pull_request) Failing after 39s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m23s
Project CI / Repository checks (pull_request) Successful in 6m31s

# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
2026-10-05 10:50:30 +08:00
165 changed files with 13827 additions and 1670 deletions
+50 -10
View File
@@ -1101,6 +1101,7 @@ function route(pathname, method, url, requestBody = '') {
publicationRevision: versions[0]?.versionNumber ?? 0,
createdAt: iso(60 * 24 * 20),
updatedAt: iso(60 * 3),
deletedAt: null,
versions,
});
const version = (versionId, versionNumber, status, bytes) => ({
@@ -1115,17 +1116,56 @@ function route(pathname, method, url, requestBody = '') {
reviewReason: null,
entryUrl: `https://games.example.com/${versionId}/index.html`,
});
const allGames = [
game('game_1001', '陶泥小世界', '阿陶', 'published', 345, [
version('ver_1001_7', 7, 'published', 18_400_000),
version('ver_1001_6', 6, 'published', 17_900_000),
]),
game('game_1002', '纸片拼图计划', '小满', 'suspended', 96, [
version('ver_1002_3', 3, 'suspended', 5_120_004),
]),
game('game_1003', '三消工坊', '老周', 'unpublished', 12, []),
{
...game('game_1004', '已删除的试作', '阿陶', 'unpublished', 4, []),
status: 'deleted',
deletedAt: iso(60 * 2),
},
];
const limit = Math.min(
Math.max(Number(url.searchParams.get('limit') ?? 50) || 50, 1),
200,
);
const keyword = (url.searchParams.get('keyword') ?? '')
.trim()
.toLowerCase();
const owner = (url.searchParams.get('owner') ?? '').trim();
const status = (url.searchParams.get('status') ?? '').trim();
const offset = Number(url.searchParams.get('cursor') ?? 0) || 0;
// 状态/关键词/作者过滤与游标语义跟服务端保持一致:默认排除已删除作品。
const filtered = allGames.filter((entry) => {
if (status === 'deleted') {
if (entry.status !== 'deleted') return false;
} else if (status) {
if (entry.status !== status || entry.deletedAt) return false;
} else if (entry.deletedAt) {
return false;
}
if (owner && entry.author.id !== owner) return false;
if (
keyword &&
!entry.title.toLowerCase().includes(keyword) &&
!entry.gameId.toLowerCase().includes(keyword) &&
!entry.author.id.toLowerCase().includes(keyword)
) {
return false;
}
return true;
});
const page = filtered.slice(offset, offset + limit);
const nextOffset = offset + page.length;
return {
games: [
game('game_1001', '陶泥小世界', '阿陶', 'published', 345, [
version('ver_1001_7', 7, 'published', 18_400_000),
version('ver_1001_6', 6, 'published', 17_900_000),
]),
game('game_1002', '纸片拼图计划', '小满', 'suspended', 96, [
version('ver_1002_3', 3, 'suspended', 5_120_004),
]),
game('game_1003', '三消工坊', '老周', 'unpublished', 12, []),
],
games: page,
nextCursor: nextOffset < filtered.length ? String(nextOffset) : null,
};
}
if (pathname === '/admin/api/editor-assets') {
+1 -1
View File
@@ -75,7 +75,7 @@ set -euo pipefail
rustc -vV > /opt/genarrative-ci/rust-cache/rustc.txt
export GITHUB_ENV=/tmp/rust-cache.env CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 CI=true
# sccache 对 CARGO_*(除 jobserver/jobs 等特例)参与 hash,必须与 workflow 对齐。
export CARGO_HTTP_MULTIPLEXING=false CARGO_NET_RETRY=10 CARGO_TERM_COLOR=always
export CARGO_HTTP_MULTIPLEXING=false CARGO_NET_RETRY=10 CARGO_TERM_COLOR=always CARGO_TERM_QUIET=true
bash /tmp/ci-rust-cache.sh prepare
set -a
source "${GITHUB_ENV}"
@@ -49,6 +49,10 @@ const PAIRS = [
'GameDistributionCreateVersionRequest',
'GameDistributionCreateVersionRequest',
],
[
'GameDistributionUpdateGameMetadataRequest',
'GameDistributionUpdateGameMetadataRequest',
],
['GameDistributionPrivateVersion', 'GameDistributionPrivateVersion'],
['AdminGameReviewGamesQuery', 'AdminGameReviewGamesQuery'],
['AdminGameReviewGame', 'AdminGameReviewGame'],
@@ -7,8 +7,8 @@
// E2E_CHROMIUM_EXECUTABLE=<ms-playwright 里的 chrome.exe,可省略>
// E2E_ADMIN_USER=... E2E_ADMIN_PASSWORD=... npm run check:game-distribution-sandbox-e2e
//
// 覆盖:ES module 与同包资源能在 opaque sandbox 下载入;外站 fetch / WebSocket 被挡;
// localStorage / document.cookie / 父文档 DOM 都拿不到;顶层跳转与弹窗被挡;
// 覆盖:ES module 与同包资源能在 opaque sandbox 下载入;运行期 storage 兼容层可用;
// 外站 fetch / WebSocket 被挡;document.cookie / 父文档 DOM 都拿不到;顶层跳转和弹窗被挡;
// 敏感权限(定位)被拒;同时核对发行网关的 CSP / nosniff / CORS / Cookie 403 策略。
import { createRequire } from 'node:module';
import path from 'node:path';
@@ -140,14 +140,14 @@ const PROBE_APP = `import { marker } from './helper.js';
const results = { moduleLoaded: marker === 'helper-ok' };
// 探针自身也可能被浏览器直接抛错挡下(例如 opaque origin 读 cookie),
// 所以每一步单独兜底,最后无论如何都把结果 postMessage 给父页面。
try {
// opaque origin 的原生 storage 不可用,但发行网关会在游戏脚本前注入运行期兼容层。
try {
window.localStorage.getItem('probe');
results.storageBlocked = false;
window.localStorage.setItem('probe', 'ok');
results.storageAvailable = window.localStorage.getItem('probe') === 'ok';
window.localStorage.removeItem('probe');
} catch {
results.storageBlocked = true;
results.storageAvailable = false;
}
try {
@@ -479,8 +479,8 @@ document.body.appendChild(frame);
results.moduleLoaded === true,
);
check(
'localStorage 在 opaque sandbox 下不可用',
results.storageBlocked === true,
'opaque sandbox 下运行期 storage 兼容层可用',
results.storageAvailable === true,
);
check(
'document.cookie 在 opaque sandbox 下为空',
+4 -1
View File
@@ -5081,7 +5081,7 @@ for (const step of steps) {
continue;
}
console.log(`[check:native-shells] ${step.label}`);
// 成功不打印进度标签;只有在失败时才知道是哪个 step,避免每个 step 一行刷屏。
const result = spawnSync(step.command, step.args, {
cwd: process.cwd(),
stdio: 'inherit',
@@ -5102,6 +5102,9 @@ for (const step of steps) {
}
if ((result.status ?? 0) !== 0) {
console.error(
`[check:native-shells] ${step.label} failed (exit=${result.status})`,
);
process.exit(result.status ?? 1);
}
}