@mbws/plugin-sdk 0.1.1 → 0.2.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/dist/contract.js CHANGED
@@ -12,6 +12,9 @@
12
12
  function injectedPluginCode() {
13
13
  return typeof __MBWS_PLUGIN_CODE__ !== "undefined" ? __MBWS_PLUGIN_CODE__ : undefined;
14
14
  }
15
+ function injectedDevContractBase() {
16
+ return typeof __MBWS_DEV_CONTRACT_BASE__ !== "undefined" ? __MBWS_DEV_CONTRACT_BASE__ : undefined;
17
+ }
15
18
  /** manifest.id 按 baseURI 记忆化——同一页面重复调用不再多发 manifest 请求 */
16
19
  const manifestIdCache = new Map();
17
20
  /** 从本工程根读 manifest.id(dev server / 解包产物内 manifest.json 均在 baseURI 根) */
@@ -38,11 +41,12 @@ async function readManifestId() {
38
41
  /** 按插件编号拉关联任务的最新契约。非 2xx 抛带状态码与后端 message 的 Error。 */
39
42
  export async function fetchContract(opts = {}) {
40
43
  const pluginCode = opts.pluginCode ?? injectedPluginCode() ?? (await readManifestId());
41
- const baseUrl = (opts.baseUrl ?? "").replace(/\/+$/, "");
44
+ // URL 前缀:dev 注入值优先(同源走插件中间件,免 CORS),否则 baseUrl(缺省同源相对)
45
+ const prefix = injectedDevContractBase() ?? (opts.baseUrl ?? "").replace(/\/+$/, "");
42
46
  const headers = {};
43
47
  if (opts.devKey)
44
48
  headers["X-Dev-Key"] = opts.devKey;
45
- const response = await fetch(`${baseUrl}/api/plugin-dev-tasks/contract/by-plugin/${encodeURIComponent(pluginCode)}`, { headers, signal: opts.signal });
49
+ const response = await fetch(`${prefix}/api/plugin-dev-tasks/contract/by-plugin/${encodeURIComponent(pluginCode)}`, { headers, signal: opts.signal });
46
50
  if (!response.ok) {
47
51
  let message = `契约拉取失败(HTTP ${response.status})`;
48
52
  try {
package/dist/vite.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Plugin } from "vite";
2
+ import { type MbwsUserConfig } from "./config.js";
3
+ export interface MbwsVitePluginOptions {
4
+ /** mbws.config.ts 的配置(defineConfig 回传值)——静态合同与动态开发配置同住 */
5
+ config: MbwsUserConfig;
6
+ }
7
+ /**
8
+ * mbws vite 插件。dev 预览下契约拉取同源化:SDK fetchContract 读到注入的
9
+ * `__MBWS_DEV_CONTRACT_BASE__` 后改打 `/__mbws__/api/...`,由本插件的中间件
10
+ * 转发到真实后端——插件 dev 端口不在后端 CORS 白名单的问题由此消解。
11
+ */
12
+ export declare function mbwsVitePlugin(opts: MbwsVitePluginOptions): Plugin;
package/dist/vite.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 插件工程的 vite 插件——把平台机械(manifest 派生 / define 注入 / dev 虚拟供给与
3
+ * 后端转发)收敛进 SDK,工程 vite.config.ts 只留 `mbwsVitePlugin({ config })` 一行。
4
+ *
5
+ * 承担四件事(对齐原脚手架模板内联版语义):
6
+ * - define 注入插件编号(build/serve 都注入),serve 额外注入 dev 契约前缀;
7
+ * - 缺省补 base: "./"(产物在 OSS 子目录 / registry 代理路径下托管)、双入口
8
+ * rollupOptions.input(panel.html / viewer.html)与 build.outDir(解包形态目录,
9
+ * 与 manifest 同目录——缺省 dist/ 根会两处分离破坏 CI 打包约定),用户已设项不覆盖;
10
+ * - dev 虚拟供给 /manifest.json(buildManifest 派生,与 build 产物同源);
11
+ * - dev 转发 /__mbws__/api/* 到后端(Node 全局 fetch,不走 server.proxy——
12
+ * 用户自己的 proxy 配置自由,互不干扰);
13
+ * - build 后往 dist/<id>@<version>/ 写 manifest.json(产物直出解包形态目录)。
14
+ *
15
+ * vite 只进 devDependencies 供类型与测试(type-only import),SDK 产物不依赖 vite。
16
+ */
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { buildManifest } from "./config.js";
20
+ /** dev 契约拉取的专属命名空间:同源走插件中间件转发,免 CORS */
21
+ const DEV_CONTRACT_BASE = "/__mbws__";
22
+ /** 后端缺省地址:插件开发者缺省连线上(拉线上 spec),本地联调走 env 或 apiBaseUrl */
23
+ const DEFAULT_API_BASE = "https://union.mobiwusi.com";
24
+ /** dev 转发目标:config.apiBaseUrl > shell env > 线上(发包即线上,地址不是工程配置项) */
25
+ function resolveApiBase(config) {
26
+ return (config.apiBaseUrl ?? process.env.MBWS_API_BASE_URL ?? DEFAULT_API_BASE).replace(/\/+$/, "");
27
+ }
28
+ /** 读请求体(POST 等带 body 的转发用;GET/HEAD 直接跳过) */
29
+ function readRequestBody(req) {
30
+ return new Promise((resolve, reject) => {
31
+ const chunks = [];
32
+ req.on("data", (chunk) => chunks.push(chunk));
33
+ req.on("end", () => resolve(Buffer.concat(chunks)));
34
+ req.on("error", reject);
35
+ });
36
+ }
37
+ /**
38
+ * mbws vite 插件。dev 预览下契约拉取同源化:SDK fetchContract 读到注入的
39
+ * `__MBWS_DEV_CONTRACT_BASE__` 后改打 `/__mbws__/api/...`,由本插件的中间件
40
+ * 转发到真实后端——插件 dev 端口不在后端 CORS 白名单的问题由此消解。
41
+ */
42
+ export function mbwsVitePlugin(opts) {
43
+ const { config } = opts;
44
+ // 工程 root:config 钩子里记(用户显式 root 优先),closeBundle 写产物时用
45
+ let projectRoot = process.cwd();
46
+ // manifest 是纯派生物,构建/供给共用同一序列化姿势(尾换行对齐文件写习惯)
47
+ const manifestJson = () => `${JSON.stringify(buildManifest(config), null, 2)}\n`;
48
+ // 解包形态目录 <id>@<version>/:静态产物与 manifest 同目录(CI 的 tar -C 按它整目录打包)
49
+ const distDir = () => path.resolve(projectRoot, `dist/${config.id}@${config.version}`);
50
+ return {
51
+ name: "mbws-plugin",
52
+ config(userConfig, { command }) {
53
+ projectRoot = userConfig.root ?? process.cwd();
54
+ const define = {
55
+ __MBWS_PLUGIN_CODE__: JSON.stringify(config.id),
56
+ };
57
+ // dev 专用前缀只在 serve 注入——build 产物绝不含 /__mbws__(宿主内无此路由)
58
+ if (command === "serve") {
59
+ define.__MBWS_DEV_CONTRACT_BASE__ = JSON.stringify(DEV_CONTRACT_BASE);
60
+ }
61
+ // 增量配置(vite 浅合并,不覆盖用户已设项)
62
+ const incremental = { define };
63
+ // 相对 base:产物在 OSS 子目录 / registry 代理路径下托管,资源引用必须是 ./ 相对路径
64
+ if (!userConfig.base)
65
+ incremental.base = "./";
66
+ // 构建缺省(用户已设项不覆盖):
67
+ // - outDir 必须钉到解包形态目录——vite 缺省吐 dist/ 根会与 closeBundle 写的
68
+ // manifest.json 分离,CI 的 tar 只打包到孤零零的 manifest(解包形态约定被破坏)
69
+ // - 只打包宿主要装载的两个入口;根 index.html 欢迎页是 dev 导航,不进产物
70
+ const buildDefaults = {};
71
+ if (!userConfig.build?.outDir)
72
+ buildDefaults.outDir = distDir();
73
+ if (!userConfig.build?.rollupOptions?.input) {
74
+ buildDefaults.rollupOptions = {
75
+ input: [path.resolve(projectRoot, "panel.html"), path.resolve(projectRoot, "viewer.html")],
76
+ };
77
+ }
78
+ if (Object.keys(buildDefaults).length > 0)
79
+ incremental.build = buildDefaults;
80
+ // 依赖预构建不消费顶层 define,漏了 SDK 里的注入读取在 dev 侧失效。
81
+ // vite 8 依赖优化换 Rolldown:esbuildOptions 已废弃(启动即告警);rolldown 的
82
+ // define 在 transform 子对象下(rolldown InputOptions.transform.define)
83
+ if (command === "serve") {
84
+ incremental.optimizeDeps = {
85
+ rolldownOptions: {
86
+ transform: { define: { __MBWS_PLUGIN_CODE__: JSON.stringify(config.id) } },
87
+ },
88
+ };
89
+ }
90
+ return incremental;
91
+ },
92
+ configureServer(server) {
93
+ server.middlewares.use((req, res, next) => {
94
+ const pathname = (req.url ?? "").split("?")[0];
95
+ if (pathname === "/manifest.json") {
96
+ // dev 虚拟供给:SDK fetchContract 的 manifest 兜底读取在 dev/产物两形态行为一致
97
+ res.setHeader("content-type", "application/json");
98
+ res.end(manifestJson());
99
+ return;
100
+ }
101
+ if (pathname.startsWith(`${DEV_CONTRACT_BASE}/api`)) {
102
+ // 转发异步进行,失败交 next(connect 约定);返回 promise 供测试 await
103
+ return forwardApi(config, req, res).catch(next);
104
+ }
105
+ next();
106
+ return undefined;
107
+ });
108
+ },
109
+ closeBundle() {
110
+ // 产物直出解包形态目录 <id>@<version>/,桌面 MBWS_DEV_PLUGIN_DIR 指向 dist 即装载
111
+ const outDir = distDir();
112
+ // mkdir 容错:在线构建场景 outDir 被平台覆盖,本目录可能不存在(写失败会炸构建)
113
+ fs.mkdirSync(outDir, { recursive: true });
114
+ fs.writeFileSync(path.join(outDir, "manifest.json"), manifestJson());
115
+ },
116
+ };
117
+ }
118
+ /** /__mbws__/<rest> → <apiBase>/<rest>:方法/头透传,响应状态码/content-type/body 透传 */
119
+ async function forwardApi(config, req, res) {
120
+ const pathname = (req.url ?? "").split("?")[0];
121
+ const rest = pathname.slice(DEV_CONTRACT_BASE.length);
122
+ try {
123
+ // 头透传(host/connection 是逐跳头,指向 dev server 自身,不能带给后端)
124
+ const headers = {};
125
+ for (const [key, value] of Object.entries(req.headers)) {
126
+ if (value === undefined || key === "host" || key === "connection")
127
+ continue;
128
+ headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
129
+ }
130
+ const method = req.method ?? "GET";
131
+ const body = method === "GET" || method === "HEAD"
132
+ ? undefined
133
+ : (await readRequestBody(req));
134
+ // 查询串原样保留(/api/x?k=v 转发不丢参)
135
+ const search = (req.url ?? "").slice(pathname.length);
136
+ const upstream = await fetch(`${resolveApiBase(config)}${rest}${search}`, {
137
+ method,
138
+ headers,
139
+ body,
140
+ });
141
+ res.statusCode = upstream.status;
142
+ const contentType = upstream.headers.get("content-type");
143
+ if (contentType)
144
+ res.setHeader("content-type", contentType);
145
+ res.end(Buffer.from(await upstream.arrayBuffer()));
146
+ }
147
+ catch (error) {
148
+ // 转发失败按网关错误处理:JSON 错误体对齐后端统一错误格式
149
+ res.statusCode = 502;
150
+ res.setHeader("content-type", "application/json");
151
+ res.end(JSON.stringify({
152
+ message: `mbws dev forward failed: ${error instanceof Error ? error.message : String(error)}`,
153
+ }));
154
+ }
155
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbws/plugin-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Moye 插件开发 SDK——panel/viewer 与宿主的类型定义与运行时端口适配",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,10 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "default": "./dist/index.js"
13
+ },
14
+ "./vite": {
15
+ "types": "./dist/vite.d.ts",
16
+ "default": "./dist/vite.js"
13
17
  }
14
18
  },
15
19
  "publishConfig": {
@@ -21,8 +25,12 @@
21
25
  "dependencies": {
22
26
  "zod": "^4.4.3"
23
27
  },
28
+ "devDependencies": {
29
+ "vite": "^8.0.14"
30
+ },
24
31
  "scripts": {
25
32
  "build": "tsc -p tsconfig.build.json",
33
+ "test": "vitest run",
26
34
  "pack:sdk": "pnpm build && pnpm pack"
27
35
  }
28
36
  }