@heybox/hb-sdk 0.9.0-alpha.1 → 0.9.0-alpha.10

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +14 -1
  3. package/dist/cli-chunks/{build-BSeiFi0q.cjs → build-CT6E9KRd.cjs} +5 -5
  4. package/dist/cli-chunks/{context-D_L2-Hla.cjs → context--dJRx4eH.cjs} +2 -2
  5. package/dist/cli-chunks/{create-Dzeodh_-.cjs → create-D6SiqyyQ.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-DQ7k_fu6.cjs → dev-CoWE5lyA.cjs} +223 -11
  7. package/dist/cli-chunks/{doctor-FIcFZiJ0.cjs → doctor-IQ5wPkWD.cjs} +1 -1
  8. package/dist/cli-chunks/{index-d1cUUoR9.cjs → index-CYxcLlON.cjs} +15 -15
  9. package/dist/cli-chunks/{index-CakUTosC.cjs → index-CnOLdO_-.cjs} +2 -2
  10. package/dist/cli-chunks/{index.esm-CerEC339.cjs → index.esm-07KOgDkI.cjs} +7 -7
  11. package/dist/cli-chunks/{login-lZKDg_9k.cjs → login-BszmxuLI.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-BM7D-pf6.cjs → project-vite-tMo1gavS.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-9-tyxq6l.cjs → remote-2oOP8M5C.cjs} +83 -17
  14. package/dist/cli-chunks/{runtime-gate-BfAGMrE2.cjs → runtime-gate-CNBf6Zkh.cjs} +8 -1
  15. package/dist/cli-chunks/{runtime-permission-env-B-GwDDT7.cjs → runtime-permission-env-GH_vWEVx.cjs} +86 -57
  16. package/dist/cli-chunks/{session-mMVA3395.cjs → session-DrhTp-x-.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-BhUlKvyY.cjs → skill-NfC1c0iQ.cjs} +2 -2
  18. package/dist/cli-chunks/{version-hsUgRL0I.cjs → version-DA6v9R62.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/browser-dev-host-C3c3yud2.js +102 -0
  21. package/dist/devtools/browser-dev-host/assets/desktop-app-launch-CHZKVQmQ.js +6 -0
  22. package/dist/devtools/browser-dev-host/assets/{index-0Ac39Njs.css → index-B5aFVoao.css} +1 -1
  23. package/dist/devtools/browser-dev-host/assets/{index-DMuA2oZx.js → index-BmiQPZvl.js} +4 -4
  24. package/dist/devtools/browser-dev-host/index.html +3 -3
  25. package/dist/index.cjs.js +10 -3
  26. package/dist/index.esm.js +10 -3
  27. package/dist/miniapp-publish.cjs.js +121 -57
  28. package/dist/miniapp-publish.esm.js +121 -57
  29. package/dist/vite.cjs.js +33 -4
  30. package/dist/vite.esm.js +33 -4
  31. package/docs/runtime-host-migration.md +3 -1
  32. package/package.json +8 -8
  33. package/skill/skill.json +4 -4
  34. package/types/core/singleton.d.ts +1 -1
  35. package/types/miniapp-manifest/basic-info-browser.d.ts +29 -1
  36. package/types/miniapp-manifest/basic-info.d.ts +23 -2
  37. package/types/miniapp-manifest/category.d.ts +9 -2
  38. package/types/miniapp-manifest/companion-types.d.ts +25 -0
  39. package/types/miniapp-manifest/permissions.d.ts +12 -0
  40. package/types/miniapp-manifest/runtime-gate.d.ts +7 -0
  41. package/types/miniapp-manifest/schema.d.ts +23 -0
  42. package/types/miniapp-publish/index.d.ts +23 -51
  43. package/types/modules/auth/index.d.ts +5 -1
  44. package/types/modules/companion/index.d.ts +27 -34
  45. package/types/modules/navigation/index.d.ts +8 -0
  46. package/types/modules/network/index.d.ts +12 -2
  47. package/types/modules/user/index.d.ts +12 -1
  48. package/types/modules/user/steam-game-list.d.ts +2 -2
  49. package/types/modules/viewport/index.d.ts +21 -1
  50. package/types/vite/index.d.ts +5 -0
  51. package/dist/devtools/browser-dev-host/assets/browser-dev-host-BLgy6aQ6.js +0 -102
  52. package/dist/devtools/browser-dev-host/assets/desktop-app-launch-D32fpjYQ.js +0 -6
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
4
- var context = require('./context-D_L2-Hla.cjs');
3
+ var runtimePermissionEnv = require('./runtime-permission-env-GH_vWEVx.cjs');
4
+ var context = require('./context--dJRx4eH.cjs');
5
5
  var promises = require('node:readline/promises');
6
- var index = require('./index-d1cUUoR9.cjs');
7
- var session = require('./session-mMVA3395.cjs');
6
+ var index = require('./index-CYxcLlON.cjs');
7
+ var session = require('./session-DrhTp-x-.cjs');
8
8
  var childProcess = require('node:child_process');
9
9
  var fs = require('node:fs');
10
10
  var fs$1 = require('node:fs/promises');
11
11
  var path = require('node:path');
12
12
  var node_crypto = require('node:crypto');
13
13
  require('node:zlib');
14
- var runtimeGate = require('./runtime-gate-BfAGMrE2.cjs');
15
- require('./version-hsUgRL0I.cjs');
14
+ var runtimeGate = require('./runtime-gate-CNBf6Zkh.cjs');
15
+ require('./version-DA6v9R62.cjs');
16
16
  require('node:async_hooks');
17
17
  require('sharp');
18
18
  require('node:module');
@@ -68,6 +68,14 @@ async function postHeyboxApi(pathWithQuery, body, session$1, runtime) {
68
68
 
69
69
  const CREATE_USER_MINIPROGRAM_UPLOAD_SESSION_API_PATH = '/mall/developer/user_miniprogram/version/upload_session/create';
70
70
  const RECORD_USER_MINIPROGRAM_UPLOAD_RESULT_API_PATH = '/mall/developer/user_miniprogram/version/upload_session/record';
71
+ const COMPANION_FINALIZATION_TIMEOUT_MS = 30 * 60_000;
72
+ async function authorizeCompanionUpload(options, runtime = {}) {
73
+ const result = await postUserMiniprogramVersionForm('/mall/developer/user_miniprogram/version/upload_session/companion_authorization', options, { version: options.version, session_id: options.sessionId, relative_paths: options.relativePaths.join(','), artifact_protocol_version: '2' }, runtime);
74
+ if (!result.bucket || !result.region || !Array.isArray(result.keys) || !result.tmp_secret_id || !result.tmp_secret_key || !result.session_token || !(result.expired_time > result.start_time)) {
75
+ throw new Error('Companion 上传凭证响应不完整');
76
+ }
77
+ return result;
78
+ }
71
79
  async function precheckUserMiniprogramVersion(options, runtime = {}) {
72
80
  return postUserMiniprogramVersionForm(runtimePermissionEnv.PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, options, {
73
81
  // 构建前轻预检不携带独立资料;权威准入读取构建 Manifest。
@@ -78,11 +86,12 @@ async function precheckUserMiniprogramVersion(options, runtime = {}) {
78
86
  }, runtime);
79
87
  }
80
88
  async function submitUserMiniprogramAudit(options, runtime = {}) {
81
- return postUserMiniprogramVersionForm(runtimePermissionEnv.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, options, { session_id: options.sessionId, preflight_token: options.preflightToken }, runtime);
89
+ return postUserMiniprogramVersionForm(runtimePermissionEnv.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, options, { session_id: options.sessionId, preflight_token: options.preflightToken, artifact_protocol_version: '2' }, runtime);
82
90
  }
83
91
  async function createUserMiniprogramUploadSession(options, runtime = {}) {
84
92
  return postUserMiniprogramVersionForm(CREATE_USER_MINIPROGRAM_UPLOAD_SESSION_API_PATH, options, {
85
93
  version: options.version,
94
+ artifact_protocol_version: '2',
86
95
  artifact_signature: options.artifactSignature,
87
96
  files: JSON.stringify(options.files),
88
97
  manifest: JSON.stringify(options.manifest),
@@ -91,11 +100,27 @@ async function createUserMiniprogramUploadSession(options, runtime = {}) {
91
100
  }, runtime);
92
101
  }
93
102
  async function recordUserMiniprogramUploadResult(options, runtime = {}) {
94
- await postUserMiniprogramVersionForm(RECORD_USER_MINIPROGRAM_UPLOAD_RESULT_API_PATH, options, {
103
+ const fields = {
95
104
  version: options.version,
105
+ artifact_protocol_version: '2',
96
106
  session_id: options.sessionId,
97
107
  files: JSON.stringify(options.files),
98
- }, runtime);
108
+ };
109
+ const deadline = Date.now() + COMPANION_FINALIZATION_TIMEOUT_MS;
110
+ for (;;) {
111
+ const result = await postUserMiniprogramVersionForm(RECORD_USER_MINIPROGRAM_UPLOAD_RESULT_API_PATH, options, fields, runtime);
112
+ if (result.artifact_protocol_version !== 2 || !Array.isArray(result.companion_files)) {
113
+ throw new Error('服务端未返回产物协议 v2 Finalize 状态');
114
+ }
115
+ const failed = result.companion_files.find((file) => file.state === 'failed');
116
+ if (failed)
117
+ throw new Error(failed.error || `Companion 归档验证失败:${failed.relative_path}`);
118
+ if (result.complete)
119
+ return;
120
+ if (Date.now() >= deadline)
121
+ throw new Error('Companion 归档验证超时,请稍后重试');
122
+ await new Promise((resolve) => setTimeout(resolve, Math.max(250, Math.min(result.retry_after_ms || 1000, 5000))));
123
+ }
99
124
  }
100
125
  async function postUserMiniprogramVersionForm(path, options, fields, runtime) {
101
126
  const fetchImpl = runtime.fetchImpl ?? fetch;
@@ -134,17 +159,21 @@ async function runUpload(options, runtime = {}) {
134
159
  createCosClient = ({ uploadToken }) => createDefaultCosClient(uploadToken, COS);
135
160
  }
136
161
  const orderedFiles = runtimePermissionEnv.prepareMiniappArtifactFiles(options.files);
137
- const batches = chunkUploadFiles(orderedFiles, runtimePermissionEnv.MINIAPP_UPLOAD_BATCH_SIZE);
162
+ const batches = [false, true].flatMap(companion => chunkUploadFiles(orderedFiles.filter(file => file.relativePath.startsWith('companions/') === companion), runtimePermissionEnv.MINIAPP_UPLOAD_BATCH_SIZE));
138
163
  await logger.task(`正在上传 ${orderedFiles.length} 个文件`, async (taskContext) => {
139
164
  logger.debug(`上传并发数: ${concurrency}`);
140
165
  logger.debug(`上传批次数: ${batches.length}`);
141
166
  let completed = 0;
142
167
  for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
143
168
  const batch = batches[batchIndex];
169
+ const companion = batch[0].relativePath.startsWith('companions/');
170
+ const grant = companion ? await (runtime.authorizeCompanionUpload ?? authorizeCompanionUpload)({
171
+ session: options.session, miniProgramId: options.miniProgramId, version: options.version, sessionId: options.uploadSessionId, relativePaths: batch.map(file => file.relativePath),
172
+ }, { baseUrl: runtime.baseUrl, fetchImpl }) : undefined;
144
173
  logger.debug(`正在处理上传批次 ${batchIndex + 1}/${batches.length},文件数: ${batch.length}`);
145
174
  logger.debug('正在获取 CDN 上传信息');
146
175
  const fileInfos = createUploadFileInfos(batch);
147
- const uploadInfo = await getCDNUploadInfo$1({
176
+ const uploadInfo = grant ?? await getCDNUploadInfo$1({
148
177
  session: options.session,
149
178
  scope: runtimePermissionEnv.MINIAPP_UPLOAD_SCOPE,
150
179
  fileInfos,
@@ -157,7 +186,10 @@ async function runUpload(options, runtime = {}) {
157
186
  logger.debug('正在获取本批 CDN 上传凭证');
158
187
  let uploadToken;
159
188
  try {
160
- uploadToken = await getCDNUploadToken$1({
189
+ uploadToken = grant ? {
190
+ credentials: { tmpSecretId: grant.tmp_secret_id, tmpSecretKey: grant.tmp_secret_key, sessionToken: grant.session_token },
191
+ startTime: grant.start_time, expiredTime: grant.expired_time,
192
+ } : await getCDNUploadToken$1({
161
193
  session: options.session,
162
194
  bucket: uploadInfo.bucket,
163
195
  keys: indexedFiles.map(item => item.key),
@@ -186,6 +218,11 @@ async function runUpload(options, runtime = {}) {
186
218
  Body: createReadStream(uploadTask.file.absolutePath),
187
219
  ContentLength: uploadTask.file.size,
188
220
  ContentType: uploadTask.file.mimeType,
221
+ ...(companion ? { Headers: { 'x-cos-forbid-overwrite': 'true' } } : {}),
222
+ }).catch(error => {
223
+ if (companion && isExistingCompanionObject(error))
224
+ return;
225
+ throw error;
189
226
  });
190
227
  successfulKeys.push(uploadTask.key);
191
228
  completed += 1;
@@ -203,7 +240,8 @@ async function runUpload(options, runtime = {}) {
203
240
  await Promise.all(workers);
204
241
  if (successfulKeys.length > 0) {
205
242
  logger.debug('正在确认 CDN 上传结果');
206
- await postCDNUploadCallback$1({ session: options.session, keys: successfulKeys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
243
+ if (!companion)
244
+ await postCDNUploadCallback$1({ session: options.session, keys: successfulKeys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
207
245
  await recordUserMiniprogramUploadResult$1({
208
246
  session: options.session,
209
247
  miniProgramId: options.miniProgramId,
@@ -227,6 +265,12 @@ function chunkUploadFiles(files, batchSize) {
227
265
  }
228
266
  return batches;
229
267
  }
268
+ function isExistingCompanionObject(error) {
269
+ if (!error || typeof error !== 'object')
270
+ return false;
271
+ const value = error;
272
+ return Number(value.statusCode) === 409 && value.code === 'ObjectAlreadyExists';
273
+ }
230
274
  function createUploadFileInfos(files) {
231
275
  return files.map((file) => ({
232
276
  name: file.relativePath.split('/').pop() ?? file.relativePath,
@@ -264,7 +308,7 @@ function createDefaultCosClient(uploadToken, COS) {
264
308
  return new Promise((resolve, reject) => {
265
309
  cos.putObject(params, (err, result) => {
266
310
  if (err) {
267
- reject(err instanceof Error ? err : new Error(readErrorMessage(err)));
311
+ reject(err);
268
312
  return;
269
313
  }
270
314
  resolve(result);
@@ -274,7 +318,7 @@ function createDefaultCosClient(uploadToken, COS) {
274
318
  };
275
319
  }
276
320
  async function loadCosConstructor() {
277
- const cosModule = await Promise.resolve().then(function () { return require('./index-CakUTosC.cjs'); }).then(function (n) { return n.index; });
321
+ const cosModule = await Promise.resolve().then(function () { return require('./index-CnOLdO_-.cjs'); }).then(function (n) { return n.index; });
278
322
  return cosModule.default;
279
323
  }
280
324
  function formatSize(bytes) {
@@ -519,6 +563,9 @@ const MIME_BY_EXT = {
519
563
  '.woff': 'font/woff',
520
564
  '.woff2': 'font/woff2',
521
565
  };
566
+ function formatDeployBytes(bytes) {
567
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;
568
+ }
522
569
  async function runDeployCommand(options, runtime = {}) {
523
570
  const logger = runtime.logger ?? index.createCliLogger();
524
571
  const cwd = runtime.cwd ?? process.cwd();
@@ -691,24 +738,43 @@ async function runDeployCommand(options, runtime = {}) {
691
738
  }
692
739
  if (!result.preflight_token)
693
740
  throw new Error('服务端未返回 preflight_token,请先升级服务端');
741
+ if (result.artifact_protocol_version !== 2) {
742
+ throw new Error('创建上传会话失败:服务端不支持产物协议 v2,请升级服务端后重试');
743
+ }
694
744
  if (result.file_receipt_version !== 1) {
695
745
  throw new Error('创建上传会话失败:服务端不支持文件完整性 receipt v1,请升级服务端后重试');
696
746
  }
697
- const resolved = runtimePermissionEnv.resolveMiniappUploadSessionFiles(artifactReceipt.files, Array.isArray(result.files) ? result.files : [], Array.isArray(result.uploaded_keys) ? result.uploaded_keys : []);
747
+ if (!Array.isArray(result.files) || !Array.isArray(result.uploaded_keys) || !Array.isArray(result.reused_companion_files)) {
748
+ throw new Error('创建上传会话失败:服务端返回的产物协议 v2 文件清单不完整');
749
+ }
750
+ const resolved = runtimePermissionEnv.resolveMiniappUploadSessionFiles(artifactReceipt.files, result.files, result.uploaded_keys, result.reused_companion_files);
698
751
  return {
699
752
  preflightToken: result.preflight_token,
700
753
  sessionId: result.session_id.trim(),
701
754
  files: resolved.files,
702
755
  missingFiles: resolved.missingFiles,
756
+ reusedCompanionFiles: resolved.reusedCompanionFiles,
703
757
  };
704
758
  }, { successText: '上传会话已就绪' });
705
759
  uploadSessionId = createdUploadSession.sessionId;
706
760
  preflightToken = createdUploadSession.preflightToken;
707
761
  const uploadedCount = createdUploadSession.files.length - createdUploadSession.missingFiles.length;
708
762
  if (uploadedCount > 0) {
709
- logger.debug(`复用已上传文件: ${uploadedCount}/${createdUploadSession.files.length}`);
763
+ logger.debug(`会话内已上传文件: ${uploadedCount}/${createdUploadSession.files.length}`);
710
764
  }
765
+ const reusedBytes = createdUploadSession.reusedCompanionFiles.reduce((sum, file) => sum + file.size, 0);
766
+ const uploadBytes = createdUploadSession.missingFiles.reduce((sum, { file }) => sum + file.size, 0);
767
+ logger.info(`Companion Blob 复用: ${createdUploadSession.reusedCompanionFiles.length} 个,免上传 ${formatDeployBytes(reusedBytes)};实际待上传 ${formatDeployBytes(uploadBytes)}`);
711
768
  try {
769
+ const missingKeys = new Set(createdUploadSession.missingFiles.map(item => item.key));
770
+ const uploadedCompanions = createdUploadSession.files.filter(item => item.file.relativePath.startsWith('companions/') && !missingKeys.has(item.key));
771
+ for (let index = 0; index < uploadedCompanions.length; index += runtimePermissionEnv.MINIAPP_UPLOAD_BATCH_SIZE) {
772
+ await (runtime.recordUserMiniprogramUploadResult ?? recordUserMiniprogramUploadResult)({
773
+ session: session$1, miniProgramId, version, sessionId: uploadSessionId,
774
+ files: uploadedCompanions.slice(index, index + runtimePermissionEnv.MINIAPP_UPLOAD_BATCH_SIZE)
775
+ .map(({ file }) => ({ path: file.relativePath, size: file.size, sha256: file.sha256 })),
776
+ }, { baseUrl: apiBaseUrl, fetchImpl: runtime.fetchImpl });
777
+ }
712
778
  if (createdUploadSession.missingFiles.length > 0) {
713
779
  await assertDeployUploadFilesStillMatchSignature({
714
780
  computeMiniappArtifactReceipt: runtime.computeMiniappArtifactReceipt ?? computeMiniappArtifactReceipt,
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
3
+ var runtimePermissionEnv = require('./runtime-permission-env-GH_vWEVx.cjs');
4
4
 
5
5
  // Shorthands
6
6
  /**
@@ -27,6 +27,13 @@ function parse(html, options) {
27
27
  const RUNTIME_GATE_MARKER = 'data-heybox-runtime-gate';
28
28
  const RUNTIME_GATE_STYLE_MARKER = 'data-heybox-runtime-gate-style';
29
29
  const RUNTIME_GATE_BOOTSTRAP_MARKER = 'data-heybox-runtime-gate-bootstrap';
30
+ /**
31
+ * 检查 HTML 是否包含 Runtime Gate 的三个结构标记。
32
+ *
33
+ * @param html - 待检查的完整 HTML 文本,使用 HTML parser 解析。
34
+ * @returns Gate、隐藏样式及 bootstrap 标记均出现时返回 `true`,否则返回 `false`。
35
+ * @remarks 只识别标记,不验证脚本内容、标记所在节点或宿主握手;不能据此判断页面安全或实际可运行。
36
+ */
30
37
  function hasMiniappRuntimeGate(html) {
31
38
  const document = parse(html);
32
39
  let hasGate = false;
@@ -2,11 +2,18 @@
2
2
 
3
3
  var fs = require('node:fs');
4
4
  var path = require('node:path');
5
- var version = require('./version-hsUgRL0I.cjs');
5
+ var version = require('./version-DA6v9R62.cjs');
6
6
  require('node:crypto');
7
7
  var node_async_hooks = require('node:async_hooks');
8
8
 
9
- /** 校验配置或产物中的类别,省略与显式非法值严格区分。 */
9
+ /**
10
+ * 校验项目配置或产物中的小程序类别。
11
+ *
12
+ * @param value - 待校验值,只接受 `tool`、`game`、`other` 或 `undefined`;不做 trim 或大小写转换。
13
+ * @param sourceLabel - 错误消息中的字段来源,默认 `package.json#heybox.category`。
14
+ * @returns 合法类别原值;输入 `undefined` 时返回 `undefined`,由发布流程另行检查是否必填。
15
+ * @throws `Error` 输入为其他值(包括 null 或空字符串)时抛出带字段来源的错误。
16
+ */
10
17
  function parseMiniappCategory(value, sourceLabel = 'package.json#heybox.category') {
11
18
  if (value === undefined)
12
19
  return undefined;
@@ -2802,7 +2809,14 @@ var semverExports = requireSemver();
2802
2809
 
2803
2810
  const BASIC_INFO_MAX_BYTES = 10 * 1024 * 1024;
2804
2811
  const BASIC_INFO_MAX_PIXELS = 40_000_000;
2805
- /** 按 NFKC 和 Unicode 空白规范化名称,并校验 1–40 个字符及禁用字符。 */
2812
+ /**
2813
+ * 规范化并校验小程序展示名称。
2814
+ *
2815
+ * @param raw - 原始名称,必须是字符串。
2816
+ * @returns NFKC 规范化、去除首尾 Unicode White_Space 后的名称;不折叠规范化后的内部空白。
2817
+ * @throws `Error` 输入不是字符串,或规范化后不是 1–40 个 Unicode code point、不含文字或数字,或包含控制字符、
2818
+ * 格式字符、行/段分隔符以及 HTML 特殊字符 `<`、`>`、`&`、双引号或单引号时抛出。
2819
+ */
2806
2820
  function normalizeBasicInfoName(raw) {
2807
2821
  if (typeof raw !== 'string')
2808
2822
  throw new Error('heybox.name 必须是字符串');
@@ -2823,7 +2837,17 @@ function validateBasicInfoPath(raw) {
2823
2837
  throw new Error('资料图片仅支持 JPEG、PNG、GIF、WebP');
2824
2838
  return raw;
2825
2839
  }
2826
- /** 解析项目或构建资料;仅早于 0.8.2 且三项全部缺失时返回 undefined。 */
2840
+ /**
2841
+ * 校验项目声明或 Manifest 中的小程序展示资料。
2842
+ *
2843
+ * @param value - `package.json#heybox` 或 Manifest 对象,读取 `name`、`icon`、`coverImages`。
2844
+ * @param sdkVersion - 产物 SDK 版本,默认 `0.8.2`;用于判断历史产物是否允许缺失全部资料。
2845
+ * @returns 名称规范化后的新资料对象;仅版本早于 `0.8.2` 且三项均无自有属性时返回 `undefined`。
2846
+ * @throws `Error` SDK 版本非法、资料字段不齐、名称/图片路径非法,或封面不是 1–5 个互不重复的路径时抛出。
2847
+ * @remarks
2848
+ * 图片路径必须使用 `/` 分隔的非空相对路径,扩展名为 JPEG、PNG、GIF 或 WebP;拒绝 URL、点段、
2849
+ * 控制字符和构建/缓存等保留目录。此函数只校验声明,不读取文件,也不验证图片字节、尺寸或存在性。
2850
+ */
2827
2851
  function parseMiniappBasicInfo(value, sdkVersion = '0.8.2') {
2828
2852
  sdkVersion = sdkVersion.trim();
2829
2853
  if (!semverExports.valid(sdkVersion) || sdkVersion.startsWith('v') || sdkVersion.includes('+'))
@@ -3576,6 +3600,16 @@ function parseMiniappManifestJson(raw, sourceLabel = 'manifest.json') {
3576
3600
  hadBom,
3577
3601
  };
3578
3602
  }
3603
+ /**
3604
+ * 校验发布 Manifest 并返回规范化后的已知字段。
3605
+ *
3606
+ * @param manifest - 从构建产物读取的 Manifest,图片和 Companion 路径相对构建目录。
3607
+ * @returns 新对象:规范化版本、名称、平台与游戏绑定,省略空绑定及未声明的可选字段;不保留未知字段。
3608
+ * @throws `Error` 版本、平台、资料、权限、窗口、游戏绑定或生成的 Companion 声明非法,以及类别缺失时抛出。
3609
+ * @remarks
3610
+ * 不修改输入对象,不执行文件 I/O;不会验证图片/归档是否存在、内容摘要是否匹配,也不查询远端发布资格。
3611
+ * 成功仅表示 Manifest 字段通过校验,不能替代文件检查与服务端发布预检。
3612
+ */
3579
3613
  function validateMiniappManifestForDeploy(manifest) {
3580
3614
  const version = validateMiniappManifestVersion(manifest.version, 'manifest.version');
3581
3615
  const sdkVersion = validateSdkVersion(manifest.sdkVersion, 'manifest.sdkVersion');
@@ -3608,6 +3642,7 @@ function validateGeneratedCompanions(value) {
3608
3642
  return undefined;
3609
3643
  if (!isRecord$2(value) || Object.keys(value).length === 0)
3610
3644
  throw new Error('manifest.companions 必须是非空对象');
3645
+ let descriptorCount = 0;
3611
3646
  for (const [alias, definition] of Object.entries(value)) {
3612
3647
  if (!/^[a-z][a-z0-9-]{0,31}$/.test(alias) ||
3613
3648
  !isRecord$2(definition) ||
@@ -3621,6 +3656,9 @@ function validateGeneratedCompanions(value) {
3621
3656
  Object.keys(definition.targets).length === 0)
3622
3657
  throw new Error(`manifest.companions.${alias} 不合法`);
3623
3658
  for (const [target, artifact] of Object.entries(definition.targets)) {
3659
+ descriptorCount += 1;
3660
+ if (descriptorCount > 64)
3661
+ throw new Error('manifest.companions 最多允许 64 个 target 描述符');
3624
3662
  if (isRecord$2(artifact) && artifact.elevation === 'required') {
3625
3663
  throw new Error(`manifest.companions.${alias}.targets.${target}.elevation V1 仅支持 none,暂不支持管理员权限启动`);
3626
3664
  }
@@ -12087,30 +12125,13 @@ const textEncoder = new TextEncoder();
12087
12125
  /**
12088
12126
  * 将扫描到的相对路径转换为统一的 `/` 分隔形式。
12089
12127
  *
12090
- * @param relativePath - 待整理的构建产物路径。TypeScript 入口声明为字符串;JavaScript 运行时会
12091
- * 先计算 `relativePath || ''`,再通过 `String(...)` 强制转换。
12092
- * @returns 仅统一分隔符形态的新字符串:所有 `\\` 转为 `/`,所有前导 `/` 被删除,其余连续
12093
- * `/` 合并为一个。空字符串以及运行时传入的 `undefined`、`null`、`false`、`0`、`NaN` 等
12094
- * falsy 值返回 `''`;truthy 非字符串按标准 `String(value)` 结果继续整理。
12128
+ * @param relativePath - 待整理的构建产物路径。
12129
+ * @returns 反斜杠转为 `/`、删除前导 `/`、合并连续 `/` 后的新字符串;空输入返回空字符串。
12095
12130
  *
12096
12131
  * @remarks
12097
- * 这是面向 build/deploy 文件扫描结果的**格式 helper**,不是路径安全校验或 canonical path
12098
- * parser。它不会 trim、URL decode、Unicode normalize、解析盘符,也不会拒绝或消解 `.` / `..`
12099
- * segment、控制字符、Windows drive prefix 或尾随 `/`。例如 `/etc/passwd` 会变成
12100
- * `etc/passwd`,`C:\\temp\\app.js` 会变成 `C:/temp/app.js`,`assets/../secret/` 仍保留点段
12101
- * 和尾随 slash;调用方不能根据返回值推断原输入是安全相对路径。
12102
- *
12103
- * SDK 的 build/deploy 流程会把扫描路径先用本函数统一分隔符,再分别使用
12104
- * {@link relativePathContainsNodeModulesSegment} 排除完整 `node_modules` 目录段、用
12105
- * {@link shouldUploadDistFile} 过滤根 `manifest.json` / `.DS_Store` / source map。`hb-sdk build`
12106
- * 随后校验大小预算;`hb-sdk remote deploy` 还会在签名和上传前通过
12107
- * `validateUploadPaths()`、`prepareMiniappArtifactFiles()` 或
12108
- * `validateMiniappUploadPathLengths()` 执行真正的空路径、点段、控制字符、重复和长度校验。
12109
- * 这些步骤不能由本函数单独替代;`shouldUploadDistFile()` 也不会自行检查 `node_modules`。
12110
- *
12111
- * 不要把本函数与 `@heybox/hb-sdk/protocol` 的 `isMiniProgramRelativePath()` 混用:协议 guard
12112
- * 不做转换,只接受已经规范的非空字符串,并拒绝前导/尾随 `/`、反斜杠、空 segment、`.`、
12113
- * `..` 与控制字符;本函数则会改写部分非法形态并保留其余不安全内容。
12132
+ * 仅整理文本格式,不做路径安全校验:保留点段、控制字符、盘符和尾随 `/`,不 trim 或 URL decode。
12133
+ * 上传前仍需校验路径安全、唯一性与长度。协议 `isMiniProgramRelativePath()` 则只校验、不改写输入。
12134
+ * JavaScript 非字符串输入按 `String(relativePath || '')` 转换,falsy 值回退为空串。
12114
12135
  *
12115
12136
  * @example
12116
12137
  * ```ts
@@ -12118,8 +12139,6 @@ const textEncoder = new TextEncoder();
12118
12139
  *
12119
12140
  * normalizeRelativePath('///dist\\assets//app.js'); // 'dist/assets/app.js'
12120
12141
  * normalizeRelativePath('assets/../secret///'); // 'assets/../secret/',仍需安全校验
12121
- * normalizeRelativePath(123 as unknown as string); // '123',JavaScript 运行时强制转换
12122
- * normalizeRelativePath(0 as unknown as string); // '',falsy 值先回退为空串
12123
12142
  * ```
12124
12143
  *
12125
12144
  * @see {@link relativePathContainsNodeModulesSegment}
@@ -12135,40 +12154,21 @@ function normalizeRelativePath(relativePath) {
12135
12154
  /**
12136
12155
  * 判断路径中是否包含大小写不敏感的完整 `node_modules` 目录段。
12137
12156
  *
12138
- * @param relativePath - 待检查的构建产物路径。函数先通过 {@link normalizeRelativePath} 把
12139
- * 反斜杠改为 `/`、删除前导 `/` 并合并连续 `/`,再按 `/` 分段。
12140
- * @returns 至少一个非空段大小写不敏感地等于完整 `node_modules` 时返回 `true`;空路径、
12141
- * 空段及 `node_modules.txt`、`node_modules-old`、`@node_modules` 等相似名称返回 `false`。
12157
+ * @param relativePath - 构建产物路径,先按 {@link normalizeRelativePath} 整理分隔符再分段比较。
12158
+ * @returns 任意目录段大小写不敏感地等于 `node_modules` 时返回 `true`;空路径和相似名称返回 `false`。
12142
12159
  *
12143
12160
  * @remarks
12144
- * 比较使用每个非空 segment 的 `toLowerCase() === 'node_modules'`,因此根目录或任意深度的
12145
- * `node_modules`、`NODE_MODULES`、`Node_Modules` 都会命中。前导和重复 slash 会先被
12146
- * `normalizeRelativePath` 整理;尾随 slash 产生的空段会被忽略。
12147
- *
12148
- * 这是同步、无副作用的**单一 predicate**,只回答“规范化后的文本是否包含该完整段”。它
12149
- * 不 trim segment、不进行 URL/percent decode、Unicode normalize 或文件系统解析。例如
12150
- * `node_modules%2Fpkg/index.js`、`%6eode_modules/pkg.js` 和 `node_modules.txt` 都不会命中;调用方
12151
- * 不应传 URL,也不能把未命中解释为路径安全。
12152
- *
12153
- * 本函数不会拒绝或消解 `.` / `..`、绝对路径、Windows drive prefix、控制字符、空路径、
12154
- * symlink、重复路径或超长路径。`assets/../node_modules/pkg.js` 仍会命中,因为点段保持原样;
12155
- * 不含 `node_modules` 的危险路径则仍可能返回 `false`。签名或上传前必须继续执行对应的上传
12156
- * 路径规范、唯一性和长度校验。
12157
- *
12158
- * SDK build/deploy 会在文件扫描后用本函数拒绝整个 `node_modules` 树,再用
12159
- * {@link shouldUploadDistFile} 排除根 `manifest.json`、`.DS_Store` 和 source map。build 随后
12160
- * 校验大小预算;deploy 还会执行上传路径安全、唯一性、长度与签名校验。这些步骤互不替代:`shouldUploadDistFile()` 本身不检查
12161
- * `node_modules`,本函数也不决定普通文件是否应该上传。
12161
+ * 只检查目录段文本,不 trim、URL decode 或解析文件系统。未命中不表示路径安全;点段仍保留。
12162
+ * SDK 用它排除 `node_modules` 树,再用 {@link shouldUploadDistFile} 过滤其他不上传的文件;
12163
+ * 路径安全、唯一性、长度与上传大小需分别校验。
12162
12164
  *
12163
12165
  * @example
12164
12166
  * ```ts
12165
12167
  * import { relativePathContainsNodeModulesSegment } from '@heybox/hb-sdk/miniapp-publish';
12166
12168
  *
12167
12169
  * relativePathContainsNodeModulesSegment('dist\\NODE_MODULES/pkg/index.js'); // true
12168
- * relativePathContainsNodeModulesSegment('dist//vendor///node_modules/'); // true
12169
12170
  * relativePathContainsNodeModulesSegment('dist/node_modules.txt'); // false
12170
12171
  * relativePathContainsNodeModulesSegment('dist/node_modules%2Fpkg.js'); // false,不做 URL decode
12171
- * relativePathContainsNodeModulesSegment('assets/../secret.js'); // false,但这不代表路径安全
12172
12172
  * ```
12173
12173
  *
12174
12174
  * @see [normalizeRelativePath](/reference/symbols/miniapp-publish/functions/normalizeRelativePath)
@@ -12469,8 +12469,9 @@ function createMiniappArtifactFileHeader(file) {
12469
12469
  * 校验服务端 session 文件映射,并计算仍需上传的文件。
12470
12470
  *
12471
12471
  * @param localFiles - 本地构建产物 receipt;除 `relativePath` 和 `size` 外,每项必须包含 64 位小写十六进制 `sha256`。
12472
- * @param serverFiles - 服务端签发的文件映射;数量、路径、大小和 SHA-256 必须与本地产物一一一致。
12472
+ * @param serverFiles - 服务端签发的会话文件映射;与复用的 Companion 合起来必须覆盖本地产物。
12473
12473
  * @param uploadedKeys - 服务端已确认上传成功的 key;每个值都必须属于当前会话,重复值会被去重。
12474
+ * @param reusedCompanionFiles - 已验证 Blob 引用;仅允许当前产物中的 Companion 文件。
12474
12475
  * @returns
12475
12476
  * 包含三个字段的对象:`files` 是按路径 UTF-8 字节序排列的全部 `{ file, key }` 映射,
12476
12477
  * `missingFiles` 是其中尚未确认上传的映射,`uploadedKeys` 是去重后的已上传 key。
@@ -12486,15 +12487,38 @@ function createMiniappArtifactFileHeader(file) {
12486
12487
  * sha256: 'a'.repeat(64),
12487
12488
  * }],
12488
12489
  * [],
12490
+ * [],
12489
12491
  * );
12490
12492
  *
12491
12493
  * console.log(resolved.missingFiles.length); // 1
12492
12494
  * ```
12493
12495
  */
12494
- function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys) {
12496
+ function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys, reusedCompanionFiles) {
12495
12497
  const preparedFiles = prepareMiniappArtifactFiles(localFiles);
12496
- if (serverFiles.length !== preparedFiles.length) {
12497
- throw new Error(`上传会话文件数量异常:期望 ${preparedFiles.length},实际 ${serverFiles.length}`);
12498
+ if (serverFiles.length + reusedCompanionFiles.length !== preparedFiles.length) {
12499
+ throw new Error(`上传会话文件数量异常:期望 ${preparedFiles.length},实际 ${serverFiles.length + reusedCompanionFiles.length}`);
12500
+ }
12501
+ const localByPath = new Map(preparedFiles.map((file) => [file.relativePath, file]));
12502
+ const reused = new Map();
12503
+ for (const reference of reusedCompanionFiles) {
12504
+ const relativePath = normalizeMiniappArtifactRelativePath(reference.relative_path);
12505
+ const local = localByPath.get(relativePath);
12506
+ if (!relativePath.startsWith('companions/') || !local) {
12507
+ throw new Error(`上传会话返回了不属于当前 Companion 产物的 Blob 引用:${relativePath}`);
12508
+ }
12509
+ if (reused.has(relativePath)) {
12510
+ throw new Error(`上传会话返回重复 Blob 引用:${relativePath}`);
12511
+ }
12512
+ if (!Number.isSafeInteger(reference.size) || reference.size < 0 || reference.size !== local.size) {
12513
+ throw new Error(`上传会话 Blob 文件大小与本地构建不一致:${relativePath}`);
12514
+ }
12515
+ if (!('sha256' in local) || !/^[a-f0-9]{64}$/.test(reference.sha256) || reference.sha256 !== local.sha256) {
12516
+ throw new Error(`上传会话 Blob 文件 SHA-256 与本地构建不一致:${relativePath}`);
12517
+ }
12518
+ if (typeof reference.blob_id !== 'string' || !reference.blob_id.trim()) {
12519
+ throw new Error(`上传会话 Blob 引用 ID 无效:${relativePath}`);
12520
+ }
12521
+ reused.set(relativePath, reference);
12498
12522
  }
12499
12523
  const mappings = new Map();
12500
12524
  const serverSizes = new Map();
@@ -12505,6 +12529,9 @@ function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys)
12505
12529
  if (mappings.has(relativePath)) {
12506
12530
  throw new Error(`上传会话返回重复文件路径:${relativePath}`);
12507
12531
  }
12532
+ if (reused.has(relativePath)) {
12533
+ throw new Error(`上传会话同时返回文件 key 和 Blob 引用:${relativePath}`);
12534
+ }
12508
12535
  const key = String(serverFile.key || '');
12509
12536
  if (!key) {
12510
12537
  throw new Error(`上传会话未返回文件 key:${relativePath}`);
@@ -12525,7 +12552,8 @@ function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys)
12525
12552
  serverHashes.set(relativePath, sha256);
12526
12553
  knownKeys.add(key);
12527
12554
  }
12528
- const files = preparedFiles.map((file) => {
12555
+ const sessionFiles = preparedFiles.filter((file) => !reused.has(file.relativePath));
12556
+ const files = sessionFiles.map((file) => {
12529
12557
  const key = mappings.get(file.relativePath);
12530
12558
  if (!key) {
12531
12559
  throw new Error(`上传会话缺少文件映射:${file.relativePath}`);
@@ -12550,6 +12578,7 @@ function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys)
12550
12578
  files,
12551
12579
  missingFiles: files.filter((file) => !completed.has(file.key)),
12552
12580
  uploadedKeys: [...completed],
12581
+ reusedCompanionFiles: [...reused.values()],
12553
12582
  };
12554
12583
  }
12555
12584
  /**
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-d1cUUoR9.cjs');
3
+ var index = require('./index-CYxcLlON.cjs');
4
4
  var node_crypto = require('node:crypto');
5
5
  var path = require('node:path');
6
6
  var require$$0$2 = require('fs');
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var version = require('./version-hsUgRL0I.cjs');
3
+ var version = require('./version-DA6v9R62.cjs');
4
4
  var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
6
  var childProcess = require('node:child_process');
7
- var index = require('./index-d1cUUoR9.cjs');
7
+ var index = require('./index-CYxcLlON.cjs');
8
8
  require('node:module');
9
9
  require('node:fs');
10
10
  require('path');
@@ -3,6 +3,6 @@
3
3
  /** 构建时替换为当前发布包的实际版本。 */
4
4
  const HB_SDK_VERSION = typeof undefined === 'string'
5
5
  ? undefined
6
- : '0.9.0-alpha.1';
6
+ : '0.9.0-alpha.10';
7
7
 
8
8
  exports.HB_SDK_VERSION = HB_SDK_VERSION;
package/dist/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./cli-chunks/index-d1cUUoR9.cjs');
3
+ var index = require('./cli-chunks/index-CYxcLlON.cjs');
4
4
  require('node:module');
5
5
  require('node:fs');
6
6
  require('node:fs/promises');