@uni_toolkit/uniapp-miniprogram-devtool 0.1.1-alpha.1778855349600
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/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/umpd.js +16 -0
- package/dist/automator-inspector.cjs +172 -0
- package/dist/automator-inspector.cjs.map +1 -0
- package/dist/automator-inspector.d.cts +35 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/cli.cjs +33 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +5 -0
- package/dist/core.cjs +593 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +71 -0
- package/dist/html-report.cjs +75 -0
- package/dist/html-report.cjs.map +1 -0
- package/dist/html-report.d.cts +7 -0
- package/dist/one-click.cjs +223 -0
- package/dist/one-click.cjs.map +1 -0
- package/dist/one-click.d.cts +5 -0
- package/dist/runtime-snippet.cjs +116 -0
- package/dist/runtime-snippet.cjs.map +1 -0
- package/dist/runtime-snippet.d.cts +7 -0
- package/dist/web-panel.cjs +882 -0
- package/dist/web-panel.cjs.map +1 -0
- package/dist/web-panel.d.cts +50 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 chouchouji
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# @uni_toolkit/uniapp-miniprogram-devtool
|
|
2
|
+
|
|
3
|
+
一个用于调试 `uni-app` / `uni-app x` 编译到微信小程序产物的运行时变量观察工具。
|
|
4
|
+
|
|
5
|
+
`uni-app` / `uni-app x` 在编译微信小程序时,会把模板返回值里的 key 压缩成 `a`、`b`、`c` 这类短字段。例如源码里写的是:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const message = ref('测试')
|
|
9
|
+
const count = ref(1)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
编译产物里可能变成:
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const __returned__ = {
|
|
16
|
+
a: common_vendor.t(common_vendor.unref(message)),
|
|
17
|
+
b: common_vendor.t(common_vendor.unref(count))
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
这个工具会读取编译产物和 sourcemap,推断出:
|
|
22
|
+
|
|
23
|
+
```txt
|
|
24
|
+
a -> message
|
|
25
|
+
b -> count
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
然后通过微信开发者工具 automator 读取当前页面运行时 `page.data`,在 Web Panel 中展示最新值。
|
|
29
|
+
|
|
30
|
+
## 特性
|
|
31
|
+
|
|
32
|
+
- 不修改 `app.js`、页面 JS、WXML 等编译产物。
|
|
33
|
+
- 读取页面 JS、WXML、sourcemap,分析 `a/b/c` 到源码变量的映射关系。
|
|
34
|
+
- 通过微信开发者工具 automator 读取运行时 `page.data`。
|
|
35
|
+
- 默认启动 Web Panel,展示当前页面变量值。
|
|
36
|
+
- 支持过滤、手动刷新、值变化高亮、Debug 面板。
|
|
37
|
+
|
|
38
|
+
## 注意事项
|
|
39
|
+
|
|
40
|
+
- `manifest.json` 里需要配置合适的 `appid`,以避免微信开发者工具连接失败
|
|
41
|
+
- 使用此工具前,请先推出微信开发者工具,确保它没有在后台运行
|
|
42
|
+
- 需要开启微信开发者工具的服务端口,以便 `automator` 能够连接
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
建议全局安装,安装后会得到命令行工具 `umpd`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @uni_toolkit/uniapp-miniprogram-devtool
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
也可以使用其他包管理器全局安装:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm add -g @uni_toolkit/uniapp-miniprogram-devtool
|
|
56
|
+
yarn global add @uni_toolkit/uniapp-miniprogram-devtool
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 快速开始
|
|
60
|
+
|
|
61
|
+
`mp-weixin` 产物目录和微信开发者工具路径都是必填参数。产物目录可以直接作为第一个参数传入,也可以用 `-p`;微信开发者工具路径推荐用 `-w`。
|
|
62
|
+
|
|
63
|
+
### 配置项
|
|
64
|
+
|
|
65
|
+
| 配置 | 缩写 / 别名 | 必填 | 默认值 | 说明 |
|
|
66
|
+
| --- | --- | --- | --- | --- |
|
|
67
|
+
| `mp-weixin` 产物目录 | 位置参数、`-p`、`--proj`、`--project` | 是 | - | `uni-app` / `uni-app x` 的微信小程序编译产物目录,一般是 `unpackage/dist/dev/mp-weixin` |
|
|
68
|
+
| 微信开发者工具路径 | `-w`、`--wd`、`--wechat-devtools` | 是 | - | 微信开发者工具 `.app` 路径;工具会自动解析到 `Contents/MacOS/cli` |
|
|
69
|
+
| 微信开发者工具 CLI 路径 | `--cli-path` | 否 | - | 如果已经拿到 `cli` 二进制路径,可以用它替代 `-w` |
|
|
70
|
+
| Web Panel 端口 | `--port` | 否 | `17890` | 本地 Web Panel 端口;如果被占用会自动尝试后续端口 |
|
|
71
|
+
| 查看帮助 | `-h`、`--help` | 否 | - | 打印命令行帮助 |
|
|
72
|
+
|
|
73
|
+
### 环境变量
|
|
74
|
+
|
|
75
|
+
| 环境变量 | 默认值 | 说明 |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `UNIAPP_MINIPROGRAM_DEVTOOL_PORT` | `17890` | 未传 `--port` 时使用的 Web Panel 端口 |
|
|
78
|
+
| `UNIAPP_MINIPROGRAM_DEVTOOL_NO_OPEN` | - | 设为 `1` 时不自动打开浏览器 |
|
|
79
|
+
|
|
80
|
+
启动示例:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
umpd unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
这里不需要你手动去找 `cli` 文件。直接传 `.app` 路径即可,工具内部会自动解析到:
|
|
87
|
+
|
|
88
|
+
```txt
|
|
89
|
+
/Volumes/Elements/Applications/wechatwebdevtools.app/Contents/MacOS/cli
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
如果你确实已经拿到了微信开发者工具的 `cli` 二进制路径,也可以显式传:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
umpd -p ./unpackage/dist/dev/mp-weixin --cli-path /Applications/wechatwebdevtools.app/Contents/MacOS/cli
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 微信开发者工具要求
|
|
99
|
+
|
|
100
|
+
需要开启微信开发者工具的服务端口:
|
|
101
|
+
|
|
102
|
+
```txt
|
|
103
|
+
微信开发者工具 -> 设置 -> 安全设置 -> 服务端口
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
如果没有开启,automator 无法连接微信开发者工具。
|
|
107
|
+
|
|
108
|
+
## Web Panel
|
|
109
|
+
|
|
110
|
+
启动后会自动打开本地页面,默认地址:
|
|
111
|
+
|
|
112
|
+
```txt
|
|
113
|
+
http://127.0.0.1:17890
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
如果端口被占用,会自动尝试后续端口。
|
|
117
|
+
|
|
118
|
+
Web Panel 当前包含:
|
|
119
|
+
|
|
120
|
+
- 当前页面 route
|
|
121
|
+
- 变量映射和运行时值
|
|
122
|
+
- 300ms 自动刷新
|
|
123
|
+
- `Refresh now` 手动刷新
|
|
124
|
+
- 过滤框
|
|
125
|
+
- 值变化高亮
|
|
126
|
+
- Debug 面板:显示 `pageId`、`rawRoute`、`keymapPages`、原始 `page.data`
|
|
127
|
+
|
|
128
|
+
## 常用参数
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
umpd <mp-weixin 产物目录> -w <wechatwebdevtools.app 路径>
|
|
132
|
+
umpd <mp-weixin 产物目录> -w <wechatwebdevtools.app 路径> --port 17890
|
|
133
|
+
umpd -p <mp-weixin 产物目录> -w <wechatwebdevtools.app 路径>
|
|
134
|
+
umpd --project <mp-weixin 产物目录> --wechat-devtools <wechatwebdevtools.app 路径>
|
|
135
|
+
umpd -p <mp-weixin 产物目录> --cli-path <微信开发者工具 cli 路径>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 工作原理
|
|
139
|
+
|
|
140
|
+
1. 读取你显式传入的 `mp-weixin` 编译产物目录。
|
|
141
|
+
2. 解析页面 JS 中的 `const __returned__ = { ... }`。
|
|
142
|
+
3. 从表达式中推断 `a -> message`、`b -> count` 这类映射。
|
|
143
|
+
4. 读取 WXML 和 sourcemap 辅助展示。
|
|
144
|
+
5. 通过 `miniprogram-automator` 连接微信开发者工具。
|
|
145
|
+
6. 读取当前页面的 `page.data`。
|
|
146
|
+
7. 将映射后的变量和值展示到 Web Panel。
|
|
147
|
+
|
|
148
|
+
## 限制
|
|
149
|
+
|
|
150
|
+
- 当前主要针对 `uni-app` / `uni-app x` 开发模式下的微信小程序产物。
|
|
151
|
+
- 运行时值依赖微信开发者工具 automator 能力。
|
|
152
|
+
- 工具不会修改编译产物,因此无法像注入脚本那样直接进入小程序运行时,只能通过 automator 读取数据。
|
|
153
|
+
|
|
154
|
+
## 开发
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pnpm install
|
|
158
|
+
pnpm run build
|
|
159
|
+
pnpm test
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
本地启动:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pnpm dev -- unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
本仓库里的可执行入口文件是:
|
|
169
|
+
|
|
170
|
+
```txt
|
|
171
|
+
bin/umpd.js
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
[MIT](/LICENSE)
|
package/bin/umpd.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
try {
|
|
4
|
+
const { main } = require('../dist/cli.cjs');
|
|
5
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
6
|
+
console.error(error && error.stack ? error.stack : String(error));
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
});
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
11
|
+
console.error('umpd 尚未构建,请先在项目内执行 `pnpm run build`。');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
} else {
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/automator-inspector.ts
|
|
3
|
+
const automator = require("miniprogram-automator");
|
|
4
|
+
function normalizeRoute(route) {
|
|
5
|
+
const withoutQuery = route.split("?")[0] || route;
|
|
6
|
+
return withoutQuery.startsWith("/") ? withoutQuery.slice(1) : withoutQuery;
|
|
7
|
+
}
|
|
8
|
+
function withTimeout(promise, timeoutMs, label) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
11
|
+
promise.then((value) => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
resolve(value);
|
|
14
|
+
}, (error) => {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
reject(error);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function readRuntimePageState(miniProgram) {
|
|
21
|
+
const current = await miniProgram.send("App.getCurrentPage");
|
|
22
|
+
if (!current || current.pageId == null) return null;
|
|
23
|
+
const dataResult = await miniProgram.send("Page.getData", { pageId: current.pageId });
|
|
24
|
+
const rawRoute = String(current.path || "");
|
|
25
|
+
return {
|
|
26
|
+
route: normalizeRoute(rawRoute),
|
|
27
|
+
rawRoute,
|
|
28
|
+
pageId: current.pageId,
|
|
29
|
+
data: dataResult?.data || {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function printableRows(items, data) {
|
|
33
|
+
return items.filter((item) => item.kind !== "event-handler").map((item) => ({
|
|
34
|
+
source: item.sourceName || item.generatedName || "unknown",
|
|
35
|
+
key: item.key,
|
|
36
|
+
value: data[item.key],
|
|
37
|
+
kind: item.kind,
|
|
38
|
+
confidence: item.confidence,
|
|
39
|
+
expressionSummary: item.expressionSummary,
|
|
40
|
+
wxmlUsages: item.wxmlUsages.map((usage) => usage.snippet)
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function explainAutomatorFailure(error) {
|
|
44
|
+
return [
|
|
45
|
+
"连接微信开发者工具自动化失败。",
|
|
46
|
+
error instanceof Error ? error.message : String(error),
|
|
47
|
+
"",
|
|
48
|
+
"请检查:",
|
|
49
|
+
"1. 已安装微信开发者工具。",
|
|
50
|
+
"2. 微信开发者工具 -> 设置 -> 安全设置 -> 服务端口 已开启。",
|
|
51
|
+
"3. 该 mp-weixin 编译产物可以被微信开发者工具正常打开。"
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
async function startAutomatorInspector(options) {
|
|
55
|
+
const miniProgram = await automator.launch({
|
|
56
|
+
projectPath: options.projectPath,
|
|
57
|
+
cliPath: options.cliPath,
|
|
58
|
+
port: options.port || 9420,
|
|
59
|
+
timeout: 45e3,
|
|
60
|
+
trustProject: true
|
|
61
|
+
});
|
|
62
|
+
let closed = false;
|
|
63
|
+
let lastText = "";
|
|
64
|
+
let busy = false;
|
|
65
|
+
const intervalMs = options.intervalMs || 500;
|
|
66
|
+
async function poll() {
|
|
67
|
+
if (closed || busy) return;
|
|
68
|
+
busy = true;
|
|
69
|
+
try {
|
|
70
|
+
const state = await withTimeout(readRuntimePageState(miniProgram), 1500, "读取当前页面数据");
|
|
71
|
+
if (!state) {
|
|
72
|
+
options.onSnapshot?.({
|
|
73
|
+
connected: true,
|
|
74
|
+
status: "已连接,等待当前页面加载",
|
|
75
|
+
route: "",
|
|
76
|
+
rows: [],
|
|
77
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
78
|
+
debug: { keymapPages: Object.keys(options.getAnalysis().pages) },
|
|
79
|
+
templateTree: null
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const route = state.route;
|
|
84
|
+
const analysis = options.getAnalysis();
|
|
85
|
+
const pageAnalysis = analysis.pages[route];
|
|
86
|
+
if (!pageAnalysis) {
|
|
87
|
+
options.onSnapshot?.({
|
|
88
|
+
connected: true,
|
|
89
|
+
status: "已连接,但当前页面没有可用映射",
|
|
90
|
+
route,
|
|
91
|
+
rows: [],
|
|
92
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93
|
+
debug: {
|
|
94
|
+
pageId: state.pageId,
|
|
95
|
+
rawRoute: state.rawRoute,
|
|
96
|
+
keymapPages: Object.keys(analysis.pages)
|
|
97
|
+
},
|
|
98
|
+
rawData: state.data,
|
|
99
|
+
templateTree: null
|
|
100
|
+
});
|
|
101
|
+
const text = `当前页面:${route}\n未找到当前页面的 key 映射。`;
|
|
102
|
+
if (!options.suppressTerminal && text !== lastText) {
|
|
103
|
+
lastText = text;
|
|
104
|
+
console.clear();
|
|
105
|
+
console.log(text);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const rows = printableRows(pageAnalysis.keys, state.data || {});
|
|
110
|
+
options.onSnapshot?.({
|
|
111
|
+
connected: true,
|
|
112
|
+
status: "已连接",
|
|
113
|
+
route,
|
|
114
|
+
rows,
|
|
115
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
|
+
debug: {
|
|
117
|
+
pageId: state.pageId,
|
|
118
|
+
rawRoute: state.rawRoute,
|
|
119
|
+
keymapPages: Object.keys(analysis.pages)
|
|
120
|
+
},
|
|
121
|
+
rawData: state.data,
|
|
122
|
+
templateTree: pageAnalysis.templateTree
|
|
123
|
+
});
|
|
124
|
+
const text = JSON.stringify({
|
|
125
|
+
route,
|
|
126
|
+
rows
|
|
127
|
+
});
|
|
128
|
+
if (!options.suppressTerminal && text !== lastText) {
|
|
129
|
+
lastText = text;
|
|
130
|
+
console.clear();
|
|
131
|
+
console.log("已在 Web 面板中展示最新运行时变量值。");
|
|
132
|
+
console.log("当前页面:", route);
|
|
133
|
+
console.log("按 Ctrl+C 停止。");
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const text = `读取运行时数据失败:${error instanceof Error ? error.message : String(error)}`;
|
|
137
|
+
options.onSnapshot?.({
|
|
138
|
+
connected: false,
|
|
139
|
+
status: text,
|
|
140
|
+
route: "",
|
|
141
|
+
rows: [],
|
|
142
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143
|
+
debug: {
|
|
144
|
+
error: text,
|
|
145
|
+
keymapPages: Object.keys(options.getAnalysis().pages)
|
|
146
|
+
},
|
|
147
|
+
templateTree: null
|
|
148
|
+
});
|
|
149
|
+
if (!options.suppressTerminal && text !== lastText) {
|
|
150
|
+
lastText = text;
|
|
151
|
+
console.clear();
|
|
152
|
+
console.log(text);
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
busy = false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const timer = setInterval(() => void poll(), intervalMs);
|
|
159
|
+
await poll();
|
|
160
|
+
return () => {
|
|
161
|
+
closed = true;
|
|
162
|
+
clearInterval(timer);
|
|
163
|
+
try {
|
|
164
|
+
miniProgram.disconnect();
|
|
165
|
+
} catch (_error) {}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
exports.explainAutomatorFailure = explainAutomatorFailure;
|
|
170
|
+
exports.startAutomatorInspector = startAutomatorInspector;
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=automator-inspector.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automator-inspector.cjs","names":[],"sources":["../src/automator-inspector.ts"],"sourcesContent":["import automator = require('miniprogram-automator');\n\nimport type { KeyMapItem, ProjectAnalysis, TemplateNode } from './core';\n\ninterface InspectorOptions {\n projectPath: string;\n getAnalysis: () => ProjectAnalysis;\n intervalMs?: number;\n cliPath?: string;\n port?: number;\n suppressTerminal?: boolean;\n onSnapshot?: (snapshot: {\n connected: boolean;\n status: string;\n route: string;\n rows: RuntimeRow[];\n updatedAt: string;\n debug?: Record<string, unknown>;\n rawData?: Record<string, unknown>;\n templateTree?: TemplateNode | null;\n }) => void;\n}\n\nexport interface RuntimeRow {\n source: string;\n key: string;\n value: unknown;\n kind: string;\n confidence: string;\n expressionSummary: string;\n wxmlUsages: string[];\n}\n\nfunction normalizeRoute(route: string): string {\n const withoutQuery = route.split('?')[0] || route;\n return withoutQuery.startsWith('/') ? withoutQuery.slice(1) : withoutQuery;\n}\n\ninterface RuntimePageState {\n route: string;\n rawRoute: string;\n pageId: number | string;\n data: Record<string, unknown>;\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nasync function readRuntimePageState(miniProgram: any): Promise<RuntimePageState | null> {\n // Use the underlying automator protocol directly instead of cached Page objects.\n // This tracks refresh/navigation better because App.getCurrentPage returns the latest pageId/path.\n const current = await miniProgram.send('App.getCurrentPage');\n if (!current || current.pageId == null) return null;\n\n const dataResult = await miniProgram.send('Page.getData', { pageId: current.pageId });\n const rawRoute = String(current.path || '');\n return {\n route: normalizeRoute(rawRoute),\n rawRoute,\n pageId: current.pageId,\n data: (dataResult?.data || {}) as Record<string, unknown>,\n };\n}\n\nfunction printableRows(items: KeyMapItem[], data: Record<string, unknown>): RuntimeRow[] {\n return items\n .filter((item) => item.kind !== 'event-handler')\n .map((item) => ({\n source: item.sourceName || item.generatedName || 'unknown',\n key: item.key,\n value: data[item.key],\n kind: item.kind,\n confidence: item.confidence,\n expressionSummary: item.expressionSummary,\n wxmlUsages: item.wxmlUsages.map((usage) => usage.snippet),\n }));\n}\n\nfunction explainAutomatorFailure(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return [\n '连接微信开发者工具自动化失败。',\n message,\n '',\n '请检查:',\n '1. 已安装微信开发者工具。',\n '2. 微信开发者工具 -> 设置 -> 安全设置 -> 服务端口 已开启。',\n '3. 该 mp-weixin 编译产物可以被微信开发者工具正常打开。',\n ].join('\\n');\n}\n\nexport async function startAutomatorInspector(options: InspectorOptions): Promise<() => void> {\n const miniProgram = await automator.launch({\n projectPath: options.projectPath,\n cliPath: options.cliPath,\n port: options.port || 9420,\n timeout: 45_000,\n trustProject: true,\n });\n\n let closed = false;\n let lastText = '';\n let busy = false;\n const intervalMs = options.intervalMs || 500;\n\n async function poll(): Promise<void> {\n if (closed || busy) return;\n busy = true;\n try {\n const state = await withTimeout(readRuntimePageState(miniProgram), 1500, '读取当前页面数据');\n if (!state) {\n options.onSnapshot?.({\n connected: true,\n status: '已连接,等待当前页面加载',\n route: '',\n rows: [],\n updatedAt: new Date().toISOString(),\n debug: { keymapPages: Object.keys(options.getAnalysis().pages) },\n templateTree: null,\n });\n return;\n }\n const route = state.route;\n const analysis = options.getAnalysis();\n const pageAnalysis = analysis.pages[route];\n if (!pageAnalysis) {\n options.onSnapshot?.({\n connected: true,\n status: '已连接,但当前页面没有可用映射',\n route,\n rows: [],\n updatedAt: new Date().toISOString(),\n debug: { pageId: state.pageId, rawRoute: state.rawRoute, keymapPages: Object.keys(analysis.pages) },\n rawData: state.data,\n templateTree: null,\n });\n const text = `当前页面:${route}\\n未找到当前页面的 key 映射。`;\n if (!options.suppressTerminal && text !== lastText) {\n lastText = text;\n console.clear();\n console.log(text);\n }\n return;\n }\n\n const rows = printableRows(pageAnalysis.keys, state.data || {});\n options.onSnapshot?.({\n connected: true,\n status: '已连接',\n route,\n rows,\n updatedAt: new Date().toISOString(),\n debug: { pageId: state.pageId, rawRoute: state.rawRoute, keymapPages: Object.keys(analysis.pages) },\n rawData: state.data,\n templateTree: pageAnalysis.templateTree,\n });\n const text = JSON.stringify({ route, rows });\n if (!options.suppressTerminal && text !== lastText) {\n lastText = text;\n console.clear();\n console.log('已在 Web 面板中展示最新运行时变量值。');\n console.log('当前页面:', route);\n console.log('按 Ctrl+C 停止。');\n }\n } catch (error) {\n const text = `读取运行时数据失败:${error instanceof Error ? error.message : String(error)}`;\n options.onSnapshot?.({\n connected: false,\n status: text,\n route: '',\n rows: [],\n updatedAt: new Date().toISOString(),\n debug: { error: text, keymapPages: Object.keys(options.getAnalysis().pages) },\n templateTree: null,\n });\n if (!options.suppressTerminal && text !== lastText) {\n lastText = text;\n console.clear();\n console.log(text);\n }\n } finally {\n busy = false;\n }\n }\n\n const timer = setInterval(() => void poll(), intervalMs);\n await poll();\n\n return () => {\n closed = true;\n clearInterval(timer);\n try {\n miniProgram.disconnect();\n } catch (_error) {\n // Ignore shutdown errors from the DevTools websocket.\n }\n };\n}\n\nexport { explainAutomatorFailure };\n"],"mappings":";;MAAO,YAAA,QAAoB,wBAAA;AAiC3B,SAAS,eAAe,OAAuB;CAC7C,MAAM,eAAe,MAAM,MAAM,IAAI,CAAC,MAAM;AAC5C,QAAO,aAAa,WAAW,IAAI,GAAG,aAAa,MAAM,EAAE,GAAG;;AAUhE,SAAS,YAAe,SAAqB,WAAmB,OAA2B;AACzF,QAAO,IAAI,SAAY,SAAS,WAAW;EACzC,MAAM,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,GAAG,MAAM,mBAAmB,UAAU,IAAI,CAAC,EAAE,UAAU;AACvG,UAAQ,MACL,UAAU;AACT,gBAAa,MAAM;AACnB,WAAQ,MAAM;MAEf,UAAU;AACT,gBAAa,MAAM;AACnB,UAAO,MAAM;IAEhB;GACD;;AAGJ,eAAe,qBAAqB,aAAoD;CAGtF,MAAM,UAAU,MAAM,YAAY,KAAK,qBAAqB;AAC5D,KAAI,CAAC,WAAW,QAAQ,UAAU,KAAM,QAAO;CAE/C,MAAM,aAAa,MAAM,YAAY,KAAK,gBAAgB,EAAE,QAAQ,QAAQ,QAAQ,CAAC;CACrF,MAAM,WAAW,OAAO,QAAQ,QAAQ,GAAG;AAC3C,QAAO;EACL,OAAO,eAAe,SAAS;EAC/B;EACA,QAAQ,QAAQ;EAChB,MAAO,YAAY,QAAQ,EAAE;EAC9B;;AAGH,SAAS,cAAc,OAAqB,MAA6C;AACvF,QAAO,MACJ,QAAQ,SAAS,KAAK,SAAS,gBAAgB,CAC/C,KAAK,UAAU;EACd,QAAQ,KAAK,cAAc,KAAK,iBAAiB;EACjD,KAAK,KAAK;EACV,OAAO,KAAK,KAAK;EACjB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,mBAAmB,KAAK;EACxB,YAAY,KAAK,WAAW,KAAK,UAAU,MAAM,QAAQ;EAC1D,EAAE;;AAGP,SAAS,wBAAwB,OAAwB;AAEvD,QAAO;EACL;EAFc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAIpE;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,eAAsB,wBAAwB,SAAgD;CAC5F,MAAM,cAAc,MAAM,UAAU,OAAO;EACzC,aAAa,QAAQ;EACrB,SAAS,QAAQ;EACjB,MAAM,QAAQ,QAAQ;EACtB,SAAS;EACT,cAAc;EACf,CAAC;CAEF,IAAI,SAAS;CACb,IAAI,WAAW;CACf,IAAI,OAAO;CACX,MAAM,aAAa,QAAQ,cAAc;CAEzC,eAAe,OAAsB;AACnC,MAAI,UAAU,KAAM;AACpB,SAAO;AACP,MAAI;GACF,MAAM,QAAQ,MAAM,YAAY,qBAAqB,YAAY,EAAE,MAAM,WAAW;AACpF,OAAI,CAAC,OAAO;AACV,YAAQ,aAAa;KACnB,WAAW;KACX,QAAQ;KACR,OAAO;KACP,MAAM,EAAE;KACR,4BAAW,IAAI,MAAM,EAAC,aAAa;KACnC,OAAO,EAAE,aAAa,OAAO,KAAK,QAAQ,aAAa,CAAC,MAAM,EAAE;KAChE,cAAc;KACf,CAAC;AACF;;GAEF,MAAM,QAAQ,MAAM;GACpB,MAAM,WAAW,QAAQ,aAAa;GACtC,MAAM,eAAe,SAAS,MAAM;AACpC,OAAI,CAAC,cAAc;AACjB,YAAQ,aAAa;KACnB,WAAW;KACX,QAAQ;KACR;KACA,MAAM,EAAE;KACR,4BAAW,IAAI,MAAM,EAAC,aAAa;KACnC,OAAO;MAAE,QAAQ,MAAM;MAAQ,UAAU,MAAM;MAAU,aAAa,OAAO,KAAK,SAAS,MAAM;MAAE;KACnG,SAAS,MAAM;KACf,cAAc;KACf,CAAC;IACF,MAAM,OAAO,QAAQ,MAAM;AAC3B,QAAI,CAAC,QAAQ,oBAAoB,SAAS,UAAU;AAClD,gBAAW;AACX,aAAQ,OAAO;AACf,aAAQ,IAAI,KAAK;;AAEnB;;GAGF,MAAM,OAAO,cAAc,aAAa,MAAM,MAAM,QAAQ,EAAE,CAAC;AAC/D,WAAQ,aAAa;IACnB,WAAW;IACX,QAAQ;IACR;IACA;IACA,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,OAAO;KAAE,QAAQ,MAAM;KAAQ,UAAU,MAAM;KAAU,aAAa,OAAO,KAAK,SAAS,MAAM;KAAE;IACnG,SAAS,MAAM;IACf,cAAc,aAAa;IAC5B,CAAC;GACF,MAAM,OAAO,KAAK,UAAU;IAAE;IAAO;IAAM,CAAC;AAC5C,OAAI,CAAC,QAAQ,oBAAoB,SAAS,UAAU;AAClD,eAAW;AACX,YAAQ,OAAO;AACf,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,IAAI,SAAS,MAAM;AAC3B,YAAQ,IAAI,eAAe;;WAEtB,OAAO;GACd,MAAM,OAAO,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAChF,WAAQ,aAAa;IACnB,WAAW;IACX,QAAQ;IACR,OAAO;IACP,MAAM,EAAE;IACR,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,OAAO;KAAE,OAAO;KAAM,aAAa,OAAO,KAAK,QAAQ,aAAa,CAAC,MAAM;KAAE;IAC7E,cAAc;IACf,CAAC;AACF,OAAI,CAAC,QAAQ,oBAAoB,SAAS,UAAU;AAClD,eAAW;AACX,YAAQ,OAAO;AACf,YAAQ,IAAI,KAAK;;YAEX;AACR,UAAO;;;CAIX,MAAM,QAAQ,kBAAkB,KAAK,MAAM,EAAE,WAAW;AACxD,OAAM,MAAM;AAEZ,cAAa;AACX,WAAS;AACT,gBAAc,MAAM;AACpB,MAAI;AACF,eAAY,YAAY;WACjB,QAAQ"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ProjectAnalysis, TemplateNode } from "./core.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/automator-inspector.d.ts
|
|
4
|
+
interface InspectorOptions {
|
|
5
|
+
projectPath: string;
|
|
6
|
+
getAnalysis: () => ProjectAnalysis;
|
|
7
|
+
intervalMs?: number;
|
|
8
|
+
cliPath?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
suppressTerminal?: boolean;
|
|
11
|
+
onSnapshot?: (snapshot: {
|
|
12
|
+
connected: boolean;
|
|
13
|
+
status: string;
|
|
14
|
+
route: string;
|
|
15
|
+
rows: RuntimeRow[];
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
debug?: Record<string, unknown>;
|
|
18
|
+
rawData?: Record<string, unknown>;
|
|
19
|
+
templateTree?: TemplateNode | null;
|
|
20
|
+
}) => void;
|
|
21
|
+
}
|
|
22
|
+
interface RuntimeRow {
|
|
23
|
+
source: string;
|
|
24
|
+
key: string;
|
|
25
|
+
value: unknown;
|
|
26
|
+
kind: string;
|
|
27
|
+
confidence: string;
|
|
28
|
+
expressionSummary: string;
|
|
29
|
+
wxmlUsages: string[];
|
|
30
|
+
}
|
|
31
|
+
declare function explainAutomatorFailure(error: unknown): string;
|
|
32
|
+
declare function startAutomatorInspector(options: InspectorOptions): Promise<() => void>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { RuntimeRow, explainAutomatorFailure, startAutomatorInspector };
|
|
35
|
+
//# sourceMappingURL=automator-inspector.d.cts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
Object.defineProperty(exports, "__toESM", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function() {
|
|
26
|
+
return __toESM;
|
|
27
|
+
}
|
|
28
|
+
});
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_one_click = require("./one-click.cjs");
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
function printHelp() {
|
|
5
|
+
console.log(`umpd
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
umpd <mp-weixin> -w <wechatwebdevtools.app> [--port <port>]
|
|
9
|
+
umpd -p <mp-weixin> -w <wechatwebdevtools.app> [--port <port>]
|
|
10
|
+
umpd --project <mp-weixin> --wechat-devtools <wechatwebdevtools.app> [--port <port>]
|
|
11
|
+
|
|
12
|
+
说明:
|
|
13
|
+
默认启动 Web Panel,并通过微信开发者工具 automator 读取当前小程序运行时 page.data。
|
|
14
|
+
mp-weixin 产物目录可直接作为第一个参数,也可用 -p / --proj / --project 指定。
|
|
15
|
+
-w / --wd / --wechat-devtools 都可以传微信开发者工具 .app 路径,工具会自动解析到 Contents/MacOS/cli。
|
|
16
|
+
|
|
17
|
+
示例:
|
|
18
|
+
umpd ./unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app
|
|
19
|
+
umpd -p ./unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app
|
|
20
|
+
umpd --project ./unpackage/dist/dev/mp-weixin --cli-path /Applications/wechatwebdevtools.app/Contents/MacOS/cli
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
async function main(argv) {
|
|
24
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
25
|
+
printHelp();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await require_one_click.main(argv);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
exports.main = main;
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["devMain"],"sources":["../src/cli.ts"],"sourcesContent":["import { main as devMain } from './one-click';\n\nfunction printHelp(): void {\n console.log(`umpd\n\n用法:\n umpd <mp-weixin> -w <wechatwebdevtools.app> [--port <port>]\n umpd -p <mp-weixin> -w <wechatwebdevtools.app> [--port <port>]\n umpd --project <mp-weixin> --wechat-devtools <wechatwebdevtools.app> [--port <port>]\n\n说明:\n 默认启动 Web Panel,并通过微信开发者工具 automator 读取当前小程序运行时 page.data。\n mp-weixin 产物目录可直接作为第一个参数,也可用 -p / --proj / --project 指定。\n -w / --wd / --wechat-devtools 都可以传微信开发者工具 .app 路径,工具会自动解析到 Contents/MacOS/cli。\n\n示例:\n umpd ./unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app\n umpd -p ./unpackage/dist/dev/mp-weixin -w /Volumes/Elements/Applications/wechatwebdevtools.app\n umpd --project ./unpackage/dist/dev/mp-weixin --cli-path /Applications/wechatwebdevtools.app/Contents/MacOS/cli\n`);\n}\n\nexport async function main(argv: string[]): Promise<void> {\n if (argv.includes('-h') || argv.includes('--help')) {\n printHelp();\n return;\n }\n await devMain(argv);\n}\n"],"mappings":";;;AAEA,SAAS,YAAkB;AACzB,SAAQ,IAAI;;;;;;;;;;;;;;;;EAgBZ;;AAGF,eAAsB,KAAK,MAA+B;AACxD,KAAI,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,SAAS,EAAE;AAClD,aAAW;AACX;;AAEF,OAAMA,kBAAAA,KAAQ,KAAK"}
|