Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
562 lines
23 KiB
Markdown
562 lines
23 KiB
Markdown
# debug/release 一键运行模式 Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 给 `framework/config` 增加一个一键运行模式开关:自动跟随 Cocos 构建类型(debug/release)、可选手动覆盖;release 强制 `prod` 并完全锁死 URL 覆盖;并把 `isDebugger` 接到 `NetClient` 的收发包日志,让 debug 模式真正可观测。
|
||
|
||
**Architecture:** 新增纯函数 `resolveRuntimeMode`(构建标志 + 可选覆盖 → 'debug'|'release')。`resolveBootstrap` 新增必填 `mode`:release 分支只用 `[BUILD_IDENTITY, nativeSettings]` 身份来源且强制 `PROFILES.prod`,从源头不构造 URL 来源;debug 分支维持现状。`NetClient` 增可选 `debug`/`logger`,在统一的发送/接收切面打印帧。
|
||
|
||
**Tech Stack:** TypeScript(Cocos 3.8.8);测试 Node 20 + `tsx` 跑 `node:test`,沿用 `npm run test:framework` / `npm run typecheck:framework`(cwd `cocoscreator_projects/`)。
|
||
|
||
> 依据 spec `docs/superpowers/specs/2026-06-28-runtime-mode-design.md`。当前分支 `feat/runtime-mode`。
|
||
> 命名红线:内部命名现代化,外部契约字符串(协议字段/原生接口名/远程配置键)逐字不变。
|
||
|
||
**所有 npm 命令工作目录 `cocoscreator_projects/`;git 命令工作目录 `G:/Works/YouleGamesCocosCreator`。导入必须带 `.ts` 后缀(tsconfig `allowImportingTsExtensions`)。`git commit` 的 CRLF 警告可忽略,用 `git log --oneline -1` 确认。**
|
||
|
||
---
|
||
|
||
## 文件结构(本计划涉及)
|
||
|
||
```
|
||
cocoscreator_projects/
|
||
├─ YouleNexus/assets/framework/
|
||
│ ├─ config/runtime-mode.ts # 新增:RuntimeMode + MODE_OVERRIDE + resolveRuntimeMode
|
||
│ ├─ config/bootstrap.ts # 修改:BootstrapOptions/Result 加 mode/isDebugger + release 分支
|
||
│ └─ net/net-client.ts # 修改:NetClientOptions 加 debug/logger + 收发日志切面
|
||
├─ framework-tests/config/
|
||
│ ├─ runtime-mode.test.ts # 新增
|
||
│ └─ bootstrap.test.ts # 修改:现有 6 用例补 mode + 新增 release 用例
|
||
└─ framework-tests/net/net-client.test.ts # 修改:追加 debug 日志 2 用例
|
||
docs/guides/配置与调试指南.md # 修改:新增模式切换章节 + 更新示例/清单/速查表
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1: config/runtime-mode.ts — 运行模式解析
|
||
|
||
**Files:**
|
||
- Create: `cocoscreator_projects/YouleNexus/assets/framework/config/runtime-mode.ts`
|
||
- Create: `cocoscreator_projects/framework-tests/config/runtime-mode.test.ts`
|
||
|
||
- [ ] **Step 1: 写失败测试**
|
||
|
||
创建 `framework-tests/config/runtime-mode.test.ts`:
|
||
|
||
```ts
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { resolveRuntimeMode } from '../../YouleNexus/assets/framework/config/runtime-mode.ts';
|
||
|
||
test('override 优先于构建类型', () => {
|
||
assert.equal(resolveRuntimeMode(true, 'release'), 'release');
|
||
assert.equal(resolveRuntimeMode(false, 'debug'), 'debug');
|
||
});
|
||
|
||
test('无 override:debug 构建 → debug', () => {
|
||
assert.equal(resolveRuntimeMode(true, null), 'debug');
|
||
});
|
||
|
||
test('无 override:release 构建 → release', () => {
|
||
assert.equal(resolveRuntimeMode(false, null), 'release');
|
||
});
|
||
```
|
||
|
||
- [ ] **Step 2: 跑测试确认失败** — `npm run test:framework` → FAIL(找不到 runtime-mode.ts)
|
||
|
||
- [ ] **Step 3: 实现 runtime-mode.ts**
|
||
|
||
创建 `config/runtime-mode.ts`:
|
||
|
||
```ts
|
||
export type RuntimeMode = 'debug' | 'release';
|
||
|
||
/**
|
||
* 手动覆盖运行模式。
|
||
* null = 自动跟随构建类型(推荐,发布前保持 null)。
|
||
* 'debug' / 'release' = 强制该模式(release 包里临时调试,或编辑器预览里验证正式行为)。
|
||
*/
|
||
export const MODE_OVERRIDE: RuntimeMode | null = null;
|
||
|
||
/**
|
||
* 解析运行模式:override 优先,否则由是否 debug 构建决定。
|
||
* isDebugBuild 由调用方读 Cocos 全局 DEBUG 传入(typeof DEBUG !== 'undefined' ? DEBUG : true)。
|
||
*/
|
||
export function resolveRuntimeMode(
|
||
isDebugBuild: boolean,
|
||
override: RuntimeMode | null = MODE_OVERRIDE,
|
||
): RuntimeMode {
|
||
if (override) return override;
|
||
return isDebugBuild ? 'debug' : 'release';
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 跑测试 + 类型检查** — `npm run test:framework`(含 runtime-mode 3 用例 PASS)+ `npm run typecheck:framework`(exit 0)
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd G:/Works/YouleGamesCocosCreator
|
||
git add cocoscreator_projects/YouleNexus/assets/framework/config/runtime-mode.ts cocoscreator_projects/framework-tests/config/runtime-mode.test.ts
|
||
git commit -m "feat(framework): config 运行模式解析 resolveRuntimeMode(自动+手动覆盖)
|
||
|
||
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: bootstrap.ts — 接入 mode + release 锁定
|
||
|
||
**Files:**
|
||
- Modify: `cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts`(全文替换为下方内容)
|
||
- Modify: `cocoscreator_projects/framework-tests/config/bootstrap.test.ts`(全文替换为下方内容)
|
||
|
||
> `mode` 设为**必填**。release 分支只用 `[BUILD_IDENTITY, nativeSettings]` 身份来源、强制 `PROFILES.prod`,从源头不构造 `queryStringSource`,杜绝 URL 覆盖。
|
||
|
||
- [ ] **Step 1: 改测试(先让其失败)—— 全文替换 `framework-tests/config/bootstrap.test.ts`**
|
||
|
||
```ts
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { resolveBootstrap } from '../../YouleNexus/assets/framework/config/bootstrap.ts';
|
||
import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts';
|
||
import type { ConfigFetcher, RemoteConfig } from '../../YouleNexus/assets/framework/config/remote-config.ts';
|
||
|
||
function fetcherReturning(config: RemoteConfig): ConfigFetcher & { calls: string[] } {
|
||
const calls: string[] = [];
|
||
return { calls, fetch: async (url: string) => { calls.push(url); return config; } };
|
||
}
|
||
const throwingFetcher: ConfigFetcher = {
|
||
fetch: async () => { throw new Error('net down'); },
|
||
};
|
||
|
||
const REMOTE: RemoteConfig = { data: { player_server_tcp: '5.5.5.5:5000' } };
|
||
|
||
test('debug + profile=local:用 profile.server 直连,不抓远程', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '?profile=local', fetcher, isNative: false });
|
||
assert.deepEqual(r.servers, ['ws://127.0.0.1:3088']);
|
||
assert.equal(r.rawConfig, null);
|
||
assert.equal(fetcher.calls.length, 0);
|
||
});
|
||
|
||
test('debug + prod + H5:抓远程配置产出 servers,identity 来自 defaults', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher, isNative: false });
|
||
assert.deepEqual(r.servers, ['ws://5.5.5.5:5000']);
|
||
assert.equal(r.identity.agentid, BUILD_IDENTITY.agentid);
|
||
assert.equal(r.identity.gameid, BUILD_IDENTITY.gameid);
|
||
assert.equal(fetcher.calls.length, 1);
|
||
});
|
||
|
||
test('debug + H5 显式 URL query 覆盖身份(最高优先级)', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '?agentid=999&channelid=C9', fetcher, isNative: false });
|
||
assert.equal(r.identity.agentid, '999');
|
||
assert.equal(r.identity.channelid, 'C9');
|
||
});
|
||
|
||
test('debug + 原生模式:identity 来自 window.settings', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const win = { settings: { getothername: () => 'NA', getchannelName: () => 'NC', getmarketname: () => 3 } };
|
||
const r = await resolveBootstrap({ mode: 'debug', win, search: '', fetcher, isNative: true });
|
||
assert.equal(r.identity.agentid, 'NA');
|
||
assert.equal(r.identity.channelid, 'NC');
|
||
assert.equal(r.identity.marketid, 3);
|
||
});
|
||
|
||
test('debug + 远程失败 + 有 fallbackServers → 降级使用', async () => {
|
||
const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher: throwingFetcher, isNative: false, fallbackServers: ['ws://fb:1'] });
|
||
assert.deepEqual(r.servers, ['ws://fb:1']);
|
||
});
|
||
|
||
test('debug + 远程失败 + 无 fallback → 抛 ConfigFetchError', async () => {
|
||
await assert.rejects(
|
||
resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher: throwingFetcher, isNative: false }),
|
||
/远程配置抓取失败/,
|
||
);
|
||
});
|
||
|
||
test('debug 结果含 mode/isDebugger', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher, isNative: false });
|
||
assert.equal(r.mode, 'debug');
|
||
assert.equal(r.isDebugger, true);
|
||
});
|
||
|
||
test('release:忽略所有 URL 覆盖,强制 prod,identity 不被 URL 改', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const r = await resolveBootstrap({ mode: 'release', win: {}, search: '?profile=local&agentid=999', fetcher, isNative: false });
|
||
assert.equal(r.profile.name, 'prod');
|
||
assert.deepEqual(r.servers, ['ws://5.5.5.5:5000']); // 走远程,不是 local 直连
|
||
assert.equal(r.identity.agentid, BUILD_IDENTITY.agentid); // 999 无效
|
||
assert.equal(r.isDebugger, false);
|
||
assert.equal(r.mode, 'release');
|
||
assert.equal(fetcher.calls.length, 1);
|
||
});
|
||
|
||
test('release:原生身份仍生效(URL 的 999 无效)', async () => {
|
||
const fetcher = fetcherReturning(REMOTE);
|
||
const win = { settings: { getothername: () => 'NA', getchannelName: () => 'NC', getmarketname: () => 3 } };
|
||
const r = await resolveBootstrap({ mode: 'release', win, search: '?agentid=999', fetcher, isNative: true });
|
||
assert.equal(r.identity.agentid, 'NA');
|
||
assert.equal(r.identity.channelid, 'NC');
|
||
});
|
||
```
|
||
|
||
- [ ] **Step 2: 跑测试确认失败** — `npm run test:framework` → FAIL(`mode` 类型缺失 / release 行为未实现 / 结果缺 mode·isDebugger)
|
||
|
||
- [ ] **Step 3: 实现 —— 全文替换 `YouleNexus/assets/framework/config/bootstrap.ts`**
|
||
|
||
```ts
|
||
import type { ChannelIdentity } from './identity.ts';
|
||
import { resolveIdentity } from './identity.ts';
|
||
import { BUILD_IDENTITY } from './sources/defaults.ts';
|
||
import { queryStringSource } from './sources/query-string.ts';
|
||
import { nativeSettingsSource, type NativeHost } from './sources/native-settings.ts';
|
||
import { resolveActiveProfile, PROFILES, DEFAULT_GAMESERVER, type DebugProfile } from './profiles.ts';
|
||
import { resolveServers, type ConfigFetcher, type RemoteConfig, ConfigFetchError } from './remote-config.ts';
|
||
import type { RuntimeMode } from './runtime-mode.ts';
|
||
|
||
export interface BootstrapOptions {
|
||
mode: RuntimeMode; // 运行模式(由 resolveRuntimeMode 得到)
|
||
win: NativeHost; // 原生注入宿主(≈ window)
|
||
search: string; // location.search
|
||
fetcher: ConfigFetcher; // 远程配置抓取
|
||
isNative: boolean; // 环境判定(URL 含 index.html → true,由调用方算)
|
||
fallbackServers?: string[]; // 远程失败/无地址时降级
|
||
cacheBust?: () => string; // 防缓存串注入(默认 Date.now)
|
||
}
|
||
|
||
export interface BootstrapResult {
|
||
mode: RuntimeMode;
|
||
isDebugger: boolean; // = (mode === 'debug')
|
||
identity: ChannelIdentity;
|
||
servers: string[];
|
||
rawConfig: RemoteConfig | null;
|
||
profile: DebugProfile;
|
||
}
|
||
|
||
/** 启动编排:按运行模式解析身份 + 决定服务器,产出可喂 NetClient 的结果。 */
|
||
export async function resolveBootstrap(opts: BootstrapOptions): Promise<BootstrapResult> {
|
||
const isDebug = opts.mode === 'debug';
|
||
|
||
// 模式决定 profile 与身份来源:release 强制 prod,且身份只走原生(绝不读 URL)。
|
||
const profile = isDebug ? resolveActiveProfile(opts.search) : PROFILES.prod;
|
||
const identity = isDebug
|
||
? resolveIdentity([
|
||
() => BUILD_IDENTITY,
|
||
opts.isNative ? nativeSettingsSource(opts.win) : queryStringSource(opts.search),
|
||
() => profile.identity ?? {},
|
||
queryStringSource(opts.search), // 显式 URL 覆盖(最高)
|
||
])
|
||
: resolveIdentity([
|
||
() => BUILD_IDENTITY,
|
||
nativeSettingsSource(opts.win), // release:身份只取原生,URL 一律无效
|
||
]);
|
||
|
||
const resultBase = { mode: opts.mode, isDebugger: isDebug, identity, profile };
|
||
|
||
// 服务器决策:profile 显式 server 直连捷径(仅 debug 的 local/lab 等档位会有)
|
||
if (profile.server) {
|
||
return { ...resultBase, servers: [profile.server], rawConfig: null };
|
||
}
|
||
|
||
// 否则远程配置
|
||
const gameserver = profile.gameserver ?? DEFAULT_GAMESERVER;
|
||
const bust = opts.cacheBust ? opts.cacheBust() : String(Date.now());
|
||
const url = gameserver + '?' + bust; // 防缓存,等价原 min_timestamp/ifast_random
|
||
|
||
let config: RemoteConfig;
|
||
try {
|
||
config = await opts.fetcher.fetch(url);
|
||
} catch {
|
||
if (opts.fallbackServers && opts.fallbackServers.length) {
|
||
return { ...resultBase, servers: opts.fallbackServers, rawConfig: null };
|
||
}
|
||
throw new ConfigFetchError(`远程配置抓取失败: ${url}`);
|
||
}
|
||
|
||
let servers = resolveServers(config, identity);
|
||
if (!servers.length && opts.fallbackServers && opts.fallbackServers.length) {
|
||
servers = opts.fallbackServers;
|
||
}
|
||
return { ...resultBase, servers, rawConfig: config };
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 跑测试确认通过** — `npm run test:framework`(bootstrap 9 用例全 PASS)+ `npm run typecheck:framework`(exit 0)
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd G:/Works/YouleGamesCocosCreator
|
||
git add cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts cocoscreator_projects/framework-tests/config/bootstrap.test.ts
|
||
git commit -m "feat(framework): config resolveBootstrap 接入 mode + release 锁死 URL 覆盖
|
||
|
||
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: net-client.ts — debug 收发包日志
|
||
|
||
**Files:**
|
||
- Modify: `cocoscreator_projects/YouleNexus/assets/framework/net/net-client.ts`
|
||
- Modify: `cocoscreator_projects/framework-tests/net/net-client.test.ts`(在文件末尾追加 2 个用例)
|
||
|
||
> 给 `NetClient` 加可选 `debug`/`logger`;统一经 `sendFrame()` 发送切面与 `onMessage` 入口打印帧。默认关闭,不影响现有行为/测试。
|
||
|
||
- [ ] **Step 1: 追加失败测试 —— 在 `framework-tests/net/net-client.test.ts` 末尾追加**
|
||
|
||
```ts
|
||
test('debug=true 记录 send 与 recv(注入 logger)', async () => {
|
||
const t = new FakeTransport();
|
||
const { clock } = fakeClock();
|
||
const logs: unknown[][] = [];
|
||
const client = new NetClient({
|
||
servers: 'ws://a',
|
||
transportFactory: () => t,
|
||
clock,
|
||
debug: true,
|
||
logger: (...a: unknown[]) => logs.push(a),
|
||
});
|
||
client.setIdentity(IDENTITY);
|
||
client.start();
|
||
await t.flush();
|
||
assert.ok(logs.some((l) => l[0] === '[net] →')); // 发出 player_login 被记录
|
||
t.serverPush(JSON.stringify({ data: JSON.stringify({ route: 'agent', rpc: 'player_login', data: { state: 0, playerid: 9 } }) }));
|
||
await t.flush();
|
||
assert.ok(logs.some((l) => l[0] === '[net] ←')); // 收包被记录
|
||
});
|
||
|
||
test('debug 默认关闭:logger 不被调用', async () => {
|
||
const t = new FakeTransport();
|
||
const { clock } = fakeClock();
|
||
const logs: unknown[][] = [];
|
||
const client = new NetClient({
|
||
servers: 'ws://a',
|
||
transportFactory: () => t,
|
||
clock,
|
||
logger: (...a: unknown[]) => logs.push(a),
|
||
});
|
||
client.setIdentity(IDENTITY);
|
||
client.start();
|
||
await t.flush();
|
||
t.serverPush(JSON.stringify({ data: JSON.stringify({ route: 'agent', rpc: 'player_login', data: { state: 0, playerid: 9 } }) }));
|
||
await t.flush();
|
||
assert.equal(logs.length, 0);
|
||
});
|
||
```
|
||
|
||
- [ ] **Step 2: 跑测试确认失败** — `npm run test:framework` → FAIL(`debug`/`logger` 选项不存在,logger 未被调用)
|
||
|
||
- [ ] **Step 3: 改 `net-client.ts` —— 共 5 处编辑**
|
||
|
||
3a. `NetClientOptions` 接口加两个可选字段(替换整个接口):
|
||
|
||
```ts
|
||
export interface NetClientOptions {
|
||
servers: string | string[];
|
||
transportFactory: () => Transport;
|
||
clock?: Clock;
|
||
bus?: EventBus<NetClientEvents>;
|
||
debug?: boolean; // 为真时打印每帧 send/recv
|
||
logger?: (...args: unknown[]) => void; // 日志函数,默认 console.log(便于测试注入)
|
||
}
|
||
```
|
||
|
||
3b. 在字段声明区(`private stopped = false;` 这一行后面)新增两个字段:
|
||
|
||
```ts
|
||
private stopped = false;
|
||
private debug: boolean;
|
||
private logger: (...args: unknown[]) => void;
|
||
```
|
||
|
||
3c. 构造函数体末尾(`this.watchdog = ...` 那一行后)新增两行:
|
||
|
||
```ts
|
||
this.watchdog = new HeartbeatWatchdog(RECV_TIMEOUT_MS, () => this.onRecvTimeout(), this.clock);
|
||
this.debug = opts.debug ?? false;
|
||
this.logger = opts.logger ?? console.log;
|
||
```
|
||
|
||
3d. 新增私有发送切面,并把 `send()` 改为走它。把现有:
|
||
|
||
```ts
|
||
/** 发业务包(单层信封)。 */
|
||
send(route: string, rpc: string, data: unknown): void {
|
||
this.transport?.send(encodeOutbound(route, rpc, data));
|
||
}
|
||
```
|
||
|
||
替换为:
|
||
|
||
```ts
|
||
/** 发业务包(单层信封)。 */
|
||
send(route: string, rpc: string, data: unknown): void {
|
||
this.sendFrame(encodeOutbound(route, rpc, data));
|
||
}
|
||
|
||
/** 统一发送切面:debug 时打印出站帧。 */
|
||
private sendFrame(frame: string): void {
|
||
if (this.debug) this.logger('[net] →', frame);
|
||
this.transport?.send(frame);
|
||
}
|
||
```
|
||
|
||
3e. `sendLogin()` 内的直接发送改走切面。把:
|
||
|
||
```ts
|
||
this.isSendLoginState = true;
|
||
this.transport?.send(encodeOutbound(Route.agent, 'player_login', this.identity));
|
||
```
|
||
|
||
替换为:
|
||
|
||
```ts
|
||
this.isSendLoginState = true;
|
||
this.sendFrame(encodeOutbound(Route.agent, 'player_login', this.identity));
|
||
```
|
||
|
||
3f. `onMessage(frame)` 入口打印入站帧。把:
|
||
|
||
```ts
|
||
private onMessage(frame: string): void {
|
||
const r = decodeFrame(frame);
|
||
```
|
||
|
||
替换为:
|
||
|
||
```ts
|
||
private onMessage(frame: string): void {
|
||
if (this.debug) this.logger('[net] ←', frame);
|
||
const r = decodeFrame(frame);
|
||
```
|
||
|
||
- [ ] **Step 4: 跑测试确认通过** — `npm run test:framework`(net-client 含新 2 用例全 PASS,原有用例不受影响)+ `npm run typecheck:framework`(exit 0)
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd G:/Works/YouleGamesCocosCreator
|
||
git add cocoscreator_projects/YouleNexus/assets/framework/net/net-client.ts cocoscreator_projects/framework-tests/net/net-client.test.ts
|
||
git commit -m "feat(framework): net NetClient 可选 debug 收发包日志
|
||
|
||
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 文档 — 更新配置与调试指南
|
||
|
||
**Files:**
|
||
- Modify: `docs/guides/配置与调试指南.md`
|
||
|
||
> 纯文档任务,无测试。改完跑一次全量回归确认未误改代码。所有改动在 `docs/` 下。
|
||
|
||
- [ ] **Step 1: 新增「一键切换 debug/release 模式」小节**
|
||
|
||
在 `## 4. 调试 Profiles` 这一节**之前**(即紧接 `## 3. 渠道身份 ChannelIdentity` 小节末尾之后)插入新的一节,并把后续小节序号顺延(4→5、5→6…;目录也同步顺延)。新节内容:
|
||
|
||
```markdown
|
||
## 4. 一键切换 debug / release 模式
|
||
|
||
运行模式是比 profile 更高一层的总开关,由 `config/runtime-mode.ts` 提供。
|
||
|
||
| 模式 | 怎么进入 | profile | URL 覆盖(?profile=/?agentid=…) | 身份来源 | 收发包日志 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| **debug** | debug/预览构建自动进入;或 `MODE_OVERRIDE='debug'` | `?profile=` 可切(默认 prod) | **全部生效** | 原生或 URL(按 `isNative`) | 开(`isDebugger=true`) |
|
||
| **release** | release 构建自动进入;或 `MODE_OVERRIDE='release'` | **强制 prod** | **全部忽略** | **只走原生 `window.settings`** | 关 |
|
||
|
||
### 三种切换姿势
|
||
|
||
1. **什么都不动**:发 release 包自动 release,编辑器预览自动 debug。模式由 Cocos 全局 `DEBUG` 决定。
|
||
2. **release 包里临时调试**:把 `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 设为 `'debug'`。
|
||
3. **预览里验证正式行为**:把 `MODE_OVERRIDE` 设为 `'release'`。
|
||
|
||
> release 之所以能放心锁死 URL:正式包是 Cocos 原生(jsb),渠道身份走 `window.settings`/`app_*`,本就不依赖 URL。
|
||
|
||
### 接入
|
||
|
||
```ts
|
||
import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode';
|
||
|
||
const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; // Cocos 全局
|
||
const mode = resolveRuntimeMode(isDebugBuild); // 'debug' | 'release'
|
||
// 传给 resolveBootstrap({ mode, ... }),并把 result.isDebugger 传给 NetClient 的 debug 选项
|
||
```
|
||
```
|
||
|
||
- [ ] **Step 2: 更新 `resolveBootstrap` 接入示例**
|
||
|
||
找到原「启动编排 resolveBootstrap 与接入 NetClient」一节里的接入示例代码块,做两处改动:①`resolveBootstrap({...})` 的入参补上 `mode`;②`new NetClient({...})` 补上 `debug`。改后关键两处应为:
|
||
|
||
```ts
|
||
const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true;
|
||
const mode = resolveRuntimeMode(isDebugBuild);
|
||
|
||
const result = await resolveBootstrap({
|
||
mode,
|
||
win: globalThis as any,
|
||
search: location.search,
|
||
fetcher: new HttpConfigFetcher(),
|
||
isNative: href.indexOf('index.html') > -1,
|
||
fallbackServers: [],
|
||
});
|
||
|
||
const net = new NetClient({
|
||
servers: result.servers,
|
||
transportFactory: () => new CocosWebSocketTransport(),
|
||
debug: result.isDebugger,
|
||
});
|
||
```
|
||
|
||
并在该示例的 import 区补一行 `import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode';`。
|
||
|
||
- [ ] **Step 3: 更新发布前检查清单**
|
||
|
||
在「发布前检查清单」一节的清单**最前面**加一项:
|
||
|
||
```markdown
|
||
- [ ] `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 为 `null`(发布走自动模式,由 release 构建决定)。
|
||
```
|
||
|
||
- [ ] **Step 4: 更新 API 速查表**
|
||
|
||
在 API 速查表里,于 `resolveBootstrap` 行**之上**新增一行:
|
||
|
||
```markdown
|
||
| `resolveRuntimeMode` | `config/runtime-mode.ts` | `(isDebugBuild, override?) => 'debug' | 'release'` 运行模式解析 |
|
||
```
|
||
|
||
并在 `resolveBootstrap` 行的说明里追加「(入参含 `mode`,结果含 `mode`/`isDebugger`)」。
|
||
|
||
- [ ] **Step 5: 全量回归 + Commit**
|
||
|
||
Run: `npm run test:framework`(应仍全绿,文档改动不影响)
|
||
Run: `npm run typecheck:framework`(exit 0)
|
||
|
||
```bash
|
||
cd G:/Works/YouleGamesCocosCreator
|
||
git add docs/guides/配置与调试指南.md
|
||
git commit -m "docs(guide): 配置与调试指南补充 debug/release 一键模式
|
||
|
||
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
|
||
```
|
||
|
||
---
|
||
|
||
## 验收标准(完成定义)
|
||
|
||
1. `npm run test:framework` 全绿;`npm run typecheck:framework` exit 0。
|
||
2. `resolveRuntimeMode`:override 优先、否则跟随构建标志。
|
||
3. `resolveBootstrap` 接入必填 `mode`:release 强制 prod、忽略所有 URL 覆盖、身份只走原生、`isDebugger=false`;debug 维持原有全部覆盖行为;结果含 `mode`/`isDebugger`。
|
||
4. `NetClient` 可选 `debug`/`logger`:debug 时记录每帧 send/recv,默认关闭不影响现有行为。
|
||
5. `docs/guides/配置与调试指南.md` 含「一键切换 debug/release」章节、更新后的接入示例、发布前清单与 API 速查表。
|
||
|
||
## 后续衔接
|
||
|
||
- 启动场景(platform/Plan 3)接入:`resolveRuntimeMode` → `resolveBootstrap({mode})` → `NetClient({debug})`。
|
||
- 远程下发 `isdebugger`(原 `get_paravalue('isdebugger')`)若需要,归 platform 层,可与本地 mode 取或。
|