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
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:
@@ -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') {
|
||||
|
||||
@@ -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 下为空',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user