@heybox/hb-sdk 0.4.5 → 0.4.7

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.
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var promises = require('node:readline/promises');
4
+ var session = require('./session-BSi5YfqO.cjs');
4
5
  var childProcess = require('node:child_process');
5
6
  var fs = require('node:fs');
6
7
  var fs$1 = require('node:fs/promises');
7
8
  var path = require('node:path');
8
- var session = require('./session-BAgaqpNL.cjs');
9
- var index = require('./index-BYMTp2I6.cjs');
10
- var node_crypto = require('node:crypto');
9
+ var index = require('./index-13_8m0Pw.cjs');
11
10
 
12
11
  var re = {exports: {}};
13
12
 
@@ -2859,7 +2858,11 @@ function formatReason(error) {
2859
2858
 
2860
2859
  const MINIAPP_UPLOAD_SCOPE = 'activity';
2861
2860
  const ACTIVITY_UPLOAD_KEY_MAX_LENGTH = 64;
2861
+ const MINIAPP_UPLOAD_BATCH_SIZE = 50;
2862
+ const MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES = 100 * 1024 * 1024;
2862
2863
  const USER_MINIPROGRAM_ACCESS_STATUS_API_PATH = '/mall/developer/user_miniprogram/access_status';
2864
+ const DEVELOPER_ENTITY_LIST_API_PATH = '/mall/developer/entity/list';
2865
+ const DEVELOPER_ENTITY_SWITCH_API_PATH = '/mall/developer/entity/switch';
2863
2866
  const LIST_USER_MINIPROGRAM_API_PATH = '/mall/developer/user_miniprogram/list';
2864
2867
  const CREATE_USER_MINIPROGRAM_API_PATH = '/mall/developer/user_miniprogram/create';
2865
2868
  const UPDATE_USER_MINIPROGRAM_API_PATH = '/mall/developer/user_miniprogram/update';
@@ -2915,6 +2918,17 @@ function validateUploadPaths(items, options) {
2915
2918
  }
2916
2919
  return undefined;
2917
2920
  }
2921
+ function validateUploadTotalSize(items, options = {}) {
2922
+ const limit = options.limitBytes ?? MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES;
2923
+ const total = items.reduce((sum, item) => {
2924
+ const size = Number(item.size);
2925
+ return sum + (Number.isFinite(size) && size > 0 ? size : 0);
2926
+ }, 0);
2927
+ if (total > limit) {
2928
+ return '构建产物总大小超过 100MB,请删除未引用资源或压缩图片后重新 deploy';
2929
+ }
2930
+ return undefined;
2931
+ }
2918
2932
  function shouldUploadDistFile(relativePath) {
2919
2933
  const normalized = normalizeRelativePath(relativePath);
2920
2934
  if (!normalized) {
@@ -2932,281 +2946,6 @@ function shouldUploadDistFile(relativePath) {
2932
2946
  return true;
2933
2947
  }
2934
2948
 
2935
- const globalHeyboxCliRequestConfig = {};
2936
- function readHeyboxCliRequestConfig() {
2937
- return normalizeHeyboxRylaiServiceTagConfig(globalHeyboxCliRequestConfig);
2938
- }
2939
- function resolveHeyboxRylaiServiceTag(pathWithQuery, config = readHeyboxCliRequestConfig()) {
2940
- const normalized = normalizeHeyboxRylaiServiceTagConfig(config);
2941
- const pathname = pathWithQuery ? getPathname(pathWithQuery) : '';
2942
- const specialTag = normalized.special_tag;
2943
- if (pathname && specialTag?.path_prefix_list.some((prefix) => pathname.startsWith(prefix))) {
2944
- return specialTag.tag_name;
2945
- }
2946
- return normalized.default_tag;
2947
- }
2948
- function normalizeHeyboxRylaiServiceTagConfig(config) {
2949
- const defaultTag = normalizeHeyboxRylaiServiceTag(config.default_tag);
2950
- const specialTagName = normalizeHeyboxRylaiServiceTag(config.special_tag?.tag_name);
2951
- const pathPrefixList = config.special_tag?.path_prefix_list?.filter(isValidPathPrefix) || [];
2952
- return {
2953
- ...(defaultTag ? { default_tag: defaultTag } : {}),
2954
- ...(specialTagName && pathPrefixList.length
2955
- ? {
2956
- special_tag: {
2957
- tag_name: specialTagName,
2958
- path_prefix_list: pathPrefixList,
2959
- },
2960
- }
2961
- : {}),
2962
- };
2963
- }
2964
- function normalizeHeyboxRylaiServiceTag(value) {
2965
- const tag = String(value ?? '').trim();
2966
- if (!tag) {
2967
- return undefined;
2968
- }
2969
- if (/[\r\n]/.test(tag)) {
2970
- throw new Error('x-rylai-service-tag 不允许包含换行符');
2971
- }
2972
- return tag;
2973
- }
2974
- function isValidPathPrefix(value) {
2975
- return typeof value === 'string' && value.startsWith('/') && !/[\r\n]/.test(value);
2976
- }
2977
- function getPathname(pathWithQuery) {
2978
- try {
2979
- return new URL(pathWithQuery, 'https://api.xiaoheihe.cn').pathname;
2980
- }
2981
- catch {
2982
- return '';
2983
- }
2984
- }
2985
-
2986
- const SIGN_VERSION = '999.0.4';
2987
- const SIGN_CHARSET = 'AB45STUVWZEFGJ6CH01D237IXYPQRKLMN89';
2988
- function createHeyboxOpenPlatformSignParams(pathname, options = {}) {
2989
- const now = options.now ?? new Date();
2990
- const time = Math.trunc(now.getTime() / 1000);
2991
- const nonce = options.nonce ?? md5(`${time}${Date.now()}${node_crypto.randomBytes(16).toString('hex')}`).toUpperCase();
2992
- return {
2993
- version: SIGN_VERSION,
2994
- hkey: generateSignature(pathname, time + 1, nonce),
2995
- _time: time,
2996
- nonce,
2997
- };
2998
- }
2999
- function generateSignature(path, time, nonce) {
3000
- const normalizedPath = `/${path
3001
- .split('/')
3002
- .filter(Boolean)
3003
- .join('/')}/`;
3004
- const transformedTime = transformWithOffset(String(time), SIGN_CHARSET, -2);
3005
- const transformedPath = transform(normalizedPath, SIGN_CHARSET);
3006
- const transformedNonce = transform(nonce, SIGN_CHARSET);
3007
- const combined = interleave([transformedTime, transformedPath, transformedNonce]).slice(0, 20);
3008
- const hash = md5(combined);
3009
- let sign = `${mixColumns(hash.slice(-6).split('').map((char) => char.charCodeAt(0))).reduce((sum, value) => sum + value, 0) % 100}`;
3010
- sign = sign.length < 2 ? `0${sign}` : sign;
3011
- return `${transformWithOffset(hash.substring(0, 5), SIGN_CHARSET, -4)}${sign}`;
3012
- }
3013
- function transformWithOffset(str, charset, offset) {
3014
- return transform(str, charset.slice(0, offset));
3015
- }
3016
- function transform(str, charset) {
3017
- let output = '';
3018
- for (let index = 0; index < str.length; index += 1) {
3019
- output += charset[str.charCodeAt(index) % charset.length];
3020
- }
3021
- return output;
3022
- }
3023
- function interleave(strings) {
3024
- let output = '';
3025
- const maxLength = Math.max(...strings.map((str) => str.length));
3026
- for (let index = 0; index < maxLength; index += 1) {
3027
- for (const str of strings) {
3028
- if (index < str.length) {
3029
- output += str[index];
3030
- }
3031
- }
3032
- }
3033
- return output;
3034
- }
3035
- function mixColumns(column) {
3036
- const output = [...column];
3037
- output[0] = multiplyE(column[0]) ^ multiply4(column[1]) ^ multiply3(column[2]) ^ multiply2(column[3]);
3038
- output[1] = multiply2(column[0]) ^ multiplyE(column[1]) ^ multiply4(column[2]) ^ multiply3(column[3]);
3039
- output[2] = multiply3(column[0]) ^ multiply2(column[1]) ^ multiplyE(column[2]) ^ multiply4(column[3]);
3040
- output[3] = multiply4(column[0]) ^ multiply3(column[1]) ^ multiply2(column[2]) ^ multiplyE(column[3]);
3041
- return output;
3042
- }
3043
- function multiply1(value) {
3044
- if (value & 0x80) {
3045
- return ((value << 1) ^ 0x1b) & 0xff;
3046
- }
3047
- return value << 1;
3048
- }
3049
- function multiply2(value) {
3050
- return multiply1(value) ^ value;
3051
- }
3052
- function multiply3(value) {
3053
- return multiply2(multiply1(value));
3054
- }
3055
- function multiply4(value) {
3056
- return multiply3(multiply2(multiply1(value)));
3057
- }
3058
- function multiplyE(value) {
3059
- return multiply4(value) ^ multiply3(value) ^ multiply2(value);
3060
- }
3061
- function md5(input) {
3062
- return node_crypto.createHash('md5').update(input).digest('hex');
3063
- }
3064
-
3065
- const DEFAULT_PLATFORM_PARAMS = {
3066
- os_type: 'web',
3067
- app: 'heybox',
3068
- x_client_type: 'web',
3069
- x_os_type: 'Mac',
3070
- x_app: 'heybox',
3071
- x_client_version: '999.999.999',
3072
- };
3073
- const HEYBOX_WEB_REFERER = 'https://www.xiaoheihe.cn/';
3074
- function resolveHeyboxId(session) {
3075
- if (session.heyboxId.trim()) {
3076
- return session.heyboxId.trim();
3077
- }
3078
- return session.cookieHeader
3079
- .split(';')
3080
- .map((part) => part.trim())
3081
- .find((part) => part.startsWith('heybox_id='))
3082
- ?.slice('heybox_id='.length);
3083
- }
3084
- function createHeyboxAuthHeaders(session, options = {}) {
3085
- const serviceTag = resolveHeyboxRylaiServiceTag(options.pathWithQuery);
3086
- return {
3087
- Cookie: session.cookieHeader,
3088
- Referer: HEYBOX_WEB_REFERER,
3089
- ...(options.contentType ? { 'Content-Type': options.contentType } : {}),
3090
- ...(serviceTag ? { 'x-rylai-service-tag': serviceTag } : {}),
3091
- };
3092
- }
3093
- function createHeyboxRequestContext(session$1, options = {}) {
3094
- const heyboxId = resolveHeyboxId(session$1);
3095
- const serviceTag = resolveHeyboxRylaiServiceTag(options.pathWithQuery);
3096
- return {
3097
- baseUrl: session.HEYBOX_API_BASE_URL,
3098
- headers: createHeyboxAuthHeaders(session$1, options),
3099
- platformParams: {
3100
- ...DEFAULT_PLATFORM_PARAMS,
3101
- ...options.platformParams,
3102
- ...(heyboxId ? { heybox_id: heyboxId } : {}),
3103
- ...(serviceTag ? { special_tag: serviceTag } : {}),
3104
- },
3105
- };
3106
- }
3107
- function createHeyboxOpenPlatformRequestContext(session, pathWithQuery, options = {}) {
3108
- return createHeyboxRequestContext(session, {
3109
- ...options,
3110
- pathWithQuery,
3111
- platformParams: {
3112
- x_app: 'heybox_website',
3113
- ...createHeyboxOpenPlatformSignParams(getApiPathname(pathWithQuery)),
3114
- ...options.platformParams,
3115
- },
3116
- });
3117
- }
3118
- function createHeyboxApiUrl(baseUrl, pathWithQuery, params) {
3119
- const url = new URL(pathWithQuery, baseUrl);
3120
- for (const [key, value] of Object.entries(params)) {
3121
- if (value !== '') {
3122
- url.searchParams.set(key, String(value));
3123
- }
3124
- }
3125
- return url.toString();
3126
- }
3127
- function getApiPathname(pathWithQuery) {
3128
- return new URL(pathWithQuery, session.HEYBOX_API_BASE_URL).pathname;
3129
- }
3130
- const ENVELOPE_BODY_PREVIEW_LENGTH = 1000;
3131
- async function readHeyboxApiEnvelope(response, options) {
3132
- const rawBody = await response.text();
3133
- let envelope = null;
3134
- let parseError = null;
3135
- if (rawBody) {
3136
- try {
3137
- envelope = JSON.parse(rawBody);
3138
- }
3139
- catch (error) {
3140
- parseError = error;
3141
- }
3142
- }
3143
- const envelopeOk = envelope?.status === 'ok' && (!options.requireResult || envelope?.result !== undefined);
3144
- if (response.ok && envelopeOk) {
3145
- return envelope.result;
3146
- }
3147
- const { message, verboseMessage } = formatHeyboxEnvelopeError({
3148
- response,
3149
- envelope,
3150
- parseError,
3151
- rawBody,
3152
- pathWithQuery: options.pathWithQuery,
3153
- });
3154
- throw new index.CliError(message, verboseMessage);
3155
- }
3156
- function formatHeyboxEnvelopeError(input) {
3157
- const parts = [`Heybox API ${input.pathWithQuery} failed`, `HTTP ${input.response.status}`];
3158
- if (input.envelope) {
3159
- const msg = typeof input.envelope.msg === 'string' ? input.envelope.msg.trim() : '';
3160
- const sbeUserMessage = readSbeUserMessage(input.envelope.result);
3161
- parts.push(`msg=${msg || '(empty)'}`);
3162
- if (typeof input.envelope.status === 'string') {
3163
- parts.push(`envelope.status=${input.envelope.status}`);
3164
- }
3165
- const { status: _s, msg: _m, result: _r, ...extras } = input.envelope;
3166
- if (Object.keys(extras).length > 0) {
3167
- parts.push(`extras=${truncate(safeJsonStringify(extras), ENVELOPE_BODY_PREVIEW_LENGTH)}`);
3168
- }
3169
- if (input.envelope.result !== undefined) {
3170
- parts.push(`result=${truncate(safeJsonStringify(input.envelope.result), ENVELOPE_BODY_PREVIEW_LENGTH)}`);
3171
- }
3172
- return {
3173
- message: sbeUserMessage || msg || `Heybox API ${input.pathWithQuery} 请求失败`,
3174
- verboseMessage: parts.join(' '),
3175
- };
3176
- }
3177
- if (input.parseError) {
3178
- const parseMsg = input.parseError instanceof Error ? input.parseError.message : String(input.parseError);
3179
- parts.push(`parseError=${parseMsg}`);
3180
- }
3181
- parts.push(input.rawBody ? `body=${truncate(input.rawBody, ENVELOPE_BODY_PREVIEW_LENGTH)}` : 'body=(empty)');
3182
- return {
3183
- message: `Heybox API ${input.pathWithQuery} 请求失败`,
3184
- verboseMessage: parts.join(' '),
3185
- };
3186
- }
3187
- function readSbeUserMessage(result) {
3188
- if (!result || typeof result !== 'object') {
3189
- return '';
3190
- }
3191
- const sbe = result.sbe;
3192
- if (!sbe || typeof sbe !== 'object') {
3193
- return '';
3194
- }
3195
- const userMessage = sbe.user_message;
3196
- return typeof userMessage === 'string' ? userMessage.trim() : '';
3197
- }
3198
- function truncate(value, max) {
3199
- return value.length > max ? `${value.slice(0, max)}...(+${value.length - max} chars)` : value;
3200
- }
3201
- function safeJsonStringify(value) {
3202
- try {
3203
- return JSON.stringify(value);
3204
- }
3205
- catch {
3206
- return String(value);
3207
- }
3208
- }
3209
-
3210
2949
  async function getCDNUploadInfo(options, runtime = {}) {
3211
2950
  const body = new URLSearchParams();
3212
2951
  body.set('file_infos', JSON.stringify(options.fileInfos));
@@ -3228,17 +2967,17 @@ async function postCDNUploadCallback(options, runtime = {}) {
3228
2967
  const query = options.isFinished ? '?is_finished=true' : '';
3229
2968
  return postHeyboxApi(`/bbs/app/api/qcloud/cos/upload/callback/v2${query}`, body, options.session, runtime);
3230
2969
  }
3231
- async function postHeyboxApi(pathWithQuery, body, session, runtime) {
2970
+ async function postHeyboxApi(pathWithQuery, body, session$1, runtime) {
3232
2971
  const fetchImpl = runtime.fetchImpl ?? fetch;
3233
- const context = createHeyboxOpenPlatformRequestContext(session, pathWithQuery, {
2972
+ const context = session.createHeyboxOpenPlatformRequestContext(session$1, pathWithQuery, {
3234
2973
  contentType: 'application/x-www-form-urlencoded',
3235
2974
  });
3236
- const response = await fetchImpl(createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, pathWithQuery, context.platformParams), {
2975
+ const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, pathWithQuery, context.platformParams), {
3237
2976
  method: 'POST',
3238
2977
  headers: context.headers,
3239
2978
  body: body.toString(),
3240
2979
  });
3241
- const result = await readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: true });
2980
+ const result = await session.readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: true });
3242
2981
  return result;
3243
2982
  }
3244
2983
 
@@ -3252,7 +2991,74 @@ async function runUpload(options, runtime = {}) {
3252
2991
  const postCDNUploadCallback$1 = runtime.postCDNUploadCallback ?? postCDNUploadCallback;
3253
2992
  const createReadStream = runtime.createReadStream ?? fs.createReadStream;
3254
2993
  const orderedFiles = [...options.files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
3255
- const fileInfos = orderedFiles.map((file) => ({
2994
+ const batches = chunkUploadFiles(orderedFiles, MINIAPP_UPLOAD_BATCH_SIZE);
2995
+ await logger.task(`正在上传 ${orderedFiles.length} 个文件`, async (taskContext) => {
2996
+ logger.debug(`上传并发数: ${concurrency}`);
2997
+ logger.debug(`上传批次数: ${batches.length}`);
2998
+ let completed = 0;
2999
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
3000
+ const batch = batches[batchIndex];
3001
+ logger.debug(`正在处理上传批次 ${batchIndex + 1}/${batches.length},文件数: ${batch.length}`);
3002
+ logger.debug('正在获取 CDN 上传信息');
3003
+ const fileInfos = createUploadFileInfos(batch, options);
3004
+ const uploadInfo = await getCDNUploadInfo$1({ session: options.session, scope: MINIAPP_UPLOAD_SCOPE, fileInfos, needCache: false }, { baseUrl: runtime.baseUrl, fetchImpl });
3005
+ logger.debug(`CDN bucket: ${uploadInfo.bucket}`);
3006
+ logger.debug(`CDN region: ${uploadInfo.region}`);
3007
+ validateUploadInfoKeys(batch, uploadInfo.keys, options);
3008
+ const indexedFiles = batch.map((file, index) => ({ file, key: uploadInfo.keys[index] }));
3009
+ const batchKeys = indexedFiles.map(({ key }) => key);
3010
+ logger.debug('正在获取 CDN 上传凭证');
3011
+ const uploadToken = await getCDNUploadToken$1({
3012
+ session: options.session,
3013
+ bucket: uploadInfo.bucket,
3014
+ keys: batchKeys,
3015
+ mimetypes: indexedFiles.map(({ file }) => file.mimeType),
3016
+ isMultipartUpload: 0,
3017
+ }, { baseUrl: runtime.baseUrl, fetchImpl });
3018
+ const cos = runtime.createCosClient ? runtime.createCosClient({ session: options.session, uploadToken }) : await createDefaultCosClient(uploadToken);
3019
+ const queue = indexedFiles.slice();
3020
+ const workers = Array.from({ length: Math.min(concurrency, indexedFiles.length) }, async () => {
3021
+ while (queue.length > 0) {
3022
+ const uploadTask = queue.shift();
3023
+ if (!uploadTask) {
3024
+ return;
3025
+ }
3026
+ try {
3027
+ await cos.putObject({
3028
+ Bucket: uploadInfo.bucket,
3029
+ Region: uploadInfo.region,
3030
+ Key: uploadTask.key,
3031
+ Body: createReadStream(uploadTask.file.absolutePath),
3032
+ ContentLength: uploadTask.file.size,
3033
+ ContentType: uploadTask.file.mimeType,
3034
+ });
3035
+ completed += 1;
3036
+ taskContext.update(`正在上传 ${completed}/${orderedFiles.length} 个文件`);
3037
+ logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] ok ${uploadTask.file.relativePath} (${formatSize(uploadTask.file.size)})`);
3038
+ }
3039
+ catch (error) {
3040
+ completed += 1;
3041
+ const message = readErrorMessage(error);
3042
+ logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] error ${uploadTask.file.relativePath} -> ${message}`);
3043
+ throw new index.CliError(`上传文件失败:${uploadTask.file.relativePath} -> ${message}`, readVerboseUploadError(uploadTask.file.relativePath, error));
3044
+ }
3045
+ }
3046
+ });
3047
+ await Promise.all(workers);
3048
+ logger.debug('正在确认 CDN 上传结果');
3049
+ await postCDNUploadCallback$1({ session: options.session, keys: batchKeys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
3050
+ }
3051
+ }, { successText: `已上传 ${orderedFiles.length} 个文件` });
3052
+ }
3053
+ function chunkUploadFiles(files, batchSize) {
3054
+ const batches = [];
3055
+ for (let index = 0; index < files.length; index += batchSize) {
3056
+ batches.push(files.slice(index, index + batchSize));
3057
+ }
3058
+ return batches;
3059
+ }
3060
+ function createUploadFileInfos(files, options) {
3061
+ return files.map((file) => ({
3256
3062
  name: file.relativePath.split('/').pop() ?? file.relativePath,
3257
3063
  mimetype: file.mimeType,
3258
3064
  fsize: file.size,
@@ -3262,56 +3068,22 @@ async function runUpload(options, runtime = {}) {
3262
3068
  relativePath: file.relativePath,
3263
3069
  }),
3264
3070
  }));
3265
- await logger.task(`正在上传 ${orderedFiles.length} 个文件`, async (taskContext) => {
3266
- logger.debug(`上传并发数: ${concurrency}`);
3267
- logger.debug('正在获取 CDN 上传信息');
3268
- const uploadInfo = await getCDNUploadInfo$1({ session: options.session, scope: MINIAPP_UPLOAD_SCOPE, fileInfos, needCache: false }, { baseUrl: runtime.baseUrl, fetchImpl });
3269
- logger.debug(`CDN bucket: ${uploadInfo.bucket}`);
3270
- logger.debug(`CDN region: ${uploadInfo.region}`);
3271
- const indexedFiles = orderedFiles.map((file, index) => ({ file, key: uploadInfo.keys[index] }));
3272
- logger.debug('正在获取 CDN 上传凭证');
3273
- const cos = runtime.createCosClient
3274
- ? runtime.createCosClient({ session: options.session })
3275
- : await createDefaultCosClient(await getCDNUploadToken$1({
3276
- session: options.session,
3277
- bucket: uploadInfo.bucket,
3278
- keys: indexedFiles.map(({ key }) => key),
3279
- mimetypes: indexedFiles.map(({ file }) => file.mimeType),
3280
- isMultipartUpload: 0,
3281
- }, { baseUrl: runtime.baseUrl, fetchImpl }));
3282
- const queue = indexedFiles.slice();
3283
- let completed = 0;
3284
- const workers = Array.from({ length: Math.min(concurrency, indexedFiles.length) }, async () => {
3285
- while (queue.length > 0) {
3286
- const uploadTask = queue.shift();
3287
- if (!uploadTask) {
3288
- return;
3289
- }
3290
- try {
3291
- await cos.putObject({
3292
- Bucket: uploadInfo.bucket,
3293
- Region: uploadInfo.region,
3294
- Key: uploadTask.key,
3295
- Body: createReadStream(uploadTask.file.absolutePath),
3296
- ContentLength: uploadTask.file.size,
3297
- ContentType: uploadTask.file.mimeType,
3298
- });
3299
- completed += 1;
3300
- taskContext.update(`正在上传 ${completed}/${indexedFiles.length} 个文件`);
3301
- logger.debug(`[${String(completed).padStart(2)}/${indexedFiles.length}] ok ${uploadTask.file.relativePath} (${formatSize(uploadTask.file.size)})`);
3302
- }
3303
- catch (error) {
3304
- completed += 1;
3305
- const message = readErrorMessage(error);
3306
- logger.debug(`[${String(completed).padStart(2)}/${indexedFiles.length}] error ${uploadTask.file.relativePath} -> ${message}`);
3307
- throw new index.CliError(`上传文件失败:${uploadTask.file.relativePath} -> ${message}`, readVerboseUploadError(uploadTask.file.relativePath, error));
3308
- }
3309
- }
3071
+ }
3072
+ function validateUploadInfoKeys(files, keys, options) {
3073
+ if (keys.length !== files.length) {
3074
+ throw new index.CliError(`CDN 上传接口返回 key 数量异常:期望 ${files.length},实际 ${keys.length}`);
3075
+ }
3076
+ for (let index$1 = 0; index$1 < files.length; index$1 += 1) {
3077
+ const expectedKey = getMiniappUploadKey({
3078
+ miniProgramId: options.miniProgramId,
3079
+ version: options.version,
3080
+ relativePath: files[index$1].relativePath,
3310
3081
  });
3311
- await Promise.all(workers);
3312
- logger.debug('正在确认 CDN 上传结果');
3313
- await postCDNUploadCallback$1({ session: options.session, keys: uploadInfo.keys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
3314
- }, { successText: `已上传 ${orderedFiles.length} 个文件` });
3082
+ const actualKey = keys[index$1];
3083
+ if (actualKey !== expectedKey) {
3084
+ throw new index.CliError(`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, [`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, `expected: ${expectedKey}`, `actual: ${actualKey}`].join('\n'));
3085
+ }
3086
+ }
3315
3087
  }
3316
3088
  async function createDefaultCosClient(uploadToken) {
3317
3089
  const COS = await loadCosConstructor();
@@ -3341,7 +3113,7 @@ async function createDefaultCosClient(uploadToken) {
3341
3113
  };
3342
3114
  }
3343
3115
  async function loadCosConstructor() {
3344
- const cosModule = await Promise.resolve().then(function () { return require('./index-DRsyeAcg.cjs'); }).then(function (n) { return n.index; });
3116
+ const cosModule = await Promise.resolve().then(function () { return require('./index-2nlBRew_.cjs'); }).then(function (n) { return n.index; });
3345
3117
  return cosModule.default;
3346
3118
  }
3347
3119
  function formatSize(bytes) {
@@ -3400,7 +3172,7 @@ async function submitUserMiniprogramAudit$1(options, runtime = {}) {
3400
3172
  }
3401
3173
  async function postUserMiniprogramVersionForm(path, options, fields, runtime) {
3402
3174
  const fetchImpl = runtime.fetchImpl ?? fetch;
3403
- const context = createHeyboxOpenPlatformRequestContext(options.session, path, {
3175
+ const context = session.createHeyboxOpenPlatformRequestContext(options.session, path, {
3404
3176
  contentType: 'application/x-www-form-urlencoded',
3405
3177
  });
3406
3178
  const body = new URLSearchParams();
@@ -3408,12 +3180,12 @@ async function postUserMiniprogramVersionForm(path, options, fields, runtime) {
3408
3180
  for (const [key, value] of Object.entries(fields)) {
3409
3181
  body.set(key, value);
3410
3182
  }
3411
- const response = await fetchImpl(createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
3183
+ const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
3412
3184
  method: 'POST',
3413
3185
  headers: context.headers,
3414
3186
  body: body.toString(),
3415
3187
  });
3416
- const result = await readHeyboxApiEnvelope(response, {
3188
+ const result = await session.readHeyboxApiEnvelope(response, {
3417
3189
  pathWithQuery: path,
3418
3190
  });
3419
3191
  return result ?? {};
@@ -3507,6 +3279,10 @@ async function runDeployCommand(options, runtime = {}) {
3507
3279
  if (pathError) {
3508
3280
  throw new Error(pathError);
3509
3281
  }
3282
+ const sizeError = validateUploadTotalSize(files);
3283
+ if (sizeError) {
3284
+ throw new Error(sizeError);
3285
+ }
3510
3286
  logger.debug(`待上传文件数: ${files.length}`);
3511
3287
  return files;
3512
3288
  }, { successText: 'dist 上传文件校验完成' });
@@ -3523,12 +3299,12 @@ async function runDeployCommand(options, runtime = {}) {
3523
3299
  catch (error) {
3524
3300
  throw translateHeyboxDeployError(error, { projectRoot, stage: 'submitAudit', version });
3525
3301
  }
3526
- printDeploySuccess({ autoPublish, logger, miniProgramId, mode: options.successOutputMode ?? 'legacy', previewUrl: submitAuditResult.preview_url, version });
3302
+ printDeploySuccess({ autoPublish, logger, miniProgramId, mode: options.successOutputMode ?? 'legacy', protocolString: submitAuditResult.protocol_string, version });
3527
3303
  return {
3528
3304
  autoPublish,
3529
3305
  changed: true,
3530
3306
  miniProgramId,
3531
- ...(submitAuditResult.preview_url ? { previewUrl: submitAuditResult.preview_url } : {}),
3307
+ ...(submitAuditResult.protocol_string ? { protocolString: submitAuditResult.protocol_string } : {}),
3532
3308
  version,
3533
3309
  };
3534
3310
  }
@@ -3536,8 +3312,8 @@ function printDeploySuccess(options) {
3536
3312
  options.logger.success(`提交审核成功:${options.miniProgramId} ${options.version}`);
3537
3313
  if (options.mode === 'remote') {
3538
3314
  options.logger.info(`发布策略:${options.autoPublish ? '审核通过后自动发布' : '审核通过后需手动发布'}`);
3539
- if (options.previewUrl) {
3540
- options.logger.info(`Preview URL: ${options.previewUrl}`);
3315
+ if (options.protocolString) {
3316
+ options.logger.info(`Protocol: ${options.protocolString}`);
3541
3317
  options.logger.info('允许他人预览:hb-sdk remote allowlist add <heybox_id>');
3542
3318
  }
3543
3319
  options.logger.info(options.autoPublish
@@ -3546,8 +3322,8 @@ function printDeploySuccess(options) {
3546
3322
  return;
3547
3323
  }
3548
3324
  options.logger.info(`发布策略:${options.autoPublish ? '审核通过后自动发布' : '审核通过后需在开放平台手动发布'}`);
3549
- if (options.previewUrl) {
3550
- options.logger.info(`Preview URL: ${options.previewUrl}`);
3325
+ if (options.protocolString) {
3326
+ options.logger.info(`Protocol: ${options.protocolString}`);
3551
3327
  }
3552
3328
  }
3553
3329
  function findProjectRoot(startDir) {
@@ -3764,6 +3540,8 @@ function suggestNextPatchVersion(version) {
3764
3540
 
3765
3541
  function createRemoteApiClient(options) {
3766
3542
  return {
3543
+ listDeveloperEntities: () => listDeveloperEntities(options),
3544
+ switchDeveloperEntity: (requestOptions) => switchDeveloperEntity(requestOptions, options),
3767
3545
  getDeveloperAccessStatus: () => getDeveloperAccessStatus(options),
3768
3546
  listUserMiniprograms: (requestOptions = {}) => listUserMiniprograms(requestOptions, options),
3769
3547
  createUserMiniprogram: (requestOptions) => createUserMiniprogram(requestOptions, options),
@@ -3781,6 +3559,12 @@ function createRemoteApiClient(options) {
3781
3559
  listUserMiniprogramVersions: (requestOptions) => listUserMiniprogramVersions(requestOptions, options),
3782
3560
  };
3783
3561
  }
3562
+ async function listDeveloperEntities(runtime) {
3563
+ return remoteGet(DEVELOPER_ENTITY_LIST_API_PATH, {}, runtime);
3564
+ }
3565
+ async function switchDeveloperEntity(options, runtime) {
3566
+ return (await remotePostForm(DEVELOPER_ENTITY_SWITCH_API_PATH, options, runtime, { requireResult: false })) ?? {};
3567
+ }
3784
3568
  async function getDeveloperAccessStatus(runtime) {
3785
3569
  return remoteGet(USER_MINIPROGRAM_ACCESS_STATUS_API_PATH, {}, runtime);
3786
3570
  }
@@ -3831,30 +3615,36 @@ async function listUserMiniprogramVersions(options, runtime) {
3831
3615
  }
3832
3616
  async function remoteGet(path, query, runtime) {
3833
3617
  const pathWithQuery = createPathWithQuery(path, query);
3834
- const context = createHeyboxOpenPlatformRequestContext(runtime.session, pathWithQuery);
3618
+ const context = session.createHeyboxOpenPlatformRequestContext(runtime.session, pathWithQuery);
3835
3619
  const fetchImpl = runtime.fetchImpl ?? fetch;
3836
- const response = await fetchImpl(createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, pathWithQuery, context.platformParams), {
3620
+ const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, pathWithQuery, context.platformParams), {
3837
3621
  method: 'GET',
3838
3622
  headers: context.headers,
3839
3623
  });
3840
3624
  return readRequiredResult(response, pathWithQuery);
3841
3625
  }
3842
- async function remotePostForm(path, fields, runtime) {
3843
- const context = createHeyboxOpenPlatformRequestContext(runtime.session, path, {
3626
+ async function remotePostForm(path, fields, runtime, options = {}) {
3627
+ const context = session.createHeyboxOpenPlatformRequestContext(runtime.session, path, {
3844
3628
  contentType: 'application/x-www-form-urlencoded',
3845
3629
  });
3846
3630
  const fetchImpl = runtime.fetchImpl ?? fetch;
3847
- const response = await fetchImpl(createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
3631
+ const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
3848
3632
  method: 'POST',
3849
3633
  headers: context.headers,
3850
3634
  body: createFormBody(fields),
3851
3635
  });
3636
+ if (options.requireResult === false) {
3637
+ return readOptionalResult(response, path);
3638
+ }
3852
3639
  return readRequiredResult(response, path);
3853
3640
  }
3854
3641
  async function readRequiredResult(response, pathWithQuery) {
3855
- const result = await readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: true });
3642
+ const result = await session.readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: true });
3856
3643
  return result;
3857
3644
  }
3645
+ async function readOptionalResult(response, pathWithQuery) {
3646
+ return session.readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: false });
3647
+ }
3858
3648
  function createPathWithQuery(path, query) {
3859
3649
  const params = new URLSearchParams();
3860
3650
  for (const [key, value] of Object.entries(query)) {
@@ -4004,6 +3794,12 @@ async function runRemoteCommand(options, runtime = {}) {
4004
3794
  switch (options.command) {
4005
3795
  case 'access':
4006
3796
  return remoteAccess(options, context, runtime);
3797
+ case 'entity:list':
3798
+ return remoteEntityList(options, context, runtime);
3799
+ case 'entity:current':
3800
+ return remoteEntityCurrent(options, context, runtime);
3801
+ case 'entity:switch':
3802
+ return remoteEntitySwitch(options, context, runtime);
4007
3803
  case 'list':
4008
3804
  return remoteList(options, context, runtime);
4009
3805
  case 'create':
@@ -4035,6 +3831,9 @@ async function runRemoteCommand(options, runtime = {}) {
4035
3831
  }
4036
3832
  async function runRemoteDeployCommand(options, runtime) {
4037
3833
  const logger = resolveRemoteLogger(options, runtime);
3834
+ const context = await createRemoteCommandContext(options, runtime);
3835
+ const miniProgramId = requireBinding(context);
3836
+ await assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId);
4038
3837
  const deployRuntime = {
4039
3838
  cwd: runtime.cwd,
4040
3839
  fetchImpl: runtime.fetchImpl,
@@ -4091,14 +3890,16 @@ async function createRemoteCommandContext(options, runtime) {
4091
3890
  function adaptRemoteApiClient(api) {
4092
3891
  return {
4093
3892
  accessStatus: () => api.getDeveloperAccessStatus(),
4094
- listMiniPrograms: ({ keyword, status }) => api.listUserMiniprograms({ keyword, status, offset: 0, limit: 200 }),
3893
+ listMiniPrograms: ({ keyword, limit = 200, offset = 0, status }) => api.listUserMiniprograms({ keyword, status, offset, limit }),
4095
3894
  createMiniProgram: ({ name, previewImageUrl }) => api.createUserMiniprogram({ name, ...(previewImageUrl ? { preview_image_url: previewImageUrl } : {}) }),
4096
3895
  updateMiniProgram: ({ miniProgramId, name, previewImageUrl }) => api.updateUserMiniprogram({ mini_program_id: miniProgramId, name, preview_image_url: previewImageUrl }),
4097
3896
  getMiniProgramDetail: (miniProgramId) => api.getUserMiniprogramDetail({ mini_program_id: miniProgramId }),
4098
3897
  getAllowlist: (miniProgramId) => api.getPreviewAllowlist({ mini_program_id: miniProgramId }),
4099
3898
  updateAllowlist: ({ miniProgramId, heyboxIds }) => api.updatePreviewAllowlist({ mini_program_id: miniProgramId, heybox_ids: heyboxIds }),
4100
3899
  listVersions: (miniProgramId) => api.listUserMiniprogramVersions({ mini_program_id: miniProgramId, offset: 0, limit: 50 }),
4101
- getPreviewInfo: ({ miniProgramId, version }) => api.getVersionPreviewInfo({ mini_program_id: miniProgramId, preview_version: version }),
3900
+ getPreviewInfo: ({ miniProgramId, version }) => api.getVersionPreviewInfo({ mini_program_id: miniProgramId, version }),
3901
+ listDeveloperEntities: () => api.listDeveloperEntities(),
3902
+ switchDeveloperEntity: ({ entityId }) => api.switchDeveloperEntity({ entity_id: entityId }),
4102
3903
  releaseVersion: ({ miniProgramId, version }) => api.releaseUserMiniprogramVersion({ mini_program_id: miniProgramId, version }),
4103
3904
  withdrawVersion: ({ miniProgramId, reason, version }) => api.withdrawUserMiniprogramVersion({ mini_program_id: miniProgramId, version, reason }),
4104
3905
  takeDown: (miniProgramId) => api.takeDownUserMiniprogram({ mini_program_id: miniProgramId }),
@@ -4119,6 +3920,66 @@ async function remoteAccess(options, context, runtime) {
4119
3920
  }
4120
3921
  return finishRemoteCommand(options, runtime, output);
4121
3922
  }
3923
+ async function remoteEntityList(options, context, runtime) {
3924
+ const result = await listDeveloperEntitiesWithAccessFallback(context);
3925
+ const items = result.items;
3926
+ const output = { changed: false, items: toCamelCaseDeep(items), total: result.total };
3927
+ if (!options.json) {
3928
+ if (items.length === 0) {
3929
+ context.logger.info('当前账号未绑定开发者主体');
3930
+ }
3931
+ else {
3932
+ printDeveloperEntities(context.logger, items);
3933
+ }
3934
+ }
3935
+ return finishRemoteCommand(options, runtime, output);
3936
+ }
3937
+ async function remoteEntityCurrent(options, context, runtime) {
3938
+ const current = await requireCurrentDeveloperEntity(context);
3939
+ const access = await context.api.accessStatus();
3940
+ const output = { changed: false, current: toCamelCaseDeep(current), access: toCamelCaseDeep(access) };
3941
+ if (!options.json) {
3942
+ printDeveloperEntity(context.logger, current, '* ');
3943
+ context.logger.info(`Access status: ${String(access.status ?? 'unknown')}`);
3944
+ if (access.disabled_reason) {
3945
+ context.logger.warn(`Disabled reason: ${String(access.disabled_reason)}`);
3946
+ }
3947
+ }
3948
+ return finishRemoteCommand(options, runtime, output);
3949
+ }
3950
+ async function remoteEntitySwitch(options, context, runtime) {
3951
+ const entityId = parseEntityId(options.entityId);
3952
+ const entityList = await listDeveloperEntitiesWithAccessFallback(context);
3953
+ const entities = entityList.items;
3954
+ const target = entities.find((entity) => entity.entity_id === entityId);
3955
+ if (!target) {
3956
+ throw new Error(`当前账号不可切换到开发者主体 ${entityId};请先运行 hb-sdk remote entity list 查看可用主体`);
3957
+ }
3958
+ const result = entityList.fromAccessFallback === true && target.is_current === true ? target : await context.api.switchDeveloperEntity({ entityId });
3959
+ const access = await context.api.accessStatus();
3960
+ const switched = normalizeDeveloperEntity(result) ?? { ...target, is_current: true };
3961
+ const switchedEntityId = normalizeEntityId(switched.entity_id);
3962
+ if (switchedEntityId === undefined) {
3963
+ throw new Error('远端响应缺少 entity_id');
3964
+ }
3965
+ const switchedOwnerHeyboxId = normalizePositiveInteger(switched.owner_heybox_id);
3966
+ await session.setSelectedDeveloperEntitySnapshot({
3967
+ entityId: switchedEntityId,
3968
+ entityName: switched.entity_name || '--',
3969
+ ...(switchedOwnerHeyboxId !== undefined ? { ownerHeyboxId: switchedOwnerHeyboxId } : {}),
3970
+ }, {
3971
+ cacheFile: options.cacheFile,
3972
+ env: options.env,
3973
+ loginBaseUrl: options.loginBaseUrl,
3974
+ now: options.now,
3975
+ });
3976
+ const output = { changed: true, current: toCamelCaseDeep(switched), access: toCamelCaseDeep(access) };
3977
+ if (!options.json) {
3978
+ context.logger.success(`已切换开发者主体:${formatDeveloperEntity(switched)}`);
3979
+ context.logger.info(`Access status: ${String(access.status ?? 'unknown')}`);
3980
+ }
3981
+ return finishRemoteCommand(options, runtime, output);
3982
+ }
4122
3983
  async function remoteList(options, context, runtime) {
4123
3984
  const result = await context.api.listMiniPrograms({ keyword: options.keyword, status: options.status });
4124
3985
  const items = Array.isArray(result.items) ? result.items : [];
@@ -4150,6 +4011,7 @@ async function remoteCreate(options, context, runtime) {
4150
4011
  if (access.status !== 'approved') {
4151
4012
  throw new Error(`当前 CLI 用户没有创建开发者用户小程序权限:${String(access.status ?? 'unknown')}`);
4152
4013
  }
4014
+ await confirmCreateWithCurrentEntity(options, context, runtime);
4153
4015
  const result = await context.api.createMiniProgram({ name, previewImageUrl });
4154
4016
  const miniProgramId = requireMiniProgramId(result);
4155
4017
  await writeBoundMiniProgramId(context.packageJson.projectRoot, miniProgramId, { force: options.forceBind });
@@ -4166,6 +4028,7 @@ async function remoteBind(options, context, runtime) {
4166
4028
  }
4167
4029
  const detail = await context.api.getMiniProgramDetail(miniProgramId);
4168
4030
  const verifiedId = requireMiniProgramId(detail);
4031
+ await assertMiniProgramEntityMatchesCurrent(context, detail);
4169
4032
  const changed = context.binding !== verifiedId;
4170
4033
  if (changed) {
4171
4034
  await writeBoundMiniProgramId(context.packageJson.projectRoot, verifiedId, { force: options.force });
@@ -4243,7 +4106,7 @@ async function remoteVersions(options, context, runtime) {
4243
4106
  const output = { changed: false, items: toCamelCaseDeep(items), miniProgramId, total: readNumber(result.total, items.length) };
4244
4107
  if (!options.json) {
4245
4108
  for (const item of items) {
4246
- context.logger.info(`${item.version ?? '--'} status=${item.status ?? '--'} preview=${item.preview_url ?? '--'} publish=${item.publish_url ?? '--'}`);
4109
+ context.logger.info(`${item.version ?? '--'} status=${item.status ?? '--'} auto_publish=${item.auto_publish === true ? 'yes' : 'no'}`);
4247
4110
  }
4248
4111
  if (items.length === 0) {
4249
4112
  context.logger.info('暂无远端版本');
@@ -4255,10 +4118,13 @@ async function remotePreview(options, context, runtime) {
4255
4118
  const miniProgramId = requireBinding(context);
4256
4119
  const version = requireNonEmpty(options.version, 'remote preview 必须传 <version>');
4257
4120
  const result = await context.api.getPreviewInfo({ miniProgramId, version });
4258
- const previewUrl = String(result.preview_url || result.publish_url || result.page_url || '');
4259
- const output = { changed: false, miniProgramId, previewUrl, version, preview: toCamelCaseDeep(result) };
4121
+ const protocolString = String(result.protocol_string || '');
4122
+ if (!protocolString) {
4123
+ throw new Error(`版本 ${version} 暂无 protocol_string,无法打开预览`);
4124
+ }
4125
+ const output = { changed: false, miniProgramId, protocolString, version, preview: toCamelCaseDeep(result) };
4260
4126
  if (!options.json) {
4261
- context.logger.info(previewUrl ? `Preview URL: ${previewUrl}` : `版本 ${version} 暂无 preview URL`);
4127
+ context.logger.info(`Protocol: ${protocolString}`);
4262
4128
  }
4263
4129
  return finishRemoteCommand(options, runtime, output);
4264
4130
  }
@@ -4332,6 +4198,89 @@ function requireBinding(context) {
4332
4198
  }
4333
4199
  return context.binding;
4334
4200
  }
4201
+ async function requireCurrentDeveloperEntity(context) {
4202
+ const entities = (await listDeveloperEntitiesWithAccessFallback(context)).items;
4203
+ const current = entities.find((entity) => entity.is_current === true);
4204
+ if (!current) {
4205
+ throw new Error('当前账号没有服务端 current 开发者主体;请先运行 hb-sdk remote entity list 查看可用主体,并用 hb-sdk remote entity switch <entity-id> 切换');
4206
+ }
4207
+ return current;
4208
+ }
4209
+ async function confirmCreateWithCurrentEntity(options, context, runtime) {
4210
+ const entities = (await listDeveloperEntitiesWithAccessFallback(context)).items;
4211
+ if (entities.length <= 1) {
4212
+ return;
4213
+ }
4214
+ const current = entities.find((entity) => entity.is_current === true);
4215
+ if (!current) {
4216
+ throw new Error('当前账号存在多个开发者主体,但没有服务端 current 主体;请先运行 hb-sdk remote entity switch <entity-id>');
4217
+ }
4218
+ if (!options.json) {
4219
+ context.logger.info(`当前创建将归属开发者主体:${formatDeveloperEntity(current)}`);
4220
+ context.logger.info(`如需切换主体,请先运行:hb-sdk remote entity switch <entity-id>`);
4221
+ }
4222
+ if (options.yes) {
4223
+ return;
4224
+ }
4225
+ const isTTY = runtime.isTTY ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
4226
+ if (!isTTY) {
4227
+ throw new Error(`当前账号存在多个开发者主体,非交互环境执行 hb-sdk remote create 必须传 --yes 确认使用当前主体:${formatDeveloperEntity(current)}。如需切换,请先运行 hb-sdk remote entity switch <entity-id>`);
4228
+ }
4229
+ const accepted = runtime.promptConfirm
4230
+ ? await runtime.promptConfirm(`确认使用当前开发者主体 ${formatDeveloperEntity(current)} 创建小程序?`)
4231
+ : await promptConfirm(`确认使用当前开发者主体 ${formatDeveloperEntity(current)} 创建小程序?输入 yes 继续: `, runtime);
4232
+ if (!accepted) {
4233
+ throw new Error('已取消操作');
4234
+ }
4235
+ }
4236
+ async function assertMiniProgramEntityMatchesCurrent(context, detail) {
4237
+ const detailEntityId = normalizeEntityId(detail.entity_id);
4238
+ if (detailEntityId === undefined) {
4239
+ return;
4240
+ }
4241
+ const current = await requireCurrentDeveloperEntity(context);
4242
+ if (current.entity_id === detailEntityId) {
4243
+ return;
4244
+ }
4245
+ throw new Error(createEntityMismatchMessage({ current, target: createEntityFromMiniProgramDetail({ ...detail, entity_id: detailEntityId }) }));
4246
+ }
4247
+ async function assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId) {
4248
+ const current = await requireCurrentDeveloperEntity(context);
4249
+ const detail = await context.api.getMiniProgramDetail(miniProgramId);
4250
+ requireMiniProgramId(detail);
4251
+ const detailEntityId = normalizeEntityId(detail.entity_id);
4252
+ if (detailEntityId === undefined) {
4253
+ const listed = await findMiniProgramInCurrentEntityList(context, miniProgramId);
4254
+ if (listed) {
4255
+ return;
4256
+ }
4257
+ throw new Error(`远端小程序 ${miniProgramId} 详情缺少 entity_id,且当前主体列表中找不到该小程序,无法校验当前主体一致性,已停止 deploy`);
4258
+ }
4259
+ if (current.entity_id !== detailEntityId) {
4260
+ throw new Error(createEntityMismatchMessage({ current, target: createEntityFromMiniProgramDetail({ ...detail, entity_id: detailEntityId }) }));
4261
+ }
4262
+ }
4263
+ async function findMiniProgramInCurrentEntityList(context, miniProgramId) {
4264
+ const pageSize = 200;
4265
+ for (let offset = 0;; offset += pageSize) {
4266
+ const result = await context.api.listMiniPrograms({ limit: pageSize, offset });
4267
+ const items = Array.isArray(result.items) ? result.items : [];
4268
+ if (items.some((item) => item.mini_program_id === miniProgramId)) {
4269
+ return true;
4270
+ }
4271
+ if (items.length < pageSize || offset + items.length >= readNumber(result.total, Number.POSITIVE_INFINITY)) {
4272
+ return false;
4273
+ }
4274
+ }
4275
+ }
4276
+ function createEntityMismatchMessage(options) {
4277
+ return [
4278
+ '当前开发者主体与远端小程序所属主体不一致,已停止操作。',
4279
+ `当前主体:${formatDeveloperEntity(options.current)}`,
4280
+ `小程序所属主体:${formatDeveloperEntity(options.target)}`,
4281
+ `请先运行:hb-sdk remote entity switch ${options.target.entity_id}`,
4282
+ ].join('\n');
4283
+ }
4335
4284
  function requireMiniProgramId(result) {
4336
4285
  const miniProgramId = result.mini_program_id;
4337
4286
  if (typeof miniProgramId !== 'string' || !miniProgramId.trim()) {
@@ -4339,6 +4288,88 @@ function requireMiniProgramId(result) {
4339
4288
  }
4340
4289
  return miniProgramId.trim();
4341
4290
  }
4291
+ function parseEntityId(value) {
4292
+ const raw = requireNonEmpty(value, 'remote entity switch 必须传 <entity-id>');
4293
+ if (!/^(0|[1-9]\d*)$/.test(raw)) {
4294
+ throw new Error(`entity-id 只接受十进制数字:${raw}`);
4295
+ }
4296
+ const entityId = Number(raw);
4297
+ if (!isValidEntityId(entityId)) {
4298
+ throw new Error(`entity-id 超出有效范围:${raw}`);
4299
+ }
4300
+ return entityId;
4301
+ }
4302
+ async function listDeveloperEntitiesWithAccessFallback(context) {
4303
+ const result = await context.api.listDeveloperEntities();
4304
+ const items = normalizeDeveloperEntities(result);
4305
+ if (items.length > 0) {
4306
+ return { items, total: readNumber(result.total, items.length) };
4307
+ }
4308
+ const fallback = createEntityFromAccessStatus(await context.api.accessStatus());
4309
+ if (!fallback) {
4310
+ return { items, total: readNumber(result.total, items.length) };
4311
+ }
4312
+ return { fromAccessFallback: true, items: [fallback], total: 1 };
4313
+ }
4314
+ function normalizeDeveloperEntities(result) {
4315
+ const rawItems = Array.isArray(result.items) ? result.items : [];
4316
+ return rawItems.map((item) => normalizeDeveloperEntity(item)).filter((item) => item !== undefined);
4317
+ }
4318
+ function normalizeDeveloperEntity(value) {
4319
+ if (!value || typeof value !== 'object') {
4320
+ return undefined;
4321
+ }
4322
+ const entityId = normalizeEntityId(value.entity_id);
4323
+ if (entityId === undefined) {
4324
+ return undefined;
4325
+ }
4326
+ const ownerHeyboxId = normalizePositiveInteger(value.owner_heybox_id);
4327
+ return {
4328
+ ...value,
4329
+ entity_id: entityId,
4330
+ entity_name: typeof value.entity_name === 'string' ? value.entity_name : undefined,
4331
+ owner_heybox_id: ownerHeyboxId,
4332
+ is_current: value.is_current === true,
4333
+ };
4334
+ }
4335
+ function createEntityFromMiniProgramDetail(detail) {
4336
+ const entityId = normalizeEntityId(detail.entity_id);
4337
+ return {
4338
+ ...(entityId !== undefined ? { entity_id: entityId } : {}),
4339
+ entity_name: typeof detail.developer_name === 'string' ? detail.developer_name : undefined,
4340
+ owner_heybox_id: normalizePositiveInteger(detail.owner_heybox_id),
4341
+ is_current: false,
4342
+ };
4343
+ }
4344
+ function createEntityFromAccessStatus(access) {
4345
+ const entity = normalizeDeveloperEntity({
4346
+ entity_id: access.entity_id,
4347
+ entity_name: access.developer_name,
4348
+ owner_heybox_id: access.owner_heybox_id,
4349
+ is_current: true,
4350
+ });
4351
+ return entity;
4352
+ }
4353
+ function isValidEntityId(value) {
4354
+ return Number.isSafeInteger(value) && Number(value) > 0;
4355
+ }
4356
+ function normalizeEntityId(value) {
4357
+ return normalizePositiveInteger(value);
4358
+ }
4359
+ function normalizePositiveInteger(value) {
4360
+ if (typeof value === 'number') {
4361
+ return isValidEntityId(value) ? value : undefined;
4362
+ }
4363
+ if (typeof value !== 'string') {
4364
+ return undefined;
4365
+ }
4366
+ const normalized = value.trim();
4367
+ if (!/^[1-9]\d*$/.test(normalized)) {
4368
+ return undefined;
4369
+ }
4370
+ const parsed = Number(normalized);
4371
+ return isValidEntityId(parsed) ? parsed : undefined;
4372
+ }
4342
4373
  function requireNonEmpty(value, message) {
4343
4374
  const normalized = typeof value === 'string' ? value.trim() : '';
4344
4375
  if (!normalized) {
@@ -4462,10 +4493,26 @@ function printMiniProgramDetail(logger, detail) {
4462
4493
  logger.info(`Name: ${detail.name ?? '--'}`);
4463
4494
  logger.info(`Status: ${detail.status ?? '--'}`);
4464
4495
  logger.info(`Version: ${detail.version || detail.latest_version_status || '--'}`);
4465
- if (detail.publish_url) {
4466
- logger.info(`Publish URL: ${detail.publish_url}`);
4496
+ if (detail.page_url) {
4497
+ logger.info(`Page URL: ${detail.page_url}`);
4498
+ }
4499
+ if (detail.protocol_string) {
4500
+ logger.info(`Protocol: ${detail.protocol_string}`);
4501
+ }
4502
+ }
4503
+ function printDeveloperEntities(logger, entities) {
4504
+ for (const entity of entities) {
4505
+ printDeveloperEntity(logger, entity, entity.is_current ? '* ' : ' ');
4467
4506
  }
4468
4507
  }
4508
+ function printDeveloperEntity(logger, entity, marker = '') {
4509
+ logger.info(`${marker}${formatDeveloperEntity(entity)}${entity.is_current ? ' current' : ''}`);
4510
+ }
4511
+ function formatDeveloperEntity(entity) {
4512
+ const name = entity.entity_name || '--';
4513
+ const owner = entity.owner_heybox_id ? ` owner=${entity.owner_heybox_id}` : '';
4514
+ return `${entity.entity_id ?? '--'} ${name}${owner}`;
4515
+ }
4469
4516
  function printAllowlist(logger, result) {
4470
4517
  const items = Array.isArray(result.items) ? result.items : [];
4471
4518
  logger.info(`Limit: ${readNumber(result.limit, 0)}`);