@heybox/hb-sdk 0.6.8-alpha.2 → 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 +56 -0
- package/README.md +2 -0
- package/dist/cli-chunks/{build-CglqyB9Z.cjs → build-9hbhrRbU.cjs} +29 -25
- package/dist/cli-chunks/{context-CP7W_8aR.cjs → context-7rmakzS_.cjs} +122 -23
- package/dist/cli-chunks/{create-DldsTIVt.cjs → create-BkX4rtRP.cjs} +1 -1
- package/dist/cli-chunks/{dev-BG0icySa.cjs → dev-DiZGT4FQ.cjs} +142 -108
- package/dist/cli-chunks/{doctor-Cd6Xq0m-.cjs → doctor-BcpqXVRh.cjs} +1 -1
- package/dist/cli-chunks/{index-jXyfZKy2.cjs → index-6HfcwZ_r.cjs} +3 -3
- package/dist/cli-chunks/{index-paN77avR.cjs → index-DofTxdoX.cjs} +14 -14
- package/dist/cli-chunks/{login-DlD9n_AF.cjs → login-DUiejZcD.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-vHezGsg7.cjs → project-vite-CfD8Bp1K.cjs} +1 -1
- package/dist/cli-chunks/{remote-O5_nRdZN.cjs → remote-A4-PKgOn.cjs} +462 -190
- package/dist/cli-chunks/{runtime-gate-DfMJQGH9.cjs → runtime-gate-BWlU-R4h.cjs} +3 -3
- package/dist/cli-chunks/{index-v4-6fbXX.cjs → runtime-permission-env-DKrhgVM3.cjs} +276 -49
- package/dist/cli-chunks/{session-CiQplems.cjs → session-D5u3XzHN.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/mock-host/main.js +609 -6
- package/dist/index.cjs.js +403 -7
- package/dist/index.esm.js +403 -7
- package/dist/miniapp-publish.cjs.js +205 -0
- package/dist/miniapp-publish.esm.js +201 -1
- package/dist/vite.cjs.js +17 -2
- package/dist/vite.esm.js +17 -2
- package/package.json +2 -2
- package/skill/SKILL.md +7 -6
- package/skill/references/api-root.md +7 -2
- package/skill/references/cli.md +12 -0
- package/skill/references/examples.md +17 -2
- package/skill/references/safety-boundaries.md +2 -0
- package/skill/scripts/sync-references.mjs +17 -2
- package/skill/skill.json +4 -4
- package/types/core/network-sanitize.d.ts +31 -0
- package/types/miniapp-publish/index.d.ts +77 -0
- package/types/modules/network/index.d.ts +2 -0
- package/types/modules/network/observability.d.ts +39 -0
- package/types/modules/network/request-shape.d.ts +44 -0
- package/types/vite/html-policy.d.ts +1 -0
- package/types/vite/runtime-permission-env.d.ts +9 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var promises = require('node:readline/promises');
|
|
4
|
-
var index = require('./index-
|
|
5
|
-
var session = require('./session-
|
|
4
|
+
var index = require('./index-DofTxdoX.cjs');
|
|
5
|
+
var session = require('./session-D5u3XzHN.cjs');
|
|
6
6
|
var childProcess = require('node:child_process');
|
|
7
7
|
var fs = require('node:fs');
|
|
8
8
|
var fs$1 = require('node:fs/promises');
|
|
9
9
|
var path = require('node:path');
|
|
10
|
-
var
|
|
11
|
-
var runtimeGate = require('./runtime-gate-
|
|
12
|
-
var
|
|
10
|
+
var runtimePermissionEnv = require('./runtime-permission-env-DKrhgVM3.cjs');
|
|
11
|
+
var runtimeGate = require('./runtime-gate-BWlU-R4h.cjs');
|
|
12
|
+
var node_crypto = require('node:crypto');
|
|
13
|
+
var context = require('./context-7rmakzS_.cjs');
|
|
13
14
|
require('node:module');
|
|
14
15
|
require('path');
|
|
15
16
|
require('os');
|
|
@@ -20,9 +21,9 @@ require('events');
|
|
|
20
21
|
require('stream');
|
|
21
22
|
require('buffer');
|
|
22
23
|
require('util');
|
|
23
|
-
require('node:crypto');
|
|
24
24
|
require('fs');
|
|
25
25
|
require('constants');
|
|
26
|
+
require('node:async_hooks');
|
|
26
27
|
|
|
27
28
|
async function getCDNUploadInfo(options, runtime = {}) {
|
|
28
29
|
const body = new URLSearchParams();
|
|
@@ -59,6 +60,63 @@ async function postHeyboxApi(pathWithQuery, body, session$1, runtime) {
|
|
|
59
60
|
return result;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const CREATE_USER_MINIPROGRAM_UPLOAD_SESSION_API_PATH = '/mall/developer/user_miniprogram/version/upload_session/create';
|
|
64
|
+
const RECORD_USER_MINIPROGRAM_UPLOAD_RESULT_API_PATH = '/mall/developer/user_miniprogram/version/upload_session/record';
|
|
65
|
+
async function precheckUserMiniprogramVersion(options, runtime = {}) {
|
|
66
|
+
return postUserMiniprogramVersionForm(runtimePermissionEnv.PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, options, {
|
|
67
|
+
// 展示资料由服务端沿用 last approved 或注入默认名/默认图;CLI 不传 name/icon/cover。
|
|
68
|
+
...(options.version ? { version: options.version } : {}),
|
|
69
|
+
...(options.source_version ? { source_version: options.source_version } : {}),
|
|
70
|
+
release_note: options.releaseNote,
|
|
71
|
+
auto_publish: String(options.autoPublish),
|
|
72
|
+
}, runtime);
|
|
73
|
+
}
|
|
74
|
+
async function submitUserMiniprogramAudit(options, runtime = {}) {
|
|
75
|
+
return postUserMiniprogramVersionForm(runtimePermissionEnv.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, options, {
|
|
76
|
+
// 展示资料由服务端沿用 last approved 或注入默认名/默认图;CLI 不传 name/icon/cover。
|
|
77
|
+
...(options.manifest ? { manifest: JSON.stringify(options.manifest) } : {}),
|
|
78
|
+
...(options.source_version ? { source_version: options.source_version } : {}),
|
|
79
|
+
...(options.sessionId ? { session_id: options.sessionId } : {}),
|
|
80
|
+
release_note: options.releaseNote,
|
|
81
|
+
auto_publish: String(options.autoPublish),
|
|
82
|
+
}, runtime);
|
|
83
|
+
}
|
|
84
|
+
async function createUserMiniprogramUploadSession(options, runtime = {}) {
|
|
85
|
+
return postUserMiniprogramVersionForm(CREATE_USER_MINIPROGRAM_UPLOAD_SESSION_API_PATH, options, {
|
|
86
|
+
version: options.version,
|
|
87
|
+
artifact_signature: options.artifactSignature,
|
|
88
|
+
relative_paths: options.relativePaths.join(','),
|
|
89
|
+
file_sizes: options.fileSizes.map(size => String(size)).join(','),
|
|
90
|
+
}, runtime);
|
|
91
|
+
}
|
|
92
|
+
async function recordUserMiniprogramUploadResult(options, runtime = {}) {
|
|
93
|
+
await postUserMiniprogramVersionForm(RECORD_USER_MINIPROGRAM_UPLOAD_RESULT_API_PATH, options, {
|
|
94
|
+
version: options.version,
|
|
95
|
+
session_id: options.sessionId,
|
|
96
|
+
keys: options.keys.join(','),
|
|
97
|
+
}, runtime);
|
|
98
|
+
}
|
|
99
|
+
async function postUserMiniprogramVersionForm(path, options, fields, runtime) {
|
|
100
|
+
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
101
|
+
const context = session.createHeyboxOpenPlatformRequestContext(options.session, path, {
|
|
102
|
+
contentType: 'application/x-www-form-urlencoded',
|
|
103
|
+
});
|
|
104
|
+
const body = new URLSearchParams();
|
|
105
|
+
body.set('mini_program_id', options.miniProgramId);
|
|
106
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
107
|
+
body.set(key, value);
|
|
108
|
+
}
|
|
109
|
+
const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? context.baseUrl, path, context.platformParams), {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: context.headers,
|
|
112
|
+
body: body.toString(),
|
|
113
|
+
});
|
|
114
|
+
const result = await session.readHeyboxApiEnvelope(response, {
|
|
115
|
+
pathWithQuery: path,
|
|
116
|
+
});
|
|
117
|
+
return result ?? {};
|
|
118
|
+
}
|
|
119
|
+
|
|
62
120
|
const DEFAULT_CONCURRENCY = 4;
|
|
63
121
|
async function runUpload(options, runtime = {}) {
|
|
64
122
|
const logger = runtime.logger ?? index.createCliLogger();
|
|
@@ -67,9 +125,15 @@ async function runUpload(options, runtime = {}) {
|
|
|
67
125
|
const getCDNUploadInfo$1 = runtime.getCDNUploadInfo ?? getCDNUploadInfo;
|
|
68
126
|
const getCDNUploadToken$1 = runtime.getCDNUploadToken ?? getCDNUploadToken;
|
|
69
127
|
const postCDNUploadCallback$1 = runtime.postCDNUploadCallback ?? postCDNUploadCallback;
|
|
128
|
+
const recordUserMiniprogramUploadResult$1 = runtime.recordUserMiniprogramUploadResult ?? recordUserMiniprogramUploadResult;
|
|
70
129
|
const createReadStream = runtime.createReadStream ?? fs.createReadStream;
|
|
71
|
-
|
|
72
|
-
|
|
130
|
+
let createCosClient = runtime.createCosClient;
|
|
131
|
+
if (!createCosClient) {
|
|
132
|
+
const COS = await loadCosConstructor();
|
|
133
|
+
createCosClient = ({ uploadToken }) => createDefaultCosClient(uploadToken, COS);
|
|
134
|
+
}
|
|
135
|
+
const orderedFiles = runtimePermissionEnv.prepareMiniappArtifactFiles(options.files);
|
|
136
|
+
const batches = chunkUploadFiles(orderedFiles, runtimePermissionEnv.MINIAPP_UPLOAD_BATCH_SIZE);
|
|
73
137
|
await logger.task(`正在上传 ${orderedFiles.length} 个文件`, async (taskContext) => {
|
|
74
138
|
logger.debug(`上传并发数: ${concurrency}`);
|
|
75
139
|
logger.debug(`上传批次数: ${batches.length}`);
|
|
@@ -78,25 +142,37 @@ async function runUpload(options, runtime = {}) {
|
|
|
78
142
|
const batch = batches[batchIndex];
|
|
79
143
|
logger.debug(`正在处理上传批次 ${batchIndex + 1}/${batches.length},文件数: ${batch.length}`);
|
|
80
144
|
logger.debug('正在获取 CDN 上传信息');
|
|
81
|
-
const fileInfos = createUploadFileInfos(batch
|
|
82
|
-
const uploadInfo = await getCDNUploadInfo$1({
|
|
145
|
+
const fileInfos = createUploadFileInfos(batch);
|
|
146
|
+
const uploadInfo = await getCDNUploadInfo$1({
|
|
147
|
+
session: options.session,
|
|
148
|
+
scope: runtimePermissionEnv.MINIAPP_UPLOAD_SCOPE,
|
|
149
|
+
fileInfos,
|
|
150
|
+
needCache: false,
|
|
151
|
+
}, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
83
152
|
logger.debug(`CDN bucket: ${uploadInfo.bucket}`);
|
|
84
153
|
logger.debug(`CDN region: ${uploadInfo.region}`);
|
|
85
|
-
validateUploadInfoKeys(batch, uploadInfo.keys
|
|
154
|
+
validateUploadInfoKeys(batch, uploadInfo.keys);
|
|
86
155
|
const indexedFiles = batch.map((file, index) => ({ file, key: uploadInfo.keys[index] }));
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
156
|
+
logger.debug('正在获取本批 CDN 上传凭证');
|
|
157
|
+
let uploadToken;
|
|
158
|
+
try {
|
|
159
|
+
uploadToken = await getCDNUploadToken$1({
|
|
160
|
+
session: options.session,
|
|
161
|
+
bucket: uploadInfo.bucket,
|
|
162
|
+
keys: indexedFiles.map(item => item.key),
|
|
163
|
+
mimetypes: indexedFiles.map(item => item.file.mimeType),
|
|
164
|
+
isMultipartUpload: 0,
|
|
165
|
+
}, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new index.CliError(`获取 CDN 上传凭证失败:${readErrorMessage(error)}`);
|
|
169
|
+
}
|
|
170
|
+
const cos = createCosClient({ session: options.session, uploadToken });
|
|
171
|
+
const successfulKeys = [];
|
|
97
172
|
const queue = indexedFiles.slice();
|
|
173
|
+
let firstUploadError;
|
|
98
174
|
const workers = Array.from({ length: Math.min(concurrency, indexedFiles.length) }, async () => {
|
|
99
|
-
while (queue.length > 0) {
|
|
175
|
+
while (queue.length > 0 && !firstUploadError) {
|
|
100
176
|
const uploadTask = queue.shift();
|
|
101
177
|
if (!uploadTask) {
|
|
102
178
|
return;
|
|
@@ -110,6 +186,7 @@ async function runUpload(options, runtime = {}) {
|
|
|
110
186
|
ContentLength: uploadTask.file.size,
|
|
111
187
|
ContentType: uploadTask.file.mimeType,
|
|
112
188
|
});
|
|
189
|
+
successfulKeys.push(uploadTask.key);
|
|
113
190
|
completed += 1;
|
|
114
191
|
taskContext.update(`正在上传 ${completed}/${orderedFiles.length} 个文件`);
|
|
115
192
|
logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] ok ${uploadTask.file.relativePath} (${formatSize(uploadTask.file.size)})`);
|
|
@@ -118,13 +195,25 @@ async function runUpload(options, runtime = {}) {
|
|
|
118
195
|
completed += 1;
|
|
119
196
|
const message = readErrorMessage(error);
|
|
120
197
|
logger.debug(`[${String(completed).padStart(2)}/${orderedFiles.length}] error ${uploadTask.file.relativePath} -> ${message}`);
|
|
121
|
-
|
|
198
|
+
firstUploadError ??= new index.CliError(`上传文件失败:${uploadTask.file.relativePath} -> ${message}`, readVerboseUploadError(uploadTask.file.relativePath, error));
|
|
122
199
|
}
|
|
123
200
|
}
|
|
124
201
|
});
|
|
125
202
|
await Promise.all(workers);
|
|
126
|
-
|
|
127
|
-
|
|
203
|
+
if (successfulKeys.length > 0) {
|
|
204
|
+
logger.debug('正在确认 CDN 上传结果');
|
|
205
|
+
await postCDNUploadCallback$1({ session: options.session, keys: successfulKeys, isFinished: true }, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
206
|
+
await recordUserMiniprogramUploadResult$1({
|
|
207
|
+
session: options.session,
|
|
208
|
+
miniProgramId: options.miniProgramId,
|
|
209
|
+
version: options.version,
|
|
210
|
+
sessionId: options.uploadSessionId,
|
|
211
|
+
keys: successfulKeys,
|
|
212
|
+
}, { baseUrl: runtime.baseUrl, fetchImpl });
|
|
213
|
+
}
|
|
214
|
+
if (firstUploadError) {
|
|
215
|
+
throw firstUploadError;
|
|
216
|
+
}
|
|
128
217
|
}
|
|
129
218
|
}, { successText: `已上传 ${orderedFiles.length} 个文件` });
|
|
130
219
|
}
|
|
@@ -135,36 +224,27 @@ function chunkUploadFiles(files, batchSize) {
|
|
|
135
224
|
}
|
|
136
225
|
return batches;
|
|
137
226
|
}
|
|
138
|
-
function createUploadFileInfos(files
|
|
227
|
+
function createUploadFileInfos(files) {
|
|
139
228
|
return files.map((file) => ({
|
|
140
229
|
name: file.relativePath.split('/').pop() ?? file.relativePath,
|
|
141
230
|
mimetype: file.mimeType,
|
|
142
231
|
fsize: file.size,
|
|
143
|
-
path:
|
|
144
|
-
miniProgramId: options.miniProgramId,
|
|
145
|
-
version: options.version,
|
|
146
|
-
relativePath: file.relativePath,
|
|
147
|
-
}),
|
|
232
|
+
path: file.key,
|
|
148
233
|
}));
|
|
149
234
|
}
|
|
150
|
-
function validateUploadInfoKeys(files, keys
|
|
235
|
+
function validateUploadInfoKeys(files, keys) {
|
|
151
236
|
if (keys.length !== files.length) {
|
|
152
237
|
throw new index.CliError(`CDN 上传接口返回 key 数量异常:期望 ${files.length},实际 ${keys.length}`);
|
|
153
238
|
}
|
|
154
|
-
for (let index$
|
|
155
|
-
const expectedKey = index$1.
|
|
156
|
-
|
|
157
|
-
version: options.version,
|
|
158
|
-
relativePath: files[index$2].relativePath,
|
|
159
|
-
});
|
|
160
|
-
const actualKey = keys[index$2];
|
|
239
|
+
for (let index$1 = 0; index$1 < files.length; index$1 += 1) {
|
|
240
|
+
const expectedKey = files[index$1].key;
|
|
241
|
+
const actualKey = keys[index$1];
|
|
161
242
|
if (actualKey !== expectedKey) {
|
|
162
|
-
throw new index.CliError(`CDN 上传接口返回 key 异常:${files[index$
|
|
243
|
+
throw new index.CliError(`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, [`CDN 上传接口返回 key 异常:${files[index$1].relativePath}`, `expected: ${expectedKey}`, `actual: ${actualKey}`].join('\n'));
|
|
163
244
|
}
|
|
164
245
|
}
|
|
165
246
|
}
|
|
166
|
-
|
|
167
|
-
const COS = await loadCosConstructor();
|
|
247
|
+
function createDefaultCosClient(uploadToken, COS) {
|
|
168
248
|
const cos = new COS({
|
|
169
249
|
getAuthorization(_input, callback) {
|
|
170
250
|
callback({
|
|
@@ -191,7 +271,7 @@ async function createDefaultCosClient(uploadToken) {
|
|
|
191
271
|
};
|
|
192
272
|
}
|
|
193
273
|
async function loadCosConstructor() {
|
|
194
|
-
const cosModule = await Promise.resolve().then(function () { return require('./index-
|
|
274
|
+
const cosModule = await Promise.resolve().then(function () { return require('./index-6HfcwZ_r.cjs'); }).then(function (n) { return n.index; });
|
|
195
275
|
return cosModule.default;
|
|
196
276
|
}
|
|
197
277
|
function formatSize(bytes) {
|
|
@@ -234,43 +314,162 @@ function isRecord(value) {
|
|
|
234
314
|
return Object.prototype.toString.call(value) === '[object Object]';
|
|
235
315
|
}
|
|
236
316
|
|
|
237
|
-
async function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
317
|
+
async function computeMiniappArtifactSignature(files, runtime = {}) {
|
|
318
|
+
const preparedFiles = runtimePermissionEnv.prepareMiniappArtifactFiles(files);
|
|
319
|
+
const hash = node_crypto.createHash('sha256');
|
|
320
|
+
const createReadStream = runtime.createReadStream ?? fs.createReadStream;
|
|
321
|
+
hash.update(runtimePermissionEnv.createMiniappArtifactSignatureHeader(preparedFiles.length));
|
|
322
|
+
for (const file of preparedFiles) {
|
|
323
|
+
hash.update(runtimePermissionEnv.createMiniappArtifactFileHeader(file));
|
|
324
|
+
let bytesRead = 0;
|
|
325
|
+
for await (const chunk of createReadStream(file.absolutePath)) {
|
|
326
|
+
const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
|
|
327
|
+
bytesRead += bytes.byteLength;
|
|
328
|
+
hash.update(bytes);
|
|
329
|
+
}
|
|
330
|
+
if (bytesRead !== file.size) {
|
|
331
|
+
throw new Error(`构建产物在签名期间发生变化:${file.relativePath}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return hash.digest('hex');
|
|
245
335
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
336
|
+
|
|
337
|
+
const DEPLOY_LOCKED_MESSAGE = '当前设备已有同版本产物正在上传,请等待完成后重试';
|
|
338
|
+
const DEPLOY_LOCK_DIRECTORY_NAME = 'deploy-locks';
|
|
339
|
+
const DEPLOY_LOCK_OWNER_FILE = 'owner.json';
|
|
340
|
+
const INCOMPLETE_LOCK_GRACE_MS = 30_000;
|
|
341
|
+
const defaultPaths = index.envPaths('hb-sdk', { suffix: '' });
|
|
342
|
+
class DeployLockConflictError extends Error {
|
|
343
|
+
}
|
|
344
|
+
async function withDeployLock(scope, operation, options = {}) {
|
|
345
|
+
let lock;
|
|
346
|
+
try {
|
|
347
|
+
lock = await acquireDeployLock(scope, options);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (error instanceof DeployLockConflictError) {
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
// 本地文件锁只减少重复操作,文件系统异常不阻断服务端上传流程。
|
|
354
|
+
return operation();
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
return await operation();
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
await lock.release().catch(() => undefined);
|
|
361
|
+
}
|
|
254
362
|
}
|
|
255
|
-
async function
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
363
|
+
async function acquireDeployLock(scope, options = {}) {
|
|
364
|
+
const lockDirectory = getDeployLockDirectory(scope, options.cacheRoot);
|
|
365
|
+
const ownerFile = path.join(lockDirectory, DEPLOY_LOCK_OWNER_FILE);
|
|
366
|
+
const owner = {
|
|
367
|
+
version: 1,
|
|
368
|
+
pid: options.pid ?? process.pid,
|
|
369
|
+
token: options.token ?? node_crypto.randomBytes(16).toString('hex'),
|
|
370
|
+
};
|
|
371
|
+
await fs$1.mkdir(path.dirname(lockDirectory), { recursive: true, mode: 0o700 });
|
|
372
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
373
|
+
let created = false;
|
|
374
|
+
try {
|
|
375
|
+
await fs$1.mkdir(lockDirectory, { mode: 0o700 });
|
|
376
|
+
created = true;
|
|
377
|
+
await fs$1.writeFile(ownerFile, `${JSON.stringify(owner)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
378
|
+
return createDeployLock(lockDirectory, ownerFile, owner.token);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
if (created) {
|
|
382
|
+
await fs$1.rm(lockDirectory, { force: true, recursive: true });
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
if (!isNodeError(error) || error.code !== 'EEXIST') {
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
if (!(await canReclaimDeployLock(lockDirectory, ownerFile, options))) {
|
|
389
|
+
throw new DeployLockConflictError(DEPLOY_LOCKED_MESSAGE);
|
|
390
|
+
}
|
|
391
|
+
await reclaimDeployLock(lockDirectory, owner.token);
|
|
392
|
+
}
|
|
264
393
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return
|
|
394
|
+
throw new DeployLockConflictError(DEPLOY_LOCKED_MESSAGE);
|
|
395
|
+
}
|
|
396
|
+
function getDeployLockDirectory(scope, cacheRoot = defaultPaths.cache) {
|
|
397
|
+
const scopeHash = node_crypto.createHash('sha256').update(`${scope.apiBaseUrl}\0${scope.miniProgramId}\0${scope.version}`).digest('hex');
|
|
398
|
+
return path.join(cacheRoot, DEPLOY_LOCK_DIRECTORY_NAME, scopeHash);
|
|
399
|
+
}
|
|
400
|
+
function createDeployLock(lockDirectory, ownerFile, token) {
|
|
401
|
+
let released = false;
|
|
402
|
+
return {
|
|
403
|
+
async release() {
|
|
404
|
+
if (released)
|
|
405
|
+
return;
|
|
406
|
+
released = true;
|
|
407
|
+
await removeOwnedLock(lockDirectory, ownerFile, token);
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function canReclaimDeployLock(lockDirectory, ownerFile, options) {
|
|
412
|
+
const owner = await readDeployLockOwner(ownerFile);
|
|
413
|
+
if (owner) {
|
|
414
|
+
return !(options.isProcessRunning ?? isProcessRunning)(owner.pid);
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const lockStat = await fs$1.stat(lockDirectory);
|
|
418
|
+
return (options.now ?? Date.now)() - lockStat.mtimeMs >= INCOMPLETE_LOCK_GRACE_MS;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
return isNodeError(error) && error.code === 'ENOENT';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function removeOwnedLock(lockDirectory, ownerFile, token) {
|
|
425
|
+
const owner = await readDeployLockOwner(ownerFile);
|
|
426
|
+
if (owner?.token === token) {
|
|
427
|
+
await fs$1.rm(lockDirectory, { force: true, recursive: true });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function reclaimDeployLock(lockDirectory, token) {
|
|
431
|
+
const reclaimDirectory = `${lockDirectory}.reclaim-${node_crypto.createHash('sha256').update(token).digest('hex')}`;
|
|
432
|
+
try {
|
|
433
|
+
await fs$1.rename(lockDirectory, reclaimDirectory);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
437
|
+
return;
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
await fs$1.rm(reclaimDirectory, { force: true, recursive: true });
|
|
441
|
+
}
|
|
442
|
+
async function readDeployLockOwner(ownerFile) {
|
|
443
|
+
try {
|
|
444
|
+
const value = JSON.parse(await fs$1.readFile(ownerFile, 'utf8'));
|
|
445
|
+
return isDeployLockOwner(value) ? value : null;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
449
|
+
return null;
|
|
450
|
+
if (error instanceof SyntaxError)
|
|
451
|
+
return null;
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function isDeployLockOwner(value) {
|
|
456
|
+
return (typeof value === 'object' &&
|
|
457
|
+
value !== null &&
|
|
458
|
+
value.version === 1 &&
|
|
459
|
+
Number.isInteger(value.pid) &&
|
|
460
|
+
typeof value.token === 'string');
|
|
461
|
+
}
|
|
462
|
+
function isProcessRunning(pid) {
|
|
463
|
+
try {
|
|
464
|
+
process.kill(pid, 0);
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
return !isNodeError(error) || error.code !== 'ESRCH';
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function isNodeError(error) {
|
|
472
|
+
return error instanceof Error && 'code' in error;
|
|
274
473
|
}
|
|
275
474
|
|
|
276
475
|
const SUPPORTED_PACKAGE_MANAGERS = [
|
|
@@ -312,110 +511,175 @@ async function runDeployCommand(options, runtime = {}) {
|
|
|
312
511
|
const releaseNote = await resolveDeployReleaseNote(options, runtime);
|
|
313
512
|
const session$1 = await logger.task('正在校验 Heybox 登录态', () => (runtime.requireAuthSession ?? session.requireHeyboxAuthSession)({ loginBaseUrl }), { successText: 'Heybox 登录态有效' });
|
|
314
513
|
logger.debug(`Heybox API: ${apiBaseUrl}`);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
else {
|
|
333
|
-
if (typeof packageJson.scripts?.build !== 'string') {
|
|
334
|
-
throw new Error('package.json scripts.build 未定义,请添加 build 脚本');
|
|
514
|
+
const uploadVersion = fromVersion ? '' : validateVersionForDeploy(packageJson.version, 'package.json.version');
|
|
515
|
+
const deploy = async () => {
|
|
516
|
+
let precheckVersion;
|
|
517
|
+
const historyArtifact = fromVersion ? { source_version: fromVersion } : {};
|
|
518
|
+
if (fromVersion) {
|
|
519
|
+
const precheck = await runVersionPrecheck({
|
|
520
|
+
session: session$1,
|
|
521
|
+
miniProgramId,
|
|
522
|
+
...historyArtifact,
|
|
523
|
+
releaseNote,
|
|
524
|
+
autoPublish,
|
|
525
|
+
}, runtime, apiBaseUrl, projectRoot, logger);
|
|
526
|
+
const sourceSdkVersion = typeof precheck.sdk_version === 'string' ? precheck.sdk_version.trim() : '';
|
|
527
|
+
if (!sourceSdkVersion) {
|
|
528
|
+
throw new Error('历史产物缺少 sdkVersion,无法复用,请重新构建并上传新版本');
|
|
529
|
+
}
|
|
530
|
+
precheckVersion = precheck.version || precheck.version_normalized || fromVersion;
|
|
335
531
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
532
|
+
else {
|
|
533
|
+
if (typeof packageJson.scripts?.build !== 'string') {
|
|
534
|
+
throw new Error('package.json scripts.build 未定义,请添加 build 脚本');
|
|
535
|
+
}
|
|
536
|
+
precheckVersion = uploadVersion;
|
|
537
|
+
await runVersionPrecheck({
|
|
538
|
+
session: session$1,
|
|
539
|
+
miniProgramId,
|
|
540
|
+
version: precheckVersion,
|
|
541
|
+
releaseNote,
|
|
542
|
+
autoPublish,
|
|
543
|
+
}, runtime, apiBaseUrl, projectRoot, logger);
|
|
544
|
+
let skipPlatformCsp = false;
|
|
545
|
+
try {
|
|
546
|
+
const { detail } = await logger.task('正在读取远端小程序权限', () => (runtime.getBoundMiniProgram ?? context.getBoundMiniProgram)({
|
|
547
|
+
allowUnsafeApiBaseUrl: options.allowUnsafeApiBaseUrl,
|
|
548
|
+
apiBaseUrl,
|
|
549
|
+
cwd: projectRoot,
|
|
550
|
+
env,
|
|
551
|
+
fetchImpl: runtime.fetchImpl,
|
|
552
|
+
loginBaseUrl,
|
|
553
|
+
session: session$1,
|
|
554
|
+
}), { successText: '已读取远端小程序权限' });
|
|
555
|
+
skipPlatformCsp = context.shouldSkipMiniappPlatformCsp(detail.runtime_permissions);
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
logger.warn('未读取到远端 Runtime 权限,构建将继续注入平台 CSP。');
|
|
559
|
+
logger.debug(`远端 Runtime 权限读取失败详情:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
560
|
+
}
|
|
561
|
+
const pm = detectPackageManager(projectRoot);
|
|
562
|
+
const platform = runtime.platform ?? process.platform;
|
|
563
|
+
const buildMode = validateBuildMode(env.HB_SDK_ENV?.trim(), platform);
|
|
564
|
+
logger.info(`开始构建: ${pm} ${createBuildArgs(pm, buildMode).join(' ')}`);
|
|
565
|
+
await runBuildScript(pm, projectRoot, runtime.spawn ?? childProcess.spawn, {
|
|
566
|
+
env: runtimePermissionEnv.createMiniappPlatformCspEnv(env, skipPlatformCsp),
|
|
567
|
+
mode: buildMode,
|
|
568
|
+
outputMode: options.buildOutputMode ?? 'inherit',
|
|
569
|
+
platform,
|
|
570
|
+
stderr: runtime.stderr,
|
|
571
|
+
});
|
|
572
|
+
logger.success('构建完成');
|
|
366
573
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
574
|
+
let submitAuditResult;
|
|
575
|
+
let version = precheckVersion;
|
|
576
|
+
let manifest;
|
|
577
|
+
let uploadSessionId = '';
|
|
578
|
+
if (!fromVersion) {
|
|
579
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
580
|
+
const parsedManifest = await readDeployManifest(projectRoot, logger);
|
|
581
|
+
({ manifest, version } = parsedManifest);
|
|
582
|
+
if (version !== precheckVersion) {
|
|
583
|
+
throw new Error(`dist/manifest.json.version (${version}) 与预检版本 (${precheckVersion}) 不一致,请重新 build 后再 deploy`);
|
|
372
584
|
}
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
585
|
+
const uploadFiles = await logger.task('正在校验 dist 上传文件', async () => {
|
|
586
|
+
const allFiles = await walkDistFiles(distDir);
|
|
587
|
+
const blocked = allFiles.find((entry) => runtimePermissionEnv.relativePathContainsNodeModulesSegment(entry.relativePath));
|
|
588
|
+
if (blocked) {
|
|
589
|
+
throw new Error(`dist 目录含 node_modules:${blocked.relativePath}`);
|
|
590
|
+
}
|
|
591
|
+
const files = runtimePermissionEnv.prepareMiniappArtifactFiles(allFiles.filter((entry) => runtimePermissionEnv.shouldUploadDistFile(entry.relativePath)));
|
|
592
|
+
const pathError = runtimePermissionEnv.validateMiniappUploadPathLengths(files, { miniProgramId });
|
|
593
|
+
if (pathError) {
|
|
594
|
+
throw new Error(pathError);
|
|
595
|
+
}
|
|
596
|
+
const sizeError = runtimePermissionEnv.validateUploadTotalSize(files);
|
|
597
|
+
if (sizeError) {
|
|
598
|
+
throw new Error(sizeError);
|
|
599
|
+
}
|
|
600
|
+
logger.debug(`待上传文件数: ${files.length}`);
|
|
601
|
+
return files;
|
|
602
|
+
}, { successText: 'dist 上传文件校验完成' });
|
|
603
|
+
// deploy 期间 dist 视为稳定构建输出;为避免持有大量文件描述符,签名和上传不会共享打开的文件句柄。
|
|
604
|
+
const artifactSignature = await logger.task('正在计算构建产物签名', () => (runtime.computeMiniappArtifactSignature ?? computeMiniappArtifactSignature)(uploadFiles), { successText: '构建产物签名计算完成' });
|
|
605
|
+
const createdUploadSession = await logger.task('正在创建或恢复上传会话', async () => {
|
|
606
|
+
const result = await (runtime.createUserMiniprogramUploadSession ?? createUserMiniprogramUploadSession)({
|
|
607
|
+
session: session$1,
|
|
608
|
+
miniProgramId,
|
|
609
|
+
version,
|
|
610
|
+
artifactSignature,
|
|
611
|
+
relativePaths: uploadFiles.map((file) => file.relativePath),
|
|
612
|
+
fileSizes: uploadFiles.map((file) => file.size),
|
|
613
|
+
}, { baseUrl: apiBaseUrl, fetchImpl: runtime.fetchImpl });
|
|
614
|
+
if (typeof result.session_id !== 'string' || !result.session_id.trim()) {
|
|
615
|
+
throw new Error('创建上传会话失败:服务端未返回 session_id');
|
|
616
|
+
}
|
|
617
|
+
const resolved = runtimePermissionEnv.resolveMiniappUploadSessionFiles(uploadFiles, Array.isArray(result.files) ? result.files : [], Array.isArray(result.uploaded_keys) ? result.uploaded_keys : []);
|
|
618
|
+
return {
|
|
619
|
+
sessionId: result.session_id.trim(),
|
|
620
|
+
files: resolved.files,
|
|
621
|
+
missingFiles: resolved.missingFiles,
|
|
622
|
+
};
|
|
623
|
+
}, { successText: '上传会话已就绪' });
|
|
624
|
+
uploadSessionId = createdUploadSession.sessionId;
|
|
625
|
+
const uploadedCount = createdUploadSession.files.length - createdUploadSession.missingFiles.length;
|
|
626
|
+
if (uploadedCount > 0) {
|
|
627
|
+
logger.debug(`复用已上传文件: ${uploadedCount}/${createdUploadSession.files.length}`);
|
|
377
628
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
629
|
+
try {
|
|
630
|
+
if (createdUploadSession.missingFiles.length > 0) {
|
|
631
|
+
await assertDeployUploadFilesStillMatchSignature({
|
|
632
|
+
computeMiniappArtifactSignature: runtime.computeMiniappArtifactSignature ?? computeMiniappArtifactSignature,
|
|
633
|
+
distDir,
|
|
634
|
+
expectedFiles: uploadFiles,
|
|
635
|
+
expectedSignature: artifactSignature,
|
|
636
|
+
miniProgramId,
|
|
637
|
+
});
|
|
638
|
+
await (runtime.runUpload ?? runUpload)({
|
|
639
|
+
session: session$1,
|
|
640
|
+
files: createdUploadSession.missingFiles.map(({ file, key }) => ({ ...file, key })),
|
|
641
|
+
miniProgramId,
|
|
642
|
+
version,
|
|
643
|
+
uploadSessionId,
|
|
644
|
+
}, { baseUrl: apiBaseUrl, fetchImpl: runtime.fetchImpl, logger });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
throw translateHeyboxDeployError(error, { projectRoot, stage: 'upload', version });
|
|
381
649
|
}
|
|
382
|
-
|
|
383
|
-
return files;
|
|
384
|
-
}, { successText: 'dist 上传文件校验完成' });
|
|
650
|
+
}
|
|
385
651
|
try {
|
|
386
|
-
|
|
652
|
+
const submitOptions = fromVersion
|
|
653
|
+
? { session: session$1, miniProgramId, ...historyArtifact, releaseNote, autoPublish }
|
|
654
|
+
: { session: session$1, miniProgramId, manifest, sessionId: uploadSessionId, releaseNote, autoPublish };
|
|
655
|
+
submitAuditResult = await logger.task('正在提交审核', () => (runtime.submitUserMiniprogramAudit ?? submitUserMiniprogramAudit)(submitOptions, {
|
|
656
|
+
baseUrl: apiBaseUrl,
|
|
657
|
+
fetchImpl: runtime.fetchImpl,
|
|
658
|
+
}), { successText: '审核提交完成' });
|
|
387
659
|
}
|
|
388
660
|
catch (error) {
|
|
389
|
-
throw translateHeyboxDeployError(error, { projectRoot, stage: '
|
|
661
|
+
throw translateHeyboxDeployError(error, { projectRoot, stage: 'submitAudit', version });
|
|
390
662
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
logger,
|
|
407
|
-
miniProgramId,
|
|
408
|
-
mode: options.successOutputMode ?? 'legacy',
|
|
409
|
-
protocolString: submitAuditResult.protocol_string,
|
|
410
|
-
version,
|
|
411
|
-
});
|
|
412
|
-
return {
|
|
413
|
-
autoPublish,
|
|
414
|
-
changed: true,
|
|
415
|
-
miniProgramId,
|
|
416
|
-
...(submitAuditResult.protocol_string ? { protocolString: submitAuditResult.protocol_string } : {}),
|
|
417
|
-
version,
|
|
663
|
+
printDeploySuccess({
|
|
664
|
+
autoPublish,
|
|
665
|
+
logger,
|
|
666
|
+
miniProgramId,
|
|
667
|
+
mode: options.successOutputMode ?? 'legacy',
|
|
668
|
+
protocolString: submitAuditResult.protocol_string,
|
|
669
|
+
version,
|
|
670
|
+
});
|
|
671
|
+
return {
|
|
672
|
+
autoPublish,
|
|
673
|
+
changed: true,
|
|
674
|
+
miniProgramId,
|
|
675
|
+
...(submitAuditResult.protocol_string ? { protocolString: submitAuditResult.protocol_string } : {}),
|
|
676
|
+
version,
|
|
677
|
+
};
|
|
418
678
|
};
|
|
679
|
+
if (fromVersion) {
|
|
680
|
+
return deploy();
|
|
681
|
+
}
|
|
682
|
+
return (runtime.withDeployLock ?? withDeployLock)({ apiBaseUrl, miniProgramId, version: uploadVersion }, deploy);
|
|
419
683
|
}
|
|
420
684
|
function printDeploySuccess(options) {
|
|
421
685
|
options.logger.success(`提交审核成功:${options.miniProgramId} ${options.version}`);
|
|
@@ -539,9 +803,7 @@ async function runBuildScript(pm, cwd, spawnImpl, options) {
|
|
|
539
803
|
const pipeStdoutToStderr = options.outputMode === 'stderr';
|
|
540
804
|
const buildArgs = createBuildArgs(pm, options.mode);
|
|
541
805
|
const isWindows = options.platform === 'win32';
|
|
542
|
-
const [executable, args] = isWindows
|
|
543
|
-
? createWindowsBuildCommand(pm, buildArgs)
|
|
544
|
-
: [pm, buildArgs];
|
|
806
|
+
const [executable, args] = isWindows ? createWindowsBuildCommand(pm, buildArgs) : [pm, buildArgs];
|
|
545
807
|
const child = spawnImpl(executable, args, {
|
|
546
808
|
cwd,
|
|
547
809
|
env: { ...process.env, ...options.env },
|
|
@@ -602,7 +864,7 @@ async function walkDistFiles(distDir) {
|
|
|
602
864
|
}
|
|
603
865
|
const fileStat = await fs$1.stat(absPath);
|
|
604
866
|
results.push({
|
|
605
|
-
relativePath:
|
|
867
|
+
relativePath: relPath,
|
|
606
868
|
absolutePath: absPath,
|
|
607
869
|
size: fileStat.size,
|
|
608
870
|
mimeType: inferMimeType(entry.name),
|
|
@@ -613,6 +875,31 @@ async function walkDistFiles(distDir) {
|
|
|
613
875
|
function inferMimeType(fileName) {
|
|
614
876
|
return MIME_BY_EXT[path.extname(fileName).toLowerCase()] || 'application/octet-stream';
|
|
615
877
|
}
|
|
878
|
+
async function assertDeployUploadFilesStillMatchSignature(options) {
|
|
879
|
+
const currentFiles = runtimePermissionEnv.prepareMiniappArtifactFiles((await walkDistFiles(options.distDir)).filter((entry) => runtimePermissionEnv.shouldUploadDistFile(entry.relativePath)));
|
|
880
|
+
const currentByPath = new Map(currentFiles.map(file => [file.relativePath, file]));
|
|
881
|
+
if (currentFiles.length !== options.expectedFiles.length) {
|
|
882
|
+
throw new Error('dist 构建产物在签名后发生变化,请重新执行 deploy');
|
|
883
|
+
}
|
|
884
|
+
for (const expected of options.expectedFiles) {
|
|
885
|
+
const current = currentByPath.get(expected.relativePath);
|
|
886
|
+
if (!current || current.size !== expected.size) {
|
|
887
|
+
throw new Error(`dist 构建产物在签名后发生变化:${expected.relativePath}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const pathError = runtimePermissionEnv.validateMiniappUploadPathLengths(currentFiles, { miniProgramId: options.miniProgramId });
|
|
891
|
+
if (pathError) {
|
|
892
|
+
throw new Error(pathError);
|
|
893
|
+
}
|
|
894
|
+
const sizeError = runtimePermissionEnv.validateUploadTotalSize(currentFiles);
|
|
895
|
+
if (sizeError) {
|
|
896
|
+
throw new Error(sizeError);
|
|
897
|
+
}
|
|
898
|
+
const currentSignature = await options.computeMiniappArtifactSignature(currentFiles);
|
|
899
|
+
if (currentSignature !== options.expectedSignature) {
|
|
900
|
+
throw new Error('dist 构建产物在签名后发生变化,请重新 build 后再 deploy');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
616
903
|
function translateHeyboxDeployError(error, options) {
|
|
617
904
|
const message = error instanceof Error && error.message ? error.message : String(error);
|
|
618
905
|
if (isVersionLifecycleConflictMessage(message) && options.stage !== 'upload') {
|
|
@@ -627,18 +914,6 @@ function translateHeyboxDeployError(error, options) {
|
|
|
627
914
|
`原始错误:${readVerboseDeployErrorMessage(error)}`,
|
|
628
915
|
].join('\n'));
|
|
629
916
|
}
|
|
630
|
-
if (isDuplicateUploadErrorMessage(message) && options.stage === 'upload') {
|
|
631
|
-
const packageJsonPath = path.join(options.projectRoot, 'package.json');
|
|
632
|
-
const nextVersion = suggestNextPatchVersion(options.version);
|
|
633
|
-
return new index.CliError([
|
|
634
|
-
`当前小程序版本 ${options.version} 的上传文件已存在,不能覆盖同版本发布产物。`,
|
|
635
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
636
|
-
].join('\n'), [
|
|
637
|
-
`当前小程序版本 ${options.version} 的上传文件已存在,不能覆盖同版本发布产物。`,
|
|
638
|
-
`请升级 ${packageJsonPath} 中的 version${nextVersion ? `(例如 ${nextVersion})` : ''},重新 build 后再 deploy。`,
|
|
639
|
-
`原始错误:${readVerboseDeployErrorMessage(error)}`,
|
|
640
|
-
].join('\n'));
|
|
641
|
-
}
|
|
642
917
|
if (isAuthExpiredErrorMessage(message)) {
|
|
643
918
|
if (options.stage === 'upload') {
|
|
644
919
|
return new index.CliError([
|
|
@@ -654,9 +929,6 @@ function translateHeyboxDeployError(error, options) {
|
|
|
654
929
|
}
|
|
655
930
|
return error instanceof Error ? error : new Error(message);
|
|
656
931
|
}
|
|
657
|
-
function isDuplicateUploadErrorMessage(message) {
|
|
658
|
-
return /重名|duplicate|already exists|exists/i.test(message);
|
|
659
|
-
}
|
|
660
932
|
function isVersionLifecycleConflictMessage(message) {
|
|
661
933
|
return /AlreadyExists\(6\)|不能复用|已进入线上生命周期|正在审核|活跃候选版本|同版本/i.test(message);
|
|
662
934
|
}
|