@heybox/hb-sdk 0.6.8 → 0.6.9-alpha.0
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 +14 -0
- package/dist/cli-chunks/{build-D4crgajb.cjs → build-9hbhrRbU.cjs} +4 -4
- package/dist/cli-chunks/{context-fRWWSXDj.cjs → context-7rmakzS_.cjs} +3 -3
- package/dist/cli-chunks/{create-BU_EFgDz.cjs → create-BkX4rtRP.cjs} +1 -1
- package/dist/cli-chunks/{dev-BrkChQ7v.cjs → dev-DiZGT4FQ.cjs} +6 -6
- package/dist/cli-chunks/{doctor-DZXVO4rL.cjs → doctor-BcpqXVRh.cjs} +1 -1
- package/dist/cli-chunks/{index-DxH41nsb.cjs → index-6HfcwZ_r.cjs} +2 -2
- package/dist/cli-chunks/{index-CSjJrnkO.cjs → index-DofTxdoX.cjs} +14 -14
- package/dist/cli-chunks/{login-DLVozxrs.cjs → login-DUiejZcD.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-CwmMfCk3.cjs → project-vite-CfD8Bp1K.cjs} +1 -1
- package/dist/cli-chunks/{remote-CfpkkC9I.cjs → remote-A4-PKgOn.cjs} +456 -202
- package/dist/cli-chunks/{runtime-gate-CgN_v4Te.cjs → runtime-gate-BWlU-R4h.cjs} +1 -1
- package/dist/cli-chunks/{runtime-permission-env-B0jK9Rt1.cjs → runtime-permission-env-DKrhgVM3.cjs} +205 -49
- package/dist/cli-chunks/{session-DB7ARwm4.cjs → session-D5u3XzHN.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/miniapp-publish.cjs.js +205 -0
- package/dist/miniapp-publish.esm.js +201 -1
- package/dist/vite.cjs.js +1 -1
- package/dist/vite.esm.js +1 -1
- package/package.json +2 -2
- package/skill/references/api-root.md +1 -1
- package/skill/references/cli.md +8 -0
- package/skill/skill.json +4 -4
- package/types/miniapp-publish/index.d.ts +77 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
3
|
+
var runtimePermissionEnv = require('./runtime-permission-env-DKrhgVM3.cjs');
|
|
4
4
|
|
|
5
5
|
function isValidMiniappManifestVersion(version) {
|
|
6
6
|
return getMiniappManifestVersionError(version) === undefined;
|
package/dist/cli-chunks/{runtime-permission-env-B0jK9Rt1.cjs → runtime-permission-env-DKrhgVM3.cjs}
RENAMED
|
@@ -2814,6 +2814,10 @@ const UPDATE_SQUARE_DISPLAY_API_PATH = '/mall/developer/user_miniprogram/square_
|
|
|
2814
2814
|
const LIST_USER_MINIPROGRAM_VERSION_API_PATH = '/mall/developer/user_miniprogram/version/list';
|
|
2815
2815
|
const FNV_OFFSET = 0x811c9dc5;
|
|
2816
2816
|
const FNV_PRIME = 0x01000193;
|
|
2817
|
+
const MINIAPP_UPLOAD_SESSION_REF_LENGTH = 10;
|
|
2818
|
+
const MINIAPP_ARTIFACT_SIGNATURE_MAGIC = 'heybox-user-miniprogram-artifact\0v1\0';
|
|
2819
|
+
const UNPAIRED_SURROGATE_PATTERN = /[\ud800-\udbff](?![\udc00-\udfff])|(^|[^\ud800-\udbff])[\udc00-\udfff]/;
|
|
2820
|
+
const textEncoder = new TextEncoder();
|
|
2817
2821
|
function normalizeRelativePath(relativePath) {
|
|
2818
2822
|
return String(relativePath || '')
|
|
2819
2823
|
.replace(/\\/g, '/')
|
|
@@ -2834,52 +2838,6 @@ function getMiniProgramUploadAlias(miniProgramId) {
|
|
|
2834
2838
|
}
|
|
2835
2839
|
return hash.toString(36);
|
|
2836
2840
|
}
|
|
2837
|
-
function getMiniappUploadKey(input) {
|
|
2838
|
-
const alias = getMiniProgramUploadAlias(input.miniProgramId);
|
|
2839
|
-
const normalized = normalizeRelativePath(input.relativePath);
|
|
2840
|
-
return `/u/${alias}/${input.version}/${normalized}`;
|
|
2841
|
-
}
|
|
2842
|
-
function validateUploadPaths(items, options) {
|
|
2843
|
-
const limit = options.maxLength;
|
|
2844
|
-
const uploadKeys = new Set();
|
|
2845
|
-
for (const item of items) {
|
|
2846
|
-
const relativePath = String(item.relativePath ?? '');
|
|
2847
|
-
const normalizedRelativePath = normalizeRelativePath(relativePath);
|
|
2848
|
-
const invalidReason = getInvalidUploadPathReason(normalizedRelativePath);
|
|
2849
|
-
if (invalidReason) {
|
|
2850
|
-
return `构建产物文件路径不合法(${invalidReason}):${JSON.stringify(relativePath)}`;
|
|
2851
|
-
}
|
|
2852
|
-
const key = getMiniappUploadKey({
|
|
2853
|
-
miniProgramId: options.miniProgramId,
|
|
2854
|
-
version: options.version,
|
|
2855
|
-
relativePath: normalizedRelativePath,
|
|
2856
|
-
});
|
|
2857
|
-
if (uploadKeys.has(key)) {
|
|
2858
|
-
return `构建产物文件路径重复,无法上传到同一位置:${JSON.stringify(relativePath)}`;
|
|
2859
|
-
}
|
|
2860
|
-
uploadKeys.add(key);
|
|
2861
|
-
if (key.length > limit) {
|
|
2862
|
-
return `文件路径过长,请缩短构建产物文件名或目录层级:${relativePath}`;
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
return undefined;
|
|
2866
|
-
}
|
|
2867
|
-
function getInvalidUploadPathReason(relativePath) {
|
|
2868
|
-
const segments = relativePath.replace(/\\/g, '/').split('/');
|
|
2869
|
-
if (segments.some((segment) => !segment)) {
|
|
2870
|
-
return '包含空路径段';
|
|
2871
|
-
}
|
|
2872
|
-
if (segments.some((segment) => segment === '.')) {
|
|
2873
|
-
return '包含当前目录';
|
|
2874
|
-
}
|
|
2875
|
-
if (segments.some((segment) => segment === '..')) {
|
|
2876
|
-
return '包含上级目录';
|
|
2877
|
-
}
|
|
2878
|
-
if (/[\u0000-\u001F\u007F]/.test(relativePath)) {
|
|
2879
|
-
return '包含控制字符';
|
|
2880
|
-
}
|
|
2881
|
-
return undefined;
|
|
2882
|
-
}
|
|
2883
2841
|
function validateUploadTotalSize(items, options = {}) {
|
|
2884
2842
|
const limit = options.limitBytes ?? MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES;
|
|
2885
2843
|
const total = items.reduce((sum, item) => {
|
|
@@ -2907,6 +2865,202 @@ function shouldUploadDistFile(relativePath) {
|
|
|
2907
2865
|
}
|
|
2908
2866
|
return true;
|
|
2909
2867
|
}
|
|
2868
|
+
/**
|
|
2869
|
+
* 校验构建产物路径和大小,并按路径 UTF-8 字节序排序。
|
|
2870
|
+
*
|
|
2871
|
+
* @param files 待处理的构建产物文件。
|
|
2872
|
+
* @returns 保留输入附加字段的有序文件列表。
|
|
2873
|
+
* @throws 路径不规范、路径重复或文件大小非法时抛出错误。
|
|
2874
|
+
*/
|
|
2875
|
+
function prepareMiniappArtifactFiles(files) {
|
|
2876
|
+
if (files.length > 0xffffffff) {
|
|
2877
|
+
throw new Error('构建产物文件数量超出签名协议上限');
|
|
2878
|
+
}
|
|
2879
|
+
const usedPaths = new Set();
|
|
2880
|
+
const prepared = files.map(file => {
|
|
2881
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2882
|
+
if (usedPaths.has(relativePath)) {
|
|
2883
|
+
throw new Error(`构建产物包含重复文件路径:${relativePath}`);
|
|
2884
|
+
}
|
|
2885
|
+
usedPaths.add(relativePath);
|
|
2886
|
+
validateArtifactFileSize(file.size, relativePath);
|
|
2887
|
+
return { ...file, relativePath };
|
|
2888
|
+
});
|
|
2889
|
+
return prepared.sort((left, right) => compareBytes(textEncoder.encode(left.relativePath), textEncoder.encode(right.relativePath)));
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* 按服务端 session key 前缀校验最终上传路径的 UTF-8 字节长度。
|
|
2893
|
+
*
|
|
2894
|
+
* @param files 待上传的构建产物文件。
|
|
2895
|
+
* @param options 小程序和长度限制配置。
|
|
2896
|
+
* @returns 第一个超长路径的错误提示;全部合法时返回 `undefined`。
|
|
2897
|
+
*/
|
|
2898
|
+
function validateMiniappUploadPathLengths(files, options) {
|
|
2899
|
+
const maxLength = options.maxLength ?? ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
|
|
2900
|
+
const prefix = `/u/${getMiniProgramUploadAlias(options.miniProgramId)}/${'x'.repeat(MINIAPP_UPLOAD_SESSION_REF_LENGTH)}/`;
|
|
2901
|
+
const prefixLength = textEncoder.encode(prefix).length;
|
|
2902
|
+
const tooLongFile = files.find(file => {
|
|
2903
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2904
|
+
return prefixLength + textEncoder.encode(relativePath).length > maxLength;
|
|
2905
|
+
});
|
|
2906
|
+
return tooLongFile ? `文件路径过长,请缩短构建产物文件名或目录层级:${tooLongFile.relativePath}` : undefined;
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* 创建 v1 整包签名头。
|
|
2910
|
+
*
|
|
2911
|
+
* @param fileCount 参与签名的文件数量。
|
|
2912
|
+
* @returns 包含协议标识和文件数量的字节数组。
|
|
2913
|
+
* @throws 文件数量超出协议范围时抛出错误。
|
|
2914
|
+
*/
|
|
2915
|
+
function createMiniappArtifactSignatureHeader(fileCount) {
|
|
2916
|
+
if (!Number.isInteger(fileCount) || fileCount < 0 || fileCount > 0xffffffff) {
|
|
2917
|
+
throw new Error('构建产物文件数量超出签名协议上限');
|
|
2918
|
+
}
|
|
2919
|
+
return concatBytes(textEncoder.encode(MINIAPP_ARTIFACT_SIGNATURE_MAGIC), encodeUint32BE(fileCount));
|
|
2920
|
+
}
|
|
2921
|
+
/**
|
|
2922
|
+
* 创建单个文件的 v1 framing header。
|
|
2923
|
+
*
|
|
2924
|
+
* @param file 已规范化或待校验的构建产物文件。
|
|
2925
|
+
* @returns 包含路径长度、路径和文件大小的字节数组。
|
|
2926
|
+
* @throws 文件路径或大小非法时抛出错误。
|
|
2927
|
+
*/
|
|
2928
|
+
function createMiniappArtifactFileHeader(file) {
|
|
2929
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2930
|
+
validateArtifactFileSize(file.size, relativePath);
|
|
2931
|
+
const pathBytes = textEncoder.encode(relativePath);
|
|
2932
|
+
return concatBytes(encodeUint32BE(pathBytes.length), pathBytes, encodeUint64BE(file.size));
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* 校验服务端 session 文件映射,并计算仍需上传的文件。
|
|
2936
|
+
*
|
|
2937
|
+
* @param localFiles 本地构建产物文件。
|
|
2938
|
+
* @param serverFiles 服务端签发的文件映射。
|
|
2939
|
+
* @param uploadedKeys 服务端已确认上传成功的 key。
|
|
2940
|
+
* @returns 完整映射、待上传文件和已确认 key。
|
|
2941
|
+
* @throws 服务端映射与本地产物不一致时抛出错误。
|
|
2942
|
+
*/
|
|
2943
|
+
function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys) {
|
|
2944
|
+
const preparedFiles = prepareMiniappArtifactFiles(localFiles);
|
|
2945
|
+
if (serverFiles.length !== preparedFiles.length) {
|
|
2946
|
+
throw new Error(`上传会话文件数量异常:期望 ${preparedFiles.length},实际 ${serverFiles.length}`);
|
|
2947
|
+
}
|
|
2948
|
+
const mappings = new Map();
|
|
2949
|
+
const serverSizes = new Map();
|
|
2950
|
+
const knownKeys = new Set();
|
|
2951
|
+
for (const serverFile of serverFiles) {
|
|
2952
|
+
const relativePath = normalizeMiniappArtifactRelativePath(serverFile.relative_path);
|
|
2953
|
+
if (mappings.has(relativePath)) {
|
|
2954
|
+
throw new Error(`上传会话返回重复文件路径:${relativePath}`);
|
|
2955
|
+
}
|
|
2956
|
+
const key = String(serverFile.key || '');
|
|
2957
|
+
if (!key) {
|
|
2958
|
+
throw new Error(`上传会话未返回文件 key:${relativePath}`);
|
|
2959
|
+
}
|
|
2960
|
+
if (knownKeys.has(key)) {
|
|
2961
|
+
throw new Error(`上传会话返回重复文件 key:${relativePath}`);
|
|
2962
|
+
}
|
|
2963
|
+
const size = Number(serverFile.size);
|
|
2964
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
2965
|
+
throw new Error(`上传会话返回文件大小异常:${relativePath}`);
|
|
2966
|
+
}
|
|
2967
|
+
mappings.set(relativePath, key);
|
|
2968
|
+
serverSizes.set(relativePath, size);
|
|
2969
|
+
knownKeys.add(key);
|
|
2970
|
+
}
|
|
2971
|
+
const files = preparedFiles.map(file => {
|
|
2972
|
+
const key = mappings.get(file.relativePath);
|
|
2973
|
+
if (!key) {
|
|
2974
|
+
throw new Error(`上传会话缺少文件映射:${file.relativePath}`);
|
|
2975
|
+
}
|
|
2976
|
+
if (serverSizes.get(file.relativePath) !== file.size) {
|
|
2977
|
+
throw new Error(`上传会话文件大小与本地构建不一致:${file.relativePath}`);
|
|
2978
|
+
}
|
|
2979
|
+
return { file, key };
|
|
2980
|
+
});
|
|
2981
|
+
const completed = new Set();
|
|
2982
|
+
for (const rawKey of uploadedKeys) {
|
|
2983
|
+
const key = String(rawKey || '');
|
|
2984
|
+
if (!knownKeys.has(key)) {
|
|
2985
|
+
throw new Error('上传会话返回了不属于当前产物的已上传文件');
|
|
2986
|
+
}
|
|
2987
|
+
completed.add(key);
|
|
2988
|
+
}
|
|
2989
|
+
return {
|
|
2990
|
+
files,
|
|
2991
|
+
missingFiles: files.filter(file => !completed.has(file.key)),
|
|
2992
|
+
uploadedKeys: [...completed],
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function normalizeMiniappArtifactRelativePath(relativePath) {
|
|
2996
|
+
const value = String(relativePath || '');
|
|
2997
|
+
if (!value) {
|
|
2998
|
+
throw new Error('构建产物包含空文件路径');
|
|
2999
|
+
}
|
|
3000
|
+
if (value.includes('\\')) {
|
|
3001
|
+
throw new Error(`构建产物路径不能包含反斜杠:${value}`);
|
|
3002
|
+
}
|
|
3003
|
+
if (value.startsWith('/') || value.endsWith('/') || /^[a-zA-Z]:\//.test(value)) {
|
|
3004
|
+
throw new Error(`构建产物必须使用相对文件路径:${value}`);
|
|
3005
|
+
}
|
|
3006
|
+
if (containsControlCharacter(value) || UNPAIRED_SURROGATE_PATTERN.test(value)) {
|
|
3007
|
+
throw new Error(`构建产物路径包含非法字符:${value}`);
|
|
3008
|
+
}
|
|
3009
|
+
if (value.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
|
|
3010
|
+
throw new Error(`构建产物路径包含非法目录段:${value}`);
|
|
3011
|
+
}
|
|
3012
|
+
if (value.includes(',')) {
|
|
3013
|
+
throw new Error(`构建产物路径不能包含逗号:${value}`);
|
|
3014
|
+
}
|
|
3015
|
+
if (value.includes('*') || value.includes('?')) {
|
|
3016
|
+
throw new Error(`构建产物路径不能包含通配符:${value}`);
|
|
3017
|
+
}
|
|
3018
|
+
return value;
|
|
3019
|
+
}
|
|
3020
|
+
function containsControlCharacter(value) {
|
|
3021
|
+
for (const character of value) {
|
|
3022
|
+
const code = character.charCodeAt(0);
|
|
3023
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
|
|
3024
|
+
return true;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return false;
|
|
3028
|
+
}
|
|
3029
|
+
function validateArtifactFileSize(size, relativePath) {
|
|
3030
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3031
|
+
throw new Error(`构建产物文件大小无效:${relativePath}`);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
function encodeUint32BE(value) {
|
|
3035
|
+
const bytes = new Uint8Array(4);
|
|
3036
|
+
new DataView(bytes.buffer).setUint32(0, value, false);
|
|
3037
|
+
return bytes;
|
|
3038
|
+
}
|
|
3039
|
+
function encodeUint64BE(value) {
|
|
3040
|
+
const bytes = new Uint8Array(8);
|
|
3041
|
+
const view = new DataView(bytes.buffer);
|
|
3042
|
+
view.setUint32(0, Math.floor(value / 0x100000000), false);
|
|
3043
|
+
view.setUint32(4, value % 0x100000000, false);
|
|
3044
|
+
return bytes;
|
|
3045
|
+
}
|
|
3046
|
+
function concatBytes(...parts) {
|
|
3047
|
+
const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
3048
|
+
let offset = 0;
|
|
3049
|
+
for (const part of parts) {
|
|
3050
|
+
result.set(part, offset);
|
|
3051
|
+
offset += part.length;
|
|
3052
|
+
}
|
|
3053
|
+
return result;
|
|
3054
|
+
}
|
|
3055
|
+
function compareBytes(left, right) {
|
|
3056
|
+
const length = Math.min(left.length, right.length);
|
|
3057
|
+
for (let index = 0; index < length; index += 1) {
|
|
3058
|
+
if (left[index] !== right[index]) {
|
|
3059
|
+
return left[index] - right[index];
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
return left.length - right.length;
|
|
3063
|
+
}
|
|
2910
3064
|
|
|
2911
3065
|
const HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN_ENV = 'HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN';
|
|
2912
3066
|
const HB_SDK_RUNTIME_PERMISSION_CONTEXT_ENV = 'HB_SDK_RUNTIME_PERMISSION_CONTEXT';
|
|
@@ -2974,7 +3128,6 @@ async function runMiniappPlatformCspEnvScope(skipPlatformCsp, action, env) {
|
|
|
2974
3128
|
}
|
|
2975
3129
|
}
|
|
2976
3130
|
|
|
2977
|
-
exports.ACTIVITY_UPLOAD_KEY_MAX_LENGTH = ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
|
|
2978
3131
|
exports.CREATE_USER_MINIPROGRAM_API_PATH = CREATE_USER_MINIPROGRAM_API_PATH;
|
|
2979
3132
|
exports.CREATE_USER_MINIPROGRAM_LEADERBOARD_API_PATH = CREATE_USER_MINIPROGRAM_LEADERBOARD_API_PATH;
|
|
2980
3133
|
exports.DELETE_USER_MINIPROGRAM_LEADERBOARD_API_PATH = DELETE_USER_MINIPROGRAM_LEADERBOARD_API_PATH;
|
|
@@ -2998,14 +3151,17 @@ exports.USER_MINIPROGRAM_ACCESS_STATUS_API_PATH = USER_MINIPROGRAM_ACCESS_STATUS
|
|
|
2998
3151
|
exports.USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH = USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH;
|
|
2999
3152
|
exports.USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH = USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH;
|
|
3000
3153
|
exports.WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH = WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH;
|
|
3154
|
+
exports.createMiniappArtifactFileHeader = createMiniappArtifactFileHeader;
|
|
3155
|
+
exports.createMiniappArtifactSignatureHeader = createMiniappArtifactSignatureHeader;
|
|
3001
3156
|
exports.createMiniappPlatformCspEnv = createMiniappPlatformCspEnv;
|
|
3002
|
-
exports.getMiniappUploadKey = getMiniappUploadKey;
|
|
3003
3157
|
exports.normalizeRelativePath = normalizeRelativePath;
|
|
3158
|
+
exports.prepareMiniappArtifactFiles = prepareMiniappArtifactFiles;
|
|
3004
3159
|
exports.relativePathContainsNodeModulesSegment = relativePathContainsNodeModulesSegment;
|
|
3005
3160
|
exports.requireSemver = requireSemver;
|
|
3161
|
+
exports.resolveMiniappUploadSessionFiles = resolveMiniappUploadSessionFiles;
|
|
3006
3162
|
exports.semverExports = semverExports;
|
|
3007
3163
|
exports.shouldSkipMiniappPlatformCspFromEnv = shouldSkipMiniappPlatformCspFromEnv;
|
|
3008
3164
|
exports.shouldUploadDistFile = shouldUploadDistFile;
|
|
3009
|
-
exports.
|
|
3165
|
+
exports.validateMiniappUploadPathLengths = validateMiniappUploadPathLengths;
|
|
3010
3166
|
exports.validateUploadTotalSize = validateUploadTotalSize;
|
|
3011
3167
|
exports.withMiniappPlatformCspEnv = withMiniappPlatformCspEnv;
|
package/dist/cli.cjs
CHANGED
package/dist/index.cjs.js
CHANGED
package/dist/index.esm.js
CHANGED
|
@@ -2836,6 +2836,10 @@ const UPDATE_SQUARE_DISPLAY_API_PATH = '/mall/developer/user_miniprogram/square_
|
|
|
2836
2836
|
const LIST_USER_MINIPROGRAM_VERSION_API_PATH = '/mall/developer/user_miniprogram/version/list';
|
|
2837
2837
|
const FNV_OFFSET = 0x811c9dc5;
|
|
2838
2838
|
const FNV_PRIME = 0x01000193;
|
|
2839
|
+
const MINIAPP_UPLOAD_SESSION_REF_LENGTH = 10;
|
|
2840
|
+
const MINIAPP_ARTIFACT_SIGNATURE_MAGIC = 'heybox-user-miniprogram-artifact\0v1\0';
|
|
2841
|
+
const UNPAIRED_SURROGATE_PATTERN = /[\ud800-\udbff](?![\udc00-\udfff])|(^|[^\ud800-\udbff])[\udc00-\udfff]/;
|
|
2842
|
+
const textEncoder = new TextEncoder();
|
|
2839
2843
|
function normalizeRelativePath(relativePath) {
|
|
2840
2844
|
return String(relativePath || '')
|
|
2841
2845
|
.replace(/\\/g, '/')
|
|
@@ -2929,6 +2933,202 @@ function shouldUploadDistFile(relativePath) {
|
|
|
2929
2933
|
}
|
|
2930
2934
|
return true;
|
|
2931
2935
|
}
|
|
2936
|
+
/**
|
|
2937
|
+
* 校验构建产物路径和大小,并按路径 UTF-8 字节序排序。
|
|
2938
|
+
*
|
|
2939
|
+
* @param files 待处理的构建产物文件。
|
|
2940
|
+
* @returns 保留输入附加字段的有序文件列表。
|
|
2941
|
+
* @throws 路径不规范、路径重复或文件大小非法时抛出错误。
|
|
2942
|
+
*/
|
|
2943
|
+
function prepareMiniappArtifactFiles(files) {
|
|
2944
|
+
if (files.length > 0xffffffff) {
|
|
2945
|
+
throw new Error('构建产物文件数量超出签名协议上限');
|
|
2946
|
+
}
|
|
2947
|
+
const usedPaths = new Set();
|
|
2948
|
+
const prepared = files.map(file => {
|
|
2949
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2950
|
+
if (usedPaths.has(relativePath)) {
|
|
2951
|
+
throw new Error(`构建产物包含重复文件路径:${relativePath}`);
|
|
2952
|
+
}
|
|
2953
|
+
usedPaths.add(relativePath);
|
|
2954
|
+
validateArtifactFileSize(file.size, relativePath);
|
|
2955
|
+
return { ...file, relativePath };
|
|
2956
|
+
});
|
|
2957
|
+
return prepared.sort((left, right) => compareBytes(textEncoder.encode(left.relativePath), textEncoder.encode(right.relativePath)));
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* 按服务端 session key 前缀校验最终上传路径的 UTF-8 字节长度。
|
|
2961
|
+
*
|
|
2962
|
+
* @param files 待上传的构建产物文件。
|
|
2963
|
+
* @param options 小程序和长度限制配置。
|
|
2964
|
+
* @returns 第一个超长路径的错误提示;全部合法时返回 `undefined`。
|
|
2965
|
+
*/
|
|
2966
|
+
function validateMiniappUploadPathLengths(files, options) {
|
|
2967
|
+
const maxLength = options.maxLength ?? ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
|
|
2968
|
+
const prefix = `/u/${getMiniProgramUploadAlias(options.miniProgramId)}/${'x'.repeat(MINIAPP_UPLOAD_SESSION_REF_LENGTH)}/`;
|
|
2969
|
+
const prefixLength = textEncoder.encode(prefix).length;
|
|
2970
|
+
const tooLongFile = files.find(file => {
|
|
2971
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2972
|
+
return prefixLength + textEncoder.encode(relativePath).length > maxLength;
|
|
2973
|
+
});
|
|
2974
|
+
return tooLongFile ? `文件路径过长,请缩短构建产物文件名或目录层级:${tooLongFile.relativePath}` : undefined;
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* 创建 v1 整包签名头。
|
|
2978
|
+
*
|
|
2979
|
+
* @param fileCount 参与签名的文件数量。
|
|
2980
|
+
* @returns 包含协议标识和文件数量的字节数组。
|
|
2981
|
+
* @throws 文件数量超出协议范围时抛出错误。
|
|
2982
|
+
*/
|
|
2983
|
+
function createMiniappArtifactSignatureHeader(fileCount) {
|
|
2984
|
+
if (!Number.isInteger(fileCount) || fileCount < 0 || fileCount > 0xffffffff) {
|
|
2985
|
+
throw new Error('构建产物文件数量超出签名协议上限');
|
|
2986
|
+
}
|
|
2987
|
+
return concatBytes(textEncoder.encode(MINIAPP_ARTIFACT_SIGNATURE_MAGIC), encodeUint32BE(fileCount));
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* 创建单个文件的 v1 framing header。
|
|
2991
|
+
*
|
|
2992
|
+
* @param file 已规范化或待校验的构建产物文件。
|
|
2993
|
+
* @returns 包含路径长度、路径和文件大小的字节数组。
|
|
2994
|
+
* @throws 文件路径或大小非法时抛出错误。
|
|
2995
|
+
*/
|
|
2996
|
+
function createMiniappArtifactFileHeader(file) {
|
|
2997
|
+
const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
|
|
2998
|
+
validateArtifactFileSize(file.size, relativePath);
|
|
2999
|
+
const pathBytes = textEncoder.encode(relativePath);
|
|
3000
|
+
return concatBytes(encodeUint32BE(pathBytes.length), pathBytes, encodeUint64BE(file.size));
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* 校验服务端 session 文件映射,并计算仍需上传的文件。
|
|
3004
|
+
*
|
|
3005
|
+
* @param localFiles 本地构建产物文件。
|
|
3006
|
+
* @param serverFiles 服务端签发的文件映射。
|
|
3007
|
+
* @param uploadedKeys 服务端已确认上传成功的 key。
|
|
3008
|
+
* @returns 完整映射、待上传文件和已确认 key。
|
|
3009
|
+
* @throws 服务端映射与本地产物不一致时抛出错误。
|
|
3010
|
+
*/
|
|
3011
|
+
function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys) {
|
|
3012
|
+
const preparedFiles = prepareMiniappArtifactFiles(localFiles);
|
|
3013
|
+
if (serverFiles.length !== preparedFiles.length) {
|
|
3014
|
+
throw new Error(`上传会话文件数量异常:期望 ${preparedFiles.length},实际 ${serverFiles.length}`);
|
|
3015
|
+
}
|
|
3016
|
+
const mappings = new Map();
|
|
3017
|
+
const serverSizes = new Map();
|
|
3018
|
+
const knownKeys = new Set();
|
|
3019
|
+
for (const serverFile of serverFiles) {
|
|
3020
|
+
const relativePath = normalizeMiniappArtifactRelativePath(serverFile.relative_path);
|
|
3021
|
+
if (mappings.has(relativePath)) {
|
|
3022
|
+
throw new Error(`上传会话返回重复文件路径:${relativePath}`);
|
|
3023
|
+
}
|
|
3024
|
+
const key = String(serverFile.key || '');
|
|
3025
|
+
if (!key) {
|
|
3026
|
+
throw new Error(`上传会话未返回文件 key:${relativePath}`);
|
|
3027
|
+
}
|
|
3028
|
+
if (knownKeys.has(key)) {
|
|
3029
|
+
throw new Error(`上传会话返回重复文件 key:${relativePath}`);
|
|
3030
|
+
}
|
|
3031
|
+
const size = Number(serverFile.size);
|
|
3032
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3033
|
+
throw new Error(`上传会话返回文件大小异常:${relativePath}`);
|
|
3034
|
+
}
|
|
3035
|
+
mappings.set(relativePath, key);
|
|
3036
|
+
serverSizes.set(relativePath, size);
|
|
3037
|
+
knownKeys.add(key);
|
|
3038
|
+
}
|
|
3039
|
+
const files = preparedFiles.map(file => {
|
|
3040
|
+
const key = mappings.get(file.relativePath);
|
|
3041
|
+
if (!key) {
|
|
3042
|
+
throw new Error(`上传会话缺少文件映射:${file.relativePath}`);
|
|
3043
|
+
}
|
|
3044
|
+
if (serverSizes.get(file.relativePath) !== file.size) {
|
|
3045
|
+
throw new Error(`上传会话文件大小与本地构建不一致:${file.relativePath}`);
|
|
3046
|
+
}
|
|
3047
|
+
return { file, key };
|
|
3048
|
+
});
|
|
3049
|
+
const completed = new Set();
|
|
3050
|
+
for (const rawKey of uploadedKeys) {
|
|
3051
|
+
const key = String(rawKey || '');
|
|
3052
|
+
if (!knownKeys.has(key)) {
|
|
3053
|
+
throw new Error('上传会话返回了不属于当前产物的已上传文件');
|
|
3054
|
+
}
|
|
3055
|
+
completed.add(key);
|
|
3056
|
+
}
|
|
3057
|
+
return {
|
|
3058
|
+
files,
|
|
3059
|
+
missingFiles: files.filter(file => !completed.has(file.key)),
|
|
3060
|
+
uploadedKeys: [...completed],
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
function normalizeMiniappArtifactRelativePath(relativePath) {
|
|
3064
|
+
const value = String(relativePath || '');
|
|
3065
|
+
if (!value) {
|
|
3066
|
+
throw new Error('构建产物包含空文件路径');
|
|
3067
|
+
}
|
|
3068
|
+
if (value.includes('\\')) {
|
|
3069
|
+
throw new Error(`构建产物路径不能包含反斜杠:${value}`);
|
|
3070
|
+
}
|
|
3071
|
+
if (value.startsWith('/') || value.endsWith('/') || /^[a-zA-Z]:\//.test(value)) {
|
|
3072
|
+
throw new Error(`构建产物必须使用相对文件路径:${value}`);
|
|
3073
|
+
}
|
|
3074
|
+
if (containsControlCharacter(value) || UNPAIRED_SURROGATE_PATTERN.test(value)) {
|
|
3075
|
+
throw new Error(`构建产物路径包含非法字符:${value}`);
|
|
3076
|
+
}
|
|
3077
|
+
if (value.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
|
|
3078
|
+
throw new Error(`构建产物路径包含非法目录段:${value}`);
|
|
3079
|
+
}
|
|
3080
|
+
if (value.includes(',')) {
|
|
3081
|
+
throw new Error(`构建产物路径不能包含逗号:${value}`);
|
|
3082
|
+
}
|
|
3083
|
+
if (value.includes('*') || value.includes('?')) {
|
|
3084
|
+
throw new Error(`构建产物路径不能包含通配符:${value}`);
|
|
3085
|
+
}
|
|
3086
|
+
return value;
|
|
3087
|
+
}
|
|
3088
|
+
function containsControlCharacter(value) {
|
|
3089
|
+
for (const character of value) {
|
|
3090
|
+
const code = character.charCodeAt(0);
|
|
3091
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
|
|
3092
|
+
return true;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
return false;
|
|
3096
|
+
}
|
|
3097
|
+
function validateArtifactFileSize(size, relativePath) {
|
|
3098
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3099
|
+
throw new Error(`构建产物文件大小无效:${relativePath}`);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
function encodeUint32BE(value) {
|
|
3103
|
+
const bytes = new Uint8Array(4);
|
|
3104
|
+
new DataView(bytes.buffer).setUint32(0, value, false);
|
|
3105
|
+
return bytes;
|
|
3106
|
+
}
|
|
3107
|
+
function encodeUint64BE(value) {
|
|
3108
|
+
const bytes = new Uint8Array(8);
|
|
3109
|
+
const view = new DataView(bytes.buffer);
|
|
3110
|
+
view.setUint32(0, Math.floor(value / 0x100000000), false);
|
|
3111
|
+
view.setUint32(4, value % 0x100000000, false);
|
|
3112
|
+
return bytes;
|
|
3113
|
+
}
|
|
3114
|
+
function concatBytes(...parts) {
|
|
3115
|
+
const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
3116
|
+
let offset = 0;
|
|
3117
|
+
for (const part of parts) {
|
|
3118
|
+
result.set(part, offset);
|
|
3119
|
+
offset += part.length;
|
|
3120
|
+
}
|
|
3121
|
+
return result;
|
|
3122
|
+
}
|
|
3123
|
+
function compareBytes(left, right) {
|
|
3124
|
+
const length = Math.min(left.length, right.length);
|
|
3125
|
+
for (let index = 0; index < length; index += 1) {
|
|
3126
|
+
if (left[index] !== right[index]) {
|
|
3127
|
+
return left[index] - right[index];
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return left.length - right.length;
|
|
3131
|
+
}
|
|
2932
3132
|
|
|
2933
3133
|
exports.ACTIVITY_UPLOAD_KEY_MAX_LENGTH = ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
|
|
2934
3134
|
exports.CREATE_USER_MINIPROGRAM_API_PATH = CREATE_USER_MINIPROGRAM_API_PATH;
|
|
@@ -2955,11 +3155,16 @@ exports.USER_MINIPROGRAM_ACCESS_STATUS_API_PATH = USER_MINIPROGRAM_ACCESS_STATUS
|
|
|
2955
3155
|
exports.USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH = USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH;
|
|
2956
3156
|
exports.USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH = USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH;
|
|
2957
3157
|
exports.WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH = WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH;
|
|
3158
|
+
exports.createMiniappArtifactFileHeader = createMiniappArtifactFileHeader;
|
|
3159
|
+
exports.createMiniappArtifactSignatureHeader = createMiniappArtifactSignatureHeader;
|
|
2958
3160
|
exports.getMiniProgramUploadAlias = getMiniProgramUploadAlias;
|
|
2959
3161
|
exports.getMiniappUploadKey = getMiniappUploadKey;
|
|
2960
3162
|
exports.isValidVersion = isValidMiniappManifestVersion;
|
|
2961
3163
|
exports.normalizeRelativePath = normalizeRelativePath;
|
|
3164
|
+
exports.prepareMiniappArtifactFiles = prepareMiniappArtifactFiles;
|
|
2962
3165
|
exports.relativePathContainsNodeModulesSegment = relativePathContainsNodeModulesSegment;
|
|
3166
|
+
exports.resolveMiniappUploadSessionFiles = resolveMiniappUploadSessionFiles;
|
|
2963
3167
|
exports.shouldUploadDistFile = shouldUploadDistFile;
|
|
3168
|
+
exports.validateMiniappUploadPathLengths = validateMiniappUploadPathLengths;
|
|
2964
3169
|
exports.validateUploadPaths = validateUploadPaths;
|
|
2965
3170
|
exports.validateUploadTotalSize = validateUploadTotalSize;
|