@lark-apaas/miaoda-cli 0.1.1-alpha.d20f110 → 0.1.1-alpha.ddba836

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.
@@ -299,18 +299,28 @@ async function preUpload(appId, req) {
299
299
  }
300
300
  /**
301
301
  * 调用 upload callback 拿到对象元数据。
302
- * 后端把对象 VO 序列化成 JSON 字符串放在 data.metadata 字段里,这里解析出实际
303
- * filePath / file_name / download_url uploadFile 调用方使用,避免 CLI 自己拼路径。
302
+ *
303
+ * 网关 IDL metadata 字段加了 api.response.converter = "decode",正常路径下
304
+ * HTTP 响应里的 metadata 已经被网关从字符串解码成对象;这里两种形态都兼容:
305
+ * - object → 直接当 CallbackObjectVO 用(网关解码场景)
306
+ * - string → JSON.parse 出来用(后端原始形态 / 网关行为变化兜底)
307
+ *
308
+ * 解析失败 / metadata 缺失时返回空对象,由 uploadFile 用本地兜底字段填充。
304
309
  */
305
310
  async function uploadCallback(appId, req) {
306
311
  const url = `/v1/storage/app/${encodeURIComponent(appId)}/object/callback`;
307
312
  const body = await (0, client_1.doPost)(url, req, { errorContext: "upload callback" });
308
313
  const data = extractEnvelope(body);
309
- if (!data.metadata) {
314
+ const metadata = data.metadata;
315
+ if (!metadata) {
310
316
  return {};
311
317
  }
318
+ if (typeof metadata === "object") {
319
+ return metadata;
320
+ }
321
+ // string 形态兜底
312
322
  try {
313
- return JSON.parse(data.metadata);
323
+ return JSON.parse(metadata);
314
324
  }
315
325
  catch (err) {
316
326
  (0, logger_1.debug)(`upload callback metadata json parse failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -341,11 +351,15 @@ async function uploadFile(opts) {
341
351
  // 这里复制到独立 ArrayBuffer 以满足 lib.dom.d.ts 的 BodyInit 约束
342
352
  const ab = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
343
353
  const uploadStart = Date.now();
354
+ // Content-Disposition 用 attachment + filename 编码原始文件名。TOS 会把这个
355
+ // header 作为对象 metadata 存住,服务端 callback 阶段 HeadObject 读回并解析
356
+ // filename 写入 DB。我们要不传 header,服务端走兜底会把 storage key 当文件名。
344
357
  const res = await fetch(pre.uploadURL, {
345
358
  method: "PUT",
346
359
  headers: {
347
360
  "Content-Type": opts.contentType,
348
361
  "Content-Length": String(opts.fileSize),
362
+ "Content-Disposition": `attachment; filename="${sanitizeFileName(opts.fileName)}"`,
349
363
  },
350
364
  body: ab,
351
365
  });
@@ -378,6 +392,8 @@ async function uploadFile(opts) {
378
392
  ? (metadata.filePath.startsWith("/") ? metadata.filePath : "/" + metadata.filePath)
379
393
  : (opts.remotePath ?? "/" + opts.fileName);
380
394
  const result = {
395
+ // 优先取服务端 ObjectVO.name(来自 PUT 时带的 Content-Disposition),
396
+ // 与后续 file ls / file stat 返回的展示名保持一致;缺失时降级用本地 fileName。
381
397
  file_name: metadata.name ?? opts.fileName,
382
398
  path,
383
399
  size: metadata.metadata?.contentLength ?? opts.fileSize,
@@ -388,6 +404,17 @@ async function uploadFile(opts) {
388
404
  }
389
405
  return result;
390
406
  }
407
+ /**
408
+ * 把文件名清理成可安全放进 Content-Disposition `filename="..."` 的形态。
409
+ * 与 fullstack-plugin 的 sanitizeFileName 行为一致:
410
+ * 1. 去掉对 TOS / 文件系统不友好的字符 [: " \ / * ? < > | , ;]
411
+ * 2. encodeURIComponent 把非 ASCII(中文等)做百分号编码,保证 header 合法
412
+ * 3. 处理后为空时退回 "download_file" 兜底
413
+ */
414
+ function sanitizeFileName(fileName) {
415
+ const illegalChars = /[:"\\/*?<>|,;]/g;
416
+ return encodeURIComponent(fileName.replace(illegalChars, "")) || "download_file";
417
+ }
391
418
  // ── 预签下载 URL ──
392
419
  /**
393
420
  * 获取预签下载 URL。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.1-alpha.d20f110",
3
+ "version": "0.1.1-alpha.ddba836",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {