@heybox/hb-sdk 0.8.2-alpha.2 → 0.8.2-alpha.6
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 +44 -1
- package/README.md +10 -3
- package/dist/cli-chunks/{build-bVThH3Z5.cjs → build-TOcahJhW.cjs} +5 -5
- package/dist/cli-chunks/{context-DpKqGnjp.cjs → context-Bk0adtVQ.cjs} +326 -104
- package/dist/cli-chunks/{create-BWC2343y.cjs → create-BxIgQ4SF.cjs} +5 -1
- package/dist/cli-chunks/{dev-t14YpHHY.cjs → dev-CyhKTGGJ.cjs} +17 -9
- package/dist/cli-chunks/{doctor-BWkrE0Hj.cjs → doctor-ItlNn-K2.cjs} +25 -13
- package/dist/cli-chunks/{index-CDtcZVWv.cjs → index-CP14FXez.cjs} +16 -18
- package/dist/cli-chunks/{index-D3FMcOZq.cjs → index-CjWRMwuI.cjs} +2 -2
- package/dist/cli-chunks/{index.esm-BrJBaJWS.cjs → index.esm-deP-KEyE.cjs} +11 -10
- package/dist/cli-chunks/{login-DRMl7nFH.cjs → login-4B-2nqgd.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-CXgdzIYw.cjs → project-vite-HKW4tSBX.cjs} +1 -1
- package/dist/cli-chunks/{remote-ibQybNiM.cjs → remote-BLdGzViG.cjs} +80 -60
- package/dist/cli-chunks/runtime-gate-B_fHNic6.cjs +58 -0
- package/dist/cli-chunks/{runtime-gate-BWb8QIcX.cjs → runtime-permission-env-CAhESVDA.cjs} +4533 -40
- package/dist/cli-chunks/{session-CIoBspg-.cjs → session-COr9TQ6O.cjs} +1 -1
- package/dist/cli-chunks/{skill-5labzuWk.cjs → skill-DiZSczbw.cjs} +2 -2
- package/dist/cli-chunks/{version-QiVEs76a.cjs → version-tRrcrp9F.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-CF7IQARw.js +101 -0
- package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-B4KsgItx.js → desktop-app-launch-CQmXIMyL.js} +1 -1
- package/dist/devtools/browser-dev-host/assets/{index-9Nb3XhtI.js → index-DdKYO0um.js} +3 -3
- package/dist/devtools/browser-dev-host/index.html +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/miniapp-publish.cjs.js +8782 -0
- package/dist/miniapp-publish.esm.js +8778 -1
- package/dist/templates/vanilla-vite-js/README.md.ejs +3 -0
- package/dist/templates/vanilla-vite-js/assets/heybox-placeholder.png +0 -0
- package/dist/templates/vanilla-vite-js/package.json.ejs +3 -0
- package/dist/vite.cjs.js +499 -346
- package/dist/vite.esm.js +500 -347
- package/package.json +8 -7
- package/skill/SKILL.md +22 -124
- package/skill/skill.json +5 -5
- package/types/miniapp-manifest/basic-info-browser.d.ts +9 -0
- package/types/miniapp-manifest/basic-info-image-header.d.ts +6 -0
- package/types/miniapp-manifest/basic-info-node.d.ts +17 -0
- package/types/miniapp-manifest/basic-info-placeholder.d.ts +1 -0
- package/types/miniapp-manifest/basic-info.d.ts +12 -0
- package/types/miniapp-manifest/schema.d.ts +7 -1
- package/types/miniapp-publish/index.d.ts +8 -0
- package/types/skill-metadata.d.ts +2 -2
- package/dist/cli-chunks/runtime-permission-env-DiTeD75Q.cjs +0 -4498
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-DxzOkljJ.js +0 -101
- package/skill/references/api-protocol.md +0 -317
- package/skill/references/api-root.md +0 -1243
- package/skill/references/cli.md +0 -401
- package/skill/references/examples.md +0 -204
- package/skill/references/llms-index.md +0 -24
- package/skill/references/recipes.md +0 -854
- package/skill/references/safety-boundaries.md +0 -63
- package/skill/references/smoke-evaluation.md +0 -26
- /package/types/{vite → miniapp-manifest}/runtime-gate.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
本文件随 `@heybox/hb-sdk` npm
|
|
3
|
+
本文件随 `@heybox/hb-sdk` npm 包公开分发,只记录外部小程序开发者可观察到的变化。
|
|
4
|
+
|
|
5
|
+
## 0.8.2-alpha.6
|
|
6
|
+
|
|
7
|
+
### 行为调整
|
|
8
|
+
|
|
9
|
+
- 随包 Agent Skill 改为每次从 canonical [`llms.txt`](https://docs.xiaoheihe.cn/hb_sdk/llms.txt) 按需读取公开文档,不再内置 API、CLI 和示例副本;索引或必需页面不可用时停止 hb-sdk 专项工作,不使用旧快照兜底。
|
|
10
|
+
- `hb-sdk doctor` 会把安装目录中残留的旧版 references 或其他额外内容判为完整性异常,使用 `hb-sdk skill install --force` 可覆盖刷新。
|
|
11
|
+
|
|
12
|
+
### 文档与工具
|
|
13
|
+
|
|
14
|
+
- 新增 `package.json#heybox` 项目配置指南,补齐版本资料、平台、权限、窗口、关联游戏、Companion 与远端绑定字段,以及构建和远端命令关系。
|
|
15
|
+
- 修正创建项目、本地调试和远端管理文档:`hb-sdk dev` 启动前必须完成 CLI 登录与项目绑定,同时明确无需预先绑定的发现和建绑命令。
|
|
16
|
+
|
|
17
|
+
## 0.8.2-alpha.5
|
|
18
|
+
|
|
19
|
+
- 无面向小程序开发者的可见变化。
|
|
20
|
+
|
|
21
|
+
## 0.8.2-alpha.4
|
|
22
|
+
|
|
23
|
+
- Companion 准备与启动的 Host 可信授权信息新增可选归档 SHA-256,供宿主展示产物完整性;公开 SDK `CompanionInfo` 与 iframe 返回保持不变。
|
|
24
|
+
- 补充授权弹窗展示产物来源、签名状态、目标平台、人类可读归档大小和完整 SHA-256 的文档说明。
|
|
25
|
+
|
|
26
|
+
## 0.8.2-alpha.3 - 2026-09-21
|
|
27
|
+
|
|
28
|
+
> 摘要:预发布验证版本名称和图片项目化、正式资料自动迁移及上传前发布预检。
|
|
29
|
+
> 影响级别:必须操作
|
|
30
|
+
> 开发者动作:仅测试项目升级至 0.8.2-alpha.3,在 package.json#heybox 配置 name、icon、coverImages;已有正式发布版本可通过 bind/dev/deploy 自动迁移全缺失配置。
|
|
31
|
+
> 兼容范围:这是 0.8.2 稳定版前的验证版本;满足最低 SDK 策略的旧产物继续兼容,平台与权限的 0.9.0 切点不变。
|
|
32
|
+
|
|
33
|
+
### 必须操作
|
|
34
|
+
|
|
35
|
+
- 在项目声明名称、icon 和 1–5 张封面;图片使用项目内 JPEG、PNG、GIF 或 WebP,icon 为 200×200,单图不超过 10 MiB 和 4000 万像素。提交前替换脚手架占位图。
|
|
36
|
+
- 每次部署重新构建当前项目,不再复用历史版本产物。详见[版本资料与迁移](https://docs.xiaoheihe.cn/hb_sdk/guide/version-profile)。
|
|
37
|
+
|
|
38
|
+
### 新增能力
|
|
39
|
+
|
|
40
|
+
- bind/dev/deploy 在三项资料全部缺失时迁移当前正式发布快照,已下架项目也可迁移;部分配置和从未发布的项目需手工配置。
|
|
41
|
+
- remote info 只读比较本地和已发布资料;图片无法读取时显示 unavailable。
|
|
42
|
+
|
|
43
|
+
### 行为调整
|
|
44
|
+
|
|
45
|
+
- build 保持离线,图片原样进入构建产物;Open 展示构建资料的只读预览。
|
|
46
|
+
- 不合规发布在上传前反馈;提交失败后保留已上传文件,再次提交相同构建时复用。
|
|
4
47
|
|
|
5
48
|
## 0.8.2-alpha.2
|
|
6
49
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
小黑盒 APP 小程序工坊的前端 SDK 与开发 CLI,用于创建、调试、构建和发布工坊小程序,并在业务页面中调用小黑盒开放能力。
|
|
4
4
|
|
|
5
|
-
运行环境要求 Node.js `>=22.20.0`。`@heybox/hb-sdk` npm 包同时包含与当前 SDK
|
|
5
|
+
运行环境要求 Node.js `>=22.20.0`。`@heybox/hb-sdk` npm 包同时包含与当前 SDK 版本严格对应的轻量 Agent Skill 入口和完整性 manifest;Skill 不内置 API、CLI 或示例副本,使用时需要联网读取 [`llms.txt`](https://docs.xiaoheihe.cn/hb_sdk/llms.txt) 及其链接的 Markdown 页面。
|
|
6
6
|
|
|
7
7
|
SDK 只提供面向小程序开发者的稳定接口,不提供客户端内部协议、登录凭据或私有实现入口。本地浏览器调试请使用 `hb-sdk dev` 提供的 Mock 环境,发布前仍需在小黑盒 APP 中完成真机验收。
|
|
8
8
|
|
|
@@ -18,6 +18,9 @@ SDK 只提供面向小程序开发者的稳定接口,不提供客户端内部
|
|
|
18
18
|
npx @heybox/hb-sdk@latest create my-miniapp
|
|
19
19
|
cd my-miniapp
|
|
20
20
|
npm install
|
|
21
|
+
npm run hb-sdk -- login
|
|
22
|
+
npm run hb-sdk -- remote create
|
|
23
|
+
# 已有远端小程序时改用:npm run hb-sdk -- remote bind <mini-program-id>
|
|
21
24
|
npm run dev
|
|
22
25
|
```
|
|
23
26
|
|
|
@@ -47,7 +50,7 @@ hb-sdk skill install --agent codex cursor
|
|
|
47
50
|
hb-sdk skill install --all-agents
|
|
48
51
|
```
|
|
49
52
|
|
|
50
|
-
使用 `hb-sdk doctor` 或 `hb-sdk doctor --json` 检查各 Agent 的 Skill 是否与当前 npm
|
|
53
|
+
使用 `hb-sdk doctor` 或 `hb-sdk doctor --json` 检查各 Agent 的 Skill 是否与当前 npm 包匹配;已有内容被修改或残留旧版离线 references 时,使用 `--force` 覆盖。`doctor` 不访问网络,因此不会检查线上文档是否可用。
|
|
51
54
|
|
|
52
55
|
SDK 会在导入时启动握手,能力调用会自动等待 SDK 就绪。声明 `userInfo` 后,可通过 `user.getInfo()` 读取当前小程序内的隔离身份和已授权资料:
|
|
53
56
|
|
|
@@ -117,7 +120,7 @@ async function revokeAuthorizationFromUserAction() {
|
|
|
117
120
|
}
|
|
118
121
|
```
|
|
119
122
|
|
|
120
|
-
`bindAppIds` 只接受规范十进制字符串(形如 `/^[1-9]\d*$/`,拒绝前导零、空串与非数字),每项必须是正 int32(最大 `2147483647`),数组内不允许重复,最多 20 项,并按数值升序写入 Manifest。它不是权限,构建不联网;游戏真实性在远端预检和提审时校验。字段缺失或 `[]`
|
|
123
|
+
`bindAppIds` 只接受规范十进制字符串(形如 `/^[1-9]\d*$/`,拒绝前导零、空串与非数字),每项必须是正 int32(最大 `2147483647`),数组内不允许重复,最多 20 项,并按数值升序写入 Manifest。它不是权限,构建不联网;游戏真实性在远端预检和提审时校验。字段缺失或 `[]` 都表示清空绑定。PC 展示仍受 `platforms` 过滤。
|
|
121
124
|
|
|
122
125
|
再启用构建插件:
|
|
123
126
|
|
|
@@ -415,3 +418,7 @@ hb-sdk remote deploy --release-note "本次更新说明"
|
|
|
415
418
|
- [Runtime Host 迁移](./docs/runtime-host-migration.md)
|
|
416
419
|
|
|
417
420
|
Host/Runtime 集成维护者请先阅读 [`@heybox/hb-sdk-runtime` README](../hb-sdk-runtime/README.md)、[Runtime Reference](../hb-sdk-runtime/docs/reference/) 和 [`@heybox/hb-sdk-protocol` README](../hb-sdk-protocol/README.md)。
|
|
421
|
+
|
|
422
|
+
## 版本名称与图片(0.8.2)
|
|
423
|
+
|
|
424
|
+
在 `package.json#heybox` 配置 `name`、`icon`、`coverImages`。图片为项目内文件,icon 200×200、封面 1–5 张,单张不超过 10 MiB 和 4000 万像素。构建原样复制到版本产物,发布时才更新线上资料。全缺失配置可通过 bind/dev/deploy 从当前正式发布快照自动迁移;离线 build 不迁移。详见[版本资料与迁移指南](https://docs.xiaoheihe.cn/hb_sdk/guide/version-profile)。
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
var fs$1 = require('node:fs');
|
|
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-CAhESVDA.cjs');
|
|
7
7
|
require('node:crypto');
|
|
8
8
|
require('node:zlib');
|
|
9
|
-
var runtimeGate = require('./runtime-gate-
|
|
10
|
-
var index = require('./index-
|
|
11
|
-
var projectVite = require('./project-vite-
|
|
12
|
-
require('./version-
|
|
9
|
+
var runtimeGate = require('./runtime-gate-B_fHNic6.cjs');
|
|
10
|
+
var index = require('./index-CP14FXez.cjs');
|
|
11
|
+
var projectVite = require('./project-vite-HKW4tSBX.cjs');
|
|
12
|
+
require('./version-tRrcrp9F.cjs');
|
|
13
13
|
require('node:async_hooks');
|
|
14
14
|
require('node:module');
|
|
15
15
|
require('path');
|
|
@@ -1,10 +1,327 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var session = require('./session-CIoBspg-.cjs');
|
|
5
|
-
var fs$1 = require('node:fs');
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
6
4
|
var fs = require('node:fs/promises');
|
|
7
5
|
var path = require('node:path');
|
|
6
|
+
var runtimePermissionEnv = require('./runtime-permission-env-CAhESVDA.cjs');
|
|
7
|
+
var fs$1 = require('node:fs');
|
|
8
|
+
var sharp = require('sharp');
|
|
9
|
+
var session = require('./session-COr9TQ6O.cjs');
|
|
10
|
+
|
|
11
|
+
async function inspectBasicInfoImage(bytes, sourcePath, kind) {
|
|
12
|
+
const inspected = await inspectDecodedBasicInfoImage(bytes, kind);
|
|
13
|
+
const extensions = { jpg: ['jpg', 'jpeg'], png: ['png'], gif: ['gif'], webp: ['webp'] };
|
|
14
|
+
const ext = sourcePath.split('.').pop().toLowerCase();
|
|
15
|
+
if (!extensions[inspected.extension]?.includes(ext))
|
|
16
|
+
throw new Error('资料图片格式与扩展名不一致');
|
|
17
|
+
return inspected;
|
|
18
|
+
}
|
|
19
|
+
async function inspectRemoteBasicInfoImage(bytes, kind) {
|
|
20
|
+
return inspectDecodedBasicInfoImage(bytes, kind);
|
|
21
|
+
}
|
|
22
|
+
async function inspectDecodedBasicInfoImage(bytes, kind) {
|
|
23
|
+
if (!bytes.length || bytes.length > runtimePermissionEnv.BASIC_INFO_MAX_BYTES)
|
|
24
|
+
throw new Error('资料图片不得为空或超过 10 MiB');
|
|
25
|
+
const decoder = sharp(bytes, { limitInputPixels: runtimePermissionEnv.BASIC_INFO_MAX_PIXELS, failOn: 'warning' });
|
|
26
|
+
const metadata = await decoder.metadata();
|
|
27
|
+
const extensions = { jpeg: 'jpg', png: 'png', gif: 'gif', webp: 'webp' };
|
|
28
|
+
if (!metadata.format || !extensions[metadata.format])
|
|
29
|
+
throw new Error('资料图片格式不支持');
|
|
30
|
+
const width = metadata.width ?? 0;
|
|
31
|
+
const height = metadata.pageHeight ?? metadata.height ?? 0;
|
|
32
|
+
if (!width || !height || width * height > runtimePermissionEnv.BASIC_INFO_MAX_PIXELS)
|
|
33
|
+
throw new Error('资料图片不得超过 4000 万像素');
|
|
34
|
+
if (kind === 'icon' && (width !== 200 || height !== 200))
|
|
35
|
+
throw new Error('icon 必须是 200×200');
|
|
36
|
+
// 强制解码而非只信任图片头;不将解码结果写回图片。
|
|
37
|
+
await decoder.stats();
|
|
38
|
+
return { sha256: node_crypto.createHash('sha256').update(bytes).digest('hex'), extension: extensions[metadata.format] };
|
|
39
|
+
}
|
|
40
|
+
async function readBasicInfoSource(projectRoot, relative, kind) {
|
|
41
|
+
runtimePermissionEnv.validateBasicInfoPath(relative);
|
|
42
|
+
const root = await fs.realpath(projectRoot);
|
|
43
|
+
const target = await fs.realpath(path.join(root, relative));
|
|
44
|
+
const resolvedRelative = path.relative(root, target).split(path.sep).join('/');
|
|
45
|
+
runtimePermissionEnv.validateBasicInfoPath(resolvedRelative);
|
|
46
|
+
if (resolvedRelative.startsWith('../') || path.isAbsolute(resolvedRelative))
|
|
47
|
+
throw new Error('资料图片不能逃逸项目目录');
|
|
48
|
+
const info = await fs.stat(target);
|
|
49
|
+
if (!info.isFile() || info.size > runtimePermissionEnv.BASIC_INFO_MAX_BYTES)
|
|
50
|
+
throw new Error('资料图片必须为不超过 10 MiB 的普通文件');
|
|
51
|
+
const file = await fs.open(target, fs$1.constants.O_RDONLY | fs$1.constants.O_NOFOLLOW);
|
|
52
|
+
try {
|
|
53
|
+
const actual = await file.stat();
|
|
54
|
+
if (!actual.isFile() || actual.size > runtimePermissionEnv.BASIC_INFO_MAX_BYTES)
|
|
55
|
+
throw new Error('资料图片文件已变化');
|
|
56
|
+
const bytes = await file.readFile();
|
|
57
|
+
return { bytes, ...(await inspectBasicInfoImage(bytes, relative, kind)) };
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await file.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** 逐级校验真实目录,避免保留目录或迁移目录中的符号链接将写入带到项目外。 */
|
|
64
|
+
async function ensureBasicInfoDirectory(rootPath, relative) {
|
|
65
|
+
const root = await fs.realpath(rootPath);
|
|
66
|
+
let current = root;
|
|
67
|
+
for (const part of relative.split('/')) {
|
|
68
|
+
if (!part || part === '.' || part === '..')
|
|
69
|
+
throw new Error('资料目录不合法');
|
|
70
|
+
current = path.join(current, part);
|
|
71
|
+
await fs.mkdir(current).catch((error) => {
|
|
72
|
+
if (error.code !== 'EEXIST')
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
const resolved = await fs.realpath(current);
|
|
76
|
+
const within = path.relative(root, resolved);
|
|
77
|
+
if (within.startsWith('..') || path.isAbsolute(within))
|
|
78
|
+
throw new Error('资料目录不能逃逸项目');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class MiniProgramProjectBindingError extends Error {
|
|
83
|
+
code;
|
|
84
|
+
constructor(message, code) {
|
|
85
|
+
super(message);
|
|
86
|
+
this.code = code;
|
|
87
|
+
this.name = 'MiniProgramProjectBindingError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function findMiniProgramProjectRoot(startDir) {
|
|
91
|
+
let current = path.resolve(startDir);
|
|
92
|
+
while (true) {
|
|
93
|
+
if (fs$1.existsSync(path.join(current, 'package.json'))) {
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
96
|
+
const parent = path.dirname(current);
|
|
97
|
+
if (parent === current) {
|
|
98
|
+
throw new MiniProgramProjectBindingError('当前目录或父目录未找到 package.json', 'PACKAGE_JSON_NOT_FOUND');
|
|
99
|
+
}
|
|
100
|
+
current = parent;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function readMiniProgramProjectPackage(startDir) {
|
|
104
|
+
const projectRoot = findMiniProgramProjectRoot(startDir);
|
|
105
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
106
|
+
const raw = await fs.readFile(packageJsonPath, 'utf8');
|
|
107
|
+
let packageJson;
|
|
108
|
+
try {
|
|
109
|
+
packageJson = JSON.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
throw new MiniProgramProjectBindingError(`package.json 不是合法 JSON:${packageJsonPath}\n${message}`, 'PACKAGE_JSON_INVALID');
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
projectRoot,
|
|
117
|
+
packageJsonPath,
|
|
118
|
+
packageJson,
|
|
119
|
+
raw,
|
|
120
|
+
indent: detectJsonIndent(raw),
|
|
121
|
+
trailingNewline: raw.endsWith('\r\n') ? '\r\n' : raw.endsWith('\n') ? '\n' : '',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function readBoundMiniProgramIdFromPackageJson(packageJson) {
|
|
125
|
+
const value = packageJson.heybox?.miniProgramId;
|
|
126
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
127
|
+
}
|
|
128
|
+
async function readBoundMiniProgramId(startDir) {
|
|
129
|
+
return readBoundMiniProgramIdFromPackageJson((await readMiniProgramProjectPackage(startDir)).packageJson);
|
|
130
|
+
}
|
|
131
|
+
async function requireBoundMiniProgramId(startDir) {
|
|
132
|
+
const context = await readMiniProgramProjectPackage(startDir);
|
|
133
|
+
const miniProgramId = readBoundMiniProgramIdFromPackageJson(context.packageJson);
|
|
134
|
+
if (!miniProgramId) {
|
|
135
|
+
throw new MiniProgramProjectBindingError(`当前项目未绑定小程序。请先运行 hb-sdk remote create 或 hb-sdk remote bind <mini-program-id>。\n配置位置:${context.packageJsonPath}#heybox.miniProgramId`, 'MINI_PROGRAM_UNBOUND');
|
|
136
|
+
}
|
|
137
|
+
return { ...context, miniProgramId };
|
|
138
|
+
}
|
|
139
|
+
async function writeBoundMiniProgramId(startDir, miniProgramId, options = {}) {
|
|
140
|
+
const context = await readMiniProgramProjectPackage(startDir);
|
|
141
|
+
const normalizedMiniProgramId = String(miniProgramId).trim();
|
|
142
|
+
if (!normalizedMiniProgramId) {
|
|
143
|
+
throw new MiniProgramProjectBindingError('miniProgramId 不能为空', 'PACKAGE_JSON_INVALID');
|
|
144
|
+
}
|
|
145
|
+
const currentMiniProgramId = readBoundMiniProgramIdFromPackageJson(context.packageJson);
|
|
146
|
+
if (currentMiniProgramId && currentMiniProgramId !== normalizedMiniProgramId && !options.force) {
|
|
147
|
+
throw new MiniProgramProjectBindingError(`当前项目已绑定小程序 ${currentMiniProgramId}。如需覆盖本地绑定,请使用 --force。`, 'MINI_PROGRAM_BINDING_EXISTS');
|
|
148
|
+
}
|
|
149
|
+
if (currentMiniProgramId === normalizedMiniProgramId) {
|
|
150
|
+
return {
|
|
151
|
+
...context,
|
|
152
|
+
changed: false,
|
|
153
|
+
miniProgramId: normalizedMiniProgramId,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (context.packageJson.heybox !== undefined && !isPlainObject(context.packageJson.heybox)) {
|
|
157
|
+
throw new MiniProgramProjectBindingError('package.json heybox 字段必须是对象,不能写入 miniProgramId', 'PACKAGE_JSON_HEYBOX_INVALID');
|
|
158
|
+
}
|
|
159
|
+
context.packageJson.heybox = {
|
|
160
|
+
...(context.packageJson.heybox ?? {}),
|
|
161
|
+
miniProgramId: normalizedMiniProgramId,
|
|
162
|
+
};
|
|
163
|
+
await fs.writeFile(context.packageJsonPath, `${JSON.stringify(context.packageJson, null, context.indent)}${context.trailingNewline || '\n'}`, 'utf8');
|
|
164
|
+
return {
|
|
165
|
+
...context,
|
|
166
|
+
changed: true,
|
|
167
|
+
miniProgramId: normalizedMiniProgramId,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function detectJsonIndent(raw) {
|
|
171
|
+
const match = raw.match(/\n(\s+)"[^"]+":/);
|
|
172
|
+
return match?.[1] ?? 2;
|
|
173
|
+
}
|
|
174
|
+
function isPlainObject(value) {
|
|
175
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function trustedProfileUrl(raw) {
|
|
179
|
+
const url = new URL(raw);
|
|
180
|
+
const trusted = ['max-c.com', 'xiaoheihe.cn', 'heybox.com'];
|
|
181
|
+
if (url.protocol !== 'https:' ||
|
|
182
|
+
url.username ||
|
|
183
|
+
url.password ||
|
|
184
|
+
(url.port && url.port !== '443') ||
|
|
185
|
+
!trusted.some((host) => url.hostname === host || url.hostname.endsWith(`.${host}`)))
|
|
186
|
+
throw new Error('线上资料图片 URL 不受信任');
|
|
187
|
+
return url;
|
|
188
|
+
}
|
|
189
|
+
async function downloadImage(raw, fetchImpl) {
|
|
190
|
+
const response = await fetchImpl(trustedProfileUrl(raw), { redirect: 'error', signal: AbortSignal.timeout(15_000) });
|
|
191
|
+
if (!response.ok || !response.body)
|
|
192
|
+
throw new Error('下载线上资料图片失败');
|
|
193
|
+
if (Number(response.headers.get('content-length')) > runtimePermissionEnv.BASIC_INFO_MAX_BYTES)
|
|
194
|
+
throw new Error('图片超过 10 MiB');
|
|
195
|
+
const chunks = [];
|
|
196
|
+
let size = 0;
|
|
197
|
+
const reader = response.body.getReader();
|
|
198
|
+
try {
|
|
199
|
+
for (;;) {
|
|
200
|
+
const part = await reader.read();
|
|
201
|
+
if (part.done)
|
|
202
|
+
break;
|
|
203
|
+
size += part.value.length;
|
|
204
|
+
if (size > runtimePermissionEnv.BASIC_INFO_MAX_BYTES)
|
|
205
|
+
throw new Error('图片超过 10 MiB');
|
|
206
|
+
chunks.push(part.value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await reader.cancel();
|
|
211
|
+
}
|
|
212
|
+
return Buffer.concat(chunks);
|
|
213
|
+
}
|
|
214
|
+
/** package-last 提交:失败只清理本次新增素材,原配置始终保留。 */
|
|
215
|
+
async function migrateBasicInfo(cwd, detail, miniProgramId, fetchImpl = fetch, force = false) {
|
|
216
|
+
const context = await readMiniProgramProjectPackage(cwd);
|
|
217
|
+
const heybox = context.packageJson.heybox ?? {};
|
|
218
|
+
const existing = heybox.miniProgramId;
|
|
219
|
+
if (existing && existing !== miniProgramId && !force)
|
|
220
|
+
throw new Error('当前项目已绑定其他小程序,请使用 --force');
|
|
221
|
+
const fields = ['name', 'icon', 'coverImages'];
|
|
222
|
+
const present = fields.filter((key) => Object.prototype.hasOwnProperty.call(heybox, key));
|
|
223
|
+
if (present.length && present.length !== 3)
|
|
224
|
+
throw new Error(`资料配置不完整,请补齐 ${fields.filter((key) => !present.includes(key)).join('、')}`);
|
|
225
|
+
const created = [];
|
|
226
|
+
const temp = `${context.packageJsonPath}.${node_crypto.randomUUID()}.tmp`;
|
|
227
|
+
try {
|
|
228
|
+
let profile = present.length ? runtimePermissionEnv.parseMiniappBasicInfo(heybox) : undefined;
|
|
229
|
+
if (profile) {
|
|
230
|
+
await readBasicInfoSource(context.projectRoot, profile.icon, 'icon');
|
|
231
|
+
for (const cover of profile.coverImages)
|
|
232
|
+
await readBasicInfoSource(context.projectRoot, cover, 'cover');
|
|
233
|
+
if (existing === miniProgramId)
|
|
234
|
+
return { changed: false, miniProgramId };
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
if (detail.mini_program_id !== miniProgramId)
|
|
238
|
+
throw new Error('远端小程序身份不一致');
|
|
239
|
+
const snapshot = detail.published_basic_info;
|
|
240
|
+
if (!snapshot?.version ||
|
|
241
|
+
!snapshot.icon_url ||
|
|
242
|
+
!Array.isArray(snapshot.cover_image_urls) ||
|
|
243
|
+
snapshot.cover_image_urls.length < 1 ||
|
|
244
|
+
snapshot.cover_image_urls.length > 5)
|
|
245
|
+
throw new Error('没有当前正式发布快照,请手工配置 heybox.name/icon/coverImages');
|
|
246
|
+
profile = { name: runtimePermissionEnv.normalizeBasicInfoName(snapshot.name), icon: '', coverImages: [] };
|
|
247
|
+
const entries = [
|
|
248
|
+
{ url: snapshot.icon_url, kind: 'icon', label: 'icon' },
|
|
249
|
+
...snapshot.cover_image_urls.map((url, i) => ({ url, kind: 'cover', label: `cover-${String(i + 1).padStart(2, '0')}` })),
|
|
250
|
+
];
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
const bytes = await downloadImage(entry.url, fetchImpl);
|
|
253
|
+
const image = await inspectRemoteBasicInfoImage(bytes, entry.kind);
|
|
254
|
+
const relative = `assets/heybox/${entry.label}.${image.sha256.slice(0, 12)}.${image.extension}`;
|
|
255
|
+
const target = path.join(context.projectRoot, relative);
|
|
256
|
+
await ensureBasicInfoDirectory(context.projectRoot, 'assets/heybox');
|
|
257
|
+
try {
|
|
258
|
+
await fs.writeFile(target, bytes, { flag: 'wx' });
|
|
259
|
+
created.push(target);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error.code !== 'EEXIST' ||
|
|
263
|
+
!(await readBasicInfoSource(context.projectRoot, relative, entry.kind).then((existing) => existing.bytes.equals(bytes), () => false)))
|
|
264
|
+
throw new Error('迁移素材名称冲突,未覆盖已有文件');
|
|
265
|
+
}
|
|
266
|
+
if (entry.kind === 'icon')
|
|
267
|
+
profile.icon = relative;
|
|
268
|
+
else
|
|
269
|
+
profile.coverImages.push(relative);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// 避免覆盖用户在下载期间修改的配置。
|
|
273
|
+
if ((await fs.readFile(context.packageJsonPath, 'utf8')) !== context.raw)
|
|
274
|
+
throw new Error('package.json 已变化,请重试迁移');
|
|
275
|
+
context.packageJson.heybox = { ...heybox, miniProgramId, ...profile };
|
|
276
|
+
await fs.writeFile(temp, JSON.stringify(context.packageJson, null, context.indent) + context.trailingNewline, { flag: 'wx' });
|
|
277
|
+
await fs.rename(temp, context.packageJsonPath);
|
|
278
|
+
return { changed: true, miniProgramId };
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
// 若用户同时修改了 package,保留可能已被引用的素材供重试复用。
|
|
282
|
+
if ((await fs.readFile(context.packageJsonPath, 'utf8').catch(() => '')) === context.raw) {
|
|
283
|
+
await Promise.all(created.map((file) => fs.unlink(file).catch(() => { })));
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
await fs.unlink(temp).catch(() => { });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function compareBasicInfo(cwd, detail, fetchImpl = fetch) {
|
|
292
|
+
const context = await readMiniProgramProjectPackage(cwd);
|
|
293
|
+
const published = detail.published_basic_info;
|
|
294
|
+
let local;
|
|
295
|
+
try {
|
|
296
|
+
local = runtimePermissionEnv.parseMiniappBasicInfo(context.packageJson.heybox ?? {});
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return { status: 'unavailable' };
|
|
300
|
+
}
|
|
301
|
+
if (!local || !published?.version)
|
|
302
|
+
return { status: 'unavailable', local, published };
|
|
303
|
+
try {
|
|
304
|
+
const pairs = [
|
|
305
|
+
{ local: local.icon, remote: published.icon_url, kind: 'icon' },
|
|
306
|
+
...local.coverImages.map((p, i) => ({ local: p, remote: published.cover_image_urls[i], kind: 'cover' })),
|
|
307
|
+
];
|
|
308
|
+
let changed = local.name !== runtimePermissionEnv.normalizeBasicInfoName(published.name) || local.coverImages.length !== published.cover_image_urls.length;
|
|
309
|
+
for (const pair of pairs) {
|
|
310
|
+
if (!pair.remote) {
|
|
311
|
+
changed = true;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const source = await readBasicInfoSource(context.projectRoot, pair.local, pair.kind);
|
|
315
|
+
const bytes = await downloadImage(pair.remote, fetchImpl);
|
|
316
|
+
const remote = await inspectRemoteBasicInfoImage(bytes, pair.kind);
|
|
317
|
+
changed ||= source.sha256 !== remote.sha256;
|
|
318
|
+
}
|
|
319
|
+
return { status: changed ? 'changed' : 'unchanged', local, published };
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return { status: 'unavailable', local, published };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
8
325
|
|
|
9
326
|
function shouldSkipMiniappPlatformCsp(runtimePermissions) {
|
|
10
327
|
const parsed = runtimePermissionEnv.parseMiniProgramRuntimePermissions(runtimePermissions);
|
|
@@ -149,11 +466,9 @@ async function precheckUserMiniprogramVersion(options, runtime) {
|
|
|
149
466
|
return remotePostForm(runtimePermissionEnv.PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, options, runtime);
|
|
150
467
|
}
|
|
151
468
|
async function submitUserMiniprogramAudit(options, runtime) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
return remotePostForm(runtimePermissionEnv.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, form, runtime);
|
|
469
|
+
return remotePostForm(runtimePermissionEnv.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, {
|
|
470
|
+
mini_program_id: options.mini_program_id, session_id: options.session_id, preflight_token: options.preflight_token,
|
|
471
|
+
}, runtime);
|
|
157
472
|
}
|
|
158
473
|
async function getVersionPreviewInfo(options, runtime) {
|
|
159
474
|
return remoteGet(runtimePermissionEnv.USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH, {
|
|
@@ -240,102 +555,6 @@ function createFormBody(fields) {
|
|
|
240
555
|
return body.toString();
|
|
241
556
|
}
|
|
242
557
|
|
|
243
|
-
class MiniProgramProjectBindingError extends Error {
|
|
244
|
-
code;
|
|
245
|
-
constructor(message, code) {
|
|
246
|
-
super(message);
|
|
247
|
-
this.code = code;
|
|
248
|
-
this.name = 'MiniProgramProjectBindingError';
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function findMiniProgramProjectRoot(startDir) {
|
|
252
|
-
let current = path.resolve(startDir);
|
|
253
|
-
while (true) {
|
|
254
|
-
if (fs$1.existsSync(path.join(current, 'package.json'))) {
|
|
255
|
-
return current;
|
|
256
|
-
}
|
|
257
|
-
const parent = path.dirname(current);
|
|
258
|
-
if (parent === current) {
|
|
259
|
-
throw new MiniProgramProjectBindingError('当前目录或父目录未找到 package.json', 'PACKAGE_JSON_NOT_FOUND');
|
|
260
|
-
}
|
|
261
|
-
current = parent;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async function readMiniProgramProjectPackage(startDir) {
|
|
265
|
-
const projectRoot = findMiniProgramProjectRoot(startDir);
|
|
266
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
267
|
-
const raw = await fs.readFile(packageJsonPath, 'utf8');
|
|
268
|
-
let packageJson;
|
|
269
|
-
try {
|
|
270
|
-
packageJson = JSON.parse(raw);
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
274
|
-
throw new MiniProgramProjectBindingError(`package.json 不是合法 JSON:${packageJsonPath}\n${message}`, 'PACKAGE_JSON_INVALID');
|
|
275
|
-
}
|
|
276
|
-
return {
|
|
277
|
-
projectRoot,
|
|
278
|
-
packageJsonPath,
|
|
279
|
-
packageJson,
|
|
280
|
-
raw,
|
|
281
|
-
indent: detectJsonIndent(raw),
|
|
282
|
-
trailingNewline: raw.endsWith('\r\n') ? '\r\n' : raw.endsWith('\n') ? '\n' : '',
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
function readBoundMiniProgramIdFromPackageJson(packageJson) {
|
|
286
|
-
const value = packageJson.heybox?.miniProgramId;
|
|
287
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
288
|
-
}
|
|
289
|
-
async function readBoundMiniProgramId(startDir) {
|
|
290
|
-
return readBoundMiniProgramIdFromPackageJson((await readMiniProgramProjectPackage(startDir)).packageJson);
|
|
291
|
-
}
|
|
292
|
-
async function requireBoundMiniProgramId(startDir) {
|
|
293
|
-
const context = await readMiniProgramProjectPackage(startDir);
|
|
294
|
-
const miniProgramId = readBoundMiniProgramIdFromPackageJson(context.packageJson);
|
|
295
|
-
if (!miniProgramId) {
|
|
296
|
-
throw new MiniProgramProjectBindingError(`当前项目未绑定小程序。请先运行 hb-sdk remote create 或 hb-sdk remote bind <mini-program-id>。\n配置位置:${context.packageJsonPath}#heybox.miniProgramId`, 'MINI_PROGRAM_UNBOUND');
|
|
297
|
-
}
|
|
298
|
-
return { ...context, miniProgramId };
|
|
299
|
-
}
|
|
300
|
-
async function writeBoundMiniProgramId(startDir, miniProgramId, options = {}) {
|
|
301
|
-
const context = await readMiniProgramProjectPackage(startDir);
|
|
302
|
-
const normalizedMiniProgramId = String(miniProgramId).trim();
|
|
303
|
-
if (!normalizedMiniProgramId) {
|
|
304
|
-
throw new MiniProgramProjectBindingError('miniProgramId 不能为空', 'PACKAGE_JSON_INVALID');
|
|
305
|
-
}
|
|
306
|
-
const currentMiniProgramId = readBoundMiniProgramIdFromPackageJson(context.packageJson);
|
|
307
|
-
if (currentMiniProgramId && currentMiniProgramId !== normalizedMiniProgramId && !options.force) {
|
|
308
|
-
throw new MiniProgramProjectBindingError(`当前项目已绑定小程序 ${currentMiniProgramId}。如需覆盖本地绑定,请使用 --force。`, 'MINI_PROGRAM_BINDING_EXISTS');
|
|
309
|
-
}
|
|
310
|
-
if (currentMiniProgramId === normalizedMiniProgramId) {
|
|
311
|
-
return {
|
|
312
|
-
...context,
|
|
313
|
-
changed: false,
|
|
314
|
-
miniProgramId: normalizedMiniProgramId,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (context.packageJson.heybox !== undefined && !isPlainObject(context.packageJson.heybox)) {
|
|
318
|
-
throw new MiniProgramProjectBindingError('package.json heybox 字段必须是对象,不能写入 miniProgramId', 'PACKAGE_JSON_HEYBOX_INVALID');
|
|
319
|
-
}
|
|
320
|
-
context.packageJson.heybox = {
|
|
321
|
-
...(context.packageJson.heybox ?? {}),
|
|
322
|
-
miniProgramId: normalizedMiniProgramId,
|
|
323
|
-
};
|
|
324
|
-
await fs.writeFile(context.packageJsonPath, `${JSON.stringify(context.packageJson, null, context.indent)}${context.trailingNewline || '\n'}`, 'utf8');
|
|
325
|
-
return {
|
|
326
|
-
...context,
|
|
327
|
-
changed: true,
|
|
328
|
-
miniProgramId: normalizedMiniProgramId,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
function detectJsonIndent(raw) {
|
|
332
|
-
const match = raw.match(/\n(\s+)"[^"]+":/);
|
|
333
|
-
return match?.[1] ?? 2;
|
|
334
|
-
}
|
|
335
|
-
function isPlainObject(value) {
|
|
336
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
337
|
-
}
|
|
338
|
-
|
|
339
558
|
async function resolveRemoteEnvironment(options = {}, runtime = {}) {
|
|
340
559
|
const env = options.env ?? process.env;
|
|
341
560
|
const apiBaseUrl = session.resolveHeyboxApiBaseUrl({ ...options, env });
|
|
@@ -393,12 +612,15 @@ function isTruthyEnvFlag(value) {
|
|
|
393
612
|
}
|
|
394
613
|
|
|
395
614
|
exports.MiniProgramProjectBindingError = MiniProgramProjectBindingError;
|
|
615
|
+
exports.compareBasicInfo = compareBasicInfo;
|
|
396
616
|
exports.createBrowserDevPermissionState = createBrowserDevPermissionState;
|
|
397
617
|
exports.createBrowserDevRuntimePermissions = createBrowserDevRuntimePermissions;
|
|
398
618
|
exports.createLocalDevLaunchToken = createLocalDevLaunchToken;
|
|
399
619
|
exports.createMobileDevSessionRuntimePermissions = createMobileDevSessionRuntimePermissions;
|
|
400
620
|
exports.createRemoteApiClientForEnvironment = createRemoteApiClientForEnvironment;
|
|
401
621
|
exports.getBoundMiniProgram = getBoundMiniProgram;
|
|
622
|
+
exports.migrateBasicInfo = migrateBasicInfo;
|
|
623
|
+
exports.readBasicInfoSource = readBasicInfoSource;
|
|
402
624
|
exports.readBoundMiniProgramId = readBoundMiniProgramId;
|
|
403
625
|
exports.readBoundMiniProgramIdFromPackageJson = readBoundMiniProgramIdFromPackageJson;
|
|
404
626
|
exports.readMiniProgramProjectPackage = readMiniProgramProjectPackage;
|
|
@@ -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-CP14FXez.cjs');
|
|
9
9
|
require('node:module');
|
|
10
10
|
require('os');
|
|
11
11
|
require('node:crypto');
|
|
@@ -1336,6 +1336,10 @@ async function renderTemplateDirectory(sourceDir, targetDir, data) {
|
|
|
1336
1336
|
if (!entry.isFile()) {
|
|
1337
1337
|
continue;
|
|
1338
1338
|
}
|
|
1339
|
+
if (!entry.name.endsWith('.ejs')) {
|
|
1340
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1339
1343
|
const content = await fs.readFile(sourcePath, 'utf8');
|
|
1340
1344
|
const rendered = ejsExports.render(content, data, {
|
|
1341
1345
|
filename: sourcePath,
|