@mbws/plugin-sdk 0.3.0 → 0.3.2

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/dist/config.d.ts CHANGED
@@ -94,6 +94,7 @@ declare const UserConfigSchema: z.ZodObject<{
94
94
  name: z.ZodString;
95
95
  entry: z.ZodOptional<z.ZodString>;
96
96
  devPath: z.ZodOptional<z.ZodString>;
97
+ url: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
97
98
  }, z.core.$strip>>>;
98
99
  devKey: z.ZodOptional<z.ZodString>;
99
100
  apiBaseUrl: z.ZodOptional<z.ZodString>;
@@ -48,14 +48,16 @@ export declare const AttachmentDeclSchema: z.ZodObject<{
48
48
  export type AttachmentDecl = z.output<typeof AttachmentDeclSchema>;
49
49
  /**
50
50
  * 开发者侧的附件引用(mbws.config.ts 可填形态)——platforms 由构建解析填充。
51
- * devPath 是 dev-only 的本机路径覆盖(线上还没有的新版本附件指到本机开发;
52
- * 未填/路径不存在时 dev 自动按 name+version 从官方附件库下载缓存),不进产物。
51
+ * devPath 是 dev-only 的本机路径覆盖(线上还没有的新版本附件指到本机开发),不进产物。
52
+ * url dev 下载直链(暂由开发者提供官方地址;自建附件中心上线后改走 name+version
53
+ * 解析):string = 全平台同地址,record = 按平台键(darwin-arm64 等),不进产物。
53
54
  */
54
55
  export declare const AttachmentRefSchema: z.ZodObject<{
55
56
  version: z.ZodString;
56
57
  name: z.ZodString;
57
58
  entry: z.ZodOptional<z.ZodString>;
58
59
  devPath: z.ZodOptional<z.ZodString>;
60
+ url: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
59
61
  }, z.core.$strip>;
60
62
  export type AttachmentRef = z.output<typeof AttachmentRefSchema>;
61
63
  export declare const ManifestSchema: z.ZodObject<{
package/dist/manifest.js CHANGED
@@ -72,11 +72,13 @@ export const AttachmentDeclSchema = z.object({
72
72
  });
73
73
  /**
74
74
  * 开发者侧的附件引用(mbws.config.ts 可填形态)——platforms 由构建解析填充。
75
- * devPath 是 dev-only 的本机路径覆盖(线上还没有的新版本附件指到本机开发;
76
- * 未填/路径不存在时 dev 自动按 name+version 从官方附件库下载缓存),不进产物。
75
+ * devPath 是 dev-only 的本机路径覆盖(线上还没有的新版本附件指到本机开发),不进产物。
76
+ * url dev 下载直链(暂由开发者提供官方地址;自建附件中心上线后改走 name+version
77
+ * 解析):string = 全平台同地址,record = 按平台键(darwin-arm64 等),不进产物。
77
78
  */
78
79
  export const AttachmentRefSchema = AttachmentDeclSchema.omit({ platforms: true }).extend({
79
80
  devPath: z.string().optional(),
81
+ url: z.union([z.string(), z.record(z.string(), z.string())]).optional(),
80
82
  });
81
83
  /**
82
84
  * 权限声明,两种语法(CSP connect-src 与权限预览文案的推导输入,设计稿 §6.5):
package/dist/version.d.ts CHANGED
@@ -12,4 +12,4 @@ export declare const PROTOCOL_VERSION: 1;
12
12
  * SDK 自身版本(与 package.json version 保持一致)。
13
13
  * init-ack 上报给宿主做审计,与 manifest.sdk(build 写入)交叉核对。
14
14
  */
15
- export declare const SDK_VERSION = "0.3.0";
15
+ export declare const SDK_VERSION = "0.3.2";
package/dist/version.js CHANGED
@@ -12,4 +12,4 @@ export const PROTOCOL_VERSION = 1;
12
12
  * SDK 自身版本(与 package.json version 保持一致)。
13
13
  * init-ack 上报给宿主做审计,与 manifest.sdk(build 写入)交叉核对。
14
14
  */
15
- export const SDK_VERSION = "0.3.0";
15
+ export const SDK_VERSION = "0.3.2";
package/dist/vite.js CHANGED
@@ -18,6 +18,8 @@ import { spawn, spawnSync } from "node:child_process";
18
18
  import crypto from "node:crypto";
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
+ import { pipeline } from "node:stream/promises";
22
+ import zlib from "node:zlib";
21
23
  import { buildManifest, resolveAttachmentRefs } from "./config.js";
22
24
  import { generateContractTypes } from "./schema-types.js";
23
25
  // 契约类型生成经本子路径暴露(Node 端工具链用;依赖 prettier,不进根入口)
@@ -30,6 +32,14 @@ const DEFAULT_API_BASE = "https://union.mobiwusi.com";
30
32
  function resolveApiBase(config) {
31
33
  return (config.apiBaseUrl ?? process.env.MBWS_API_BASE_URL ?? DEFAULT_API_BASE).replace(/\/+$/, "");
32
34
  }
35
+ /**
36
+ * 官方附件库地址(构建期解析 platforms hash 用):env > 线上,**刻意不走
37
+ * config.apiBaseUrl**——它是本地联调字段,而附件库是平台生产设施,产物 hash 必须
38
+ * 钉生产库(在线构建容器里按本地地址 fetch 会不可达)。
39
+ */
40
+ function attachmentApiBase() {
41
+ return (process.env.MBWS_API_BASE_URL ?? DEFAULT_API_BASE).replace(/\/+$/, "");
42
+ }
33
43
  /** 读请求体(POST 等带 body 的转发用;GET/HEAD 直接跳过) */
34
44
  function readRequestBody(req) {
35
45
  return new Promise((resolve, reject) => {
@@ -57,7 +67,7 @@ export function mbwsVitePlugin(opts) {
57
67
  return [];
58
68
  if (resolvedAttachmentsCache)
59
69
  return resolvedAttachmentsCache;
60
- resolvedAttachmentsCache = await resolveAttachmentRefs(refs, resolveApiBase(config));
70
+ resolvedAttachmentsCache = await resolveAttachmentRefs(refs, attachmentApiBase());
61
71
  return resolvedAttachmentsCache;
62
72
  };
63
73
  // manifest 是纯派生物,构建/供给共用同一序列化姿势(尾换行对齐文件写习惯)。
@@ -180,7 +190,7 @@ export function mbwsVitePlugin(opts) {
180
190
  // mkdir 容错:在线构建场景 outDir 被平台覆盖,本目录可能不存在(写失败会炸构建)
181
191
  fs.mkdirSync(outDir, { recursive: true });
182
192
  const refs = config.attachments ?? [];
183
- const decls = refs.length > 0 ? await resolveAttachmentRefs(refs, resolveApiBase(config)) : [];
193
+ const decls = refs.length > 0 ? await resolveAttachmentRefs(refs, attachmentApiBase()) : [];
184
194
  fs.writeFileSync(path.join(outDir, "manifest.json"), `${JSON.stringify(buildManifest(config, { resolvedAttachments: decls }), null, 2)}\n`);
185
195
  },
186
196
  };
@@ -245,10 +255,41 @@ const inflightDevAttachments = new Map();
245
255
  */
246
256
  const devAttachmentResolves = new Map();
247
257
  /**
248
- * 确保官方附件库附件在本地就绪:按 config.attachments 声明查 resolve 端点,
249
- * 内容寻址缓存 `.mbws/attachments/<name>@<version>/<sha256>/<entry>`——命中零下载;
250
- * 未命中流式下载(sha256 校验)+ `tar -xf` 解包(bsdtar 通吃 tar.gz/zip)+ chmod 755。
251
- * 附件未声明 / 库未登记返回 null(调用方回 501);下载/校验/解包失败抛错。
258
+ * 流式下载到文件:边下边算 sha256(不整包进内存),每 5MB 报一次进度行。
259
+ * sizeHint 已知(库 resolve 下发)报「x / y MB」,直链未知报「x MB」。返回 sha256
260
+ */
261
+ async function downloadToFile(url, dest, log, sizeHint) {
262
+ const response = await fetch(url);
263
+ if (!response.ok || !response.body)
264
+ throw new Error(`下载 HTTP ${response.status}`);
265
+ const hash = crypto.createHash("sha256");
266
+ const out = fs.createWriteStream(dest);
267
+ let received = 0;
268
+ let lastReported = 0;
269
+ // Node fetch 的 body 是 async iterable
270
+ for await (const chunk of response.body) {
271
+ const bytes = Buffer.from(chunk);
272
+ hash.update(bytes);
273
+ out.write(bytes);
274
+ received += bytes.byteLength;
275
+ if (received - lastReported >= 5 * 1e6) {
276
+ lastReported = received;
277
+ log(sizeHint != null
278
+ ? `[mbws:dev] 下载中 ${Math.round(received / 1e6)} / ${Math.round(sizeHint / 1e6)} MB`
279
+ : `[mbws:dev] 下载中 ${Math.round(received / 1e6)} MB`);
280
+ }
281
+ }
282
+ await new Promise((resolve, reject) => out.end((error) => (error ? reject(error) : resolve())));
283
+ return hash.digest("hex");
284
+ }
285
+ /**
286
+ * 确保附件在本地就绪(两条来源,优先级:url 直链 > 官方附件库 resolve):
287
+ * - url 直链(暂由开发者提供官方地址):无 sha256,缓存目录不带 hash 层
288
+ * (`.mbws/attachments/<name>@<version>/`,同 version 重传视为修正);
289
+ * 按后缀分派 tar.gz/zip 解包、.gz 单文件解压、其余当裸二进制。
290
+ * - 官方附件库:resolve 端点拿 url/sha256/size,内容寻址缓存
291
+ * `.mbws/attachments/<name>@<version>/<sha256>/`,下载后 sha256 校验 + tar -xf。
292
+ * 附件未声明 / 无 url 且库未登记返回 null(调用方回 501);下载/校验/解包失败抛错。
252
293
  * log 收下载进度行(调用方转 NDJSON 事件流)。
253
294
  */
254
295
  async function ensureLibraryAttachment(config, root, name, log) {
@@ -261,11 +302,49 @@ async function ensureLibraryAttachment(config, root, name, log) {
261
302
  if (!ref)
262
303
  return null;
263
304
  const platform = currentAttachmentPlatform();
305
+ // win 平台包内入口带 .exe 后缀(publish 脚本布局),显式 entry 原样用
306
+ const entry = ref.entry ?? `${name}${platform.startsWith("win") ? ".exe" : ""}`;
307
+ // —— url 直链分支(开发者提供的官方地址)——
308
+ const directUrl = typeof ref.url === "string" ? ref.url : ref.url?.[platform];
309
+ if (directUrl) {
310
+ const dir = path.join(root, DEV_ATTACHMENT_CACHE, `${name}@${ref.version}`);
311
+ const entryPath = path.join(dir, entry);
312
+ if (fs.existsSync(entryPath))
313
+ return entryPath;
314
+ log(`[mbws:dev] 下载附件 ${name}@${ref.version}(${platform} 直链,无 hash 校验)`);
315
+ fs.mkdirSync(dir, { recursive: true });
316
+ const tmpPath = path.join(dir, "download.tmp");
317
+ try {
318
+ await downloadToFile(directUrl, tmpPath, log);
319
+ const lower = directUrl.toLowerCase();
320
+ if (/\.(tar\.gz|tgz|zip)$/.test(lower)) {
321
+ const extracted = spawnSync("tar", ["-xf", tmpPath, "-C", dir]);
322
+ if (extracted.status !== 0) {
323
+ throw new Error(`解包失败:${extracted.stderr?.toString().trim() || `tar exit ${extracted.status}`}`);
324
+ }
325
+ }
326
+ else if (/\.gz$/.test(lower)) {
327
+ await pipeline(fs.createReadStream(tmpPath), zlib.createGunzip(), fs.createWriteStream(entryPath));
328
+ }
329
+ else {
330
+ fs.copyFileSync(tmpPath, entryPath);
331
+ }
332
+ if (!fs.existsSync(entryPath))
333
+ throw new Error(`解包后未见入口 ${entry}`);
334
+ fs.chmodSync(entryPath, 0o755);
335
+ log(`[mbws:dev] 附件 ${name}@${ref.version} 就绪(已缓存,后续零下载)`);
336
+ return entryPath;
337
+ }
338
+ finally {
339
+ fs.rmSync(tmpPath, { force: true });
340
+ }
341
+ }
342
+ // —— 官方附件库分支 ——
264
343
  const resolveKey = `${name}@${ref.version}`;
265
344
  let resolved = devAttachmentResolves.get(resolveKey);
266
345
  if (!resolved) {
267
346
  try {
268
- const response = await fetch(`${resolveApiBase(config)}/api/plugin-attachments/resolve` +
347
+ const response = await fetch(`${attachmentApiBase()}/api/plugin-attachments/resolve` +
269
348
  `?name=${encodeURIComponent(name)}&version=${encodeURIComponent(ref.version)}` +
270
349
  `&platform=${platform}`);
271
350
  // 未登记/平台缺失/后端不可达都归「dev 不可用」,交给调用方 501 文案
@@ -278,8 +357,6 @@ async function ensureLibraryAttachment(config, root, name, log) {
278
357
  return null;
279
358
  }
280
359
  }
281
- // win 平台包内入口带 .exe 后缀(publish 脚本布局),显式 entry 原样用
282
- const entry = ref.entry ?? `${name}${platform.startsWith("win") ? ".exe" : ""}`;
283
360
  const dir = path.join(root, DEV_ATTACHMENT_CACHE, `${name}@${ref.version}`, resolved.sha256);
284
361
  const entryPath = path.join(dir, entry);
285
362
  if (fs.existsSync(entryPath))
@@ -288,26 +365,7 @@ async function ensureLibraryAttachment(config, root, name, log) {
288
365
  fs.mkdirSync(dir, { recursive: true });
289
366
  const tmpPath = path.join(dir, `download.${resolved.archive}`);
290
367
  try {
291
- const response = await fetch(resolved.url);
292
- if (!response.ok || !response.body)
293
- throw new Error(`下载 HTTP ${response.status}`);
294
- const hash = crypto.createHash("sha256");
295
- const out = fs.createWriteStream(tmpPath);
296
- let received = 0;
297
- let lastReported = 0;
298
- // Node fetch 的 body 是 async iterable:流式落盘 + 边下边算 hash(不整包进内存)
299
- for await (const chunk of response.body) {
300
- const bytes = Buffer.from(chunk);
301
- hash.update(bytes);
302
- out.write(bytes);
303
- received += bytes.byteLength;
304
- if (received - lastReported >= 5 * 1e6) {
305
- lastReported = received;
306
- log(`[mbws:dev] 下载中 ${Math.round(received / 1e6)} / ${Math.round(resolved.size / 1e6)} MB`);
307
- }
308
- }
309
- await new Promise((resolve, reject) => out.end((error) => (error ? reject(error) : resolve())));
310
- const sha256 = hash.digest("hex");
368
+ const sha256 = await downloadToFile(resolved.url, tmpPath, log, resolved.size);
311
369
  if (sha256 !== resolved.sha256) {
312
370
  throw new Error(`sha256 不符(期望 ${resolved.sha256},实得 ${sha256})`);
313
371
  }
@@ -375,7 +433,7 @@ async function handleDevExec(config, root, req, res) {
375
433
  res.statusCode = 501;
376
434
  res.end(JSON.stringify({
377
435
  message: declared
378
- ? `附件 ${attachment} 未在官方附件库登记当前平台产物(发布见 scripts/publish-attachment.mjs),或后端不可达;本机覆盖可配 attachments 的 devPath / env MBWS_DEV_ATTACHMENT_${attachment.replace(/-/g, "_").toUpperCase()}`
436
+ ? `附件 ${attachment} url 直链且未在官方附件库登记当前平台产物(或后端不可达);可配 attachments 的 url 直链 / devPath / env MBWS_DEV_ATTACHMENT_${attachment.replace(/-/g, "_").toUpperCase()}`
379
437
  : `mbws.config.ts 的 attachments 未声明附件 ${attachment}(dev 自动下载依赖声明);本机覆盖可配该项 devPath / env MBWS_DEV_ATTACHMENT_${attachment.replace(/-/g, "_").toUpperCase()}`,
380
438
  }));
381
439
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbws/plugin-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Moye 插件开发 SDK——panel/viewer 与宿主的类型定义与运行时端口适配",
5
5
  "license": "MIT",
6
6
  "type": "module",