minecraft-dev-starter 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +12 -0
- package/README.md +67 -74
- package/lib/client.js +121 -9
- package/package.json +1 -1
- package/src/client/DetailTab.js +2 -1
- package/src/client/LogTab.js +17 -1
- package/src/client/dock.js +20 -0
- package/src/client/index.js +3 -3
- package/src/client/logFormat.js +109 -0
- package/src/client/runtime.js +36 -3
- package/src/client/state.js +4 -4
- package/src/client/styles.js +14 -0
- package/src/index.js +5 -1
package/AGENTS.md
CHANGED
|
@@ -176,6 +176,18 @@ server:`Done (…s)!`);状态落盘并做重启后对账。顺带修了 GB
|
|
|
176
176
|
- 新版本发出去后,用干净 profile 装一次并看启动日志里的 `host ready (x.y.z)`,
|
|
177
177
|
这条同时验证了「版本号读对了」与「包结构完整」。
|
|
178
178
|
|
|
179
|
+
## 安全告警(npm / Socket)
|
|
180
|
+
|
|
181
|
+
npm 包页面上会出现两条告警,都来自 peer 依赖图,不是本包代码:
|
|
182
|
+
|
|
183
|
+
- **High CVE**:`zod@<4.6.6`(GHSA-mrhx-c7xc-m3w5),经 `@deepseek-ai/dsh-llm` 传导,
|
|
184
|
+
属于官方运行时 `@deepseek-ai/dsh-tools` 的树。本包 `dependencies` 为空,改不了;只能等上游升 zod。
|
|
185
|
+
- **Uses eval**:**误报**。`@deepseek-ai/dsh-ptc-runtime/lib/index.js` 里有一张保留字表,
|
|
186
|
+
其中一项就是字符串 `"eval"`,静态扫描按字符串命中了。本仓库与产物里 `\beval\b` 零命中(可复查)。
|
|
187
|
+
|
|
188
|
+
结论:不要为了这两条去改依赖声明(把 peer 改成 optional 也挡不住 CVE,因为它是真实的传递依赖)。
|
|
189
|
+
需要清除误报就往 Socket 的 false positive 通道提;需要修 CVE 就等官方升 `zod`。
|
|
190
|
+
|
|
179
191
|
## 还没做的事
|
|
180
192
|
|
|
181
193
|
- **M6 通用启动器**:HMCL 实例库 + 直启 java,让面板也能起非 Gradle 工程(估 5–8 天)。
|
package/README.md
CHANGED
|
@@ -1,136 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
给做模组的人用,不是给 AI 用的——AI 侧只是顺带留了几个工具。
|
|
6
|
-
|
|
7
|
-
## 能做什么
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Chemiofitor4096/minecraft-dev-starter/main/assets/banner.png" alt="minecraft-dev-starter" width="100%">
|
|
3
|
+
</p>
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
单模块、多模块、聚合仓库都能认;loader 写在子模块里也能认。
|
|
11
|
-
|
|
12
|
-
**启动**:Client / Server / Data / GameTestServer。自动挑 JDK(读 `<jdk>/release`,不乱起进程);
|
|
13
|
-
构建完能判断"进游戏了"而不是"进程还活着";关掉游戏后状态同步;宿主重启后按 pid 对账,不伪造结束原因。
|
|
5
|
+
# minecraft-dev-starter
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
崩溃时给一句归因(依赖没解析、JDK 版本不对、端口被占、认证失败等 8 类)。
|
|
7
|
+
为 DeepSeek Harness 添加 Minecraft 模组开发运行控件。
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
插件识别当前工作区中的 Minecraft 模组与插件工程,在会话标题栏提供运行目标的选择与启动、停止操作,并将运行日志输出到侧边栏标签页。
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
## 功能
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
- **工程识别**:支持 Fabric、Forge、NeoForge、Architectury、Paper 与 Spigot,覆盖 Minecraft 1.7.10 至 26.x。支持单模块、多模块与聚合仓库;loader 声明位于子模块时同样可以识别。
|
|
14
|
+
- **运行目标**:从 Gradle 配置中提取 Client、Server、Data、GameTestServer 等任务。按 loader 惯例推测的目标会标记为未验证,不影响选择与启动。
|
|
15
|
+
- **托管启动**:通过 Gradle Wrapper 启动任务,依据日志标记判定是否进入运行状态,而非仅依据进程存活。停止时终止 Gradle 及其全部子进程。
|
|
16
|
+
- **状态保持**:运行记录持久化到本地。宿主重启后按进程 ID 校验:确认存活则继续跟踪,无法确认则记为未知,不推测退出原因。
|
|
17
|
+
- **运行日志**:独立侧边栏标签页,按工程展示启动历史,支持跟随输出。日志按级别着色:WARN 与 ERROR 高亮,堆栈帧与 Gradle 任务行区分显示,时间、线程与 logger 弱化。异常退出时给出可能原因,覆盖依赖解析失败、JDK 版本不匹配、端口占用、认证失败等情况。
|
|
18
|
+
- **工作区跟随**:以当前会话的工作目录作为工作区,切换会话时自动重新扫描,无需手动刷新。
|
|
19
|
+
- **模型工具**(可选):提供 `mc_projects`、`mc_launch`、`mc_launch_logs`、`mc_launch_stop` 四个工具,与面板共用同一套服务。
|
|
20
|
+
- **扩展集成**:安装 [dsh-better-sidebar](https://github.com/topics/dsh-better-sidebar) 后,面板与日志可在其侧边栏中作为带图标的标签页打开。
|
|
23
21
|
|
|
24
22
|
## 安装
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
要求 DSH 桌面版 0.2.1 或更高版本,或对应的 `dsh` CLI。
|
|
27
25
|
|
|
28
26
|
```powershell
|
|
29
|
-
#
|
|
30
|
-
dsh plugin --profile desktop add
|
|
27
|
+
# 从 npm 安装
|
|
28
|
+
dsh plugin --profile desktop add minecraft-dev-starter
|
|
31
29
|
|
|
32
|
-
#
|
|
33
|
-
dsh plugin --profile desktop add
|
|
30
|
+
# 本地仓库(修改后即时生效)
|
|
31
|
+
dsh plugin --profile desktop add link:C:/Projects/minecraft-dev-starter
|
|
34
32
|
|
|
35
|
-
#
|
|
36
|
-
dsh plugin --profile desktop add minecraft-dev-starter
|
|
33
|
+
# 使用打包文件
|
|
34
|
+
dsh plugin --profile desktop add file:./minecraft-dev-starter-0.1.3.tgz
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
安装完成后需重启 DSH 生效。卸载:
|
|
40
38
|
|
|
41
39
|
```powershell
|
|
42
40
|
dsh plugin --profile desktop remove minecraft-dev-starter
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
`dsh plugin`
|
|
46
|
-
`dsh.profile.bundles` 层列表。所以手工在 profile 的 `package.json` 里加一行再用 pnpm 安装,
|
|
47
|
-
效果是一样的。
|
|
43
|
+
`dsh plugin` 是 pnpm 的封装:将插件加入 profile 的依赖,并把声明了 `dsh.bundle` 的包加入 `dsh.profile.bundles` 列表。直接编辑 profile 的 `package.json` 后执行 `pnpm install`,效果相同。
|
|
48
44
|
|
|
49
45
|
## 使用
|
|
50
46
|
|
|
51
|
-
1.
|
|
52
|
-
2.
|
|
53
|
-
3.
|
|
54
|
-
4.
|
|
47
|
+
1. 打开包含 Minecraft 工程的工作区会话。
|
|
48
|
+
2. 标题栏显示当前选中的运行目标。点击目标名称打开浮层,从中选择工程与任务。
|
|
49
|
+
3. 点击启动按钮开始构建并运行。启动后自动打开「运行日志」标签页,该行为可通过配置关闭。
|
|
50
|
+
4. 运行期间按钮切换为停止;点击后终止 Gradle 及其子进程。
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
有多个工程时列表固定高度,选谁只换下面的目标区,界面不会跳。
|
|
52
|
+
浮层布局随工程数量变化:单工程工作区不显示工程列表,表头直接呈现工程名称、loader 与版本、路径;多工程工作区显示固定高度的列表,切换工程时仅更新下方目标区域,页面不会跳动。
|
|
58
53
|
|
|
59
54
|
## 配置
|
|
60
55
|
|
|
61
|
-
在 profile 的 `cordis.patch.yml`
|
|
56
|
+
在 profile 的 `cordis.patch.yml` 中为该插件条目添加 `config` 字段:
|
|
62
57
|
|
|
63
58
|
```yaml
|
|
64
59
|
- id: mcdev-host
|
|
65
60
|
name: minecraft-dev-starter
|
|
66
61
|
config:
|
|
67
|
-
autoOpenLogTab: true
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
autoOpenLogTab: true
|
|
63
|
+
logDock: right # right:DSH 原生右侧栏;bottom:dsh-better-sidebar 的底部工作台
|
|
64
|
+
readyTimeoutMs: 90000
|
|
65
|
+
logRingLines: 2000
|
|
70
66
|
```
|
|
71
67
|
|
|
72
|
-
|
|
68
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `autoOpenLogTab` | boolean | `true` | 启动后自动打开运行日志标签页 |
|
|
71
|
+
| `logDock` | `right` \| `bottom` | `right` | 日志打开位置。`bottom` 需要安装 dsh-better-sidebar;未安装时自动回退到右侧栏 |
|
|
72
|
+
| `readyTimeoutMs` | number | `90000` | 等待进入运行状态的超时时间;超时不计为失败 |
|
|
73
|
+
| `logRingLines` | number | `2000` | 内存中保留的日志行数;磁盘上的日志始终完整 |
|
|
74
|
+
|
|
75
|
+
`logDock` 只影响「自动打开」与面板里的「打开日志」按钮;无论配置为何,两个标签页都同时存在于原生右侧栏与 better-sidebar 的页签列表中,可随时手动打开。
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
## 兼容范围
|
|
78
|
+
|
|
79
|
+
| 平台 | 版本范围 | 说明 |
|
|
75
80
|
|---|---|---|
|
|
76
|
-
| Fabric | 1.14 – 26.x | loom
|
|
77
|
-
| Forge | 1.7.10 / 1.12.2 / 1.16.5 / 1.20.1 |
|
|
81
|
+
| Fabric | 1.14 – 26.x | loom 提供的 `runClient` 等任务 |
|
|
82
|
+
| Forge | 1.7.10 / 1.12.2 / 1.16.5 / 1.20.1 | 包含 legacyforge |
|
|
78
83
|
| NeoForge | 1.20.1 – 26.x | moddev 插件 |
|
|
79
|
-
| Architectury | 1.16 – 26.x |
|
|
84
|
+
| Architectury | 1.16 – 26.x | 按子模块拆分任务 |
|
|
80
85
|
| Paper / Spigot | 1.7.10 – 26.x | 服务端插件工程 |
|
|
81
86
|
|
|
82
|
-
JDK
|
|
83
|
-
和机器上已装的 JDK 比对,缺了会在面板里提示,不会带着错的版本硬跑。
|
|
87
|
+
JDK 版本按 Minecraft 版本推断:1.7.10 与 1.12.2 需要 Java 8,1.18 至 1.20.4 需要 Java 17,1.20.5 及以上需要 Java 21,26.x 需要 Java 25。插件将需求与已安装的 JDK 比对,缺失或版本不符时在面板中提示。
|
|
84
88
|
|
|
85
89
|
## 开发
|
|
86
90
|
|
|
87
91
|
```powershell
|
|
88
92
|
npm install
|
|
89
|
-
npm run build # 构建客户端到 lib/client.js
|
|
93
|
+
npm run build # 构建客户端到 lib/client.js;修改 src/client 后必须执行
|
|
90
94
|
npm test # 59 个测试
|
|
91
95
|
npm run build -- --watch
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
设计文档:[策划](./doc/策划.md) · [DSH 插件机制调研](./doc/调研-DSH插件机制.md)。
|
|
98
|
+
仓库结构、已知问题与历次验证结论见 [AGENTS.md](./AGENTS.md)。设计文档:[策划](./doc/策划.md)、[DSH 插件机制调研](./doc/调研-DSH插件机制.md)。
|
|
96
99
|
|
|
97
100
|
## 发布
|
|
98
101
|
|
|
99
|
-
DSH
|
|
100
|
-
|
|
101
|
-
1. **清单**:`package.json` 里声明 `dsh.bundle.patch`(指向 `cordis.patch.yml`)与
|
|
102
|
-
`dsh.client.platform`(有客户端时)。宿主靠这两项决定"这是不是一个 DSH 插件"。
|
|
103
|
-
2. **产物**:`npm run build` 把客户端打进 `lib/client.js` 并**提交进仓库**——
|
|
104
|
-
运行时直接读它,不装依赖也不做构建。
|
|
105
|
-
3. **自检**:`npm test`,`npm pack --dry-run` 看包里有没有多余文件(`files` 字段已限定
|
|
106
|
-
`src`、`lib`、`cordis.patch.yml`、README、AGENTS)。
|
|
107
|
-
4. **发布**:`npm publish --access public`。版本号走 semver,破坏性改动升主版本。
|
|
102
|
+
DSH 插件以 npm 包形式分发,不需要独立注册流程。
|
|
108
103
|
|
|
109
|
-
|
|
104
|
+
1. **清单**:`package.json` 声明 `dsh.bundle.patch`(指向 `cordis.patch.yml`);提供浏览器界面时同时声明 `dsh.client.platform`。宿主依据这两项识别插件。
|
|
105
|
+
2. **产物**:执行 `npm run build` 生成 `lib/client.js`,并将该文件提交到仓库。运行时直接读取产物,不安装依赖,也不执行构建。
|
|
106
|
+
3. **校验**:执行 `npm test`;使用 `npm pack --dry-run` 确认包内容(`files` 字段已限定为 `src`、`lib`、`cordis.patch.yml`、README 与 AGENTS)。
|
|
107
|
+
4. **发布**:`npm publish --access public`。若使用仅允许暂存的 granular token,改为以下流程:
|
|
110
108
|
|
|
111
109
|
```powershell
|
|
112
|
-
npm stage publish #
|
|
113
|
-
npm stage list #
|
|
114
|
-
npm stage approve <stage-id> #
|
|
110
|
+
npm stage publish # 提交到暂存区
|
|
111
|
+
npm stage list # 等待状态变为 validated
|
|
112
|
+
npm stage approve <stage-id> # 人工完成 2FA 确认
|
|
115
113
|
```
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
5. **兼容性**:宿主 API 会演进(本插件按 0.2.1-rc.3 的 `dsh-tools` 开发)。发布前把
|
|
121
|
-
`dependencies` 里的 `@deepseek-ai/dsh-tools` 与 `peerDependencies` 的 cordis 范围对齐
|
|
122
|
-
目标运行时;跨大版本时最好发两个版本而不是硬兼容。
|
|
123
|
-
6. **安装**:用户 `dsh plugin --profile <name> add <包名>` 后重启;也可以在 Plugins 面板里装。
|
|
115
|
+
暂存后由人工批准是 npm 的设计:机器提交、人工确认,即使 token 泄露也无法直接发布正式版本。批准后 `latest` 标签自动指向新版本。
|
|
116
|
+
5. **兼容性**:宿主 API 会持续演进。发布前将 `@deepseek-ai/dsh-tools` 与 `@deepseek-ai/cordis` 的版本范围对齐目标运行时;跨大版本时应分别发布版本,而非放宽范围强行兼容。
|
|
117
|
+
6. **安装**:用户执行 `dsh plugin --profile <name> add <包名>` 后重启,或在 Plugins 面板中安装。
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
它会走和 npm 安装完全相同的 pnpm 路径。
|
|
119
|
+
在本地验证发布形态时,可先 `npm pack`,再通过 `dsh plugin add file:<tarball>` 安装;该路径与 npm 安装完全一致。
|
|
127
120
|
|
|
128
121
|
## 已知限制
|
|
129
122
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
- Windows
|
|
123
|
+
- 原生右侧栏的标签类型不提供图标字段,图标仅支持 `dsh-better-sidebar` 中的标签。
|
|
124
|
+
- 当前仅支持 Gradle 工程;HMCL 实例库中的整合包尚不支持(见 AGENTS.md 的 M6)。
|
|
125
|
+
- 运行状态保存于 `~/.dsh/mc-dev-starter/`,多个 profile 共用同一份记录;日志标签按工程过滤,功能不受影响。
|
|
126
|
+
- 在 Windows 上首次构建需要下载 Gradle 与项目依赖,耗时可能达到数分钟;后续构建可使用 Gradle daemon。
|
|
134
127
|
|
|
135
128
|
## License
|
|
136
129
|
|
package/lib/client.js
CHANGED
|
@@ -238,9 +238,19 @@ async function call(method, payload) {
|
|
|
238
238
|
return body.result;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
// src/client/dock.js
|
|
242
|
+
var LOG_DOCK_RIGHT = "right";
|
|
243
|
+
var LOG_DOCK_BOTTOM = "bottom";
|
|
244
|
+
function chooseLogDock(config, hasBetterSidebar) {
|
|
245
|
+
if (hasBetterSidebar !== true) return LOG_DOCK_RIGHT;
|
|
246
|
+
return config?.logDock === LOG_DOCK_BOTTOM ? LOG_DOCK_BOTTOM : LOG_DOCK_RIGHT;
|
|
247
|
+
}
|
|
248
|
+
|
|
241
249
|
// src/client/runtime.js
|
|
242
250
|
var DETAIL_KIND = "mcdev.detail";
|
|
243
251
|
var LOG_KIND = "mcdev.logs";
|
|
252
|
+
var BETTER_DETAIL_ID = "minecraft-dev-starter:project";
|
|
253
|
+
var BETTER_LOG_ID = "minecraft-dev-starter:logs";
|
|
244
254
|
var runtime = {};
|
|
245
255
|
function open(kind) {
|
|
246
256
|
const sidebar = runtime.ctx?.sidebarRight;
|
|
@@ -252,10 +262,25 @@ function open(kind) {
|
|
|
252
262
|
return false;
|
|
253
263
|
}
|
|
254
264
|
}
|
|
265
|
+
function betterSidebar() {
|
|
266
|
+
try {
|
|
267
|
+
return runtime.ctx?.get?.("betterSidebar", false);
|
|
268
|
+
} catch {
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
255
272
|
function openDetailTab() {
|
|
256
273
|
return open(DETAIL_KIND);
|
|
257
274
|
}
|
|
258
|
-
function openLogTab() {
|
|
275
|
+
function openLogTab(config) {
|
|
276
|
+
const service = betterSidebar();
|
|
277
|
+
if (chooseLogDock(config, service !== void 0) === LOG_DOCK_BOTTOM) {
|
|
278
|
+
try {
|
|
279
|
+
service.openTab({ type: BETTER_LOG_ID, title: "\u8FD0\u884C\u65E5\u5FD7", target: "bottom" });
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
259
284
|
return open(LOG_KIND);
|
|
260
285
|
}
|
|
261
286
|
|
|
@@ -296,15 +321,15 @@ var store = createStore({
|
|
|
296
321
|
flyoutOpen: false,
|
|
297
322
|
/** @type {Record<string, string>} 每个工程记住的目标 key */
|
|
298
323
|
selection: readSelection(),
|
|
299
|
-
/** @type {{ autoOpenLogTab?: boolean }} 宿主下发的部署配置 */
|
|
300
|
-
config: { autoOpenLogTab: true },
|
|
324
|
+
/** @type {{ autoOpenLogTab?: boolean, logDock?: 'right' | 'bottom' }} 宿主下发的部署配置 */
|
|
325
|
+
config: { autoOpenLogTab: true, logDock: "right" },
|
|
301
326
|
/** @type {string | undefined} 当前会话的工作区(由胶囊从会话上下文注入) */
|
|
302
327
|
workspaceRoot: void 0
|
|
303
328
|
});
|
|
304
329
|
async function loadStateInfo() {
|
|
305
330
|
try {
|
|
306
331
|
const info = await call("state.info", {});
|
|
307
|
-
store.set({ config: { autoOpenLogTab: true, ...info?.config ?? {} } });
|
|
332
|
+
store.set({ config: { autoOpenLogTab: true, logDock: "right", ...info?.config ?? {} } });
|
|
308
333
|
} catch {
|
|
309
334
|
}
|
|
310
335
|
}
|
|
@@ -396,7 +421,7 @@ async function startTarget(project, target) {
|
|
|
396
421
|
kind: target.kind,
|
|
397
422
|
module: target.module
|
|
398
423
|
});
|
|
399
|
-
if (store.get().config.autoOpenLogTab !== false) openLogTab();
|
|
424
|
+
if (store.get().config.autoOpenLogTab !== false) openLogTab(store.get().config);
|
|
400
425
|
} catch (error) {
|
|
401
426
|
launchStore.set({ error: error instanceof Error ? error.message : String(error) });
|
|
402
427
|
} finally {
|
|
@@ -837,7 +862,7 @@ function DetailTab() {
|
|
|
837
862
|
),
|
|
838
863
|
h(
|
|
839
864
|
"button",
|
|
840
|
-
{ type: "button", className: "mcdev-run", onClick: () => openLogTab() },
|
|
865
|
+
{ type: "button", className: "mcdev-run", onClick: () => openLogTab(store.get().config) },
|
|
841
866
|
"\u6253\u5F00\u65E5\u5FD7"
|
|
842
867
|
)
|
|
843
868
|
)
|
|
@@ -920,6 +945,66 @@ function row(key, value) {
|
|
|
920
945
|
|
|
921
946
|
// src/client/LogTab.js
|
|
922
947
|
var import_react5 = require("react");
|
|
948
|
+
|
|
949
|
+
// src/client/logFormat.js
|
|
950
|
+
var ANSI = /\u001b\[[0-9;]*[A-Za-z]/g;
|
|
951
|
+
function seg(text2, kind) {
|
|
952
|
+
return { text: text2, kind };
|
|
953
|
+
}
|
|
954
|
+
function levelKind(level) {
|
|
955
|
+
const upper = level.toUpperCase();
|
|
956
|
+
if (upper === "WARN" || upper === "WARNING") return "level-warn";
|
|
957
|
+
if (upper === "ERROR" || upper === "FATAL" || upper === "SEVERE") return "level-error";
|
|
958
|
+
if (upper === "DEBUG" || upper === "TRACE" || upper === "FINE" || upper === "FINER") return "level-debug";
|
|
959
|
+
return "level-info";
|
|
960
|
+
}
|
|
961
|
+
var LOG4J = /^\[(\d{2}:\d{2}:\d{2})\]\s+\[([^\]]+?)\/([A-Z]+)\]\s*(?:\[([^\]]*)\]:\s*|:\s*)?(.*)$/;
|
|
962
|
+
var PAPER = /^\[(\d{2}:\d{2}:\d{2}) ([A-Z]+)\]:\s*(.*)$/;
|
|
963
|
+
var BARE_LEVEL = /^(TRACE|DEBUG|INFO|WARN|WARNING|ERROR|FATAL)\s+(.*)$/;
|
|
964
|
+
var STACK = /^\s+(at |\.\.\. \d+ more)/;
|
|
965
|
+
function highlightLine(raw) {
|
|
966
|
+
const line = String(raw ?? "").replace(ANSI, "");
|
|
967
|
+
if (line === "") return [seg("", "plain")];
|
|
968
|
+
if (/^Caused by:|^\s+Suppressed:/.test(line)) return [seg(line, "fail")];
|
|
969
|
+
if (STACK.test(line)) return [seg(line, "stack")];
|
|
970
|
+
const log4j = LOG4J.exec(line);
|
|
971
|
+
if (log4j !== null) {
|
|
972
|
+
const [, time, thread, level, logger, message] = log4j;
|
|
973
|
+
const kind = levelKind(level);
|
|
974
|
+
const parts = [
|
|
975
|
+
seg(`[${time}] `, "time"),
|
|
976
|
+
seg(`[${thread}/`, "thread"),
|
|
977
|
+
seg(level, kind),
|
|
978
|
+
seg(logger === void 0 ? "]: " : "] ", "thread")
|
|
979
|
+
];
|
|
980
|
+
if (logger !== void 0) parts.push(seg(`[${logger}]: `, "logger"));
|
|
981
|
+
parts.push(seg(message, kind === "level-error" ? "level-error" : "message"));
|
|
982
|
+
return parts;
|
|
983
|
+
}
|
|
984
|
+
const paper = PAPER.exec(line);
|
|
985
|
+
if (paper !== null) {
|
|
986
|
+
const [, time, level, message] = paper;
|
|
987
|
+
return [
|
|
988
|
+
seg(`[${time} `, "time"),
|
|
989
|
+
seg(level, levelKind(level)),
|
|
990
|
+
seg("]: ", "time"),
|
|
991
|
+
seg(message, levelKind(level) === "level-error" ? "level-error" : "message")
|
|
992
|
+
];
|
|
993
|
+
}
|
|
994
|
+
const bare = BARE_LEVEL.exec(line);
|
|
995
|
+
if (bare !== null) {
|
|
996
|
+
const [, level, message] = bare;
|
|
997
|
+
return [seg(`${level} `, levelKind(level)), seg(message, levelKind(level) === "level-error" ? "level-error" : "message")];
|
|
998
|
+
}
|
|
999
|
+
if (/^> Task /.test(line) || /^> Configure project /.test(line)) return [seg(line, "task")];
|
|
1000
|
+
if (/^BUILD SUCCESSFUL/.test(line)) return [seg(line, "ok")];
|
|
1001
|
+
if (/^BUILD FAILED/.test(line) || /^FAILURE:/.test(line)) return [seg(line, "fail")];
|
|
1002
|
+
if (/^\* What went wrong:|^\* Where:|^\* Try:/.test(line)) return [seg(line, "level-warn")];
|
|
1003
|
+
if (/^(Reusing configuration cache|Deprecated Gradle features|You can use)/.test(line)) return [seg(line, "muted")];
|
|
1004
|
+
return [seg(line, "plain")];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/client/LogTab.js
|
|
923
1008
|
var lastLogId;
|
|
924
1009
|
function LogTab() {
|
|
925
1010
|
const launches = useLaunches(1500);
|
|
@@ -1029,7 +1114,20 @@ function LogTab() {
|
|
|
1029
1114
|
),
|
|
1030
1115
|
h("div", { className: "mcdev-hint mcdev-code" }, log.diagnosis.line)
|
|
1031
1116
|
),
|
|
1032
|
-
log?.error === void 0 ? log === void 0 && running ? h(Skeleton, { rows: 4, hint: "\u6B63\u5728\u8BFB\u53D6\u65E5\u5FD7\u2026" }) : h(
|
|
1117
|
+
log?.error === void 0 ? log === void 0 && running ? h(Skeleton, { rows: 4, hint: "\u6B63\u5728\u8BFB\u53D6\u65E5\u5FD7\u2026" }) : h(
|
|
1118
|
+
"pre",
|
|
1119
|
+
{ ref: body, className: "mcdev-logs mcdev-logs-full" },
|
|
1120
|
+
(log?.lines ?? []).length === 0 ? "\uFF08\u8FD8\u6CA1\u6709\u8F93\u51FA\uFF09" : (log?.lines ?? []).map(
|
|
1121
|
+
(line, index) => h(
|
|
1122
|
+
"span",
|
|
1123
|
+
{ key: index, className: "mcdev-log-line" },
|
|
1124
|
+
highlightLine(line).map(
|
|
1125
|
+
(part, partIndex) => h("span", { key: partIndex, "data-kind": part.kind }, part.text)
|
|
1126
|
+
),
|
|
1127
|
+
"\n"
|
|
1128
|
+
)
|
|
1129
|
+
)
|
|
1130
|
+
) : h("div", { className: "mcdev-error", style: { marginTop: 8 } }, log.error),
|
|
1033
1131
|
h(
|
|
1034
1132
|
"div",
|
|
1035
1133
|
{ style: { marginTop: 8 } },
|
|
@@ -1302,6 +1400,20 @@ var CSS = `
|
|
|
1302
1400
|
.mcdev-diagnosis-hint { color: var(--dsw-alias-state-error-primary); font: var(--dsw-font-xxs-strong-12); }
|
|
1303
1401
|
.mcdev-logs-full { flex: 1 1 auto; min-height: 0; max-height: none; margin: 0; }
|
|
1304
1402
|
|
|
1403
|
+
/* \u65E5\u5FD7\u9AD8\u4EAE\uFF1A\u53EA\u6309\u8BED\u4E49\u4E0A\u8272\uFF0C\u65F6\u95F4/\u7EBF\u7A0B/logger \u4E00\u5F8B\u538B\u5230 tertiary\uFF0C\u8BA9\u6B63\u6587\u66F4\u6E05\u695A */
|
|
1404
|
+
.mcdev-log-line { display: inline; }
|
|
1405
|
+
.mcdev-log-line [data-kind="time"],
|
|
1406
|
+
.mcdev-log-line [data-kind="thread"],
|
|
1407
|
+
.mcdev-log-line [data-kind="logger"] { color: var(--dsw-alias-label-tertiary); }
|
|
1408
|
+
.mcdev-log-line [data-kind="muted"] { color: var(--dsw-alias-label-tertiary); }
|
|
1409
|
+
.mcdev-log-line [data-kind="level-warn"] { color: var(--dsw-alias-state-warn-primary); }
|
|
1410
|
+
.mcdev-log-line [data-kind="level-error"] { color: var(--dsw-alias-state-error-primary); }
|
|
1411
|
+
.mcdev-log-line [data-kind="level-debug"] { color: var(--dsw-alias-label-tertiary); }
|
|
1412
|
+
.mcdev-log-line [data-kind="task"] { color: var(--dsw-alias-label-secondary); }
|
|
1413
|
+
.mcdev-log-line [data-kind="ok"] { color: var(--dsw-alias-state-success-primary); }
|
|
1414
|
+
.mcdev-log-line [data-kind="fail"] { color: var(--dsw-alias-state-error-primary); font: var(--dsw-font-xxxs-strong-11); }
|
|
1415
|
+
.mcdev-log-line [data-kind="stack"] { color: var(--dsw-alias-label-secondary); }
|
|
1416
|
+
|
|
1305
1417
|
.mcdev-section { margin-top: 12px; }
|
|
1306
1418
|
.mcdev-section > h4 {
|
|
1307
1419
|
margin: 0 0 4px;
|
|
@@ -1422,7 +1534,7 @@ function registerBetterSidebarTabs(ctx) {
|
|
|
1422
1534
|
try {
|
|
1423
1535
|
disposers.push(
|
|
1424
1536
|
sidebar.registerTab({
|
|
1425
|
-
id:
|
|
1537
|
+
id: BETTER_DETAIL_ID,
|
|
1426
1538
|
title: PANEL_TITLE,
|
|
1427
1539
|
icon: (size) => h(IconCube, { size }),
|
|
1428
1540
|
component: () => h(DetailTab)
|
|
@@ -1430,7 +1542,7 @@ function registerBetterSidebarTabs(ctx) {
|
|
|
1430
1542
|
);
|
|
1431
1543
|
disposers.push(
|
|
1432
1544
|
sidebar.registerTab({
|
|
1433
|
-
id:
|
|
1545
|
+
id: BETTER_LOG_ID,
|
|
1434
1546
|
title: "\u8FD0\u884C\u65E5\u5FD7",
|
|
1435
1547
|
icon: (size) => h(IconTerminal, { size }),
|
|
1436
1548
|
component: () => h(LogTab)
|
package/package.json
CHANGED
package/src/client/DetailTab.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
selectedProject,
|
|
18
18
|
startTarget,
|
|
19
19
|
stopLaunch,
|
|
20
|
+
store,
|
|
20
21
|
useLaunches,
|
|
21
22
|
useStoreState,
|
|
22
23
|
} from './state.js'
|
|
@@ -122,7 +123,7 @@ export function DetailTab() {
|
|
|
122
123
|
),
|
|
123
124
|
h(
|
|
124
125
|
'button',
|
|
125
|
-
{ type: 'button', className: 'mcdev-run', onClick: () => openLogTab() },
|
|
126
|
+
{ type: 'button', className: 'mcdev-run', onClick: () => openLogTab(store.get().config) },
|
|
126
127
|
'打开日志',
|
|
127
128
|
),
|
|
128
129
|
),
|
package/src/client/LogTab.js
CHANGED
|
@@ -9,6 +9,7 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
9
9
|
import { call } from './api.js'
|
|
10
10
|
import { h } from './h.js'
|
|
11
11
|
import { IconRefresh, IconWarning } from './icons.js'
|
|
12
|
+
import { highlightLine } from './logFormat.js'
|
|
12
13
|
import { launchStatusText, pickLogTarget } from './selection.js'
|
|
13
14
|
import { Skeleton } from './Skeleton.js'
|
|
14
15
|
import { loadLaunches, selectedProject, useLaunches } from './state.js'
|
|
@@ -140,7 +141,22 @@ export function LogTab() {
|
|
|
140
141
|
log?.error === undefined
|
|
141
142
|
? log === undefined && running
|
|
142
143
|
? h(Skeleton, { rows: 4, hint: '正在读取日志…' })
|
|
143
|
-
: h(
|
|
144
|
+
: h(
|
|
145
|
+
'pre',
|
|
146
|
+
{ ref: body, className: 'mcdev-logs mcdev-logs-full' },
|
|
147
|
+
(log?.lines ?? []).length === 0
|
|
148
|
+
? '(还没有输出)'
|
|
149
|
+
: (log?.lines ?? []).map((line, index) =>
|
|
150
|
+
h(
|
|
151
|
+
'span',
|
|
152
|
+
{ key: index, className: 'mcdev-log-line' },
|
|
153
|
+
highlightLine(line).map((part, partIndex) =>
|
|
154
|
+
h('span', { key: partIndex, 'data-kind': part.kind }, part.text),
|
|
155
|
+
),
|
|
156
|
+
'\n',
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
)
|
|
144
160
|
: h('div', { className: 'mcdev-error', style: { marginTop: 8 } }, log.error),
|
|
145
161
|
h(
|
|
146
162
|
'div',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志标签的落点选择。
|
|
3
|
+
*
|
|
4
|
+
* 右列属于 DSH 原生右侧栏;底部工作台是 dsh-better-sidebar 提供的。
|
|
5
|
+
* 未安装 better-sidebar 时只能落在右侧栏,配置值在这种情况下被忽略。
|
|
6
|
+
* @module minecraft-dev-starter/client/dock
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const LOG_DOCK_RIGHT = 'right'
|
|
10
|
+
export const LOG_DOCK_BOTTOM = 'bottom'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {{ logDock?: string } | undefined} config
|
|
14
|
+
* @param {boolean} hasBetterSidebar
|
|
15
|
+
* @returns {'right' | 'bottom'}
|
|
16
|
+
*/
|
|
17
|
+
export function chooseLogDock(config, hasBetterSidebar) {
|
|
18
|
+
if (hasBetterSidebar !== true) return LOG_DOCK_RIGHT
|
|
19
|
+
return config?.logDock === LOG_DOCK_BOTTOM ? LOG_DOCK_BOTTOM : LOG_DOCK_RIGHT
|
|
20
|
+
}
|
package/src/client/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { LogTab } from './LogTab.js'
|
|
|
17
17
|
import { report } from './debug.js'
|
|
18
18
|
import { h } from './h.js'
|
|
19
19
|
import { IconCube, IconTerminal } from './icons.js'
|
|
20
|
-
import { DETAIL_KIND, LOG_KIND, runtime } from './runtime.js'
|
|
20
|
+
import { BETTER_DETAIL_ID, BETTER_LOG_ID, DETAIL_KIND, LOG_KIND, runtime } from './runtime.js'
|
|
21
21
|
import { ensureStyles } from './styles.js'
|
|
22
22
|
|
|
23
23
|
export const name = 'mcdev-client'
|
|
@@ -99,7 +99,7 @@ function registerBetterSidebarTabs(ctx) {
|
|
|
99
99
|
try {
|
|
100
100
|
disposers.push(
|
|
101
101
|
sidebar.registerTab({
|
|
102
|
-
id:
|
|
102
|
+
id: BETTER_DETAIL_ID,
|
|
103
103
|
title: PANEL_TITLE,
|
|
104
104
|
icon: (size) => h(IconCube, { size }),
|
|
105
105
|
component: () => h(DetailTab),
|
|
@@ -107,7 +107,7 @@ function registerBetterSidebarTabs(ctx) {
|
|
|
107
107
|
)
|
|
108
108
|
disposers.push(
|
|
109
109
|
sidebar.registerTab({
|
|
110
|
-
id:
|
|
110
|
+
id: BETTER_LOG_ID,
|
|
111
111
|
title: '运行日志',
|
|
112
112
|
icon: (size) => h(IconTerminal, { size }),
|
|
113
113
|
component: () => h(LogTab),
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行日志的逐行解析:把一行拆成若干片段,交给 CSS 按语义上色。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不按 ANSI 上色:Gradle 在管道里默认关闭颜色,Minecraft 的 log4j 也只在
|
|
5
|
+
* 终端里输出色码。多数日志查看器(mclo.gs / Aternos codex)同样是先解析出 level
|
|
6
|
+
* 再染色,而不是解析转义序列。这里沿用同一思路,顺便把万一出现的 ANSI 码剥掉。
|
|
7
|
+
*
|
|
8
|
+
* 支持的三种真实格式:
|
|
9
|
+
* [01:28:45] [Render thread/WARN] [minecraft/SoundEngine]: message ← log4j
|
|
10
|
+
* [01:28:45 INFO]: message ← Paper
|
|
11
|
+
* WARN StatusConsoleListener ... ← 无时间戳的 log4j 状态行
|
|
12
|
+
* 另外识别 Gradle 的 `> Task`、`BUILD SUCCESSFUL`、`FAILURE:` 与堆栈帧。
|
|
13
|
+
*
|
|
14
|
+
* @module minecraft-dev-starter/client/logFormat
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const ANSI = /\u001b\[[0-9;]*[A-Za-z]/g
|
|
18
|
+
|
|
19
|
+
/** @typedef {'time'|'thread'|'level-info'|'level-warn'|'level-error'|'level-debug'|'logger'|'message'|'task'|'ok'|'fail'|'stack'|'muted'} SegmentKind */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} text
|
|
23
|
+
* @param {SegmentKind} kind
|
|
24
|
+
* @returns {{ text: string, kind: SegmentKind }}
|
|
25
|
+
*/
|
|
26
|
+
function seg(text, kind) {
|
|
27
|
+
return { text, kind }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 把 WARN / ERROR / FATAL 等归一成三档。
|
|
32
|
+
* @param {string} level
|
|
33
|
+
* @returns {SegmentKind}
|
|
34
|
+
*/
|
|
35
|
+
function levelKind(level) {
|
|
36
|
+
const upper = level.toUpperCase()
|
|
37
|
+
if (upper === 'WARN' || upper === 'WARNING') return 'level-warn'
|
|
38
|
+
if (upper === 'ERROR' || upper === 'FATAL' || upper === 'SEVERE') return 'level-error'
|
|
39
|
+
if (upper === 'DEBUG' || upper === 'TRACE' || upper === 'FINE' || upper === 'FINER') return 'level-debug'
|
|
40
|
+
return 'level-info'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const LOG4J = /^\[(\d{2}:\d{2}:\d{2})\]\s+\[([^\]]+?)\/([A-Z]+)\]\s*(?:\[([^\]]*)\]:\s*|:\s*)?(.*)$/
|
|
44
|
+
const PAPER = /^\[(\d{2}:\d{2}:\d{2}) ([A-Z]+)\]:\s*(.*)$/
|
|
45
|
+
const BARE_LEVEL = /^(TRACE|DEBUG|INFO|WARN|WARNING|ERROR|FATAL)\s+(.*)$/
|
|
46
|
+
const STACK = /^\s+(at |\.\.\. \d+ more)/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 一行日志 → 片段数组。永远至少返回一个片段。
|
|
50
|
+
* @param {string} raw
|
|
51
|
+
* @returns {Array<{ text: string, kind: SegmentKind }>}
|
|
52
|
+
*/
|
|
53
|
+
export function highlightLine(raw) {
|
|
54
|
+
const line = String(raw ?? '').replace(ANSI, '')
|
|
55
|
+
if (line === '') return [seg('', 'plain')]
|
|
56
|
+
|
|
57
|
+
if (/^Caused by:|^\s+Suppressed:/.test(line)) return [seg(line, 'fail')]
|
|
58
|
+
if (STACK.test(line)) return [seg(line, 'stack')]
|
|
59
|
+
|
|
60
|
+
const log4j = LOG4J.exec(line)
|
|
61
|
+
if (log4j !== null) {
|
|
62
|
+
const [, time, thread, level, logger, message] = log4j
|
|
63
|
+
const kind = levelKind(level)
|
|
64
|
+
/** @type {Array<{ text: string, kind: SegmentKind }>} */
|
|
65
|
+
const parts = [
|
|
66
|
+
seg(`[${time}] `, 'time'),
|
|
67
|
+
seg(`[${thread}/`, 'thread'),
|
|
68
|
+
seg(level, kind),
|
|
69
|
+
seg(logger === undefined ? ']: ' : '] ', 'thread'),
|
|
70
|
+
]
|
|
71
|
+
if (logger !== undefined) parts.push(seg(`[${logger}]: `, 'logger'))
|
|
72
|
+
parts.push(seg(message, kind === 'level-error' ? 'level-error' : 'message'))
|
|
73
|
+
return parts
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const paper = PAPER.exec(line)
|
|
77
|
+
if (paper !== null) {
|
|
78
|
+
const [, time, level, message] = paper
|
|
79
|
+
return [
|
|
80
|
+
seg(`[${time} `, 'time'),
|
|
81
|
+
seg(level, levelKind(level)),
|
|
82
|
+
seg(']: ', 'time'),
|
|
83
|
+
seg(message, levelKind(level) === 'level-error' ? 'level-error' : 'message'),
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const bare = BARE_LEVEL.exec(line)
|
|
88
|
+
if (bare !== null) {
|
|
89
|
+
const [, level, message] = bare
|
|
90
|
+
return [seg(`${level} `, levelKind(level)), seg(message, levelKind(level) === 'level-error' ? 'level-error' : 'message')]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (/^> Task /.test(line) || /^> Configure project /.test(line)) return [seg(line, 'task')]
|
|
94
|
+
if (/^BUILD SUCCESSFUL/.test(line)) return [seg(line, 'ok')]
|
|
95
|
+
if (/^BUILD FAILED/.test(line) || /^FAILURE:/.test(line)) return [seg(line, 'fail')]
|
|
96
|
+
if (/^\* What went wrong:|^\* Where:|^\* Try:/.test(line)) return [seg(line, 'level-warn')]
|
|
97
|
+
if (/^(Reusing configuration cache|Deprecated Gradle features|You can use)/.test(line)) return [seg(line, 'muted')]
|
|
98
|
+
|
|
99
|
+
return [seg(line, 'plain')]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 批量解析,便于测试与 memo 化调用。
|
|
104
|
+
* @param {string[]} lines
|
|
105
|
+
* @returns {Array<Array<{ text: string, kind: SegmentKind }>>}
|
|
106
|
+
*/
|
|
107
|
+
export function highlightLines(lines) {
|
|
108
|
+
return (lines ?? []).map(highlightLine)
|
|
109
|
+
}
|
package/src/client/runtime.js
CHANGED
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
* @module minecraft-dev-starter/client/runtime
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { chooseLogDock, LOG_DOCK_BOTTOM } from './dock.js'
|
|
10
|
+
|
|
11
|
+
/** 右侧栏「Mod Dev」详情标签的 kind。 */
|
|
10
12
|
export const DETAIL_KIND = 'mcdev.detail'
|
|
11
13
|
|
|
12
14
|
/** 右侧栏「运行日志」标签的 kind。 */
|
|
13
15
|
export const LOG_KIND = 'mcdev.logs'
|
|
14
16
|
|
|
17
|
+
/** 向 dsh-better-sidebar 注册时用的 tab id;打开底部工作台需要同一个 id。 */
|
|
18
|
+
export const BETTER_DETAIL_ID = 'minecraft-dev-starter:project'
|
|
19
|
+
export const BETTER_LOG_ID = 'minecraft-dev-starter:logs'
|
|
20
|
+
|
|
15
21
|
/** @type {{ ctx?: any }} */
|
|
16
22
|
export const runtime = {}
|
|
17
23
|
|
|
@@ -30,12 +36,39 @@ function open(kind) {
|
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 取可选的 better-sidebar 服务;未安装时返回 undefined(不抛错)。
|
|
41
|
+
* @returns {any}
|
|
42
|
+
*/
|
|
43
|
+
export function betterSidebar() {
|
|
44
|
+
try {
|
|
45
|
+
return runtime.ctx?.get?.('betterSidebar', false)
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
/** 打开工程详情标签(openTab 会顺带展开面板)。 */
|
|
34
52
|
export function openDetailTab() {
|
|
35
53
|
return open(DETAIL_KIND)
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 打开运行日志标签。
|
|
58
|
+
*
|
|
59
|
+
* 落点由配置 `logDock` 决定:`bottom` 且装了 better-sidebar 时落到底部工作台,
|
|
60
|
+
* 其余情况一律落原生右侧栏。配置由调用方传入,避免 runtime ←→ state 循环依赖。
|
|
61
|
+
* @param {{ logDock?: string } | undefined} [config]
|
|
62
|
+
*/
|
|
63
|
+
export function openLogTab(config) {
|
|
64
|
+
const service = betterSidebar()
|
|
65
|
+
if (chooseLogDock(config, service !== undefined) === LOG_DOCK_BOTTOM) {
|
|
66
|
+
try {
|
|
67
|
+
service.openTab({ type: BETTER_LOG_ID, title: '运行日志', target: 'bottom' })
|
|
68
|
+
return true
|
|
69
|
+
} catch {
|
|
70
|
+
/* 落底部失败就退回右侧栏,不吞掉日志入口 */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
40
73
|
return open(LOG_KIND)
|
|
41
74
|
}
|
package/src/client/state.js
CHANGED
|
@@ -25,8 +25,8 @@ export const store = createStore({
|
|
|
25
25
|
flyoutOpen: false,
|
|
26
26
|
/** @type {Record<string, string>} 每个工程记住的目标 key */
|
|
27
27
|
selection: readSelection(),
|
|
28
|
-
/** @type {{ autoOpenLogTab?: boolean }} 宿主下发的部署配置 */
|
|
29
|
-
config: { autoOpenLogTab: true },
|
|
28
|
+
/** @type {{ autoOpenLogTab?: boolean, logDock?: 'right' | 'bottom' }} 宿主下发的部署配置 */
|
|
29
|
+
config: { autoOpenLogTab: true, logDock: 'right' },
|
|
30
30
|
/** @type {string | undefined} 当前会话的工作区(由胶囊从会话上下文注入) */
|
|
31
31
|
workspaceRoot: undefined,
|
|
32
32
|
})
|
|
@@ -37,7 +37,7 @@ export const store = createStore({
|
|
|
37
37
|
export async function loadStateInfo() {
|
|
38
38
|
try {
|
|
39
39
|
const info = await call('state.info', {})
|
|
40
|
-
store.set({ config: { autoOpenLogTab: true, ...(info?.config ?? {}) } })
|
|
40
|
+
store.set({ config: { autoOpenLogTab: true, logDock: 'right', ...(info?.config ?? {}) } })
|
|
41
41
|
} catch {
|
|
42
42
|
/* 拉不到就用默认值 */
|
|
43
43
|
}
|
|
@@ -172,7 +172,7 @@ export async function startTarget(project, target) {
|
|
|
172
172
|
module: target.module,
|
|
173
173
|
})
|
|
174
174
|
// 启动成功就把日志摆到人眼前(部署配置可关)
|
|
175
|
-
if (store.get().config.autoOpenLogTab !== false) openLogTab()
|
|
175
|
+
if (store.get().config.autoOpenLogTab !== false) openLogTab(store.get().config)
|
|
176
176
|
} catch (error) {
|
|
177
177
|
launchStore.set({ error: error instanceof Error ? error.message : String(error) })
|
|
178
178
|
} finally {
|
package/src/client/styles.js
CHANGED
|
@@ -257,6 +257,20 @@ export const CSS = `
|
|
|
257
257
|
.mcdev-diagnosis-hint { color: var(--dsw-alias-state-error-primary); font: var(--dsw-font-xxs-strong-12); }
|
|
258
258
|
.mcdev-logs-full { flex: 1 1 auto; min-height: 0; max-height: none; margin: 0; }
|
|
259
259
|
|
|
260
|
+
/* 日志高亮:只按语义上色,时间/线程/logger 一律压到 tertiary,让正文更清楚 */
|
|
261
|
+
.mcdev-log-line { display: inline; }
|
|
262
|
+
.mcdev-log-line [data-kind="time"],
|
|
263
|
+
.mcdev-log-line [data-kind="thread"],
|
|
264
|
+
.mcdev-log-line [data-kind="logger"] { color: var(--dsw-alias-label-tertiary); }
|
|
265
|
+
.mcdev-log-line [data-kind="muted"] { color: var(--dsw-alias-label-tertiary); }
|
|
266
|
+
.mcdev-log-line [data-kind="level-warn"] { color: var(--dsw-alias-state-warn-primary); }
|
|
267
|
+
.mcdev-log-line [data-kind="level-error"] { color: var(--dsw-alias-state-error-primary); }
|
|
268
|
+
.mcdev-log-line [data-kind="level-debug"] { color: var(--dsw-alias-label-tertiary); }
|
|
269
|
+
.mcdev-log-line [data-kind="task"] { color: var(--dsw-alias-label-secondary); }
|
|
270
|
+
.mcdev-log-line [data-kind="ok"] { color: var(--dsw-alias-state-success-primary); }
|
|
271
|
+
.mcdev-log-line [data-kind="fail"] { color: var(--dsw-alias-state-error-primary); font: var(--dsw-font-xxxs-strong-11); }
|
|
272
|
+
.mcdev-log-line [data-kind="stack"] { color: var(--dsw-alias-label-secondary); }
|
|
273
|
+
|
|
260
274
|
.mcdev-section { margin-top: 12px; }
|
|
261
275
|
.mcdev-section > h4 {
|
|
262
276
|
margin: 0 0 4px;
|
package/src/index.js
CHANGED
|
@@ -117,6 +117,7 @@ function resolveWorkspaceRoot(ctx) {
|
|
|
117
117
|
/** 部署配置默认值;patch 行的 `config` 会覆盖同名项。 */
|
|
118
118
|
const DEFAULTS = {
|
|
119
119
|
autoOpenLogTab: true,
|
|
120
|
+
logDock: 'right',
|
|
120
121
|
readyTimeoutMs: 90_000,
|
|
121
122
|
logRingLines: 2_000,
|
|
122
123
|
}
|
|
@@ -131,7 +132,10 @@ export function apply(ctx, config = {}) {
|
|
|
131
132
|
version: VERSION,
|
|
132
133
|
resolveRoot: () => resolveWorkspaceRoot(ctx),
|
|
133
134
|
resolveRoots: () => sessionCwds(ctx),
|
|
134
|
-
config: {
|
|
135
|
+
config: {
|
|
136
|
+
autoOpenLogTab: settings.autoOpenLogTab !== false,
|
|
137
|
+
logDock: settings.logDock === 'bottom' ? 'bottom' : 'right',
|
|
138
|
+
},
|
|
135
139
|
launchService: createLaunchService({
|
|
136
140
|
readyTimeoutMs: Number(settings.readyTimeoutMs) || DEFAULTS.readyTimeoutMs,
|
|
137
141
|
maxLogLines: Number(settings.logRingLines) || DEFAULTS.logRingLines,
|