@heybox/hb-sdk 0.5.18 → 0.6.1
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/README.md +29 -25
- package/dist/cli-chunks/{context-CKJlNcSN.cjs → context-D0BowvGu.cjs} +37 -1
- package/dist/cli-chunks/{create-OZOIdeBz.cjs → create-C0afRvs9.cjs} +1 -1
- package/dist/cli-chunks/{dev-DXz8izSJ.cjs → dev-CN307p_G.cjs} +117 -3
- package/dist/cli-chunks/{doctor-DjMSJoQ5.cjs → doctor-VW0nKg5q.cjs} +1 -1
- package/dist/cli-chunks/{index-CsOcGUzF.cjs → index-BnCDW-BB.cjs} +21 -16
- package/dist/cli-chunks/{index-Cl5HFTBW.cjs → index-DcZLSI_y.cjs} +2 -2
- package/dist/cli-chunks/{login-Ct-46gLx.cjs → login-BsyENL1d.cjs} +2 -2
- package/dist/cli-chunks/remote-BSvvxvVv.cjs +9905 -0
- package/dist/cli-chunks/{session-dRGPpyS1.cjs → session-0XuUaVOO.cjs} +2 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/mock-host/index.html +40 -0
- package/dist/devtools/mock-host/main.js +85 -0
- package/dist/index.cjs.js +232 -4
- package/dist/index.esm.js +233 -3
- package/dist/miniapp-publish.cjs.js +4 -0
- package/dist/miniapp-publish.esm.js +4 -1
- package/dist/protocol.cjs.js +9 -0
- package/dist/protocol.esm.js +7 -1
- package/dist/vite.cjs.js +9159 -22
- package/dist/vite.esm.js +9159 -23
- package/package.json +19 -7
- package/skill/SKILL.md +23 -23
- package/skill/references/api-protocol.md +11 -2
- package/skill/references/api-root.md +148 -23
- package/skill/references/cli.md +15 -15
- package/skill/references/recipes.md +12 -44
- package/skill/references/safety-boundaries.md +1 -2
- package/skill/scripts/sync-references.mjs +2 -2
- package/skill/skill.json +4 -4
- package/types/cli/auth/base-url.d.ts +20 -0
- package/types/cli/config.d.ts +11 -0
- package/types/core/client.d.ts +7 -0
- package/types/core/csp-violation.d.ts +5 -0
- package/types/core/history-observer.d.ts +4 -0
- package/types/core/version.d.ts +2 -0
- package/types/index.d.ts +0 -2
- package/types/miniapp-manifest/schema.d.ts +6 -1
- package/types/miniapp-manifest/sdk-version-policy.d.ts +9 -0
- package/types/miniapp-publish/index.d.ts +1 -0
- package/types/protocol/constants.d.ts +6 -0
- package/types/protocol/types.d.ts +45 -0
- package/types/protocol.d.ts +2 -2
- package/types/vite/html-policy.d.ts +4 -0
- package/types/vite/index.d.ts +23 -3
- package/types/vite/runtime-gate.d.ts +5 -0
- package/types/vite/sdk-version-gate.d.ts +8 -0
- package/dist/cli-chunks/remote-C77axNSw.cjs +0 -1604
|
@@ -1,1604 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var promises = require('node:readline/promises');
|
|
4
|
-
var index = require('./index-CsOcGUzF.cjs');
|
|
5
|
-
var session = require('./session-dRGPpyS1.cjs');
|
|
6
|
-
var childProcess = require('node:child_process');
|
|
7
|
-
var fs = require('node:fs');
|
|
8
|
-
var fs$1 = require('node:fs/promises');
|
|
9
|
-
var path = require('node:path');
|
|
10
|
-
var context = require('./context-CKJlNcSN.cjs');
|
|
11
|
-
require('node:module');
|
|
12
|
-
require('path');
|
|
13
|
-
require('os');
|
|
14
|
-
require('readline');
|
|
15
|
-
require('tty');
|
|
16
|
-
require('assert');
|
|
17
|
-
require('events');
|
|
18
|
-
require('stream');
|
|
19
|
-
require('buffer');
|
|
20
|
-
require('util');
|
|
21
|
-
require('node:crypto');
|
|
22
|
-
require('fs');
|
|
23
|
-
require('constants');
|
|
24
|
-
|
|
25
|
-
function isValidMiniappManifestVersion(version) {
|
|
26
|
-
return getMiniappManifestVersionError(version) === undefined;
|
|
27
|
-
}
|
|
28
|
-
function validateMiniappManifestVersion(version, sourceLabel = 'manifest.version') {
|
|
29
|
-
const error = getMiniappManifestVersionError(version);
|
|
30
|
-
if (error) {
|
|
31
|
-
throw new Error(`${sourceLabel} ${error}`);
|
|
32
|
-
}
|
|
33
|
-
return String(version).trim();
|
|
34
|
-
}
|
|
35
|
-
function parseMiniappManifestJson(raw, sourceLabel = 'manifest.json') {
|
|
36
|
-
const hadBom = raw.charCodeAt(0) === 0xfeff;
|
|
37
|
-
const text = hadBom ? raw.slice(1) : raw;
|
|
38
|
-
let parsed;
|
|
39
|
-
try {
|
|
40
|
-
parsed = JSON.parse(text);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
throw new Error(`${sourceLabel} 不是合法 JSON:${formatReason(error)}`);
|
|
44
|
-
}
|
|
45
|
-
if (!isManifestRecord(parsed)) {
|
|
46
|
-
throw new Error(`${sourceLabel} 必须是 JSON 对象`);
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
manifest: parsed,
|
|
50
|
-
hadBom,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function validateMiniappManifestForDeploy(manifest) {
|
|
54
|
-
return validateMiniappManifestVersion(manifest.version, 'manifest.version');
|
|
55
|
-
}
|
|
56
|
-
function isManifestRecord(value) {
|
|
57
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
58
|
-
}
|
|
59
|
-
function getMiniappManifestVersionError(version) {
|
|
60
|
-
if (typeof version !== 'string' || version.trim() === '') {
|
|
61
|
-
return `必须是合法 SemVer:${String(version)}`;
|
|
62
|
-
}
|
|
63
|
-
const normalized = version.trim();
|
|
64
|
-
const parsed = context.semverExports.parse(normalized);
|
|
65
|
-
if (!parsed) {
|
|
66
|
-
return `必须是合法 SemVer:${normalized}`;
|
|
67
|
-
}
|
|
68
|
-
if (parsed.build.length > 0) {
|
|
69
|
-
return `不能包含 build metadata:${normalized}`;
|
|
70
|
-
}
|
|
71
|
-
if (parsed.version !== normalized) {
|
|
72
|
-
return `必须是合法 SemVer:${normalized}`;
|
|
73
|
-
}
|
|
74
|
-
if (parsed.major === 0 && parsed.minor === 0 && parsed.patch === 0) {
|
|
75
|
-
return '不能是模板默认版本 0.0.0';
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
function formatReason(error) {
|
|
80
|
-
if (error instanceof Error && error.message) {
|
|
81
|
-
return error.message;
|
|
82
|
-
}
|
|
83
|
-
return String(error);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function getCDNUploadInfo(options, runtime = {}) {
|
|
87
|
-
const body = new URLSearchParams();
|
|
88
|
-
body.set('file_infos', JSON.stringify(options.fileInfos));
|
|
89
|
-
body.set('scope', options.scope);
|
|
90
|
-
body.set('need_cache', options.needCache ? '1' : '0');
|
|
91
|
-
return postHeyboxApi('/bbs/app/api/qcloud/cos/upload/info/v2', body, options.session, runtime);
|
|
92
|
-
}
|
|
93
|
-
async function getCDNUploadToken(options, runtime = {}) {
|
|
94
|
-
const body = new URLSearchParams();
|
|
95
|
-
body.set('bucket', options.bucket);
|
|
96
|
-
body.set('keys', JSON.stringify(options.keys));
|
|
97
|
-
body.set('mimetypes', JSON.stringify(options.mimetypes));
|
|
98
|
-
body.set('is_multipart_upload', String(options.isMultipartUpload));
|
|
99
|
-
return postHeyboxApi('/bbs/app/api/qcloud/cos/upload/token/v2', body, options.session, runtime);
|
|
100
|
-
}
|
|
101
|
-
async function postCDNUploadCallback(options, runtime = {}) {
|
|
102
|
-
const body = new URLSearchParams();
|
|
103
|
-
body.set('keys', JSON.stringify(options.keys));
|
|
104
|
-
const query = options.isFinished ? '?is_finished=true' : '';
|
|
105
|
-
return postHeyboxApi(`/bbs/app/api/qcloud/cos/upload/callback/v2${query}`, body, options.session, runtime);
|
|
106
|
-
}
|
|
107
|
-
async function postHeyboxApi(pathWithQuery, body, session$1, runtime) {
|
|
108
|
-
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
109
|
-
const context = session.createHeyboxOpenPlatformRequestContext(session$1, pathWithQuery, {
|
|
110
|
-
contentType: 'application/x-www-form-urlencoded',
|
|
111
|
-
});
|
|
112
|
-
const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, pathWithQuery, context.platformParams), {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers: context.headers,
|
|
115
|
-
body: body.toString(),
|
|
116
|
-
});
|
|
117
|
-
const result = await session.readHeyboxApiEnvelope(response, { pathWithQuery, requireResult: true });
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const DEFAULT_CONCURRENCY = 4;
|
|
122
|
-
async function runUpload(options, runtime = {}) {
|
|
123
|
-
const logger = runtime.logger ?? index.createCliLogger();
|
|
124
|
-
const concurrency = runtime.concurrency ?? DEFAULT_CONCURRENCY;
|
|
125
|
-
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
126
|
-
const getCDNUploadInfo$1 = runtime.getCDNUploadInfo ?? getCDNUploadInfo;
|
|
127
|
-
const getCDNUploadToken$1 = runtime.getCDNUploadToken ?? getCDNUploadToken;
|
|
128
|
-
const postCDNUploadCallback$1 = runtime.postCDNUploadCallback ?? postCDNUploadCallback;
|
|
129
|
-
const createReadStream = runtime.createReadStream ?? fs.createReadStream;
|
|
130
|
-
const orderedFiles = [...options.files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
131
|
-
const batches = chunkUploadFiles(orderedFiles, context.MINIAPP_UPLOAD_BATCH_SIZE);
|
|
132
|
-
await logger.task(`正在上传 ${orderedFiles.length} 个文件`, async (taskContext) => {
|
|
133
|
-
logger.debug(`上传并发数: ${concurrency}`);
|
|
134
|
-
logger.debug(`上传批次数: ${batches.length}`);
|
|
135
|
-
let completed = 0;
|
|
136
|
-
for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
|
|
137
|
-
const batch = batches[batchIndex];
|
|
138
|
-
logger.debug(`正在处理上传批次 ${batchIndex + 1}/${batches.length},文件数: ${batch.length}`);
|
|
139
|
-
logger.debug('正在获取 CDN 上传信息');
|
|
140
|
-
const fileInfos = createUploadFileInfos(batch, options);
|
|
141
|
-
const uploadInfo = await getCDNUploadInfo$1({ session: options.session, scope: context.MINIAPP_UPLOAD_SCOPE, fileInfos, needCache: false }, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
142
|
-
logger.debug(`CDN bucket: ${uploadInfo.bucket}`);
|
|
143
|
-
logger.debug(`CDN region: ${uploadInfo.region}`);
|
|
144
|
-
validateUploadInfoKeys(batch, uploadInfo.keys, options);
|
|
145
|
-
const indexedFiles = batch.map((file, index) => ({ file, key: uploadInfo.keys[index] }));
|
|
146
|
-
const batchKeys = indexedFiles.map(({ key }) => key);
|
|
147
|
-
logger.debug('正在获取 CDN 上传凭证');
|
|
148
|
-
const uploadToken = await getCDNUploadToken$1({
|
|
149
|
-
session: options.session,
|
|
150
|
-
bucket: uploadInfo.bucket,
|
|
151
|
-
keys: batchKeys,
|
|
152
|
-
mimetypes: indexedFiles.map(({ file }) => file.mimeType),
|
|
153
|
-
isMultipartUpload: 0,
|
|
154
|
-
}, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
155
|
-
const cos = runtime.createCosClient ? runtime.createCosClient({ session: options.session, uploadToken }) : await createDefaultCosClient(uploadToken);
|
|
156
|
-
const queue = indexedFiles.slice();
|
|
157
|
-
const workers = Array.from({ length: Math.min(concurrency, indexedFiles.length) }, async () => {
|
|
158
|
-
while (queue.length > 0) {
|
|
159
|
-
const uploadTask = queue.shift();
|
|
160
|
-
if (!uploadTask) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
await cos.putObject({
|
|
165
|
-
Bucket: uploadInfo.bucket,
|
|
166
|
-
Region: uploadInfo.region,
|
|
167
|
-
Key: uploadTask.key,
|
|
168
|
-
Body: createReadStream(uploadTask.file.absolutePath),
|
|
169
|
-
ContentLength: uploadTask.file.size,
|
|
170
|
-
ContentType: uploadTask.file.mimeType,
|
|
171
|
-
});
|
|
172
|
-
completed += 1;
|
|
173
|
-
taskContext.update(`正在上传 ${completed}/${orderedFiles.length} 个文件`);
|
|
174
|
-
logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] ok ${uploadTask.file.relativePath} (${formatSize(uploadTask.file.size)})`);
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
completed += 1;
|
|
178
|
-
const message = readErrorMessage(error);
|
|
179
|
-
logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] error ${uploadTask.file.relativePath} -> ${message}`);
|
|
180
|
-
throw new index.CliError(`上传文件失败:${uploadTask.file.relativePath} -> ${message}`, readVerboseUploadError(uploadTask.file.relativePath, error));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
await Promise.all(workers);
|
|
185
|
-
logger.debug('正在确认 CDN 上传结果');
|
|
186
|
-
await postCDNUploadCallback$1({ session: options.session, keys: batchKeys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
187
|
-
}
|
|
188
|
-
}, { successText: `已上传 ${orderedFiles.length} 个文件` });
|
|
189
|
-
}
|
|
190
|
-
function chunkUploadFiles(files, batchSize) {
|
|
191
|
-
const batches = [];
|
|
192
|
-
for (let index = 0; index < files.length; index += batchSize) {
|
|
193
|
-
batches.push(files.slice(index, index + batchSize));
|
|
194
|
-
}
|
|
195
|
-
return batches;
|
|
196
|
-
}
|
|
197
|
-
function createUploadFileInfos(files, options) {
|
|
198
|
-
return files.map((file) => ({
|
|
199
|
-
name: file.relativePath.split('/').pop() ?? file.relativePath,
|
|
200
|
-
mimetype: file.mimeType,
|
|
201
|
-
fsize: file.size,
|
|
202
|
-
path: context.getMiniappUploadKey({
|
|
203
|
-
miniProgramId: options.miniProgramId,
|
|
204
|
-
version: options.version,
|
|
205
|
-
relativePath: file.relativePath,
|
|
206
|
-
}),
|
|
207
|
-
}));
|
|
208
|
-
}
|
|
209
|
-
function validateUploadInfoKeys(files, keys, options) {
|
|
210
|
-
if (keys.length !== files.length) {
|
|
211
|
-
throw new index.CliError(`CDN 上传接口返回 key 数量异常:期望 ${files.length},实际 ${keys.length}`);
|
|
212
|
-
}
|
|
213
|
-
for (let index$1 = 0; index$1 < files.length; index$1 += 1) {
|
|
214
|
-
const expectedKey = context.getMiniappUploadKey({
|
|
215
|
-
miniProgramId: options.miniProgramId,
|
|
216
|
-
version: options.version,
|
|
217
|
-
relativePath: files[index$1].relativePath,
|
|
218
|
-
});
|
|
219
|
-
const actualKey = keys[index$1];
|
|
220
|
-
if (actualKey !== expectedKey) {
|
|
221
|
-
throw new index.CliError(`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, [`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, `expected: ${expectedKey}`, `actual: ${actualKey}`].join('\n'));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
async function createDefaultCosClient(uploadToken) {
|
|
226
|
-
const COS = await loadCosConstructor();
|
|
227
|
-
const cos = new COS({
|
|
228
|
-
getAuthorization(_input, callback) {
|
|
229
|
-
callback({
|
|
230
|
-
TmpSecretId: uploadToken.credentials.tmpSecretId,
|
|
231
|
-
TmpSecretKey: uploadToken.credentials.tmpSecretKey,
|
|
232
|
-
XCosSecurityToken: uploadToken.credentials.sessionToken,
|
|
233
|
-
StartTime: uploadToken.startTime,
|
|
234
|
-
ExpiredTime: uploadToken.expiredTime,
|
|
235
|
-
});
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
return {
|
|
239
|
-
putObject(params) {
|
|
240
|
-
return new Promise((resolve, reject) => {
|
|
241
|
-
cos.putObject(params, (err, result) => {
|
|
242
|
-
if (err) {
|
|
243
|
-
reject(err instanceof Error ? err : new Error(readErrorMessage(err)));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
resolve(result);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
async function loadCosConstructor() {
|
|
253
|
-
const cosModule = await Promise.resolve().then(function () { return require('./index-Cl5HFTBW.cjs'); }).then(function (n) { return n.index; });
|
|
254
|
-
return cosModule.default;
|
|
255
|
-
}
|
|
256
|
-
function formatSize(bytes) {
|
|
257
|
-
if (bytes < 1024) {
|
|
258
|
-
return `${bytes} B`;
|
|
259
|
-
}
|
|
260
|
-
if (bytes < 1024 * 1024) {
|
|
261
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
262
|
-
}
|
|
263
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
264
|
-
}
|
|
265
|
-
function readErrorMessage(error) {
|
|
266
|
-
if (error instanceof Error && error.message) {
|
|
267
|
-
return error.message;
|
|
268
|
-
}
|
|
269
|
-
if (isRecord(error)) {
|
|
270
|
-
const primary = readStringField(error, ['message', 'Message', 'errorMessage', 'msg']);
|
|
271
|
-
const code = readStringField(error, ['code', 'Code', 'errorCode', 'name']);
|
|
272
|
-
const statusCode = readStringField(error, ['statusCode']);
|
|
273
|
-
return [code, statusCode, primary].filter(Boolean).join(' ') || JSON.stringify(error);
|
|
274
|
-
}
|
|
275
|
-
return String(error);
|
|
276
|
-
}
|
|
277
|
-
function readVerboseUploadError(relativePath, error) {
|
|
278
|
-
return [`上传文件失败:${relativePath}`, `原始错误:${readErrorMessage(error)}`].join('\n');
|
|
279
|
-
}
|
|
280
|
-
function readStringField(record, fields) {
|
|
281
|
-
for (const field of fields) {
|
|
282
|
-
const value = record[field];
|
|
283
|
-
if (typeof value === 'string' && value.trim()) {
|
|
284
|
-
return value.trim();
|
|
285
|
-
}
|
|
286
|
-
if (typeof value === 'number') {
|
|
287
|
-
return String(value);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return '';
|
|
291
|
-
}
|
|
292
|
-
function isRecord(value) {
|
|
293
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function precheckUserMiniprogramVersion(options, runtime = {}) {
|
|
297
|
-
return postUserMiniprogramVersionForm(context.PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, options, {
|
|
298
|
-
name: options.name,
|
|
299
|
-
icon_url: options.iconUrl,
|
|
300
|
-
cover_image_urls: options.coverImageUrls.join(','),
|
|
301
|
-
...(options.version ? { version: options.version } : {}),
|
|
302
|
-
...(options.source_version ? { source_version: options.source_version } : {}),
|
|
303
|
-
release_note: options.releaseNote,
|
|
304
|
-
auto_publish: String(options.autoPublish),
|
|
305
|
-
}, runtime);
|
|
306
|
-
}
|
|
307
|
-
async function submitUserMiniprogramAudit(options, runtime = {}) {
|
|
308
|
-
return postUserMiniprogramVersionForm(context.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, options, {
|
|
309
|
-
name: options.name,
|
|
310
|
-
icon_url: options.iconUrl,
|
|
311
|
-
cover_image_urls: options.coverImageUrls.join(','),
|
|
312
|
-
...(options.manifest ? { manifest: JSON.stringify(options.manifest) } : {}),
|
|
313
|
-
...(options.source_version ? { source_version: options.source_version } : {}),
|
|
314
|
-
release_note: options.releaseNote,
|
|
315
|
-
auto_publish: String(options.autoPublish),
|
|
316
|
-
}, runtime);
|
|
317
|
-
}
|
|
318
|
-
async function postUserMiniprogramVersionForm(path, options, fields, runtime) {
|
|
319
|
-
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
320
|
-
const context = session.createHeyboxOpenPlatformRequestContext(options.session, path, {
|
|
321
|
-
contentType: 'application/x-www-form-urlencoded',
|
|
322
|
-
});
|
|
323
|
-
const body = new URLSearchParams();
|
|
324
|
-
body.set('mini_program_id', options.miniProgramId);
|
|
325
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
326
|
-
body.set(key, value);
|
|
327
|
-
}
|
|
328
|
-
const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
|
|
329
|
-
method: 'POST',
|
|
330
|
-
headers: context.headers,
|
|
331
|
-
body: body.toString(),
|
|
332
|
-
});
|
|
333
|
-
const result = await session.readHeyboxApiEnvelope(response, {
|
|
334
|
-
pathWithQuery: path,
|
|
335
|
-
});
|
|
336
|
-
return result ?? {};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const SUPPORTED_PACKAGE_MANAGERS = [
|
|
340
|
-
{ name: 'pnpm', lockfile: 'pnpm-lock.yaml' },
|
|
341
|
-
{ name: 'yarn', lockfile: 'yarn.lock' },
|
|
342
|
-
{ name: 'npm', lockfile: 'package-lock.json' },
|
|
343
|
-
];
|
|
344
|
-
const MIME_BY_EXT = {
|
|
345
|
-
'.css': 'text/css',
|
|
346
|
-
'.gif': 'image/gif',
|
|
347
|
-
'.html': 'text/html',
|
|
348
|
-
'.jpeg': 'image/jpeg',
|
|
349
|
-
'.jpg': 'image/jpeg',
|
|
350
|
-
'.js': 'application/javascript',
|
|
351
|
-
'.json': 'application/json',
|
|
352
|
-
'.png': 'image/png',
|
|
353
|
-
'.svg': 'image/svg+xml',
|
|
354
|
-
'.ttf': 'font/ttf',
|
|
355
|
-
'.txt': 'text/plain',
|
|
356
|
-
'.wasm': 'application/wasm',
|
|
357
|
-
'.webp': 'image/webp',
|
|
358
|
-
'.woff': 'font/woff',
|
|
359
|
-
'.woff2': 'font/woff2',
|
|
360
|
-
};
|
|
361
|
-
async function runDeployCommand(options, runtime = {}) {
|
|
362
|
-
const logger = runtime.logger ?? index.createCliLogger();
|
|
363
|
-
const cwd = runtime.cwd ?? process.cwd();
|
|
364
|
-
const projectRoot = findProjectRoot(cwd);
|
|
365
|
-
const packageJson = await readPackageJson(projectRoot);
|
|
366
|
-
const miniProgramId = packageJson.heybox?.miniProgramId;
|
|
367
|
-
const autoPublish = Boolean(options.autoPublish);
|
|
368
|
-
const env = options.env ?? process.env;
|
|
369
|
-
const apiBaseUrl = session.resolveHeyboxApiBaseUrl({ ...options, env });
|
|
370
|
-
const loginBaseUrl = session.resolveHeyboxLoginBaseUrl({ ...options, env });
|
|
371
|
-
if (typeof miniProgramId !== 'string' || !miniProgramId.trim()) {
|
|
372
|
-
throw new Error('未在 package.json 中找到 heybox.miniProgramId,请先配置 mini program id');
|
|
373
|
-
}
|
|
374
|
-
const miniProgramProfile = readMiniProgramVersionProfile(packageJson);
|
|
375
|
-
const fromVersion = options.fromVersion?.trim();
|
|
376
|
-
if (fromVersion && options.skipBuild) {
|
|
377
|
-
throw new Error('--from-version 与 --skip-build 不能同时使用:--from-version 会复用远端历史产物,不读取本地 dist/');
|
|
378
|
-
}
|
|
379
|
-
const releaseNote = await resolveDeployReleaseNote(options, runtime);
|
|
380
|
-
const session$1 = await logger.task('正在校验 Heybox 登录态', () => (runtime.requireAuthSession ?? session.requireHeyboxAuthSession)({ loginBaseUrl }), { successText: 'Heybox 登录态有效' });
|
|
381
|
-
logger.debug(`Heybox API: ${apiBaseUrl}`);
|
|
382
|
-
let precheckVersion;
|
|
383
|
-
let parsedManifest;
|
|
384
|
-
const historyArtifact = fromVersion ? { source_version: fromVersion } : {};
|
|
385
|
-
if (fromVersion) {
|
|
386
|
-
const precheck = await runVersionPrecheck({
|
|
387
|
-
session: session$1,
|
|
388
|
-
miniProgramId,
|
|
389
|
-
...miniProgramProfile,
|
|
390
|
-
...historyArtifact,
|
|
391
|
-
releaseNote,
|
|
392
|
-
autoPublish,
|
|
393
|
-
}, runtime, apiBaseUrl, projectRoot, logger);
|
|
394
|
-
precheckVersion = precheck.version || precheck.version_normalized || fromVersion;
|
|
395
|
-
}
|
|
396
|
-
else if (!options.skipBuild) {
|
|
397
|
-
if (typeof packageJson.scripts?.build !== 'string') {
|
|
398
|
-
throw new Error('package.json scripts.build 未定义,请添加 build 脚本或使用 --skip-build 跳过构建');
|
|
399
|
-
}
|
|
400
|
-
precheckVersion = validateVersionForDeploy(packageJson.version, 'package.json.version');
|
|
401
|
-
await runVersionPrecheck({
|
|
402
|
-
session: session$1,
|
|
403
|
-
miniProgramId,
|
|
404
|
-
...miniProgramProfile,
|
|
405
|
-
version: precheckVersion,
|
|
406
|
-
releaseNote,
|
|
407
|
-
autoPublish,
|
|
408
|
-
}, runtime, apiBaseUrl, projectRoot, logger);
|
|
409
|
-
const pm = detectPackageManager(projectRoot);
|
|
410
|
-
logger.info(`开始构建: ${pm} run build`);
|
|
411
|
-
await runBuildScript(pm, projectRoot, runtime.spawn ?? childProcess.spawn, {
|
|
412
|
-
outputMode: options.buildOutputMode ?? 'inherit',
|
|
413
|
-
stderr: runtime.stderr,
|
|
414
|
-
});
|
|
415
|
-
logger.success('构建完成');
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
parsedManifest = await readDeployManifest(projectRoot, logger);
|
|
419
|
-
precheckVersion = parsedManifest.version;
|
|
420
|
-
await runVersionPrecheck({
|
|
421
|
-
session: session$1,
|
|
422
|
-
miniProgramId,
|
|
423
|
-
...miniProgramProfile,
|
|
424
|
-
version: precheckVersion,
|
|
425
|
-
releaseNote,
|
|
426
|
-
autoPublish,
|
|
427
|
-
}, runtime, apiBaseUrl, projectRoot, logger);
|
|
428
|
-
}
|
|
429
|
-
let submitAuditResult;
|
|
430
|
-
let version = precheckVersion;
|
|
431
|
-
let manifest;
|
|
432
|
-
if (!fromVersion) {
|
|
433
|
-
const distDir = path.join(projectRoot, 'dist');
|
|
434
|
-
parsedManifest ??= await readDeployManifest(projectRoot, logger);
|
|
435
|
-
({ manifest, version } = parsedManifest);
|
|
436
|
-
if (version !== precheckVersion) {
|
|
437
|
-
throw new Error(`dist/manifest.json.version (${version}) 与预检版本 (${precheckVersion}) 不一致,请重新 build 后再 deploy`);
|
|
438
|
-
}
|
|
439
|
-
const uploadFiles = await logger.task('正在校验 dist 上传文件', async () => {
|
|
440
|
-
const allFiles = await walkDistFiles(distDir);
|
|
441
|
-
const blocked = allFiles.find((entry) => context.relativePathContainsNodeModulesSegment(entry.relativePath));
|
|
442
|
-
if (blocked) {
|
|
443
|
-
throw new Error(`dist 目录含 node_modules:${blocked.relativePath}`);
|
|
444
|
-
}
|
|
445
|
-
const files = allFiles.filter((entry) => context.shouldUploadDistFile(entry.relativePath));
|
|
446
|
-
const pathError = context.validateUploadPaths(files, { miniProgramId, version, maxLength: context.ACTIVITY_UPLOAD_KEY_MAX_LENGTH });
|
|
447
|
-
if (pathError) {
|
|
448
|
-
throw new Error(pathError);
|
|
449
|
-
}
|
|
450
|
-
const sizeError = context.validateUploadTotalSize(files);
|
|
451
|
-
if (sizeError) {
|
|
452
|
-
throw new Error(sizeError);
|
|
453
|
-
}
|
|
454
|
-
logger.debug(`待上传文件数: ${files.length}`);
|
|
455
|
-
return files;
|
|
456
|
-
}, { successText: 'dist 上传文件校验完成' });
|
|
457
|
-
try {
|
|
458
|
-
await (runtime.runUpload ?? runUpload)({ session: session$1, miniProgramId, version, files: uploadFiles }, { baseUrl: apiBaseUrl, fetchImpl: runtime.fetchImpl, logger });
|
|
459
|
-
}
|
|
460
|
-
catch (error) {
|
|
461
|
-
throw translateHeyboxDeployError(error, { projectRoot, stage: 'upload', version });
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const submitOptions = fromVersion
|
|
466
|
-
? { session: session$1, miniProgramId, ...miniProgramProfile, ...historyArtifact, releaseNote, autoPublish }
|
|
467
|
-
: { session: session$1, miniProgramId, ...miniProgramProfile, manifest, releaseNote, autoPublish };
|
|
468
|
-
submitAuditResult = await logger.task('正在提交审核', () => (runtime.submitUserMiniprogramAudit ?? submitUserMiniprogramAudit)(submitOptions, {
|
|
469
|
-
baseUrl: apiBaseUrl,
|
|
470
|
-
fetchImpl: runtime.fetchImpl,
|
|
471
|
-
}), { successText: '审核提交完成' });
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
throw translateHeyboxDeployError(error, { projectRoot, stage: 'submitAudit', version });
|
|
475
|
-
}
|
|
476
|
-
printDeploySuccess({
|
|
477
|
-
autoPublish,
|
|
478
|
-
logger,
|
|
479
|
-
miniProgramId,
|
|
480
|
-
mode: options.successOutputMode ?? 'legacy',
|
|
481
|
-
protocolString: submitAuditResult.protocol_string,
|
|
482
|
-
version,
|
|
483
|
-
});
|
|
484
|
-
return {
|
|
485
|
-
autoPublish,
|
|
486
|
-
changed: true,
|
|
487
|
-
miniProgramId,
|
|
488
|
-
...(submitAuditResult.protocol_string ? { protocolString: submitAuditResult.protocol_string } : {}),
|
|
489
|
-
version,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
function printDeploySuccess(options) {
|
|
493
|
-
options.logger.success(`提交审核成功:${options.miniProgramId} ${options.version}`);
|
|
494
|
-
if (options.mode === 'remote') {
|
|
495
|
-
options.logger.info(`发布策略:${options.autoPublish ? '审核通过后自动发布' : '审核通过后需手动发布'}`);
|
|
496
|
-
if (options.protocolString) {
|
|
497
|
-
options.logger.info(`Protocol: ${options.protocolString}`);
|
|
498
|
-
options.logger.info('允许他人预览:hb-sdk remote allowlist add <heybox_id>');
|
|
499
|
-
}
|
|
500
|
-
options.logger.info(options.autoPublish
|
|
501
|
-
? '下一步:hb-sdk remote versions'
|
|
502
|
-
: `下一步:hb-sdk remote versions;审核通过后运行 hb-sdk remote release ${options.version}`);
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
options.logger.info(`发布策略:${options.autoPublish ? '审核通过后自动发布' : '审核通过后需在开放平台手动发布'}`);
|
|
506
|
-
if (options.protocolString) {
|
|
507
|
-
options.logger.info(`Protocol: ${options.protocolString}`);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function findProjectRoot(startDir) {
|
|
511
|
-
let current = path.resolve(startDir);
|
|
512
|
-
while (true) {
|
|
513
|
-
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
514
|
-
return current;
|
|
515
|
-
}
|
|
516
|
-
const parent = path.dirname(current);
|
|
517
|
-
if (parent === current) {
|
|
518
|
-
throw new Error('当前目录或父目录未找到 package.json');
|
|
519
|
-
}
|
|
520
|
-
current = parent;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async function readPackageJson(projectRoot) {
|
|
524
|
-
return JSON.parse(await fs$1.readFile(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
525
|
-
}
|
|
526
|
-
function readMiniProgramVersionProfile(packageJson) {
|
|
527
|
-
const profile = packageJson.heybox?.miniProgramProfile;
|
|
528
|
-
return {
|
|
529
|
-
name: readRequiredProfileText(profile?.name, 'heybox.miniProgramProfile.name'),
|
|
530
|
-
iconUrl: readRequiredHttpsUrl(profile?.iconUrl, 'heybox.miniProgramProfile.iconUrl'),
|
|
531
|
-
coverImageUrls: readCoverImageUrls(profile?.coverImageUrls),
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
function readRequiredProfileText(value, pathLabel) {
|
|
535
|
-
const text = typeof value === 'string' ? value.trim() : '';
|
|
536
|
-
if (!text) {
|
|
537
|
-
throw new Error(`${pathLabel} 不能为空;请在 package.json 配置本次提交审核的小程序资料`);
|
|
538
|
-
}
|
|
539
|
-
return text;
|
|
540
|
-
}
|
|
541
|
-
function readRequiredHttpsUrl(value, pathLabel) {
|
|
542
|
-
const text = readRequiredProfileText(value, pathLabel);
|
|
543
|
-
if (!/^https:\/\//i.test(text)) {
|
|
544
|
-
throw new Error(`${pathLabel} 必须是 HTTPS 地址`);
|
|
545
|
-
}
|
|
546
|
-
return text;
|
|
547
|
-
}
|
|
548
|
-
function readCoverImageUrls(value) {
|
|
549
|
-
if (!Array.isArray(value)) {
|
|
550
|
-
throw new Error('heybox.miniProgramProfile.coverImageUrls 至少配置 1 张 HTTPS 图片');
|
|
551
|
-
}
|
|
552
|
-
const urls = value.map((item) => (typeof item === 'string' ? item.trim() : '')).filter(Boolean);
|
|
553
|
-
if (urls.length === 0) {
|
|
554
|
-
throw new Error('heybox.miniProgramProfile.coverImageUrls 至少配置 1 张 HTTPS 图片');
|
|
555
|
-
}
|
|
556
|
-
const invalidUrl = urls.find((url) => !/^https:\/\//i.test(url));
|
|
557
|
-
if (invalidUrl) {
|
|
558
|
-
throw new Error(`heybox.miniProgramProfile.coverImageUrls 必须都是 HTTPS 地址:${invalidUrl}`);
|
|
559
|
-
}
|
|
560
|
-
return urls;
|
|
561
|
-
}
|
|
562
|
-
async function readDeployManifest(projectRoot, logger) {
|
|
563
|
-
const distDir = path.join(projectRoot, 'dist');
|
|
564
|
-
const manifestPath = path.join(distDir, 'manifest.json');
|
|
565
|
-
const entryHtmlPath = path.join(distDir, 'index.html');
|
|
566
|
-
if (!fs.existsSync(manifestPath)) {
|
|
567
|
-
throw new Error('未找到 dist/manifest.json,可能需要先跑 build 或检查 vite miniappManifest 插件');
|
|
568
|
-
}
|
|
569
|
-
if (!fs.existsSync(entryHtmlPath)) {
|
|
570
|
-
throw new Error('未找到 dist/index.html,dist 目录残缺,请重新 build');
|
|
571
|
-
}
|
|
572
|
-
const { manifest, hadBom } = parseMiniappManifestJson(await fs$1.readFile(manifestPath, 'utf8'), 'dist/manifest.json');
|
|
573
|
-
if (hadBom) {
|
|
574
|
-
logger.warn('dist/manifest.json 包含 BOM,已自动剥离');
|
|
575
|
-
}
|
|
576
|
-
return {
|
|
577
|
-
manifest,
|
|
578
|
-
version: validateMiniappManifestForDeploy(manifest),
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function validateVersionForDeploy(version, sourceLabel) {
|
|
582
|
-
if (typeof version !== 'string' || !isValidMiniappManifestVersion(version)) {
|
|
583
|
-
throw new Error(`${sourceLabel} 必须是合法 SemVer,允许 prerelease,不允许 build metadata 和 0.0.0:${String(version)}`);
|
|
584
|
-
}
|
|
585
|
-
return version.trim();
|
|
586
|
-
}
|
|
587
|
-
async function resolveDeployReleaseNote(options, runtime = {}) {
|
|
588
|
-
const rawReleaseNote = options.releaseNote ?? (await maybePromptReleaseNote(runtime));
|
|
589
|
-
return validateDeployReleaseNote(rawReleaseNote);
|
|
590
|
-
}
|
|
591
|
-
function validateDeployReleaseNote(value) {
|
|
592
|
-
const releaseNote = String(value ?? '').trim();
|
|
593
|
-
if (!releaseNote) {
|
|
594
|
-
throw new Error('release note 不能为空,请通过 --release-note <text> 传入发布日志');
|
|
595
|
-
}
|
|
596
|
-
if (Array.from(releaseNote).length > 500) {
|
|
597
|
-
throw new Error('release note 不能超过 500 个字符');
|
|
598
|
-
}
|
|
599
|
-
return releaseNote;
|
|
600
|
-
}
|
|
601
|
-
async function maybePromptReleaseNote(runtime) {
|
|
602
|
-
if (runtime.promptReleaseNote) {
|
|
603
|
-
return runtime.promptReleaseNote();
|
|
604
|
-
}
|
|
605
|
-
const isTTY = runtime.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
606
|
-
if (!isTTY) {
|
|
607
|
-
throw new Error('非交互环境执行 hb-sdk remote deploy 必须传 --release-note <text>');
|
|
608
|
-
}
|
|
609
|
-
const input = runtime.stdin ?? process.stdin;
|
|
610
|
-
const output = runtime.stdout ?? process.stdout;
|
|
611
|
-
const rl = promises.createInterface({ input, output });
|
|
612
|
-
try {
|
|
613
|
-
return await rl.question('请输入发布日志 release note: ');
|
|
614
|
-
}
|
|
615
|
-
finally {
|
|
616
|
-
rl.close();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
async function runVersionPrecheck(options, runtime, apiBaseUrl, projectRoot, logger) {
|
|
620
|
-
try {
|
|
621
|
-
const label = options.version ?? options.source_version ?? '';
|
|
622
|
-
return await logger.task(`正在预检版本 ${label}`, () => (runtime.precheckUserMiniprogramVersion ?? precheckUserMiniprogramVersion)(options, {
|
|
623
|
-
baseUrl: apiBaseUrl,
|
|
624
|
-
fetchImpl: runtime.fetchImpl,
|
|
625
|
-
}), { successText: `版本 ${label} 预检通过` });
|
|
626
|
-
}
|
|
627
|
-
catch (error) {
|
|
628
|
-
throw translateHeyboxDeployError(error, { projectRoot, stage: 'precheck', version: options.version ?? options.source_version ?? '' });
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
function detectPackageManager(projectRoot) {
|
|
632
|
-
for (const candidate of SUPPORTED_PACKAGE_MANAGERS) {
|
|
633
|
-
if (fs.existsSync(path.join(projectRoot, candidate.lockfile))) {
|
|
634
|
-
return candidate.name;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
return 'npm';
|
|
638
|
-
}
|
|
639
|
-
async function runBuildScript(pm, cwd, spawnImpl, options) {
|
|
640
|
-
await new Promise((resolve, reject) => {
|
|
641
|
-
const pipeStdoutToStderr = options.outputMode === 'stderr';
|
|
642
|
-
const child = spawnImpl(pm, ['run', 'build'], {
|
|
643
|
-
cwd,
|
|
644
|
-
stdio: pipeStdoutToStderr ? ['inherit', 'pipe', 'inherit'] : 'inherit',
|
|
645
|
-
});
|
|
646
|
-
if (pipeStdoutToStderr) {
|
|
647
|
-
child.stdout?.on('data', (chunk) => {
|
|
648
|
-
(options.stderr ?? process.stderr).write(chunk);
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
child.on('error', reject);
|
|
652
|
-
child.on('exit', (code) => {
|
|
653
|
-
if (code === 0) {
|
|
654
|
-
resolve();
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
reject(new Error(`${pm} run build exited with code ${code ?? 'null'}`));
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
async function walkDistFiles(distDir) {
|
|
663
|
-
const results = [];
|
|
664
|
-
await walk(distDir, '');
|
|
665
|
-
return results;
|
|
666
|
-
async function walk(absDir, relDir) {
|
|
667
|
-
const entries = await fs$1.readdir(absDir, { withFileTypes: true });
|
|
668
|
-
for (const entry of entries) {
|
|
669
|
-
const absPath = path.join(absDir, entry.name);
|
|
670
|
-
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
671
|
-
const link = await fs$1.lstat(absPath);
|
|
672
|
-
if (link.isSymbolicLink()) {
|
|
673
|
-
throw new Error(`dist 目录包含 symlink,拒绝处理:${relPath}`);
|
|
674
|
-
}
|
|
675
|
-
if (entry.isDirectory()) {
|
|
676
|
-
await walk(absPath, relPath);
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
if (!entry.isFile()) {
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
const fileStat = await fs$1.stat(absPath);
|
|
683
|
-
results.push({
|
|
684
|
-
relativePath: context.normalizeRelativePath(relPath),
|
|
685
|
-
absolutePath: absPath,
|
|
686
|
-
size: fileStat.size,
|
|
687
|
-
mimeType: inferMimeType(entry.name),
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
function inferMimeType(fileName) {
|
|
693
|
-
return MIME_BY_EXT[path.extname(fileName).toLowerCase()] || 'application/octet-stream';
|
|
694
|
-
}
|
|
695
|
-
function translateHeyboxDeployError(error, options) {
|
|
696
|
-
const message = error instanceof Error && error.message ? error.message : String(error);
|
|
697
|
-
if (isVersionLifecycleConflictMessage(message) && options.stage !== 'upload') {
|
|
698
|
-
const packageJsonPath = path.join(options.projectRoot, 'package.json');
|
|
699
|
-
const nextVersion = suggestNextPatchVersion(options.version);
|
|
700
|
-
return new index.CliError([
|
|
701
|
-
`当前小程序版本 ${options.version} 已进入线上生命周期或正在审核,不能复用。`,
|
|
702
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
703
|
-
].join('\n'), [
|
|
704
|
-
`当前小程序版本 ${options.version} 已进入线上生命周期或正在审核,不能复用。`,
|
|
705
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
706
|
-
`原始错误:${readVerboseDeployErrorMessage(error)}`,
|
|
707
|
-
].join('\n'));
|
|
708
|
-
}
|
|
709
|
-
if (isDuplicateUploadErrorMessage(message) && options.stage === 'upload') {
|
|
710
|
-
const packageJsonPath = path.join(options.projectRoot, 'package.json');
|
|
711
|
-
const nextVersion = suggestNextPatchVersion(options.version);
|
|
712
|
-
return new index.CliError([
|
|
713
|
-
`当前小程序版本 ${options.version} 的上传文件已存在,不能覆盖同版本发布产物。`,
|
|
714
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
715
|
-
].join('\n'), [
|
|
716
|
-
`当前小程序版本 ${options.version} 的上传文件已存在,不能覆盖同版本发布产物。`,
|
|
717
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
718
|
-
`原始错误:${readVerboseDeployErrorMessage(error)}`,
|
|
719
|
-
].join('\n'));
|
|
720
|
-
}
|
|
721
|
-
if (isAuthExpiredErrorMessage(message)) {
|
|
722
|
-
if (options.stage === 'upload') {
|
|
723
|
-
return new index.CliError([
|
|
724
|
-
'CDN 上传接口拒绝了当前登录态或请求上下文。',
|
|
725
|
-
'如果版本预检刚通过,通常不是本地登录缓存损坏;请用 `--verbose` 查看 upload/info 原始返回,并确认当前 API 环境支持 CDN 上传接口。',
|
|
726
|
-
].join('\n'), [
|
|
727
|
-
'CDN 上传接口拒绝了当前登录态或请求上下文。',
|
|
728
|
-
'如果版本预检刚通过,通常不是本地登录缓存损坏;请用 `--verbose` 查看 upload/info 原始返回,并确认当前 API 环境支持 CDN 上传接口。',
|
|
729
|
-
`原始错误:${readVerboseDeployErrorMessage(error)}`,
|
|
730
|
-
].join('\n'));
|
|
731
|
-
}
|
|
732
|
-
return new index.CliError(['Heybox 登录态已失效(服务端 session 已过期或被清除)。', '请运行 `hb-sdk login` 重新登录后再执行 deploy。'].join('\n'), ['Heybox 登录态已失效(服务端 session 已过期或被清除)。', '请运行 `hb-sdk login` 重新登录后再执行 deploy。', `原始错误:${message}`].join('\n'));
|
|
733
|
-
}
|
|
734
|
-
return error instanceof Error ? error : new Error(message);
|
|
735
|
-
}
|
|
736
|
-
function isDuplicateUploadErrorMessage(message) {
|
|
737
|
-
return /重名|duplicate|already exists|exists/i.test(message);
|
|
738
|
-
}
|
|
739
|
-
function isVersionLifecycleConflictMessage(message) {
|
|
740
|
-
return /AlreadyExists\(6\)|不能复用|已进入线上生命周期|正在审核|活跃候选版本|同版本/i.test(message);
|
|
741
|
-
}
|
|
742
|
-
function isAuthExpiredErrorMessage(message) {
|
|
743
|
-
return /请重新登录|未登录|登录已失效|登录态已失效|unauthorized|未授权/i.test(message);
|
|
744
|
-
}
|
|
745
|
-
function readVerboseDeployErrorMessage(error) {
|
|
746
|
-
if (error instanceof index.CliError && error.verboseMessage) {
|
|
747
|
-
return error.verboseMessage;
|
|
748
|
-
}
|
|
749
|
-
return error instanceof Error && error.message ? error.message : String(error);
|
|
750
|
-
}
|
|
751
|
-
function suggestNextPatchVersion(version) {
|
|
752
|
-
const matched = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
753
|
-
if (!matched) {
|
|
754
|
-
return '';
|
|
755
|
-
}
|
|
756
|
-
return `${matched[1]}.${matched[2]}.${Number(matched[3]) + 1}`;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
async function runRemoteCommand(options, runtime = {}) {
|
|
760
|
-
if (options.command === 'deploy') {
|
|
761
|
-
return runRemoteDeployCommand(options, runtime);
|
|
762
|
-
}
|
|
763
|
-
const context = await createRemoteCommandContext(options, runtime);
|
|
764
|
-
switch (options.command) {
|
|
765
|
-
case 'access':
|
|
766
|
-
return remoteAccess(options, context, runtime);
|
|
767
|
-
case 'entity:list':
|
|
768
|
-
return remoteEntityList(options, context, runtime);
|
|
769
|
-
case 'entity:current':
|
|
770
|
-
return remoteEntityCurrent(options, context, runtime);
|
|
771
|
-
case 'entity:switch':
|
|
772
|
-
return remoteEntitySwitch(options, context, runtime);
|
|
773
|
-
case 'list':
|
|
774
|
-
return remoteList(options, context, runtime);
|
|
775
|
-
case 'create':
|
|
776
|
-
return remoteCreate(options, context, runtime);
|
|
777
|
-
case 'bind':
|
|
778
|
-
return remoteBind(options, context, runtime);
|
|
779
|
-
case 'info':
|
|
780
|
-
return remoteInfo(options, context, runtime);
|
|
781
|
-
case 'allowlist:list':
|
|
782
|
-
return remoteAllowlistList(options, context, runtime);
|
|
783
|
-
case 'allowlist:add':
|
|
784
|
-
case 'allowlist:remove':
|
|
785
|
-
case 'allowlist:set':
|
|
786
|
-
return remoteAllowlistWrite(options, context, runtime);
|
|
787
|
-
case 'cloud:leaderboard:create':
|
|
788
|
-
return remoteLeaderboardCreate(options, context, runtime);
|
|
789
|
-
case 'cloud:leaderboard:get':
|
|
790
|
-
return remoteLeaderboardGet(options, context, runtime);
|
|
791
|
-
case 'cloud:leaderboard:list':
|
|
792
|
-
return remoteLeaderboardList(options, context, runtime);
|
|
793
|
-
case 'cloud:leaderboard:delete':
|
|
794
|
-
return remoteLeaderboardDelete(options, context, runtime);
|
|
795
|
-
case 'versions':
|
|
796
|
-
return remoteVersions(options, context, runtime);
|
|
797
|
-
case 'preview':
|
|
798
|
-
return remotePreview(options, context, runtime);
|
|
799
|
-
case 'release':
|
|
800
|
-
case 'withdraw':
|
|
801
|
-
case 'take-down':
|
|
802
|
-
case 'reopen':
|
|
803
|
-
return remoteDangerousWrite(options, context, runtime);
|
|
804
|
-
case 'square:hide':
|
|
805
|
-
case 'square:show':
|
|
806
|
-
return remoteSquareWrite(options, context, runtime);
|
|
807
|
-
default:
|
|
808
|
-
throw new Error(`不支持的 remote 命令:${String(options.command)}`);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
async function runRemoteDeployCommand(options, runtime) {
|
|
812
|
-
const logger = resolveRemoteLogger(options, runtime);
|
|
813
|
-
const context = await createRemoteCommandContext(options, runtime);
|
|
814
|
-
const miniProgramId = requireBinding(context);
|
|
815
|
-
await assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId);
|
|
816
|
-
const deployRuntime = {
|
|
817
|
-
cwd: runtime.cwd,
|
|
818
|
-
fetchImpl: runtime.fetchImpl,
|
|
819
|
-
isTTY: runtime.isTTY,
|
|
820
|
-
logger: options.json ? createJsonSafeLogger(runtime) : logger,
|
|
821
|
-
requireAuthSession: runtime.requireAuthSession,
|
|
822
|
-
stdin: runtime.stdin,
|
|
823
|
-
stdout: runtime.stdout,
|
|
824
|
-
};
|
|
825
|
-
const deployOptions = {
|
|
826
|
-
allowUnsafeApiBaseUrl: options.allowUnsafeApiBaseUrl,
|
|
827
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
828
|
-
autoPublish: options.autoPublish,
|
|
829
|
-
env: options.env,
|
|
830
|
-
fromVersion: options.fromVersion,
|
|
831
|
-
loginBaseUrl: options.loginBaseUrl,
|
|
832
|
-
releaseNote: options.releaseNote,
|
|
833
|
-
skipBuild: options.skipBuild,
|
|
834
|
-
buildOutputMode: options.json ? 'stderr' : 'inherit',
|
|
835
|
-
successOutputMode: 'remote',
|
|
836
|
-
};
|
|
837
|
-
const result = await (runtime.runDeployCommand ?? runDeployCommand)(deployOptions, deployRuntime);
|
|
838
|
-
const output = result ?? { changed: true };
|
|
839
|
-
return finishRemoteCommand(options, runtime, output);
|
|
840
|
-
}
|
|
841
|
-
async function createRemoteCommandContext(options, runtime) {
|
|
842
|
-
index.applyServiceTagIfNeeded(options.serviceTag);
|
|
843
|
-
const logger = resolveRemoteLogger(options, runtime);
|
|
844
|
-
const environment = await context.resolveRemoteEnvironment({
|
|
845
|
-
allowUnsafeApiBaseUrl: options.allowUnsafeApiBaseUrl,
|
|
846
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
847
|
-
env: options.env,
|
|
848
|
-
fetchImpl: runtime.fetchImpl,
|
|
849
|
-
loginBaseUrl: options.loginBaseUrl,
|
|
850
|
-
}, {
|
|
851
|
-
fetchImpl: runtime.fetchImpl,
|
|
852
|
-
requireAuthSession: runtime.requireAuthSession,
|
|
853
|
-
});
|
|
854
|
-
const packageJson = await context.readMiniProgramProjectPackage(runtime.cwd ?? process.cwd());
|
|
855
|
-
const binding = context.readBoundMiniProgramIdFromPackageJson(packageJson.packageJson);
|
|
856
|
-
const api = {
|
|
857
|
-
...adaptRemoteApiClient(context.createRemoteApiClientForEnvironment(environment)),
|
|
858
|
-
...runtime.api,
|
|
859
|
-
};
|
|
860
|
-
logger.debug(`Heybox API: ${environment.apiBaseUrl}`);
|
|
861
|
-
return {
|
|
862
|
-
api,
|
|
863
|
-
apiBaseUrl: environment.apiBaseUrl,
|
|
864
|
-
binding,
|
|
865
|
-
logger,
|
|
866
|
-
loginBaseUrl: environment.loginBaseUrl,
|
|
867
|
-
packageJson,
|
|
868
|
-
session: environment.session,
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
function adaptRemoteApiClient(api) {
|
|
872
|
-
return {
|
|
873
|
-
accessStatus: () => api.getDeveloperAccessStatus(),
|
|
874
|
-
listMiniPrograms: ({ keyword, limit = 200, offset = 0, status }) => api.listUserMiniprograms({ keyword, status, offset, limit }),
|
|
875
|
-
createMiniProgram: () => api.createUserMiniprogram(),
|
|
876
|
-
getMiniProgramDetail: (miniProgramId) => api.getUserMiniprogramDetail({ mini_program_id: miniProgramId }),
|
|
877
|
-
getAllowlist: (miniProgramId) => api.getPreviewAllowlist({ mini_program_id: miniProgramId }),
|
|
878
|
-
updateAllowlist: ({ miniProgramId, heyboxIds }) => api.updatePreviewAllowlist({ mini_program_id: miniProgramId, heybox_ids: heyboxIds }),
|
|
879
|
-
createLeaderboard: ({ miniProgramId, key, order, rankLimit }) => api.createUserMiniprogramLeaderboard({
|
|
880
|
-
mini_program_id: miniProgramId,
|
|
881
|
-
key,
|
|
882
|
-
order,
|
|
883
|
-
rank_limit: rankLimit,
|
|
884
|
-
}),
|
|
885
|
-
getLeaderboard: ({ miniProgramId, key }) => api.getUserMiniprogramLeaderboard({ mini_program_id: miniProgramId, key }),
|
|
886
|
-
listLeaderboards: ({ miniProgramId, createdAtStart, createdAtEnd }) => api.listUserMiniprogramLeaderboards({
|
|
887
|
-
mini_program_id: miniProgramId,
|
|
888
|
-
created_at_start: createdAtStart,
|
|
889
|
-
created_at_end: createdAtEnd,
|
|
890
|
-
}),
|
|
891
|
-
deleteLeaderboard: ({ miniProgramId, key, confirm }) => api.deleteUserMiniprogramLeaderboard({ mini_program_id: miniProgramId, key, confirm }),
|
|
892
|
-
listVersions: (miniProgramId) => api.listUserMiniprogramVersions({ mini_program_id: miniProgramId, offset: 0, limit: 50 }),
|
|
893
|
-
getPreviewInfo: ({ miniProgramId, version }) => api.getVersionPreviewInfo({ mini_program_id: miniProgramId, version }),
|
|
894
|
-
listDeveloperEntities: () => api.listDeveloperEntities(),
|
|
895
|
-
switchDeveloperEntity: ({ entityId }) => api.switchDeveloperEntity({ entity_id: entityId }),
|
|
896
|
-
releaseVersion: ({ miniProgramId, version }) => api.releaseUserMiniprogramVersion({ mini_program_id: miniProgramId, version }),
|
|
897
|
-
withdrawVersion: ({ miniProgramId, reason, version }) => api.withdrawUserMiniprogramVersion({ mini_program_id: miniProgramId, version, reason }),
|
|
898
|
-
takeDown: (miniProgramId) => api.takeDownUserMiniprogram({ mini_program_id: miniProgramId }),
|
|
899
|
-
reopen: (miniProgramId) => api.reopenUserMiniprogram({ mini_program_id: miniProgramId }),
|
|
900
|
-
updateSquareDisplay: ({ miniProgramId, isHidden }) => api.updateSquareDisplay({ mini_program_id: miniProgramId, is_hidden: isHidden }),
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
async function remoteAccess(options, context, runtime) {
|
|
904
|
-
const result = await context.api.accessStatus();
|
|
905
|
-
const output = { changed: false, access: toCamelCaseDeep(result) };
|
|
906
|
-
if (!options.json) {
|
|
907
|
-
context.logger.info(`Access status: ${String(result.status ?? 'unknown')}`);
|
|
908
|
-
if (result.developer_name) {
|
|
909
|
-
context.logger.info(`Developer: ${String(result.developer_name)}`);
|
|
910
|
-
}
|
|
911
|
-
if (result.disabled_reason) {
|
|
912
|
-
context.logger.warn(`Disabled reason: ${String(result.disabled_reason)}`);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return finishRemoteCommand(options, runtime, output);
|
|
916
|
-
}
|
|
917
|
-
async function remoteEntityList(options, context, runtime) {
|
|
918
|
-
const result = await listDeveloperEntitiesWithAccessFallback(context);
|
|
919
|
-
const items = result.items;
|
|
920
|
-
const output = { changed: false, items: toCamelCaseDeep(items), total: result.total };
|
|
921
|
-
if (!options.json) {
|
|
922
|
-
if (items.length === 0) {
|
|
923
|
-
context.logger.info('当前账号未绑定开发者主体');
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
printDeveloperEntities(context.logger, items);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
return finishRemoteCommand(options, runtime, output);
|
|
930
|
-
}
|
|
931
|
-
async function remoteEntityCurrent(options, context, runtime) {
|
|
932
|
-
const current = await requireCurrentDeveloperEntity(context);
|
|
933
|
-
const access = await context.api.accessStatus();
|
|
934
|
-
const output = { changed: false, current: toCamelCaseDeep(current), access: toCamelCaseDeep(access) };
|
|
935
|
-
if (!options.json) {
|
|
936
|
-
printDeveloperEntity(context.logger, current, '* ');
|
|
937
|
-
context.logger.info(`Access status: ${String(access.status ?? 'unknown')}`);
|
|
938
|
-
if (access.disabled_reason) {
|
|
939
|
-
context.logger.warn(`Disabled reason: ${String(access.disabled_reason)}`);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return finishRemoteCommand(options, runtime, output);
|
|
943
|
-
}
|
|
944
|
-
async function remoteEntitySwitch(options, context, runtime) {
|
|
945
|
-
const entityId = parseEntityId(options.entityId);
|
|
946
|
-
const entityList = await listDeveloperEntitiesWithAccessFallback(context);
|
|
947
|
-
const entities = entityList.items;
|
|
948
|
-
const target = entities.find((entity) => entity.entity_id === entityId);
|
|
949
|
-
if (!target) {
|
|
950
|
-
throw new Error(`当前账号不可切换到开发者主体 ${entityId};请先运行 hb-sdk remote entity list 查看可用主体`);
|
|
951
|
-
}
|
|
952
|
-
const result = entityList.fromAccessFallback === true && target.is_current === true ? target : await context.api.switchDeveloperEntity({ entityId });
|
|
953
|
-
const access = await context.api.accessStatus();
|
|
954
|
-
const switched = normalizeDeveloperEntity(result) ?? { ...target, is_current: true };
|
|
955
|
-
const switchedEntityId = normalizeEntityId(switched.entity_id);
|
|
956
|
-
if (switchedEntityId === undefined) {
|
|
957
|
-
throw new Error('远端响应缺少 entity_id');
|
|
958
|
-
}
|
|
959
|
-
const switchedOwnerHeyboxId = normalizePositiveInteger(switched.owner_heybox_id);
|
|
960
|
-
await session.setSelectedDeveloperEntitySnapshot({
|
|
961
|
-
entityId: switchedEntityId,
|
|
962
|
-
entityName: switched.entity_name || '--',
|
|
963
|
-
...(switchedOwnerHeyboxId !== undefined ? { ownerHeyboxId: switchedOwnerHeyboxId } : {}),
|
|
964
|
-
}, {
|
|
965
|
-
cacheFile: options.cacheFile,
|
|
966
|
-
env: options.env,
|
|
967
|
-
loginBaseUrl: options.loginBaseUrl,
|
|
968
|
-
now: options.now,
|
|
969
|
-
});
|
|
970
|
-
const output = { changed: true, current: toCamelCaseDeep(switched), access: toCamelCaseDeep(access) };
|
|
971
|
-
if (!options.json) {
|
|
972
|
-
context.logger.success(`已切换开发者主体:${formatDeveloperEntity(switched)}`);
|
|
973
|
-
context.logger.info(`Access status: ${String(access.status ?? 'unknown')}`);
|
|
974
|
-
}
|
|
975
|
-
return finishRemoteCommand(options, runtime, output);
|
|
976
|
-
}
|
|
977
|
-
async function remoteList(options, context, runtime) {
|
|
978
|
-
const result = await context.api.listMiniPrograms({ keyword: options.keyword, status: options.status });
|
|
979
|
-
const items = Array.isArray(result.items) ? result.items : [];
|
|
980
|
-
const outputItems = items.map((item) => ({ ...toCamelCaseDeep(item), bound: item.mini_program_id === context.binding }));
|
|
981
|
-
const output = { changed: false, items: outputItems, total: readNumber(result.total, items.length) };
|
|
982
|
-
if (!options.json) {
|
|
983
|
-
if (items.length === 0) {
|
|
984
|
-
context.logger.info('未找到远端小程序');
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
for (const item of items) {
|
|
988
|
-
const marker = item.mini_program_id === context.binding ? '* ' : ' ';
|
|
989
|
-
context.logger.info(`${marker}${item.mini_program_id ?? '--'} ${item.name ?? '--'} status=${item.status ?? '--'} version=${item.version || item.latest_version_status || '--'}`);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
if (!context.binding) {
|
|
993
|
-
context.logger.info('当前项目尚未绑定远端小程序。绑定已有小程序:hb-sdk remote bind <mini-program-id>');
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return finishRemoteCommand(options, runtime, output);
|
|
997
|
-
}
|
|
998
|
-
async function remoteCreate(options, context$1, runtime) {
|
|
999
|
-
if (context$1.binding && !options.forceBind) {
|
|
1000
|
-
throw new Error(`当前项目已绑定 ${context$1.binding};如需覆盖,请使用 --force-bind`);
|
|
1001
|
-
}
|
|
1002
|
-
const access = await context$1.api.accessStatus();
|
|
1003
|
-
if (access.status !== 'approved') {
|
|
1004
|
-
throw new Error(`当前 CLI 用户没有创建工坊小程序权限:${String(access.status ?? 'unknown')}`);
|
|
1005
|
-
}
|
|
1006
|
-
await confirmCreateWithCurrentEntity(options, context$1, runtime);
|
|
1007
|
-
const result = await context$1.api.createMiniProgram();
|
|
1008
|
-
const miniProgramId = requireMiniProgramId(result);
|
|
1009
|
-
await context.writeBoundMiniProgramId(context$1.packageJson.projectRoot, miniProgramId, { force: options.forceBind });
|
|
1010
|
-
const output = { changed: true, miniProgramId, remote: toCamelCaseDeep(result) };
|
|
1011
|
-
if (!options.json) {
|
|
1012
|
-
context$1.logger.success(`创建并绑定成功:${miniProgramId}`);
|
|
1013
|
-
}
|
|
1014
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1015
|
-
}
|
|
1016
|
-
async function remoteBind(options, context$1, runtime) {
|
|
1017
|
-
const miniProgramId = requireNonEmpty(options.miniProgramId, 'remote bind 必须传 <mini-program-id>');
|
|
1018
|
-
if (context$1.binding && context$1.binding !== miniProgramId && !options.force) {
|
|
1019
|
-
throw new Error(`当前项目已绑定 ${context$1.binding};如需覆盖本地绑定,请使用 --force`);
|
|
1020
|
-
}
|
|
1021
|
-
const detail = await context$1.api.getMiniProgramDetail(miniProgramId);
|
|
1022
|
-
const verifiedId = requireMiniProgramId(detail);
|
|
1023
|
-
await assertMiniProgramEntityMatchesCurrent(context$1, detail);
|
|
1024
|
-
const changed = context$1.binding !== verifiedId;
|
|
1025
|
-
if (changed) {
|
|
1026
|
-
await context.writeBoundMiniProgramId(context$1.packageJson.projectRoot, verifiedId, { force: options.force });
|
|
1027
|
-
}
|
|
1028
|
-
const output = { changed, miniProgramId: verifiedId, remote: toCamelCaseDeep(detail) };
|
|
1029
|
-
if (!options.json) {
|
|
1030
|
-
context$1.logger.success(changed ? `绑定成功:${verifiedId}` : `已绑定:${verifiedId}`);
|
|
1031
|
-
}
|
|
1032
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1033
|
-
}
|
|
1034
|
-
async function remoteInfo(options, context, runtime) {
|
|
1035
|
-
const miniProgramId = requireBinding(context);
|
|
1036
|
-
const detail = await context.api.getMiniProgramDetail(miniProgramId);
|
|
1037
|
-
const output = { changed: false, miniProgramId, detail: toCamelCaseDeep(detail) };
|
|
1038
|
-
if (!options.json) {
|
|
1039
|
-
printMiniProgramDetail(context.logger, detail);
|
|
1040
|
-
}
|
|
1041
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1042
|
-
}
|
|
1043
|
-
async function remoteAllowlistList(options, context, runtime) {
|
|
1044
|
-
const miniProgramId = requireBinding(context);
|
|
1045
|
-
const result = await context.api.getAllowlist(miniProgramId);
|
|
1046
|
-
const output = {
|
|
1047
|
-
changed: false,
|
|
1048
|
-
items: toCamelCaseDeep(Array.isArray(result.items) ? result.items : []),
|
|
1049
|
-
limit: readNumber(result.limit, 0),
|
|
1050
|
-
miniProgramId,
|
|
1051
|
-
};
|
|
1052
|
-
if (!options.json) {
|
|
1053
|
-
printAllowlist(context.logger, result);
|
|
1054
|
-
}
|
|
1055
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1056
|
-
}
|
|
1057
|
-
async function remoteAllowlistWrite(options, context, runtime) {
|
|
1058
|
-
const miniProgramId = requireBinding(context);
|
|
1059
|
-
const inputIds = parseHeyboxIds(options.heyboxIds ?? []);
|
|
1060
|
-
const current = await context.api.getAllowlist(miniProgramId);
|
|
1061
|
-
const nextIds = mergeAllowlistIds(options.command, current, inputIds);
|
|
1062
|
-
assertAllowlistLimit(nextIds, current.limit);
|
|
1063
|
-
const result = await context.api.updateAllowlist({ miniProgramId, heyboxIds: nextIds });
|
|
1064
|
-
const output = {
|
|
1065
|
-
changed: true,
|
|
1066
|
-
items: toCamelCaseDeep(Array.isArray(result.items) ? result.items : []),
|
|
1067
|
-
limit: readNumber(result.limit, readNumber(current.limit, 0)),
|
|
1068
|
-
miniProgramId,
|
|
1069
|
-
};
|
|
1070
|
-
if (!options.json) {
|
|
1071
|
-
context.logger.success(`白名单已更新:${nextIds.join(',') || '(empty)'}`);
|
|
1072
|
-
}
|
|
1073
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1074
|
-
}
|
|
1075
|
-
async function remoteLeaderboardCreate(options, context, runtime) {
|
|
1076
|
-
const miniProgramId = requireBinding(context);
|
|
1077
|
-
const order = requireNonEmpty(options.order, 'remote cloud leaderboard create 必须传 --order asc|desc');
|
|
1078
|
-
if (order !== 'asc' && order !== 'desc') {
|
|
1079
|
-
throw new Error(`排行榜排序方向只支持 asc 或 desc:${order}`);
|
|
1080
|
-
}
|
|
1081
|
-
const key = options.key === undefined ? undefined : requireNonEmpty(options.key, '排行榜 key 不能为空');
|
|
1082
|
-
await assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId);
|
|
1083
|
-
const result = await context.api.createLeaderboard({
|
|
1084
|
-
miniProgramId,
|
|
1085
|
-
key,
|
|
1086
|
-
order,
|
|
1087
|
-
rankLimit: options.rankLimit,
|
|
1088
|
-
});
|
|
1089
|
-
const output = { changed: true, miniProgramId, leaderboard: toCamelCaseDeep(result) };
|
|
1090
|
-
if (!options.json) {
|
|
1091
|
-
context.logger.success(`排行榜创建成功:${String(result.key || key || 'default')}`);
|
|
1092
|
-
}
|
|
1093
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1094
|
-
}
|
|
1095
|
-
async function remoteLeaderboardGet(options, context, runtime) {
|
|
1096
|
-
const miniProgramId = requireBinding(context);
|
|
1097
|
-
const key = requireNonEmpty(options.key, 'remote cloud leaderboard get 必须传 <key>');
|
|
1098
|
-
const result = await context.api.getLeaderboard({ miniProgramId, key });
|
|
1099
|
-
const output = { changed: false, miniProgramId, leaderboard: toCamelCaseDeep(result) };
|
|
1100
|
-
if (!options.json) {
|
|
1101
|
-
printLeaderboardDetail(context.logger, result);
|
|
1102
|
-
}
|
|
1103
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1104
|
-
}
|
|
1105
|
-
async function remoteLeaderboardList(options, context, runtime) {
|
|
1106
|
-
const miniProgramId = requireBinding(context);
|
|
1107
|
-
const result = await context.api.listLeaderboards({
|
|
1108
|
-
miniProgramId,
|
|
1109
|
-
createdAtStart: options.createdAtStart,
|
|
1110
|
-
createdAtEnd: options.createdAtEnd,
|
|
1111
|
-
});
|
|
1112
|
-
const leaderboards = Array.isArray(result.leaderboards) ? result.leaderboards : [];
|
|
1113
|
-
const output = { changed: false, miniProgramId, leaderboards: toCamelCaseDeep(leaderboards) };
|
|
1114
|
-
if (!options.json) {
|
|
1115
|
-
printLeaderboards(context.logger, leaderboards);
|
|
1116
|
-
}
|
|
1117
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1118
|
-
}
|
|
1119
|
-
async function remoteLeaderboardDelete(options, context, runtime) {
|
|
1120
|
-
const miniProgramId = requireBinding(context);
|
|
1121
|
-
const key = requireNonEmpty(options.key, 'remote cloud leaderboard delete 必须传 <key>');
|
|
1122
|
-
const confirm = requireNonEmpty(options.confirm, 'remote cloud leaderboard delete 必须传 --confirm <key>');
|
|
1123
|
-
if (confirm !== key) {
|
|
1124
|
-
throw new Error('--confirm 必须与要删除的排行榜 key 完全一致');
|
|
1125
|
-
}
|
|
1126
|
-
await assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId);
|
|
1127
|
-
await context.api.deleteLeaderboard({ miniProgramId, key, confirm });
|
|
1128
|
-
const output = { changed: true, miniProgramId, key };
|
|
1129
|
-
if (!options.json) {
|
|
1130
|
-
context.logger.success(`排行榜已删除:${key}`);
|
|
1131
|
-
}
|
|
1132
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1133
|
-
}
|
|
1134
|
-
async function remoteVersions(options, context, runtime) {
|
|
1135
|
-
const miniProgramId = requireBinding(context);
|
|
1136
|
-
const result = await context.api.listVersions(miniProgramId);
|
|
1137
|
-
const items = Array.isArray(result.items) ? result.items : [];
|
|
1138
|
-
const output = { changed: false, items: toCamelCaseDeep(items), miniProgramId, total: readNumber(result.total, items.length) };
|
|
1139
|
-
if (!options.json) {
|
|
1140
|
-
for (const item of items) {
|
|
1141
|
-
context.logger.info(`${item.version ?? '--'} status=${item.status ?? '--'} auto_publish=${item.auto_publish === true ? 'yes' : 'no'}`);
|
|
1142
|
-
}
|
|
1143
|
-
if (items.length === 0) {
|
|
1144
|
-
context.logger.info('暂无远端版本');
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1148
|
-
}
|
|
1149
|
-
async function remotePreview(options, context, runtime) {
|
|
1150
|
-
const miniProgramId = requireBinding(context);
|
|
1151
|
-
const version = requireNonEmpty(options.version, 'remote preview 必须传 <version>');
|
|
1152
|
-
const result = await context.api.getPreviewInfo({ miniProgramId, version });
|
|
1153
|
-
const protocolString = String(result.protocol_string || '');
|
|
1154
|
-
if (!protocolString) {
|
|
1155
|
-
throw new Error(`版本 ${version} 暂无 protocol_string,无法打开预览`);
|
|
1156
|
-
}
|
|
1157
|
-
const output = { changed: false, miniProgramId, protocolString, version, preview: toCamelCaseDeep(result) };
|
|
1158
|
-
if (!options.json) {
|
|
1159
|
-
context.logger.info(`Protocol: ${protocolString}`);
|
|
1160
|
-
}
|
|
1161
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1162
|
-
}
|
|
1163
|
-
async function remoteDangerousWrite(options, context, runtime) {
|
|
1164
|
-
const miniProgramId = requireBinding(context);
|
|
1165
|
-
const version = options.command === 'release' || options.command === 'withdraw' ? requireNonEmpty(options.version, `remote ${options.command} 必须传 <version>`) : undefined;
|
|
1166
|
-
const detail = await context.api.getMiniProgramDetail(miniProgramId);
|
|
1167
|
-
await confirmDangerousRemoteChange(options, runtime, context.logger, {
|
|
1168
|
-
miniProgramId,
|
|
1169
|
-
name: detail.name,
|
|
1170
|
-
operation: options.command,
|
|
1171
|
-
status: detail.status,
|
|
1172
|
-
version,
|
|
1173
|
-
});
|
|
1174
|
-
if (options.command === 'release') {
|
|
1175
|
-
const result = await context.api.releaseVersion({ miniProgramId, version: version });
|
|
1176
|
-
return finishDangerousWrite(options, runtime, context.logger, miniProgramId, result, { status: result.status, version });
|
|
1177
|
-
}
|
|
1178
|
-
if (options.command === 'withdraw') {
|
|
1179
|
-
const result = await context.api.withdrawVersion({ miniProgramId, reason: options.reason, version: version });
|
|
1180
|
-
return finishDangerousWrite(options, runtime, context.logger, miniProgramId, result, { status: result.status, version });
|
|
1181
|
-
}
|
|
1182
|
-
if (options.command === 'take-down') {
|
|
1183
|
-
const result = await context.api.takeDown(miniProgramId);
|
|
1184
|
-
return finishDangerousWrite(options, runtime, context.logger, miniProgramId, result, { status: result.status });
|
|
1185
|
-
}
|
|
1186
|
-
const result = await context.api.reopen(miniProgramId);
|
|
1187
|
-
return finishDangerousWrite(options, runtime, context.logger, miniProgramId, result, { status: result.status });
|
|
1188
|
-
}
|
|
1189
|
-
async function remoteSquareWrite(options, context, runtime) {
|
|
1190
|
-
const miniProgramId = requireBinding(context);
|
|
1191
|
-
const isHidden = options.command === 'square:hide';
|
|
1192
|
-
if (isHidden) {
|
|
1193
|
-
const detail = await context.api.getMiniProgramDetail(miniProgramId);
|
|
1194
|
-
await confirmDangerousRemoteChange(options, runtime, context.logger, {
|
|
1195
|
-
miniProgramId,
|
|
1196
|
-
name: detail.name,
|
|
1197
|
-
operation: 'square hide',
|
|
1198
|
-
status: detail.status,
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
const result = await context.api.updateSquareDisplay({ miniProgramId, isHidden });
|
|
1202
|
-
const output = { changed: true, isHidden: result.is_hidden === true, miniProgramId, remote: toCamelCaseDeep(result) };
|
|
1203
|
-
if (!options.json) {
|
|
1204
|
-
context.logger.success(`${isHidden ? '隐藏' : '展示'}广场展示状态成功:${miniProgramId}`);
|
|
1205
|
-
}
|
|
1206
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1207
|
-
}
|
|
1208
|
-
function finishDangerousWrite(options, runtime, logger, miniProgramId, result, extras) {
|
|
1209
|
-
const output = { changed: true, miniProgramId, ...extras, remote: toCamelCaseDeep(result) };
|
|
1210
|
-
if (!options.json) {
|
|
1211
|
-
logger.success(`操作成功:${options.command} ${miniProgramId}`);
|
|
1212
|
-
}
|
|
1213
|
-
return finishRemoteCommand(options, runtime, output);
|
|
1214
|
-
}
|
|
1215
|
-
function finishRemoteCommand(options, runtime, output) {
|
|
1216
|
-
const normalized = toCamelCaseDeep(output);
|
|
1217
|
-
if (options.json) {
|
|
1218
|
-
writeStdout(runtime, `${JSON.stringify(normalized)}\n`);
|
|
1219
|
-
}
|
|
1220
|
-
return normalized;
|
|
1221
|
-
}
|
|
1222
|
-
function resolveRemoteLogger(options, runtime) {
|
|
1223
|
-
if (options.json) {
|
|
1224
|
-
return createJsonSafeLogger(runtime);
|
|
1225
|
-
}
|
|
1226
|
-
if (runtime.logger) {
|
|
1227
|
-
return runtime.logger;
|
|
1228
|
-
}
|
|
1229
|
-
return index.createCliLogger({
|
|
1230
|
-
isTTY: runtime.isTTY,
|
|
1231
|
-
stderr: runtime.stderr,
|
|
1232
|
-
stdout: runtime.stdout,
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
function createJsonSafeLogger(runtime) {
|
|
1236
|
-
const stderr = runtime.stderr ?? process.stderr;
|
|
1237
|
-
return index.createCliLogger({
|
|
1238
|
-
isTTY: runtime.isTTY,
|
|
1239
|
-
stderr,
|
|
1240
|
-
stdout: stderr,
|
|
1241
|
-
});
|
|
1242
|
-
}
|
|
1243
|
-
function writeStdout(runtime, message) {
|
|
1244
|
-
(runtime.stdout ?? process.stdout).write(message);
|
|
1245
|
-
}
|
|
1246
|
-
function requireBinding(context) {
|
|
1247
|
-
if (!context.binding) {
|
|
1248
|
-
throw new Error('当前项目尚未绑定远端小程序,请先运行 hb-sdk remote create 或 hb-sdk remote bind <mini-program-id>');
|
|
1249
|
-
}
|
|
1250
|
-
return context.binding;
|
|
1251
|
-
}
|
|
1252
|
-
async function requireCurrentDeveloperEntity(context) {
|
|
1253
|
-
const entities = (await listDeveloperEntitiesWithAccessFallback(context)).items;
|
|
1254
|
-
const current = entities.find((entity) => entity.is_current === true);
|
|
1255
|
-
if (!current) {
|
|
1256
|
-
throw new Error('当前账号没有服务端 current 开发者主体;请先运行 hb-sdk remote entity list 查看可用主体,并用 hb-sdk remote entity switch <entity-id> 切换');
|
|
1257
|
-
}
|
|
1258
|
-
return current;
|
|
1259
|
-
}
|
|
1260
|
-
async function confirmCreateWithCurrentEntity(options, context, runtime) {
|
|
1261
|
-
const entities = (await listDeveloperEntitiesWithAccessFallback(context)).items;
|
|
1262
|
-
if (entities.length <= 1) {
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
const current = entities.find((entity) => entity.is_current === true);
|
|
1266
|
-
if (!current) {
|
|
1267
|
-
throw new Error('当前账号存在多个开发者主体,但没有服务端 current 主体;请先运行 hb-sdk remote entity switch <entity-id>');
|
|
1268
|
-
}
|
|
1269
|
-
if (!options.json) {
|
|
1270
|
-
context.logger.info(`当前创建将归属开发者主体:${formatDeveloperEntity(current)}`);
|
|
1271
|
-
context.logger.info(`如需切换主体,请先运行:hb-sdk remote entity switch <entity-id>`);
|
|
1272
|
-
}
|
|
1273
|
-
if (options.yes) {
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
const isTTY = runtime.isTTY ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
1277
|
-
if (!isTTY) {
|
|
1278
|
-
throw new Error(`当前账号存在多个开发者主体,非交互环境执行 hb-sdk remote create 必须传 --yes 确认使用当前主体:${formatDeveloperEntity(current)}。如需切换,请先运行 hb-sdk remote entity switch <entity-id>`);
|
|
1279
|
-
}
|
|
1280
|
-
const accepted = runtime.promptConfirm
|
|
1281
|
-
? await runtime.promptConfirm(`确认使用当前开发者主体 ${formatDeveloperEntity(current)} 创建小程序?`)
|
|
1282
|
-
: await promptConfirm(`确认使用当前开发者主体 ${formatDeveloperEntity(current)} 创建小程序?输入 yes 继续: `, runtime);
|
|
1283
|
-
if (!accepted) {
|
|
1284
|
-
throw new Error('已取消操作');
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
async function assertMiniProgramEntityMatchesCurrent(context, detail) {
|
|
1288
|
-
const detailEntityId = normalizeEntityId(detail.entity_id);
|
|
1289
|
-
if (detailEntityId === undefined) {
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
const current = await requireCurrentDeveloperEntity(context);
|
|
1293
|
-
if (current.entity_id === detailEntityId) {
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
throw new Error(createEntityMismatchMessage({ current, target: createEntityFromMiniProgramDetail({ ...detail, entity_id: detailEntityId }) }));
|
|
1297
|
-
}
|
|
1298
|
-
async function assertBoundMiniProgramEntityMatchesCurrent(context, miniProgramId) {
|
|
1299
|
-
const current = await requireCurrentDeveloperEntity(context);
|
|
1300
|
-
const detail = await context.api.getMiniProgramDetail(miniProgramId);
|
|
1301
|
-
requireMiniProgramId(detail);
|
|
1302
|
-
const detailEntityId = normalizeEntityId(detail.entity_id);
|
|
1303
|
-
if (detailEntityId === undefined) {
|
|
1304
|
-
const listed = await findMiniProgramInCurrentEntityList(context, miniProgramId);
|
|
1305
|
-
if (listed) {
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
throw new Error(`远端小程序 ${miniProgramId} 详情缺少 entity_id,且当前主体列表中找不到该小程序,无法校验当前主体一致性,已停止 deploy`);
|
|
1309
|
-
}
|
|
1310
|
-
if (current.entity_id !== detailEntityId) {
|
|
1311
|
-
throw new Error(createEntityMismatchMessage({ current, target: createEntityFromMiniProgramDetail({ ...detail, entity_id: detailEntityId }) }));
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
async function findMiniProgramInCurrentEntityList(context, miniProgramId) {
|
|
1315
|
-
const pageSize = 200;
|
|
1316
|
-
for (let offset = 0;; offset += pageSize) {
|
|
1317
|
-
const result = await context.api.listMiniPrograms({ limit: pageSize, offset });
|
|
1318
|
-
const items = Array.isArray(result.items) ? result.items : [];
|
|
1319
|
-
if (items.some((item) => item.mini_program_id === miniProgramId)) {
|
|
1320
|
-
return true;
|
|
1321
|
-
}
|
|
1322
|
-
if (items.length < pageSize || offset + items.length >= readNumber(result.total, Number.POSITIVE_INFINITY)) {
|
|
1323
|
-
return false;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
function createEntityMismatchMessage(options) {
|
|
1328
|
-
return [
|
|
1329
|
-
'当前开发者主体与远端小程序所属主体不一致,已停止操作。',
|
|
1330
|
-
`当前主体:${formatDeveloperEntity(options.current)}`,
|
|
1331
|
-
`小程序所属主体:${formatDeveloperEntity(options.target)}`,
|
|
1332
|
-
`请先运行:hb-sdk remote entity switch ${options.target.entity_id}`,
|
|
1333
|
-
].join('\n');
|
|
1334
|
-
}
|
|
1335
|
-
function requireMiniProgramId(result) {
|
|
1336
|
-
const miniProgramId = result.mini_program_id;
|
|
1337
|
-
if (typeof miniProgramId !== 'string' || !miniProgramId.trim()) {
|
|
1338
|
-
throw new Error('远端响应缺少 mini_program_id');
|
|
1339
|
-
}
|
|
1340
|
-
return miniProgramId.trim();
|
|
1341
|
-
}
|
|
1342
|
-
function parseEntityId(value) {
|
|
1343
|
-
const raw = requireNonEmpty(value, 'remote entity switch 必须传 <entity-id>');
|
|
1344
|
-
if (!/^(0|[1-9]\d*)$/.test(raw)) {
|
|
1345
|
-
throw new Error(`entity-id 只接受十进制数字:${raw}`);
|
|
1346
|
-
}
|
|
1347
|
-
const entityId = Number(raw);
|
|
1348
|
-
if (!isValidEntityId(entityId)) {
|
|
1349
|
-
throw new Error(`entity-id 超出有效范围:${raw}`);
|
|
1350
|
-
}
|
|
1351
|
-
return entityId;
|
|
1352
|
-
}
|
|
1353
|
-
async function listDeveloperEntitiesWithAccessFallback(context) {
|
|
1354
|
-
const result = await context.api.listDeveloperEntities();
|
|
1355
|
-
const items = normalizeDeveloperEntities(result);
|
|
1356
|
-
if (items.length > 0) {
|
|
1357
|
-
return { items, total: readNumber(result.total, items.length) };
|
|
1358
|
-
}
|
|
1359
|
-
const fallback = createEntityFromAccessStatus(await context.api.accessStatus());
|
|
1360
|
-
if (!fallback) {
|
|
1361
|
-
return { items, total: readNumber(result.total, items.length) };
|
|
1362
|
-
}
|
|
1363
|
-
return { fromAccessFallback: true, items: [fallback], total: 1 };
|
|
1364
|
-
}
|
|
1365
|
-
function normalizeDeveloperEntities(result) {
|
|
1366
|
-
const rawItems = Array.isArray(result.items) ? result.items : [];
|
|
1367
|
-
return rawItems.map((item) => normalizeDeveloperEntity(item)).filter((item) => item !== undefined);
|
|
1368
|
-
}
|
|
1369
|
-
function normalizeDeveloperEntity(value) {
|
|
1370
|
-
if (!value || typeof value !== 'object') {
|
|
1371
|
-
return undefined;
|
|
1372
|
-
}
|
|
1373
|
-
const entityId = normalizeEntityId(value.entity_id);
|
|
1374
|
-
if (entityId === undefined) {
|
|
1375
|
-
return undefined;
|
|
1376
|
-
}
|
|
1377
|
-
const ownerHeyboxId = normalizePositiveInteger(value.owner_heybox_id);
|
|
1378
|
-
return {
|
|
1379
|
-
...value,
|
|
1380
|
-
entity_id: entityId,
|
|
1381
|
-
entity_name: typeof value.entity_name === 'string' ? value.entity_name : undefined,
|
|
1382
|
-
owner_heybox_id: ownerHeyboxId,
|
|
1383
|
-
is_current: value.is_current === true,
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
function createEntityFromMiniProgramDetail(detail) {
|
|
1387
|
-
const entityId = normalizeEntityId(detail.entity_id);
|
|
1388
|
-
return {
|
|
1389
|
-
...(entityId !== undefined ? { entity_id: entityId } : {}),
|
|
1390
|
-
entity_name: typeof detail.developer_name === 'string' ? detail.developer_name : undefined,
|
|
1391
|
-
owner_heybox_id: normalizePositiveInteger(detail.owner_heybox_id),
|
|
1392
|
-
is_current: false,
|
|
1393
|
-
};
|
|
1394
|
-
}
|
|
1395
|
-
function createEntityFromAccessStatus(access) {
|
|
1396
|
-
const entity = normalizeDeveloperEntity({
|
|
1397
|
-
entity_id: access.entity_id,
|
|
1398
|
-
entity_name: access.developer_name,
|
|
1399
|
-
owner_heybox_id: access.owner_heybox_id,
|
|
1400
|
-
is_current: true,
|
|
1401
|
-
});
|
|
1402
|
-
return entity;
|
|
1403
|
-
}
|
|
1404
|
-
function isValidEntityId(value) {
|
|
1405
|
-
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
1406
|
-
}
|
|
1407
|
-
function normalizeEntityId(value) {
|
|
1408
|
-
return normalizePositiveInteger(value);
|
|
1409
|
-
}
|
|
1410
|
-
function normalizePositiveInteger(value) {
|
|
1411
|
-
if (typeof value === 'number') {
|
|
1412
|
-
return isValidEntityId(value) ? value : undefined;
|
|
1413
|
-
}
|
|
1414
|
-
if (typeof value !== 'string') {
|
|
1415
|
-
return undefined;
|
|
1416
|
-
}
|
|
1417
|
-
const normalized = value.trim();
|
|
1418
|
-
if (!/^[1-9]\d*$/.test(normalized)) {
|
|
1419
|
-
return undefined;
|
|
1420
|
-
}
|
|
1421
|
-
const parsed = Number(normalized);
|
|
1422
|
-
return isValidEntityId(parsed) ? parsed : undefined;
|
|
1423
|
-
}
|
|
1424
|
-
function requireNonEmpty(value, message) {
|
|
1425
|
-
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
1426
|
-
if (!normalized) {
|
|
1427
|
-
throw new Error(message);
|
|
1428
|
-
}
|
|
1429
|
-
return normalized;
|
|
1430
|
-
}
|
|
1431
|
-
function parseHeyboxIds(values) {
|
|
1432
|
-
if (values.length === 0) {
|
|
1433
|
-
throw new Error('请传入至少一个十进制 heybox_id');
|
|
1434
|
-
}
|
|
1435
|
-
const ids = [];
|
|
1436
|
-
for (const value of values) {
|
|
1437
|
-
const raw = String(value).trim();
|
|
1438
|
-
if (!/^(0|[1-9]\d*)$/.test(raw)) {
|
|
1439
|
-
throw new Error(`heybox_id 只接受十进制数字:${value}`);
|
|
1440
|
-
}
|
|
1441
|
-
const id = Number(raw);
|
|
1442
|
-
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
1443
|
-
throw new Error(`heybox_id 超出有效范围:${value}`);
|
|
1444
|
-
}
|
|
1445
|
-
if (!ids.includes(id)) {
|
|
1446
|
-
ids.push(id);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
return ids;
|
|
1450
|
-
}
|
|
1451
|
-
function mergeAllowlistIds(command, current, inputIds) {
|
|
1452
|
-
const items = Array.isArray(current.items) ? current.items : [];
|
|
1453
|
-
const ownerIds = items.filter((item) => item.is_owner === true && isValidHeyboxIdNumber(item.heybox_id)).map((item) => item.heybox_id);
|
|
1454
|
-
const currentIds = items.filter((item) => isValidHeyboxIdNumber(item.heybox_id)).map((item) => item.heybox_id);
|
|
1455
|
-
if (command === 'allowlist:add') {
|
|
1456
|
-
return uniqueNumbers([...currentIds, ...inputIds]);
|
|
1457
|
-
}
|
|
1458
|
-
if (command === 'allowlist:remove') {
|
|
1459
|
-
return uniqueNumbers(currentIds.filter((id) => ownerIds.includes(id) || !inputIds.includes(id)));
|
|
1460
|
-
}
|
|
1461
|
-
return uniqueNumbers([...ownerIds, ...inputIds.filter((id) => !ownerIds.includes(id))]);
|
|
1462
|
-
}
|
|
1463
|
-
function assertAllowlistLimit(ids, limit) {
|
|
1464
|
-
const parsedLimit = typeof limit === 'number' && Number.isFinite(limit) ? limit : undefined;
|
|
1465
|
-
if (parsedLimit !== undefined && ids.length > parsedLimit) {
|
|
1466
|
-
throw new Error(`内测白名单最多 ${parsedLimit} 人,当前操作会变为 ${ids.length} 人`);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
function uniqueNumbers(values) {
|
|
1470
|
-
const result = [];
|
|
1471
|
-
for (const value of values) {
|
|
1472
|
-
if (!result.includes(value)) {
|
|
1473
|
-
result.push(value);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
return result;
|
|
1477
|
-
}
|
|
1478
|
-
function isValidHeyboxIdNumber(value) {
|
|
1479
|
-
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
1480
|
-
}
|
|
1481
|
-
async function confirmDangerousRemoteChange(options, runtime, logger, context) {
|
|
1482
|
-
const lines = [
|
|
1483
|
-
`操作:${context.operation}`,
|
|
1484
|
-
`Mini-program: ${context.miniProgramId}`,
|
|
1485
|
-
`名称:${context.name || '--'}`,
|
|
1486
|
-
`当前状态:${context.status || '--'}`,
|
|
1487
|
-
...(context.version ? [`目标版本:${context.version}`] : []),
|
|
1488
|
-
];
|
|
1489
|
-
if (!options.json) {
|
|
1490
|
-
for (const line of lines) {
|
|
1491
|
-
logger.info(line);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
else if (!options.yes) {
|
|
1495
|
-
for (const line of lines) {
|
|
1496
|
-
logger.info(line, { stream: 'stderr' });
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
if (options.yes) {
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
const isTTY = runtime.isTTY ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
1503
|
-
if (!isTTY) {
|
|
1504
|
-
throw new Error(`非交互环境执行 hb-sdk remote ${context.operation} 必须传 --yes`);
|
|
1505
|
-
}
|
|
1506
|
-
const accepted = runtime.promptConfirm
|
|
1507
|
-
? await runtime.promptConfirm(`确认执行 ${context.operation}?`)
|
|
1508
|
-
: await promptConfirm(`确认执行 ${context.operation}?输入 yes 继续: `, runtime);
|
|
1509
|
-
if (!accepted) {
|
|
1510
|
-
throw new Error('已取消操作');
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
async function promptConfirm(message, runtime) {
|
|
1514
|
-
const input = runtime.stdin ?? process.stdin;
|
|
1515
|
-
const output = runtime.stderr ?? process.stderr;
|
|
1516
|
-
const rl = promises.createInterface({ input, output: output });
|
|
1517
|
-
try {
|
|
1518
|
-
const answer = await rl.question(message);
|
|
1519
|
-
return answer.trim().toLowerCase() === 'yes';
|
|
1520
|
-
}
|
|
1521
|
-
finally {
|
|
1522
|
-
rl.close();
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
function printMiniProgramDetail(logger, detail) {
|
|
1526
|
-
logger.info(`Mini-program: ${detail.mini_program_id ?? '--'}`);
|
|
1527
|
-
logger.info(`Name: ${detail.name ?? '--'}`);
|
|
1528
|
-
logger.info(`Status: ${detail.status ?? '--'}`);
|
|
1529
|
-
logger.info(`Version: ${detail.version || detail.latest_version_status || '--'}`);
|
|
1530
|
-
if (detail.page_url) {
|
|
1531
|
-
logger.info(`Page URL: ${detail.page_url}`);
|
|
1532
|
-
}
|
|
1533
|
-
if (detail.protocol_string) {
|
|
1534
|
-
logger.info(`Protocol: ${detail.protocol_string}`);
|
|
1535
|
-
}
|
|
1536
|
-
const runtimePermissions = detail.runtime_permissions;
|
|
1537
|
-
if (runtimePermissions) {
|
|
1538
|
-
logger.info(`Runtime 权限: schema=${String(runtimePermissions.schema_version ?? '--')} revision=${String(runtimePermissions.revision ?? '--')}`);
|
|
1539
|
-
if (Array.isArray(runtimePermissions.entries)) {
|
|
1540
|
-
runtimePermissions.entries.forEach((entry) => {
|
|
1541
|
-
logger.info(` ${JSON.stringify(entry)}`);
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
function printDeveloperEntities(logger, entities) {
|
|
1547
|
-
for (const entity of entities) {
|
|
1548
|
-
printDeveloperEntity(logger, entity, entity.is_current ? '* ' : ' ');
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
function printDeveloperEntity(logger, entity, marker = '') {
|
|
1552
|
-
logger.info(`${marker}${formatDeveloperEntity(entity)}${entity.is_current ? ' current' : ''}`);
|
|
1553
|
-
}
|
|
1554
|
-
function formatDeveloperEntity(entity) {
|
|
1555
|
-
const name = entity.entity_name || '--';
|
|
1556
|
-
const owner = entity.owner_heybox_id ? ` owner=${entity.owner_heybox_id}` : '';
|
|
1557
|
-
return `${entity.entity_id ?? '--'} ${name}${owner}`;
|
|
1558
|
-
}
|
|
1559
|
-
function printAllowlist(logger, result) {
|
|
1560
|
-
const items = Array.isArray(result.items) ? result.items : [];
|
|
1561
|
-
logger.info(`Limit: ${readNumber(result.limit, 0)}`);
|
|
1562
|
-
if (items.length === 0) {
|
|
1563
|
-
logger.info('内测白名单为空');
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1566
|
-
for (const item of items) {
|
|
1567
|
-
logger.info(`${item.heybox_id ?? '--'}${item.is_owner ? ' (owner)' : ''}${item.nickname ? ` ${item.nickname}` : ''}`);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
function printLeaderboards(logger, leaderboards) {
|
|
1571
|
-
if (leaderboards.length === 0) {
|
|
1572
|
-
logger.info('暂无云端排行榜');
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
for (const item of leaderboards) {
|
|
1576
|
-
logger.info(`${item.key ?? '--'} order=${item.order ?? '--'} rankLimit=${readNumber(item.rank_limit, 0)}`);
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
function printLeaderboardDetail(logger, detail) {
|
|
1580
|
-
logger.info(`Leaderboard: ${detail.key ?? '--'}`);
|
|
1581
|
-
logger.info(`Order: ${detail.order ?? '--'}`);
|
|
1582
|
-
logger.info(`Rank limit: ${readNumber(detail.rank_limit, 0)}`);
|
|
1583
|
-
}
|
|
1584
|
-
function readNumber(value, fallback) {
|
|
1585
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
1586
|
-
}
|
|
1587
|
-
function toCamelCaseDeep(value) {
|
|
1588
|
-
if (Array.isArray(value)) {
|
|
1589
|
-
return value.map((item) => toCamelCaseDeep(item));
|
|
1590
|
-
}
|
|
1591
|
-
if (!value || Object.prototype.toString.call(value) !== '[object Object]') {
|
|
1592
|
-
return value;
|
|
1593
|
-
}
|
|
1594
|
-
const result = {};
|
|
1595
|
-
for (const [key, item] of Object.entries(value)) {
|
|
1596
|
-
result[toCamelKey(key)] = toCamelCaseDeep(item);
|
|
1597
|
-
}
|
|
1598
|
-
return result;
|
|
1599
|
-
}
|
|
1600
|
-
function toCamelKey(key) {
|
|
1601
|
-
return key.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
exports.runRemoteCommand = runRemoteCommand;
|