@heybox/hb-sdk 0.9.2-alpha.7 → 0.9.2-alpha.9
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/CHANGELOG.md +25 -0
- package/README.md +4 -2
- package/dist/card-controller.cjs.js +35 -3
- package/dist/card-controller.esm.js +35 -3
- package/dist/cli-chunks/{build-Do22AWbq.cjs → build-DvHmPnZZ.cjs} +5 -5
- package/dist/cli-chunks/{context-DQhjWa7X.cjs → context-DYlZV8UG.cjs} +2 -2
- package/dist/cli-chunks/{create-QuI_mw8x.cjs → create-CJ0cyrJG.cjs} +1 -1
- package/dist/cli-chunks/deploy-lock-CmKl9M1c.cjs +148 -0
- package/dist/cli-chunks/{dev-KAhj5UTy.cjs → dev-DhEZBjd6.cjs} +8 -8
- package/dist/cli-chunks/{doctor-DNoQWBpo.cjs → doctor-B8q0UuXs.cjs} +1 -1
- package/dist/cli-chunks/games-CmO-AKXg.cjs +140 -0
- package/dist/cli-chunks/{index-DZUYwb3h.cjs → index-CRwNLk5w.cjs} +1 -1
- package/dist/cli-chunks/{index-bZIxID9N.cjs → index-hYvQR19Q.cjs} +35 -16
- package/dist/cli-chunks/{index.esm-EWleN3S5.cjs → index.esm-DU7YhFWI.cjs} +6 -6
- package/dist/cli-chunks/{login-BYM3Nwau.cjs → login-DMJ45_Bs.cjs} +1 -1
- package/dist/cli-chunks/{project-vite-6eLmb4Em.cjs → project-vite-BGy9w-Bx.cjs} +1 -1
- package/dist/cli-chunks/{remote-dvOjaxJu.cjs → remote-CSuwqlKA.cjs} +33 -146
- package/dist/cli-chunks/{runtime-gate-BKc2zPV-.cjs → runtime-gate-BvcLwvzi.cjs} +3 -2
- package/dist/cli-chunks/{runtime-permission-env-B9SqoP85.cjs → runtime-permission-env-CbqmHz0r.cjs} +14 -2
- package/dist/cli-chunks/{skill-DFXZ0SK_.cjs → skill-CtKW2J0U.cjs} +2 -2
- package/dist/cli-chunks/{version-CwEOrhL1.cjs → version-BQMfLqms.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/{browser-dev-host-BitDUIsy.js → browser-dev-host-Cjw0mAi_.js} +1 -1
- package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-B0yrI7xe.js → desktop-app-launch-D9lPBkUV.js} +1 -1
- package/dist/devtools/browser-dev-host/assets/{index-D_mC4Hvt.js → index-DrbZkH9C.js} +3 -3
- package/dist/devtools/browser-dev-host/index.html +2 -2
- package/dist/index.cjs.js +13 -5
- package/dist/index.esm.js +13 -5
- package/dist/miniapp-publish.cjs.js +35 -81
- package/dist/miniapp-publish.esm.js +35 -80
- package/dist/protocol.cjs.js +13 -0
- package/dist/protocol.esm.js +13 -0
- package/dist/templates/vanilla-vite-js/README.md.ejs +4 -0
- package/dist/vite.cjs.js +51 -3
- package/dist/vite.esm.js +51 -3
- package/package.json +5 -4
- package/skill/SKILL.md +9 -0
- package/skill/skill.json +5 -5
- package/types/card-controller.d.ts +35 -3
- package/types/index.d.ts +51 -51
- package/types/miniapp-publish/index.d.ts +28 -60
- package/types/protocol.d.ts +10 -10
- package/types/vite/index.d.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
本文件随 `@heybox/hb-sdk` npm 包公开分发,只记录外部小程序开发者可观察到的变化。
|
|
4
4
|
|
|
5
|
+
## 0.9.2-alpha.9 - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### 文档与工具
|
|
8
|
+
|
|
9
|
+
- 公开 API Reference、指南与包 README 中的 API 可用版本只展示正式版,不再出现 `-alpha` 等预发布标记;尚未发布的 API 标注目标正式版。
|
|
10
|
+
- 修正 5 处「可用版本早于实际可导入版本」的标注:`viewport.setDisplay` 改为 `0.8.3`,`workspaceCards.requestRefresh` / `getLaunchContext` / `onLaunchContextChange` 改为 `0.9.1`,`cloud.storage` 四个方法改为目标正式版 `0.9.2`,`hasMiniappRuntimeGate` 与 `validateMiniappManifestForDeploy` 改为 `0.8.2`。
|
|
11
|
+
- 移除 `./miniapp-publish` 中已无调用方的 `getMiniappUploadKey()` 与 `validateUploadPaths()` 及其类型;其余发布流程内部工具仍然导出,供 CLI 与运营后台复用,不再作为文档承诺的 API 契约。
|
|
12
|
+
|
|
13
|
+
## 0.9.2-alpha.8 - 2026-10-20
|
|
14
|
+
|
|
15
|
+
### 新增能力
|
|
16
|
+
|
|
17
|
+
- 新增 `hb-sdk remote games search "游戏名" --json`,查询平台游戏 appid、中英文名称和分页结果,无需先绑定小程序项目。
|
|
18
|
+
- CLI 游戏搜索增加请求频率保护,操作过于频繁时提示稍后重试。
|
|
19
|
+
|
|
20
|
+
### 行为调整
|
|
21
|
+
|
|
22
|
+
- 开发启动、构建和部署时,对简介缺失、工具类未关联游戏给出非阻断提醒;部署不重复输出内部构建的资料提示。
|
|
23
|
+
- Agent Skill 主动检查小程序分类、简介和游戏绑定,保留已有内容,通用工具可保持不关联游戏。
|
|
24
|
+
|
|
25
|
+
### 问题修复
|
|
26
|
+
|
|
27
|
+
- 修复 Vite 5/6 开发服务在输出资料或旧模板平台提醒时无法启动的问题。
|
|
28
|
+
|
|
5
29
|
## 0.9.2-alpha.7 - 2026-10-20
|
|
6
30
|
|
|
7
31
|
### 行为调整
|
|
@@ -1134,4 +1158,5 @@
|
|
|
1134
1158
|
|
|
1135
1159
|
## 0.5.6
|
|
1136
1160
|
|
|
1161
|
+
- 未发布到 npm 的稳定版本候选,不进入公开文档投影。
|
|
1137
1162
|
- 开始维护公开 Package Changelog。
|
package/README.md
CHANGED
|
@@ -124,6 +124,8 @@ async function revokeAuthorizationFromUserAction() {
|
|
|
124
124
|
|
|
125
125
|
小程序类别需在 `package.json#heybox.category` 声明为 `tool`、`game` 或 `other`,构建后写入 Manifest,CLI/Open 提审自动采用,发布后生效。开发者提审页不展示或提供手动选择类别;新上传版本(含已有小程序更新)必须声明,缺少类别的项目须补配置后重新构建上传,详见 [小程序类别](https://docs.xiaoheihe.cn/hb_sdk/guide/cli#小程序类别)。
|
|
126
126
|
|
|
127
|
+
使用 `hb-sdk remote games search "游戏名" --json` 查询平台游戏候选,无需先绑定项目;核对后将返回的字符串 `appid` 填入 `heybox.bindAppIds`,能否绑定以部署预检为准。简介缺失、工具类未关联游戏时,开发启动、构建和部署会给出非阻断提醒;具体流程见 [CLI 指南](https://docs.xiaoheihe.cn/hb_sdk/guide/cli)。
|
|
128
|
+
|
|
127
129
|
`bindAppIds` 只接受规范十进制字符串(形如 `/^[1-9]\d*$/`,拒绝前导零、空串与非数字),每项必须是正 int32(最大 `2147483647`),数组内不允许重复,最多 20 项,并按数值升序写入 Manifest。它不是权限,构建不联网;游戏真实性在远端预检和提审时校验。字段缺失或 `[]` 都表示清空绑定。PC 展示仍受 `platforms` 过滤。
|
|
128
130
|
|
|
129
131
|
再启用构建插件:
|
|
@@ -292,7 +294,7 @@ const stopDisplay = hbSDK.on('display_change', ({ orientation, fullscreen }) =>
|
|
|
292
294
|
|
|
293
295
|
全屏同时收起胶囊和宿主额外顶部栏,不保证原生内容铺满容器;页面应按实际 WebView 窗口和安全区适配;这不代表系统状态栏、手势条被隐藏或设备安全区消失,重要控件仍应按实际窗口信息避让。宿主 Runtime 统一提供小尺寸“退出全屏”浮动按钮:首次出现和点击右侧边缘重新唤出后,均在 1 秒后淡出并收至边缘,保留可点击的唤出入口。展开时点击按钮会调用 `setDisplay({ orientation: 'portrait', fullscreen: false })`,恢复竖屏、非全屏;退出全屏或销毁会话时移除按钮。H5 无需另行提供退出控件,也可按需提供自己的入口。业务也可通过 `navigation.close()` 提供退出入口;窗口变化事件本身不代表设备方向发生变化。
|
|
294
296
|
|
|
295
|
-
该 API 为实验能力,自 SDK `0.8.
|
|
297
|
+
该 API 为实验能力,自 SDK `0.8.3` 提供,Android/iOS/OHOS 原生能力最低客户端版本为 `1.3.396`,实际以桥接能力为准。SDK 将 `METHOD_FORBIDDEN`、`METHOD_NOT_FOUND` 和 `REQUEST_UNSUPPORTED` 统一转换为 `{ supported: false }`。本地 Web/PC Host 不伪造方向或胶囊支持,请在已接入的移动端真机验收。
|
|
296
298
|
|
|
297
299
|
## 错误处理
|
|
298
300
|
|
|
@@ -347,7 +349,7 @@ SDK 还提供 `getHandshakeState()` / `onHandshakeStateChange()` 管理持久握
|
|
|
347
349
|
- `user.revokeAuthorization()` 在两种网络模式下都要求可信用户手势;成功后必须停止使用并清理业务缓存的旧身份、资料和服务端会话。
|
|
348
350
|
- `on()` 返回取消监听函数,组件卸载或页面销毁时需要调用。
|
|
349
351
|
- `storage.setStorage()` 每个小程序最多写入 1000 个不同的 key(已写过的 key 可反复更新);超出时返回 `STORAGE_QUOTA_EXCEEDED`。不要用时间戳、URL 等动态值拼 key,批量或大体积数据请写入 `files` sandbox。支持清理的宿主允许用户从简介页清理本地数据,范围与限制见[本地存储](https://docs.xiaoheihe.cn/hb_sdk/guide/storage)。
|
|
350
|
-
- 现有业务网络请求仍使用 `network.request()`;该 API 仍受支持,但目前没有替代调用入口,不要依赖浏览器原生网络出口。自 `0.8.1
|
|
352
|
+
- 现有业务网络请求仍使用 `network.request()`;该 API 仍受支持,但目前没有替代调用入口,不要依赖浏览器原生网络出口。自 `0.8.1` 起 `withCredentials` 配置已移除,传入会被忽略且不报错。
|
|
351
353
|
- 公开 `network.request()` 与 `network.download()` 均使用 no-follow redirect policy;3xx 不会在 Host 内静默跳转。
|
|
352
354
|
- `network.request()` 不能访问平台保留的 runtime auth 与 OpenAPI 内部路径;页面不得持有或交换服务端应用凭据。
|
|
353
355
|
- `network.request` 不支持 `multipart/form-data`;App Host 仅支持 form(`application/x-www-form-urlencoded`)与 JSON。表单请用 `URLSearchParams#toString()` 作为 `data`,文件上传请走专用上传能力。
|
|
@@ -89,6 +89,11 @@ function viewDocument(regions, options = {}) {
|
|
|
89
89
|
return compact({ schema: VIEW_SCHEMA_VERSION, regions: [...regions], ...options });
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 工作区卡片定义入口。
|
|
94
|
+
*
|
|
95
|
+
* @since 0.9.1
|
|
96
|
+
*/
|
|
92
97
|
function defineCard(definition) { return definition; }
|
|
93
98
|
const actionInput = {
|
|
94
99
|
string: (options = {}) => ({ type: 'string', ...options }),
|
|
@@ -99,6 +104,11 @@ const actionInput = {
|
|
|
99
104
|
type: 'object', properties, required: Object.keys(properties), additionalProperties: false,
|
|
100
105
|
}),
|
|
101
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* 工作区卡片 action 定义入口。
|
|
109
|
+
*
|
|
110
|
+
* @since 0.9.1
|
|
111
|
+
*/
|
|
102
112
|
function defineAction(definition) {
|
|
103
113
|
let actionId;
|
|
104
114
|
const pending = [];
|
|
@@ -121,6 +131,11 @@ function defineAction(definition) {
|
|
|
121
131
|
} });
|
|
122
132
|
return handle;
|
|
123
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* 打开小程序 action 定义入口。
|
|
136
|
+
*
|
|
137
|
+
* @since 0.9.1
|
|
138
|
+
*/
|
|
124
139
|
function defineOpenMiniProgramAction(options = {}) {
|
|
125
140
|
let actionId;
|
|
126
141
|
const pending = [];
|
|
@@ -142,18 +157,35 @@ function defineOpenMiniProgramAction(options = {}) {
|
|
|
142
157
|
} });
|
|
143
158
|
return handle;
|
|
144
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* 工作区卡片控制器定义入口。
|
|
162
|
+
*
|
|
163
|
+
* @since 0.9.1
|
|
164
|
+
*/
|
|
145
165
|
function defineWorkspaceCardsController(controller) {
|
|
146
166
|
for (const [id, action] of Object.entries(controller.actions ?? {}))
|
|
147
167
|
action.bindActionId?.(id);
|
|
148
168
|
return controller;
|
|
149
169
|
}
|
|
150
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* 定义控制器;构建器和 Host 分别验证静态 action 表。
|
|
172
|
+
*
|
|
173
|
+
* @since 0.9.1
|
|
174
|
+
*/
|
|
151
175
|
function defineWorkspaceCardController(controller) { return controller; }
|
|
152
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* 生成固定受管 invoke 入口;身份由 Host 根据真实卡片绑定。
|
|
178
|
+
*
|
|
179
|
+
* @since 0.9.1
|
|
180
|
+
*/
|
|
153
181
|
function invoke(actionId, params) {
|
|
154
182
|
return { commandId: WORKSPACE_CARD_COMMANDS.invoke, input: { actionId, ...(params === undefined ? {} : { params }) } };
|
|
155
183
|
}
|
|
156
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* 只允许打开当前小程序。
|
|
186
|
+
*
|
|
187
|
+
* @since 0.9.1
|
|
188
|
+
*/
|
|
157
189
|
function openMiniProgram(actionId, params) {
|
|
158
190
|
return { commandId: WORKSPACE_CARD_COMMANDS.openMiniProgram, input: { actionId, ...(params === undefined ? {} : { params }) } };
|
|
159
191
|
}
|
|
@@ -87,6 +87,11 @@ function viewDocument(regions, options = {}) {
|
|
|
87
87
|
return compact({ schema: VIEW_SCHEMA_VERSION, regions: [...regions], ...options });
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* 工作区卡片定义入口。
|
|
92
|
+
*
|
|
93
|
+
* @since 0.9.1
|
|
94
|
+
*/
|
|
90
95
|
function defineCard(definition) { return definition; }
|
|
91
96
|
const actionInput = {
|
|
92
97
|
string: (options = {}) => ({ type: 'string', ...options }),
|
|
@@ -97,6 +102,11 @@ const actionInput = {
|
|
|
97
102
|
type: 'object', properties, required: Object.keys(properties), additionalProperties: false,
|
|
98
103
|
}),
|
|
99
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* 工作区卡片 action 定义入口。
|
|
107
|
+
*
|
|
108
|
+
* @since 0.9.1
|
|
109
|
+
*/
|
|
100
110
|
function defineAction(definition) {
|
|
101
111
|
let actionId;
|
|
102
112
|
const pending = [];
|
|
@@ -119,6 +129,11 @@ function defineAction(definition) {
|
|
|
119
129
|
} });
|
|
120
130
|
return handle;
|
|
121
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* 打开小程序 action 定义入口。
|
|
134
|
+
*
|
|
135
|
+
* @since 0.9.1
|
|
136
|
+
*/
|
|
122
137
|
function defineOpenMiniProgramAction(options = {}) {
|
|
123
138
|
let actionId;
|
|
124
139
|
const pending = [];
|
|
@@ -140,18 +155,35 @@ function defineOpenMiniProgramAction(options = {}) {
|
|
|
140
155
|
} });
|
|
141
156
|
return handle;
|
|
142
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* 工作区卡片控制器定义入口。
|
|
160
|
+
*
|
|
161
|
+
* @since 0.9.1
|
|
162
|
+
*/
|
|
143
163
|
function defineWorkspaceCardsController(controller) {
|
|
144
164
|
for (const [id, action] of Object.entries(controller.actions ?? {}))
|
|
145
165
|
action.bindActionId?.(id);
|
|
146
166
|
return controller;
|
|
147
167
|
}
|
|
148
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* 定义控制器;构建器和 Host 分别验证静态 action 表。
|
|
170
|
+
*
|
|
171
|
+
* @since 0.9.1
|
|
172
|
+
*/
|
|
149
173
|
function defineWorkspaceCardController(controller) { return controller; }
|
|
150
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* 生成固定受管 invoke 入口;身份由 Host 根据真实卡片绑定。
|
|
176
|
+
*
|
|
177
|
+
* @since 0.9.1
|
|
178
|
+
*/
|
|
151
179
|
function invoke(actionId, params) {
|
|
152
180
|
return { commandId: WORKSPACE_CARD_COMMANDS.invoke, input: { actionId, ...(params === undefined ? {} : { params }) } };
|
|
153
181
|
}
|
|
154
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* 只允许打开当前小程序。
|
|
184
|
+
*
|
|
185
|
+
* @since 0.9.1
|
|
186
|
+
*/
|
|
155
187
|
function openMiniProgram(actionId, params) {
|
|
156
188
|
return { commandId: WORKSPACE_CARD_COMMANDS.openMiniProgram, input: { actionId, ...(params === undefined ? {} : { params }) } };
|
|
157
189
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var runtimeGate = require('./runtime-gate-
|
|
3
|
+
var runtimeGate = require('./runtime-gate-BvcLwvzi.cjs');
|
|
4
4
|
var fs$1 = require('node:fs');
|
|
5
5
|
var fs = require('node:fs/promises');
|
|
6
6
|
var path = require('node:path');
|
|
7
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
8
|
-
var index = require('./index-
|
|
7
|
+
var runtimePermissionEnv = require('./runtime-permission-env-CbqmHz0r.cjs');
|
|
8
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
9
9
|
require('node:crypto');
|
|
10
10
|
require('node:zlib');
|
|
11
|
-
var projectVite = require('./project-vite-
|
|
11
|
+
var projectVite = require('./project-vite-BGy9w-Bx.cjs');
|
|
12
12
|
require('node:module');
|
|
13
13
|
require('node:url');
|
|
14
14
|
require('./_commonjs-dynamic-modules-DUamuDzp.cjs');
|
|
@@ -16,7 +16,7 @@ require('fs');
|
|
|
16
16
|
require('path');
|
|
17
17
|
require('os');
|
|
18
18
|
require('inspector');
|
|
19
|
-
require('./version-
|
|
19
|
+
require('./version-BQMfLqms.cjs');
|
|
20
20
|
require('node:async_hooks');
|
|
21
21
|
require('readline');
|
|
22
22
|
require('tty');
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
var node_crypto = require('node:crypto');
|
|
4
4
|
var fs = require('node:fs/promises');
|
|
5
5
|
var path = require('node:path');
|
|
6
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
6
|
+
var runtimePermissionEnv = require('./runtime-permission-env-CbqmHz0r.cjs');
|
|
7
7
|
var fs$1 = require('node:fs');
|
|
8
8
|
var sharp = require('sharp');
|
|
9
|
-
var index = require('./index-
|
|
9
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
10
10
|
|
|
11
11
|
async function inspectBasicInfoImage(bytes, sourcePath, kind) {
|
|
12
12
|
const inspected = await inspectDecodedBasicInfoImage(bytes, kind);
|
|
@@ -5,7 +5,7 @@ var fs = require('node:fs/promises');
|
|
|
5
5
|
var path = require('node:path');
|
|
6
6
|
var require$$0 = require('fs');
|
|
7
7
|
var require$$1 = require('path');
|
|
8
|
-
var index = require('./index-
|
|
8
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
9
9
|
require('node:module');
|
|
10
10
|
require('readline');
|
|
11
11
|
require('os');
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
4
|
+
var fs = require('node:fs/promises');
|
|
5
|
+
var path = require('node:path');
|
|
6
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
7
|
+
|
|
8
|
+
const DEPLOY_LOCKED_MESSAGE = '当前设备已有同版本产物正在上传,请等待完成后重试';
|
|
9
|
+
const DEPLOY_LOCK_DIRECTORY_NAME = 'deploy-locks';
|
|
10
|
+
const DEPLOY_LOCK_OWNER_FILE = 'owner.json';
|
|
11
|
+
const INCOMPLETE_LOCK_GRACE_MS = 30_000;
|
|
12
|
+
const defaultPaths = index.envPaths('hb-sdk', { suffix: '' });
|
|
13
|
+
class DeployLockConflictError extends Error {
|
|
14
|
+
}
|
|
15
|
+
async function withDeployLock(scope, operation, options = {}) {
|
|
16
|
+
let lock;
|
|
17
|
+
try {
|
|
18
|
+
lock = await acquireDeployLock(scope, options);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof DeployLockConflictError) {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
// 本地文件锁只减少重复操作,文件系统异常不阻断服务端上传流程。
|
|
25
|
+
return operation();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await operation();
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await lock.release().catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function acquireDeployLock(scope, options = {}) {
|
|
35
|
+
const lockDirectory = getDeployLockDirectory(scope, options.cacheRoot);
|
|
36
|
+
const ownerFile = path.join(lockDirectory, DEPLOY_LOCK_OWNER_FILE);
|
|
37
|
+
const owner = {
|
|
38
|
+
version: 1,
|
|
39
|
+
pid: options.pid ?? process.pid,
|
|
40
|
+
token: options.token ?? node_crypto.randomBytes(16).toString('hex'),
|
|
41
|
+
};
|
|
42
|
+
await fs.mkdir(path.dirname(lockDirectory), { recursive: true, mode: 0o700 });
|
|
43
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
44
|
+
let created = false;
|
|
45
|
+
try {
|
|
46
|
+
await fs.mkdir(lockDirectory, { mode: 0o700 });
|
|
47
|
+
created = true;
|
|
48
|
+
await fs.writeFile(ownerFile, `${JSON.stringify(owner)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
49
|
+
return createDeployLock(lockDirectory, ownerFile, owner.token);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (created) {
|
|
53
|
+
await fs.rm(lockDirectory, { force: true, recursive: true });
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
if (!isNodeError(error) || error.code !== 'EEXIST') {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (!(await canReclaimDeployLock(lockDirectory, ownerFile, options))) {
|
|
60
|
+
throw new DeployLockConflictError(DEPLOY_LOCKED_MESSAGE);
|
|
61
|
+
}
|
|
62
|
+
await reclaimDeployLock(lockDirectory, owner.token);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw new DeployLockConflictError(DEPLOY_LOCKED_MESSAGE);
|
|
66
|
+
}
|
|
67
|
+
function getDeployLockDirectory(scope, cacheRoot = defaultPaths.cache) {
|
|
68
|
+
const scopeHash = node_crypto.createHash('sha256').update(`${scope.apiBaseUrl}\0${scope.miniProgramId}\0${scope.version}`).digest('hex');
|
|
69
|
+
return path.join(cacheRoot, DEPLOY_LOCK_DIRECTORY_NAME, scopeHash);
|
|
70
|
+
}
|
|
71
|
+
function createDeployLock(lockDirectory, ownerFile, token) {
|
|
72
|
+
let released = false;
|
|
73
|
+
return {
|
|
74
|
+
async release() {
|
|
75
|
+
if (released)
|
|
76
|
+
return;
|
|
77
|
+
released = true;
|
|
78
|
+
await removeOwnedLock(lockDirectory, ownerFile, token);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function canReclaimDeployLock(lockDirectory, ownerFile, options) {
|
|
83
|
+
const owner = await readDeployLockOwner(ownerFile);
|
|
84
|
+
if (owner) {
|
|
85
|
+
return !(options.isProcessRunning ?? isProcessRunning)(owner.pid);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const lockStat = await fs.stat(lockDirectory);
|
|
89
|
+
return (options.now ?? Date.now)() - lockStat.mtimeMs >= INCOMPLETE_LOCK_GRACE_MS;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return isNodeError(error) && error.code === 'ENOENT';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function removeOwnedLock(lockDirectory, ownerFile, token) {
|
|
96
|
+
const owner = await readDeployLockOwner(ownerFile);
|
|
97
|
+
if (owner?.token === token) {
|
|
98
|
+
await fs.rm(lockDirectory, { force: true, recursive: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function reclaimDeployLock(lockDirectory, token) {
|
|
102
|
+
const reclaimDirectory = `${lockDirectory}.reclaim-${node_crypto.createHash('sha256').update(token).digest('hex')}`;
|
|
103
|
+
try {
|
|
104
|
+
await fs.rename(lockDirectory, reclaimDirectory);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
108
|
+
return;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
await fs.rm(reclaimDirectory, { force: true, recursive: true });
|
|
112
|
+
}
|
|
113
|
+
async function readDeployLockOwner(ownerFile) {
|
|
114
|
+
try {
|
|
115
|
+
const value = JSON.parse(await fs.readFile(ownerFile, 'utf8'));
|
|
116
|
+
return isDeployLockOwner(value) ? value : null;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
120
|
+
return null;
|
|
121
|
+
if (error instanceof SyntaxError)
|
|
122
|
+
return null;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function isDeployLockOwner(value) {
|
|
127
|
+
return (typeof value === 'object' &&
|
|
128
|
+
value !== null &&
|
|
129
|
+
value.version === 1 &&
|
|
130
|
+
Number.isInteger(value.pid) &&
|
|
131
|
+
typeof value.token === 'string');
|
|
132
|
+
}
|
|
133
|
+
function isProcessRunning(pid) {
|
|
134
|
+
try {
|
|
135
|
+
process.kill(pid, 0);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return !isNodeError(error) || error.code !== 'ESRCH';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function isNodeError(error) {
|
|
143
|
+
return error instanceof Error && 'code' in error;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
exports.DEPLOY_LOCKED_MESSAGE = DEPLOY_LOCKED_MESSAGE;
|
|
147
|
+
exports.acquireDeployLock = acquireDeployLock;
|
|
148
|
+
exports.withDeployLock = withDeployLock;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
4
|
-
var context = require('./context-
|
|
3
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
4
|
+
var context = require('./context-DYlZV8UG.cjs');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
var fs = require('node:fs');
|
|
7
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
7
|
+
var runtimePermissionEnv = require('./runtime-permission-env-CbqmHz0r.cjs');
|
|
8
8
|
var os = require('node:os');
|
|
9
9
|
var path = require('node:path');
|
|
10
10
|
var net = require('node:net');
|
|
@@ -16,8 +16,8 @@ var fs$1 = require('node:fs/promises');
|
|
|
16
16
|
var require$$0$1 = require('crypto');
|
|
17
17
|
var require$$0 = require('buffer');
|
|
18
18
|
var browser = require('./browser-RAy8e8cV.cjs');
|
|
19
|
-
var projectVite = require('./project-vite-
|
|
20
|
-
var version = require('./version-
|
|
19
|
+
var projectVite = require('./project-vite-BGy9w-Bx.cjs');
|
|
20
|
+
var version = require('./version-BQMfLqms.cjs');
|
|
21
21
|
require('node:zlib');
|
|
22
22
|
|
|
23
23
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -1995,8 +1995,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
|
|
|
1995
1995
|
};
|
|
1996
1996
|
const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
|
|
1997
1997
|
IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
|
|
1998
|
-
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1999
|
-
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1998
|
+
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DhEZBjd6.cjs', document.baseURI).href)))('ws');
|
|
1999
|
+
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DhEZBjd6.cjs', document.baseURI).href)))('parse5');
|
|
2000
2000
|
const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
|
|
2001
2001
|
path.resolve(__dirname, 'devtools/browser-dev-host'),
|
|
2002
2002
|
path.resolve(__dirname, '../devtools/browser-dev-host'),
|
|
@@ -11100,7 +11100,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
|
|
|
11100
11100
|
function createNodeRealtimeDriverRegistry() {
|
|
11101
11101
|
const registry = createRuntimeRealtimeDriverRegistry();
|
|
11102
11102
|
registry.registerLazy('node-websocket', async () => {
|
|
11103
|
-
const mod = await Promise.resolve().then(function () { return require('./index.esm-
|
|
11103
|
+
const mod = await Promise.resolve().then(function () { return require('./index.esm-DU7YhFWI.cjs'); });
|
|
11104
11104
|
return mod.createNodeWebSocketRuntimeDriver();
|
|
11105
11105
|
});
|
|
11106
11106
|
return registry;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
4
|
+
var fs = require('node:fs/promises');
|
|
5
|
+
var path = require('node:path');
|
|
6
|
+
var index = require('./index-hYvQR19Q.cjs');
|
|
7
|
+
var deployLock = require('./deploy-lock-CmKl9M1c.cjs');
|
|
8
|
+
require('node:module');
|
|
9
|
+
require('readline');
|
|
10
|
+
require('os');
|
|
11
|
+
require('tty');
|
|
12
|
+
require('assert');
|
|
13
|
+
require('events');
|
|
14
|
+
require('stream');
|
|
15
|
+
require('buffer');
|
|
16
|
+
require('util');
|
|
17
|
+
require('path');
|
|
18
|
+
require('fs');
|
|
19
|
+
require('constants');
|
|
20
|
+
require('node:fs');
|
|
21
|
+
require('node:zlib');
|
|
22
|
+
|
|
23
|
+
const WINDOW_MS = 30 * 60 * 1000;
|
|
24
|
+
const MAX_REQUESTS = 50;
|
|
25
|
+
const INTERVAL_MS = 1000;
|
|
26
|
+
/** 仅限制当前设备 CLI 的出站搜索,账号隔离并跨进程、跨项目持久保存。 */
|
|
27
|
+
async function reserveGameSearchRequest(account, apiBaseUrl, options = {}) {
|
|
28
|
+
const cacheRoot = options.cacheRoot ?? index.envPaths('hb-sdk', { suffix: '' }).cache;
|
|
29
|
+
const scope = node_crypto.createHash('sha256').update(`${apiBaseUrl}\0${account}`).digest('hex');
|
|
30
|
+
let lock;
|
|
31
|
+
try {
|
|
32
|
+
lock = await deployLock.acquireDeployLock({ apiBaseUrl, miniProgramId: `game-search:${scope}`, version: 'rate-limit-v1' }, { cacheRoot });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof Error && error.message === deployLock.DEPLOY_LOCKED_MESSAGE) {
|
|
36
|
+
throw new Error('游戏搜索请求正在处理中,请稍后重试');
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
const directory = path.join(cacheRoot, 'game-search-limits');
|
|
41
|
+
const file = path.join(directory, `${scope}.json`);
|
|
42
|
+
const temp = `${file}.${node_crypto.randomUUID()}.tmp`;
|
|
43
|
+
try {
|
|
44
|
+
const now = (options.now ?? Date.now)();
|
|
45
|
+
let timestamps = [];
|
|
46
|
+
try {
|
|
47
|
+
const raw = await fs.readFile(file, 'utf8');
|
|
48
|
+
let stored;
|
|
49
|
+
try {
|
|
50
|
+
stored = JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!(error instanceof SyntaxError))
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(stored) && stored.length <= MAX_REQUESTS && stored.every((value) => Number.isSafeInteger(value) && value >= 0)) {
|
|
57
|
+
timestamps = stored.filter((value) => value > now - WINDOW_MS).sort((a, b) => a - b);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// 在账号锁内清除损坏的辅助缓存,本次按新记录继续,避免永久阻断搜索。
|
|
61
|
+
await fs.rm(file, { force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error.code !== 'ENOENT')
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
const last = timestamps.at(-1);
|
|
69
|
+
const intervalWait = last === undefined ? 0 : last + INTERVAL_MS - now;
|
|
70
|
+
const windowWait = timestamps.length < MAX_REQUESTS ? 0 : timestamps[0] + WINDOW_MS - now;
|
|
71
|
+
const wait = Math.max(intervalWait, windowWait);
|
|
72
|
+
if (wait > 0) {
|
|
73
|
+
throw new Error(`游戏搜索过于频繁,请 ${Math.ceil(wait / 1000)} 秒后重试`);
|
|
74
|
+
}
|
|
75
|
+
timestamps.push(now);
|
|
76
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
77
|
+
await fs.writeFile(temp, JSON.stringify(timestamps), { mode: 0o600, flag: 'wx' });
|
|
78
|
+
await fs.rename(temp, file);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await fs.rm(temp, { force: true }).finally(() => lock.release());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const GAME_SEARCH_API_PATH = '/bbs/app/topic/search_game';
|
|
86
|
+
/** 查询平台游戏候选,不依赖项目绑定,也不写入本地配置或远端状态。 */
|
|
87
|
+
async function runGameSearchCommand(query, options = {}, runtime = {}) {
|
|
88
|
+
const q = query.trim();
|
|
89
|
+
if (!q || Array.from(q).length > 100)
|
|
90
|
+
throw new Error('游戏搜索关键词须为 1–100 个字符');
|
|
91
|
+
const offset = parsePagination(options.offset ?? '0', 'offset', 0, 10_000);
|
|
92
|
+
const limit = parsePagination(options.limit ?? '10', 'limit', 1, 20);
|
|
93
|
+
const session = runtime.session ?? (await (runtime.requireAuthSession ?? index.requireHeyboxAuthSession)());
|
|
94
|
+
const context = index.createHeyboxOpenPlatformRequestContext(session, GAME_SEARCH_API_PATH);
|
|
95
|
+
await reserveGameSearchRequest(session.heyboxId, context.baseUrl, runtime.rateLimit);
|
|
96
|
+
const response = await (runtime.fetchImpl ?? fetch)(index.createHeyboxApiUrl(context.baseUrl, GAME_SEARCH_API_PATH, { ...context.platformParams, q, offset, limit: limit + 1 }), { headers: context.headers, redirect: 'error', signal: AbortSignal.timeout(15_000) });
|
|
97
|
+
const result = await index.readHeyboxApiEnvelope(response, { pathWithQuery: GAME_SEARCH_API_PATH, requireResult: true });
|
|
98
|
+
if (!Array.isArray(result?.games))
|
|
99
|
+
throw new Error('游戏搜索返回格式异常:缺少 games 数组');
|
|
100
|
+
const games = result.games.slice(0, limit).map(normalizeGame);
|
|
101
|
+
const output = {
|
|
102
|
+
games,
|
|
103
|
+
has_more: result.games.length > limit,
|
|
104
|
+
next_offset: offset + games.length,
|
|
105
|
+
};
|
|
106
|
+
const message = options.json
|
|
107
|
+
? JSON.stringify(output, null, 2)
|
|
108
|
+
: [
|
|
109
|
+
...(games.length ? games.map((game) => `${game.appid}\t${game.name}${game.name_en ? `(${game.name_en})` : ''}`) : ['未找到匹配游戏,请尝试中文名或英文名。']),
|
|
110
|
+
'搜索结果为游戏候选,请核对具体游戏及平台;能否绑定以部署预检为准。',
|
|
111
|
+
...(output.has_more ? [`更多结果:--offset ${output.next_offset} --limit ${limit}`] : []),
|
|
112
|
+
].join('\n');
|
|
113
|
+
(runtime.write ?? ((text) => process.stdout.write(`${text}\n`)))(message);
|
|
114
|
+
return output;
|
|
115
|
+
}
|
|
116
|
+
function parsePagination(value, name, min, max) {
|
|
117
|
+
if (!/^(0|[1-9]\d*)$/.test(value))
|
|
118
|
+
throw new Error(`--${name} 必须为 ${min}–${max} 的整数`);
|
|
119
|
+
const parsed = Number(value);
|
|
120
|
+
if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max)
|
|
121
|
+
throw new Error(`--${name} 必须为 ${min}–${max} 的整数`);
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
function normalizeGame(value) {
|
|
125
|
+
if (!value || typeof value !== 'object')
|
|
126
|
+
throw new Error('游戏搜索返回格式异常');
|
|
127
|
+
const game = value;
|
|
128
|
+
const appid = typeof game.appid === 'number' || typeof game.appid === 'string' ? String(game.appid) : '';
|
|
129
|
+
if (!/^[1-9]\d*$/.test(appid) ||
|
|
130
|
+
!Number.isSafeInteger(Number(appid)) ||
|
|
131
|
+
Number(appid) > 2_147_483_647 ||
|
|
132
|
+
typeof game.name !== 'string' ||
|
|
133
|
+
!game.name.trim()) {
|
|
134
|
+
throw new Error('游戏搜索返回无效 appid 或名称');
|
|
135
|
+
}
|
|
136
|
+
const icon = typeof game.vertical_image === 'string' && game.vertical_image ? game.vertical_image : game.image;
|
|
137
|
+
return { appid, name: game.name, ...(typeof game.name_en === 'string' && game.name_en.trim() ? { name_en: game.name_en } : {}), ...(typeof icon === 'string' && icon ? { icon_url: icon } : {}) };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
exports.runGameSearchCommand = runGameSearchCommand;
|