@minus-ai/dev-vite-plugin 0.1.0-beta.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/index.js ADDED
@@ -0,0 +1,653 @@
1
+ // src/index.ts
2
+ import { createProxyMiddleware } from "http-proxy-middleware";
3
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, rmSync, cpSync, renameSync, realpathSync, statSync } from "node:fs";
4
+ import { resolve as resolve2, join as join2 } from "node:path";
5
+ import http from "node:http";
6
+ import https from "node:https";
7
+
8
+ // src/cleanup.ts
9
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
10
+ import { join, resolve } from "node:path";
11
+ var PID_FILE = ".minus/dev.pid";
12
+ function killOldProcess(pidFile) {
13
+ if (!existsSync(pidFile)) return;
14
+ let pid;
15
+ try {
16
+ pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
17
+ } catch {
18
+ return;
19
+ }
20
+ if (!pid || pid <= 0) return;
21
+ try {
22
+ process.kill(pid, 0);
23
+ } catch {
24
+ try {
25
+ unlinkSync(pidFile);
26
+ } catch {
27
+ }
28
+ return;
29
+ }
30
+ console.log(`[minus-dev] killing residual dev server (PID ${pid})`);
31
+ try {
32
+ process.kill(-pid, "SIGTERM");
33
+ } catch {
34
+ try {
35
+ process.kill(pid, "SIGTERM");
36
+ } catch {
37
+ }
38
+ }
39
+ const deadline = Date.now() + 3e3;
40
+ while (Date.now() < deadline) {
41
+ try {
42
+ process.kill(pid, 0);
43
+ } catch {
44
+ break;
45
+ }
46
+ const wait = Date.now() + 100;
47
+ while (Date.now() < wait) {
48
+ }
49
+ }
50
+ try {
51
+ process.kill(-pid, "SIGKILL");
52
+ } catch {
53
+ try {
54
+ process.kill(pid, "SIGKILL");
55
+ } catch {
56
+ }
57
+ }
58
+ try {
59
+ unlinkSync(pidFile);
60
+ } catch {
61
+ }
62
+ console.log("[minus-dev] cleanup done");
63
+ }
64
+ function writePid(projectDir) {
65
+ const dir = join(projectDir, ".minus");
66
+ mkdirSync(dir, { recursive: true });
67
+ writeFileSync(join(projectDir, PID_FILE), String(process.pid) + "\n");
68
+ }
69
+ function cleanupDev(projectDir) {
70
+ const dir = resolve(projectDir ?? process.cwd());
71
+ killOldProcess(join(dir, PID_FILE));
72
+ }
73
+ if (process.argv[1]?.endsWith("minus-dev-cleanup") || process.argv[1]?.endsWith("cleanup.ts") || process.argv[1]?.endsWith("cleanup.js")) {
74
+ cleanupDev();
75
+ }
76
+
77
+ // src/index.ts
78
+ function readSkillJson(skillDir) {
79
+ const p = join2(skillDir, ".minus", "skill.json");
80
+ if (!existsSync2(p)) return {};
81
+ try {
82
+ return JSON.parse(readFileSync2(p, "utf8"));
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+ function readPipelineVersion(skillDir) {
88
+ const path = join2(skillDir, "pipeline.py");
89
+ if (!existsSync2(path)) return null;
90
+ const src = readFileSync2(path, "utf8");
91
+ const m = src.match(/^\s*version\s*=\s*["']([^"']+)["']/m);
92
+ return m ? m[1] : null;
93
+ }
94
+ function promoteToBuilds(skillDir) {
95
+ const meta = readSkillJson(skillDir);
96
+ const version = meta.version ?? readPipelineVersion(skillDir);
97
+ if (!version) {
98
+ console.log("[minus-dev] No version found, skipping promote.");
99
+ return;
100
+ }
101
+ const staticDir = join2(skillDir, "static");
102
+ if (!existsSync2(staticDir)) {
103
+ console.log("[minus-dev] No static/ dir, skipping promote.");
104
+ return;
105
+ }
106
+ const buildsRoot = join2(skillDir, "builds");
107
+ mkdirSync2(buildsRoot, { recursive: true });
108
+ const targetDir = join2(buildsRoot, version);
109
+ if (existsSync2(targetDir)) {
110
+ rmSync(targetDir, { recursive: true, force: true });
111
+ }
112
+ try {
113
+ renameSync(staticDir, targetDir);
114
+ } catch (e) {
115
+ if (e.code === "EXDEV") {
116
+ mkdirSync2(targetDir, { recursive: true });
117
+ cpSync(staticDir, targetDir, { recursive: true });
118
+ rmSync(staticDir, { recursive: true, force: true });
119
+ } else {
120
+ throw e;
121
+ }
122
+ }
123
+ console.log(`[minus-dev] static/ \u2192 builds/${version}/`);
124
+ const embedPath = join2(targetDir, "embed.html");
125
+ if (existsSync2(embedPath)) {
126
+ const IMPORT_MAP = `<script type="importmap">{"imports":{"react":"/runtime/react-all/react-all.js","react-dom":"/runtime/react-all/react-all.js","react-dom/client":"/runtime/react-all/react-all.js","react/jsx-runtime":"/runtime/react-all/react-all.js","react/jsx-dev-runtime":"/runtime/react-all/react-all.js","@minus/widget-framework":"/runtime/widget-framework/index.js","@minus/embed-sdk":"/runtime/embed-sdk/index.js","@minus/platform-widgets":"/runtime/platform-widgets/index.js","@minus/platform-utils":"/runtime/platform-utils/index.js","@minus/platform-hooks":"/runtime/platform-hooks/index.js"}}</script>`;
127
+ const ES_MODULE_SHIMS = `<script async src="https://ga.jspm.io/npm:es-module-shims@1.10.1/dist/es-module-shims.js"></script>`;
128
+ let html = readFileSync2(embedPath, "utf8");
129
+ html = html.replace("<head>", `<head>
130
+ ${ES_MODULE_SHIMS}
131
+ ${IMPORT_MAP}`);
132
+ writeFileSync2(embedPath, html, "utf8");
133
+ console.log("[minus-dev] importmap injected into embed.html");
134
+ }
135
+ writeFileSync2(join2(buildsRoot, ".current"), version + "\n", "utf8");
136
+ }
137
+ function minusDev(opts = {}) {
138
+ const skillDir = resolve2(process.cwd(), "..");
139
+ const meta = readSkillJson(skillDir);
140
+ let envPlatformUrl;
141
+ const envLocalPath = join2(skillDir, ".env.local");
142
+ if (existsSync2(envLocalPath)) {
143
+ const envContent = readFileSync2(envLocalPath, "utf8");
144
+ const match = envContent.match(/^MINUS_AI_PLATFORM_URL=(.+)$/m);
145
+ if (match) envPlatformUrl = match[1].trim();
146
+ }
147
+ let devApiKey;
148
+ const credentialsPath = join2(process.env.HOME || "", ".minus", "credentials.json");
149
+ if (existsSync2(credentialsPath)) {
150
+ try {
151
+ const creds = JSON.parse(readFileSync2(credentialsPath, "utf8"));
152
+ if (creds.api_key) devApiKey = creds.api_key;
153
+ } catch {
154
+ }
155
+ }
156
+ const gateway = opts.gateway ?? envPlatformUrl ?? meta.gateway ?? "http://localhost:18686";
157
+ const version = opts.version ?? meta.version ?? readPipelineVersion(skillDir) ?? "1.0.0";
158
+ const skillId = meta.skillId;
159
+ const localBackend = opts.localBackend;
160
+ let localRuntimeDir = null;
161
+ for (const base of [skillDir, join2(skillDir, "frontend")]) {
162
+ try {
163
+ const pluginLink = join2(base, "node_modules", "@minus", "dev-vite-plugin");
164
+ const pluginReal = realpathSync(pluginLink);
165
+ const candidate = resolve2(pluginReal, "..", "..", "runtime");
166
+ if (existsSync2(candidate)) {
167
+ localRuntimeDir = candidate;
168
+ break;
169
+ }
170
+ } catch {
171
+ }
172
+ }
173
+ if (!localRuntimeDir) {
174
+ for (const base of [skillDir, join2(skillDir, "frontend")]) {
175
+ try {
176
+ const pkg = JSON.parse(readFileSync2(join2(base, "package.json"), "utf8"));
177
+ const ref = pkg.dependencies?.["@minus-ai/dev-vite-plugin"] ?? pkg.devDependencies?.["@minus-ai/dev-vite-plugin"];
178
+ if (typeof ref === "string" && ref.startsWith("file:")) {
179
+ const pluginSrc = resolve2(base, ref.slice("file:".length));
180
+ const candidate = resolve2(pluginSrc, "..", "..", "runtime");
181
+ if (existsSync2(candidate)) {
182
+ localRuntimeDir = candidate;
183
+ break;
184
+ }
185
+ }
186
+ } catch {
187
+ }
188
+ }
189
+ }
190
+ const devPlugin = {
191
+ name: "minus-dev-proxy",
192
+ apply: "serve",
193
+ enforce: "pre",
194
+ configureServer(server) {
195
+ cleanupDev(skillDir);
196
+ writePid(skillDir);
197
+ server.httpServer?.once("close", () => {
198
+ const pidFile = join2(skillDir, ".minus", "dev.pid");
199
+ try {
200
+ unlinkSync2(pidFile);
201
+ } catch {
202
+ }
203
+ });
204
+ const streamingProxy = createProxyMiddleware({
205
+ target: gateway,
206
+ changeOrigin: true,
207
+ cookieDomainRewrite: "",
208
+ proxyTimeout: 0,
209
+ timeout: 0
210
+ });
211
+ const bufferedProxy = createProxyMiddleware({
212
+ target: gateway,
213
+ changeOrigin: true,
214
+ cookieDomainRewrite: "",
215
+ selfHandleResponse: true,
216
+ proxyTimeout: 0,
217
+ timeout: 0,
218
+ on: {
219
+ proxyRes: (proxyRes, _req, res) => {
220
+ const chunks = [];
221
+ let finished = false;
222
+ const finalize = () => {
223
+ if (finished) return;
224
+ finished = true;
225
+ const body = Buffer.concat(chunks);
226
+ const headers = { ...proxyRes.headers };
227
+ delete headers["transfer-encoding"];
228
+ delete headers["connection"];
229
+ headers["content-length"] = String(body.length);
230
+ if (!res.headersSent) res.writeHead(proxyRes.statusCode || 502, headers);
231
+ res.end(body);
232
+ };
233
+ proxyRes.on("data", (c) => chunks.push(c));
234
+ proxyRes.on("end", finalize);
235
+ proxyRes.on("aborted", finalize);
236
+ proxyRes.on("error", finalize);
237
+ }
238
+ }
239
+ });
240
+ let localStreamingProxy = null;
241
+ let localBufferedProxy = null;
242
+ if (localBackend) {
243
+ localStreamingProxy = createProxyMiddleware({
244
+ target: localBackend,
245
+ changeOrigin: true,
246
+ proxyTimeout: 0,
247
+ timeout: 0
248
+ });
249
+ localBufferedProxy = createProxyMiddleware({
250
+ target: localBackend,
251
+ changeOrigin: true,
252
+ selfHandleResponse: true,
253
+ proxyTimeout: 0,
254
+ timeout: 0,
255
+ on: {
256
+ proxyRes: (proxyRes, _req, res) => {
257
+ const chunks = [];
258
+ let finished = false;
259
+ const finalize = () => {
260
+ if (finished) return;
261
+ finished = true;
262
+ const body = Buffer.concat(chunks);
263
+ const headers = { ...proxyRes.headers };
264
+ delete headers["transfer-encoding"];
265
+ delete headers["connection"];
266
+ headers["content-length"] = String(body.length);
267
+ if (!res.headersSent) res.writeHead(proxyRes.statusCode || 502, headers);
268
+ res.end(body);
269
+ };
270
+ proxyRes.on("data", (c) => chunks.push(c));
271
+ proxyRes.on("end", finalize);
272
+ proxyRes.on("aborted", finalize);
273
+ proxyRes.on("error", finalize);
274
+ }
275
+ }
276
+ });
277
+ }
278
+ const isSSE = (p) => p.includes("/pipeline/stream");
279
+ const localPrefix = skillId ? `/skill/${skillId}/` : null;
280
+ let devSessionInstalled = false;
281
+ if (devApiKey) {
282
+ server.middlewares.use((req, res, next) => {
283
+ if (devSessionInstalled) return next();
284
+ const cookie = req.headers.cookie || "";
285
+ const sidMatch = cookie.match(/MINUS_AI_SID=([^;]*)/);
286
+ if (sidMatch && sidMatch[1] === devApiKey) {
287
+ devSessionInstalled = true;
288
+ return next();
289
+ }
290
+ const urlPath = (req.url || "").split("?")[0];
291
+ if (!urlPath.endsWith(".html") && urlPath !== "/" && urlPath !== "") return next();
292
+ const body = JSON.stringify({ apiKey: devApiKey });
293
+ const gwUrl = new URL("/api/auth/dev-session", gateway);
294
+ const mod = gwUrl.protocol === "https:" ? https : http;
295
+ const gwReq = mod.request(gwUrl, { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } }, (gwRes) => {
296
+ const setCookie = gwRes.headers["set-cookie"];
297
+ if (setCookie) {
298
+ devSessionInstalled = true;
299
+ res.setHeader("Set-Cookie", setCookie);
300
+ }
301
+ next();
302
+ });
303
+ gwReq.on("error", () => next());
304
+ gwReq.end(body);
305
+ });
306
+ }
307
+ server.middlewares.use((req, res, next) => {
308
+ const urlPath = (req.url || "").split("?")[0];
309
+ if (urlPath === "/login") {
310
+ if (devApiKey) {
311
+ devSessionInstalled = false;
312
+ res.setHeader("Set-Cookie", "MINUS_AI_SID=; Path=/; Max-Age=0");
313
+ res.writeHead(302, { Location: "/" });
314
+ res.end();
315
+ } else {
316
+ const devOrigin = `http://localhost:${server.httpServer?.address()?.port || 5173}`;
317
+ const loginUrl = new URL("/login", gateway);
318
+ loginUrl.searchParams.set("returnTo", devOrigin);
319
+ res.writeHead(302, { Location: loginUrl.toString() });
320
+ res.end();
321
+ }
322
+ return;
323
+ }
324
+ next();
325
+ });
326
+ server.middlewares.use((req, res, next) => {
327
+ const url = req.url || "";
328
+ const urlPath = url.split("?")[0];
329
+ if (!req.headers["x-workspace-mode"]) req.headers["x-workspace-mode"] = "dev";
330
+ if (localPrefix && localBackend && urlPath.startsWith(localPrefix) && urlPath.includes("/api/")) {
331
+ const apiStart = urlPath.indexOf("/api/");
332
+ req.url = url.slice(apiStart);
333
+ return (isSSE(urlPath) ? localStreamingProxy : localBufferedProxy)(req, res, next);
334
+ }
335
+ if (urlPath.startsWith("/runtime/") && localRuntimeDir) {
336
+ let localPath = join2(localRuntimeDir, urlPath.slice("/runtime/".length));
337
+ if (!existsSync2(localPath)) {
338
+ const parts = urlPath.slice("/runtime/".length).split("/");
339
+ if (parts.length >= 2) {
340
+ const pkgDir = join2(localRuntimeDir, parts[0]);
341
+ const fileName = parts.slice(1).join("/");
342
+ if (existsSync2(pkgDir)) {
343
+ const subdirs = readdirSync(pkgDir).filter((d) => {
344
+ try {
345
+ return statSync(join2(pkgDir, d)).isDirectory();
346
+ } catch {
347
+ return false;
348
+ }
349
+ });
350
+ if (subdirs.length > 0) {
351
+ const resolved = join2(pkgDir, subdirs[0], fileName);
352
+ if (existsSync2(resolved)) localPath = resolved;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ if (existsSync2(localPath)) {
358
+ const content = readFileSync2(localPath);
359
+ const ext = localPath.split(".").pop();
360
+ const mime = ext === "js" ? "application/javascript" : ext === "css" ? "text/css" : "application/octet-stream";
361
+ res.writeHead(200, { "Content-Type": mime, "Content-Length": content.length });
362
+ res.end(content);
363
+ return;
364
+ }
365
+ }
366
+ if (urlPath.startsWith("/api/") || urlPath === "/api" || urlPath.startsWith("/auth/") || urlPath === "/auth" || urlPath.startsWith("/skill/") || urlPath === "/skill" || urlPath.startsWith("/runtime/")) {
367
+ return (isSSE(urlPath) ? streamingProxy : bufferedProxy)(req, res, next);
368
+ }
369
+ if (skillId && (urlPath === "/" || urlPath === "")) {
370
+ res.writeHead(302, { Location: `/embed.html?skill_id=${skillId}` });
371
+ res.end();
372
+ return;
373
+ }
374
+ return next();
375
+ });
376
+ },
377
+ // 注入 dev 配置到 HTML;独立模式下模拟 embed host 完成握手
378
+ transformIndexHtml(html) {
379
+ const devConfig = `<script>window.__MINUS_DEV__=${JSON.stringify({ version })}</script>`;
380
+ const importMap = `<script type="importmap">{"imports":{"react":"/runtime/react-all/react-all.js","react-dom":"/runtime/react-all/react-all.js","react-dom/client":"/runtime/react-all/react-all.js","react/jsx-runtime":"/runtime/react-all/react-all.js","react/jsx-dev-runtime":"/runtime/react-all/react-all.js","@minus/widget-framework":"/runtime/widget-framework/index.js","@minus/embed-sdk":"/runtime/embed-sdk/index.js","@minus/platform-widgets":"/runtime/platform-widgets/index.js","@minus/platform-utils":"/runtime/platform-utils/index.js","@minus/platform-hooks":"/runtime/platform-hooks/index.js"}}</script>`;
381
+ const embedShim = `<script>
382
+ (function(){
383
+ if (window.parent !== window) return;
384
+ var skillId = new URLSearchParams(location.search).get("skill_id");
385
+ if (!skillId) return;
386
+ var VER = ${JSON.stringify(version)};
387
+
388
+ function uid(){ return crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2); }
389
+ function env(kind, type, payload, reqId){ return { sif:"v1", id: reqId||uid(), kind:kind, type:type, payload:payload, error:null, meta:{ts:Date.now(),from:"parent",skillId:skillId} }; }
390
+ function respond(reqId, payload){ window.postMessage(Object.assign(env("res","",payload,reqId),{type:""}), "*"); }
391
+
392
+ // \u901A\u7528 req \u54CD\u5E94\uFF1A\u5339\u914D id\uFF0C\u586B\u5145 kind=res
393
+ function sendRes(reqId, type, payload){
394
+ window.postMessage({ sif:"v1", id:reqId, kind:"res", type:type, payload:payload, error:null, meta:{ts:Date.now(),from:"parent",skillId:skillId} }, "*");
395
+ }
396
+ function sendEvt(type, payload){
397
+ window.postMessage(env("evt",type,payload), "*");
398
+ }
399
+
400
+ window.addEventListener("message", function(e){
401
+ var d = e.data;
402
+ if (!d || d.sif !== "v1") return;
403
+ if (d.meta && d.meta.from === "parent") return; // \u5FFD\u7565\u81EA\u5DF1\u53D1\u7684
404
+ // \u62E6\u622A child \u53D1\u51FA\u7684\u6D88\u606F\uFF0C\u9632\u6B62 child \u7684 RpcChannel \u6536\u5230\u81EA\u5DF1\u7684 req
405
+ e.stopImmediatePropagation();
406
+
407
+ // embed.ready (evt) \u2192 \u56DE embed.init (req)
408
+ if (d.kind === "evt" && d.type === "embed.ready") {
409
+ fetch("/api/skills/" + encodeURIComponent(skillId) + "/versions/" + encodeURIComponent(VER), { credentials:"include" })
410
+ .then(function(r){ return r.ok ? r.json() : null; })
411
+ .catch(function(){ return null; })
412
+ .then(function(skill){
413
+ var flow = skill ? { title:skill.displayName||skillId, description:skill.description||"", skillId:skillId, useCases:skill.useCases||[], tags:skill.tags||[] } : { title:skillId, description:"", skillId:skillId };
414
+ window.postMessage(env("req","embed.init",{ flow:flow, instanceId:null, currentStep:0 }), "*");
415
+ });
416
+ return;
417
+ }
418
+
419
+ // flow.start (req) \u2192 \u521B\u5EFA session\uFF0C\u542F\u52A8 SSE
420
+ if (d.kind === "req" && d.type === "flow.start") {
421
+ var reqId = d.id;
422
+ var input = d.payload && d.payload.input || {};
423
+ fetch("/api/skills/" + encodeURIComponent(skillId) + "/versions/" + encodeURIComponent(VER) + "/sessions", {
424
+ method:"POST", credentials:"include",
425
+ headers: {"Content-Type":"application/json"},
426
+ body: JSON.stringify({ entryParams: input })
427
+ })
428
+ .then(function(r){ return r.json(); })
429
+ .then(function(ses){
430
+ sendRes(reqId, "flow.start", { sessionId: ses.id, instanceId: ses.id });
431
+ // \u542F\u52A8 SSE pipeline stream\uFF08\u901A\u8FC7 dev proxy \u5230\u672C\u5730\u540E\u7AEF\uFF09
432
+ var streamUrl = "/skill/" + encodeURIComponent(skillId) + "/" + VER + "/api/sessions/" + encodeURIComponent(ses.id) + "/pipeline/stream";
433
+ var es = new EventSource(streamUrl);
434
+ es.onmessage = function(ev){
435
+ try {
436
+ var data = JSON.parse(ev.data);
437
+ sendEvt("state.update", data);
438
+ } catch(e){}
439
+ };
440
+ es.onerror = function(){ es.close(); };
441
+ })
442
+ .catch(function(err){
443
+ window.postMessage({ sif:"v1", id:reqId, kind:"res", type:"flow.start", payload:null, error:{code:"INTERNAL_ERROR",message:err.message}, meta:{ts:Date.now(),from:"parent",skillId:skillId} }, "*");
444
+ });
445
+ return;
446
+ }
447
+
448
+ // flow.resolve (req) \u2192 \u76F4\u63A5 ack
449
+ if (d.kind === "req" && d.type === "flow.resolve") {
450
+ sendRes(d.id, "flow.resolve", { ok:true });
451
+ return;
452
+ }
453
+
454
+ // api.call (req) \u2192 \u4EE3\u7406 API \u8C03\u7528
455
+ if (d.kind === "req" && d.type === "api.call") {
456
+ var p = d.payload;
457
+ fetch(p.url, { method:p.method||"GET", headers:p.headers||{}, body:p.body||null, credentials:"include" })
458
+ .then(function(r){ return r.text().then(function(t){ return {status:r.status,body:t}; }); })
459
+ .then(function(res){ sendRes(d.id, "api.call", res); })
460
+ .catch(function(err){ sendRes(d.id, "api.call", {status:500,body:err.message}); });
461
+ return;
462
+ }
463
+
464
+ // notify.toast (req) \u2192 ack
465
+ if (d.kind === "req" && d.type === "notify.toast") {
466
+ sendRes(d.id, "notify.toast", { ok:true });
467
+ return;
468
+ }
469
+ });
470
+ })();
471
+ </script>`;
472
+ return html.replace("<head>", `<head>
473
+ ${importMap}`).replace("</head>", `${devConfig}
474
+ ${embedShim}
475
+ </head>`);
476
+ }
477
+ };
478
+ const EXTERNAL_URL_MAP = {
479
+ "react": "/runtime/react-all/react-all.js",
480
+ "react-dom": "/runtime/react-all/react-all.js",
481
+ "react-dom/client": "/runtime/react-all/react-all.js",
482
+ "react/jsx-runtime": "/runtime/react-all/react-all.js",
483
+ "react/jsx-dev-runtime": "/runtime/react-all/react-all.js",
484
+ "@minus/widget-framework": "/runtime/widget-framework/index.js",
485
+ "@minus/embed-sdk": "/runtime/embed-sdk/index.js",
486
+ "@minus/platform-widgets": "/runtime/platform-widgets/index.js",
487
+ "@minus/platform-utils": "/runtime/platform-utils/index.js",
488
+ "@minus/platform-hooks": "/runtime/platform-hooks/index.js"
489
+ };
490
+ const EXTERNALS = Object.keys(EXTERNAL_URL_MAP);
491
+ let isServe = false;
492
+ let devServerOrigin = "";
493
+ const VIRTUAL_PREFIX = "\0runtime:";
494
+ const exportsCache = /* @__PURE__ */ new Map();
495
+ async function getRemoteExports(source) {
496
+ if (exportsCache.has(source)) return exportsCache.get(source);
497
+ let code;
498
+ const relPath = EXTERNAL_URL_MAP[source];
499
+ let localFile = null;
500
+ if (localRuntimeDir) {
501
+ const direct = join2(localRuntimeDir, relPath.slice("/runtime/".length));
502
+ if (existsSync2(direct)) {
503
+ localFile = direct;
504
+ } else {
505
+ const parts = relPath.slice("/runtime/".length).split("/");
506
+ if (parts.length >= 2) {
507
+ const pkgDir = join2(localRuntimeDir, parts[0]);
508
+ const fileName = parts.slice(1).join("/");
509
+ if (existsSync2(pkgDir)) {
510
+ const latestFile = join2(pkgDir, "latest");
511
+ let hash = null;
512
+ if (existsSync2(latestFile)) {
513
+ hash = readFileSync2(latestFile, "utf8").trim();
514
+ } else {
515
+ const subdirs = readdirSync(pkgDir).filter((d) => {
516
+ try {
517
+ return statSync(join2(pkgDir, d)).isDirectory();
518
+ } catch {
519
+ return false;
520
+ }
521
+ });
522
+ if (subdirs.length > 0) hash = subdirs[0];
523
+ }
524
+ if (hash) {
525
+ const versioned = join2(pkgDir, hash, fileName);
526
+ if (existsSync2(versioned)) localFile = versioned;
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+ if (localFile) {
533
+ code = readFileSync2(localFile, "utf8");
534
+ } else {
535
+ const resp = await fetch(gateway + relPath, { redirect: "follow" });
536
+ code = await resp.text();
537
+ }
538
+ const names = [];
539
+ for (const m of code.matchAll(/export\s*\{([^}]+)\}/g)) {
540
+ for (const item of m[1].split(",")) {
541
+ const trimmed = item.trim();
542
+ if (!trimmed) continue;
543
+ const asMatch = trimmed.match(/(?:\S+\s+as\s+)?(\S+)/);
544
+ if (asMatch) names.push(asMatch[1]);
545
+ }
546
+ }
547
+ for (const m of code.matchAll(/export\s+(function|const|let|var|class)\s+(\w+)/g)) {
548
+ names.push(m[2]);
549
+ }
550
+ const unique = [...new Set(names)].filter((n) => n !== "default");
551
+ exportsCache.set(source, unique);
552
+ return unique;
553
+ }
554
+ const externalsPlugin = {
555
+ name: "minus-dev-externals",
556
+ enforce: "pre",
557
+ config(_config, { command }) {
558
+ if (command !== "serve") return;
559
+ return {
560
+ optimizeDeps: {
561
+ esbuildOptions: {
562
+ plugins: [{
563
+ name: "externalize-react",
564
+ setup(build) {
565
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, (args) => {
566
+ if (args.importer) {
567
+ return { path: args.path, external: true };
568
+ }
569
+ });
570
+ }
571
+ }]
572
+ }
573
+ }
574
+ };
575
+ },
576
+ configResolved(config) {
577
+ isServe = config.command === "serve";
578
+ if (isServe) {
579
+ const port = config.server?.port ?? 5173;
580
+ const host = "localhost";
581
+ devServerOrigin = `http://${host}:${port}`;
582
+ }
583
+ },
584
+ configureServer(server) {
585
+ server.httpServer?.once("listening", () => {
586
+ const addr = server.httpServer?.address();
587
+ if (addr && typeof addr === "object") {
588
+ devServerOrigin = `http://localhost:${addr.port}`;
589
+ const portsFile = join2(skillDir, ".minus", "dev-ports.json");
590
+ const ports = { frontend: addr.port };
591
+ if (localBackend) {
592
+ try {
593
+ ports.backend = new URL(localBackend).port ? parseInt(new URL(localBackend).port, 10) : 0;
594
+ } catch {
595
+ }
596
+ }
597
+ try {
598
+ mkdirSync2(join2(skillDir, ".minus"), { recursive: true });
599
+ writeFileSync2(portsFile, JSON.stringify(ports, null, 2) + "\n");
600
+ } catch {
601
+ }
602
+ }
603
+ });
604
+ },
605
+ resolveId(source) {
606
+ if (!EXTERNAL_URL_MAP[source]) return;
607
+ if (isServe) return VIRTUAL_PREFIX + source;
608
+ return { id: source, external: true };
609
+ },
610
+ async load(id) {
611
+ if (!id.startsWith(VIRTUAL_PREFIX)) return;
612
+ const source = id.slice(VIRTUAL_PREFIX.length);
613
+ const path = EXTERNAL_URL_MAP[source];
614
+ const exports = await getRemoteExports(source);
615
+ const importUrl = `${devServerOrigin}${path}`;
616
+ const lines = [
617
+ `const __m = await import("${importUrl}");`,
618
+ `export default __m.default;`
619
+ ];
620
+ if (exports.length > 0) {
621
+ lines.push(`const { ${exports.join(", ")} } = __m;`);
622
+ lines.push(`export { ${exports.join(", ")} };`);
623
+ }
624
+ if (source === "react/jsx-dev-runtime" || source === "react/jsx-runtime") {
625
+ if (!exports.includes("jsxDEV") && exports.includes("jsx")) {
626
+ lines.push(`export const jsxDEV = __m.jsx;`);
627
+ }
628
+ }
629
+ return lines.join("\n");
630
+ }
631
+ };
632
+ const buildPlugin = {
633
+ name: "minus-dev-build",
634
+ apply: "build",
635
+ config(_config, { command }) {
636
+ if (command !== "build") return {};
637
+ return {
638
+ build: {
639
+ rollupOptions: { external: EXTERNALS }
640
+ }
641
+ };
642
+ },
643
+ closeBundle() {
644
+ promoteToBuilds(skillDir);
645
+ }
646
+ };
647
+ return [externalsPlugin, devPlugin, buildPlugin];
648
+ }
649
+ export {
650
+ cleanupDev,
651
+ minusDev,
652
+ writePid
653
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@minus-ai/dev-vite-plugin",
3
+ "version": "0.1.0-beta.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "src/index.ts",
7
+ "bin": {
8
+ "minus-dev-cleanup": "dist/cleanup.js"
9
+ },
10
+ "files": [
11
+ "dist/",
12
+ "src/"
13
+ ],
14
+ "scripts": {
15
+ "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node --target=node18 --packages=external && esbuild src/cleanup.ts --bundle --outfile=dist/cleanup.js --format=esm --platform=node --target=node18 --packages=external && chmod +x dist/cleanup.js"
16
+ },
17
+ "dependencies": {
18
+ "http-proxy-middleware": "^3.0.3"
19
+ },
20
+ "devDependencies": {
21
+ "esbuild": "^0.21.0"
22
+ },
23
+ "peerDependencies": {
24
+ "vite": "^5.0.0"
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ 7