@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.21 → 0.1.2-dev.22

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/cli/src/build.mjs CHANGED
@@ -9,10 +9,47 @@
9
9
  // Muscat/robot and preview-host provide those modules through a sandbox import
10
10
  // map, so plugin ZIPs do not carry the UI runtime.
11
11
  import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
- import { resolve } from "node:path";
12
+ import { createRequire } from "node:module";
13
+ import { dirname, resolve } from "node:path";
13
14
  import { spawnSync } from "node:child_process";
14
- import { buildWithFallback } from "./esbuild.mjs";
15
- import { findPluginRoot, log } from "./util.mjs";
15
+ import { findPluginRoot, hasBin, log } from "./util.mjs";
16
+
17
+ // Find a usable node binary (for running vite as subprocess).
18
+ function findNodeBin() {
19
+ if (typeof Bun === "undefined") return process.execPath; // already node
20
+ for (const c of ["node", "node20", "node18"]) {
21
+ if (hasBin(c)) return c;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ // Resolve vite bin path from the plugin root, hoisting-aware.
27
+ function findViteBin(pluginRoot) {
28
+ try {
29
+ const req = createRequire(resolve(pluginRoot, "package.json"));
30
+ const vitePkg = req.resolve("vite/package.json");
31
+ return resolve(dirname(vitePkg), "bin", "vite.js");
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ // Run vite build in the plugin project.
38
+ // Returns true on success, false if vite/node not available.
39
+ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
40
+ const node = findNodeBin();
41
+ if (!node) return false;
42
+ const viteBin = findViteBin(pluginRoot);
43
+ if (!viteBin || !existsSync(viteBin)) return false;
44
+
45
+ log.step(`vite build (mode: ${mode})`);
46
+ const r = spawnSync(node, [viteBin, "build", "--mode", mode], {
47
+ cwd: pluginRoot,
48
+ stdio: "inherit",
49
+ });
50
+ if (r.status !== 0) throw new Error("vite build failed");
51
+ return true;
52
+ }
16
53
 
17
54
  function isMainBranch() {
18
55
  const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
@@ -109,7 +146,7 @@ export async function pinRequirements(root, { stableOnly = false } = {}) {
109
146
  if (pinned > 0) writeFileSync(reqPath, out.join("\n"));
110
147
  }
111
148
 
112
- const SANDBOX_HTML = `<!doctype html>
149
+ export const SANDBOX_HTML = `<!doctype html>
113
150
  <html lang="en">
114
151
  <head>
115
152
  <meta charset="UTF-8" />
@@ -135,39 +172,16 @@ const SANDBOX_HTML = `<!doctype html>
135
172
  </html>
136
173
  `;
137
174
 
138
- const HOST_PROVIDED_EXTERNALS = [
139
- "@rainbow-robotics/plugin-ui",
140
- "@rainbow-robotics/plugin-ui/*",
141
- "react",
142
- "react/jsx-runtime",
143
- "react/jsx-dev-runtime",
144
- "react-dom",
145
- "react-dom/*",
146
- ];
147
-
148
175
  export async function buildPlugin({ root, minify = false } = {}) {
149
176
  const pluginRoot = root ?? findPluginRoot();
150
- const entry = resolve(pluginRoot, "ui/content.tsx");
151
- if (!existsSync(entry)) {
152
- throw new Error(`missing entry: ${entry}`);
153
- }
154
177
  const distDir = resolve(pluginRoot, "dist");
155
178
  rmSync(distDir, { recursive: true, force: true });
156
179
  mkdirSync(distDir, { recursive: true });
157
180
 
158
- log.step("bundling ui/content.tsx → dist/content.js");
159
- await buildWithFallback({
160
- entryPoints: [entry],
161
- outfile: resolve(distDir, "content.js"),
162
- bundle: true,
163
- format: "esm",
164
- jsx: "automatic",
165
- minify,
166
- loader: { ".tsx": "tsx", ".ts": "ts", ".svg": "dataurl" },
167
- define: { "process.env.NODE_ENV": JSON.stringify(minify ? "production" : "development") },
168
- external: HOST_PROVIDED_EXTERNALS,
169
- logLevel: "warning",
170
- });
181
+ const viteOk = runViteBuild(pluginRoot, { mode: minify ? "production" : "development" });
182
+ if (!viteOk) {
183
+ log.warn("vite not found — UI bundle skipped. Install Node.js and run: npm install");
184
+ }
171
185
 
172
186
  // Stage distribution files.
173
187
  const manifestSrc = resolve(pluginRoot, "manifest.json");
package/cli/src/dev.mjs CHANGED
@@ -6,7 +6,7 @@ import { log } from "./util.mjs";
6
6
 
7
7
  export async function runDev(args = []) {
8
8
  log.step("starting Socket.IO bridge + dev:ui + dev:back");
9
- const bridge = startSocketBridge();
9
+ const bridge = await startSocketBridge();
10
10
  const cleanup = () => bridge.close();
11
11
  process.once("SIGINT", cleanup);
12
12
  process.once("SIGTERM", cleanup);
@@ -39,19 +39,22 @@ function runPython(root) {
39
39
  spawnSync("uv", ["venv", venv, "--python", "3.12", "--seed"], { cwd: root, stdio: "inherit" });
40
40
  } else if (hasBin("conda")) {
41
41
  spawnSync("conda", ["create", "-p", venv, "python=3.12", "-y", "--quiet"], { cwd: root, stdio: "inherit" });
42
- } else if (hasBin("python3.12") || hasBin("python3") || hasBin("python")) {
43
- const { cmd, is312 } = detectPythonCmd();
44
- if (!is312) log.warn("python3.12 not found; using fallback (robot targets 3.12).");
45
- spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venv], { cwd: root, stdio: "inherit" });
42
+ } else if (hasBin("python3.12")) {
43
+ spawnSync("python3.12", ["-m", "venv", venv], { cwd: root, stdio: "inherit" });
46
44
  } else {
47
- throw new Error("Python not found (uv / conda / python3 none available). Install Python 3.12 to run the py.");
45
+ throw new Error(
46
+ "Python 3.12 not found.\n" +
47
+ " The rainbow SDK requires Python >=3.12,<3.13.\n" +
48
+ " Install Python 3.12: https://www.python.org/downloads/release/python-3120/\n" +
49
+ " Or use uv (recommended): https://docs.astral.sh/uv/getting-started/installation/"
50
+ );
48
51
  }
49
52
  }
50
53
 
51
54
  const reqs = resolve(root, "requirements.txt");
52
55
  if (existsSync(reqs)) {
53
56
  log.step("pip install -r requirements.txt…");
54
- spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], { cwd: root, stdio: "inherit" });
57
+ spawnSync(py, ["-m", "pip", "install", "-q", "--no-cache-dir", "-r", reqs], { cwd: root, stdio: "inherit" });
55
58
  }
56
59
 
57
60
  log.step("pip install -q watchfiles (dev reloader)…");
@@ -61,10 +64,10 @@ function runPython(root) {
61
64
  const backEnv = { ...process.env, ...(pluginId ? { RB_PLUGIN_ID: pluginId } : {}) };
62
65
  if (pluginId) log.step(`RB_PLUGIN_ID=${pluginId}`);
63
66
 
64
- log.step("watchfiles → python py/main.py (reload on any .py change in py/)");
67
+ log.step("watchfiles → py/main.py (reload on any .py change in py/)");
65
68
  const child = spawn(
66
69
  py,
67
- ["-m", "watchfiles", "--filter", "python", "--target-type", "command", "python py/main.py", "py"],
70
+ ["-m", "watchfiles", "--filter", "python", "--target-type", "command", `${py} py/main.py`, "py"],
68
71
  { cwd: root, stdio: "inherit", env: backEnv },
69
72
  );
70
73
  const cleanup = () => { if (!child.killed) child.kill(); };
package/cli/src/devUi.mjs CHANGED
@@ -1,38 +1,37 @@
1
1
  // muscat-pk dev:ui — preview the plugin inside a mini-Muscat (preview-host).
2
+ // UI bundling: vite build --watch (uses plugin project's own node_modules/vite).
3
+ // Falls back gracefully if Node.js or vite is not available.
2
4
  import { spawn, spawnSync } from "node:child_process";
3
5
  import { createRequire } from "node:module";
4
- import { existsSync, mkdirSync } from "node:fs";
6
+ import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
5
7
  import { homedir } from "node:os";
6
8
  import { dirname, resolve } from "node:path";
7
- import { buildPlugin } from "./build.mjs";
9
+ import { SANDBOX_HTML } from "./build.mjs";
8
10
  import { checkKitUpdate, findPluginRoot, hasBin, kitRoot, log } from "./util.mjs";
9
11
 
10
12
  const PREVIEW_PKG = "@rainbow-robotics/preview-host";
11
13
  const CACHE_DIR = resolve(homedir(), ".muscat-pk");
12
14
  const CACHE_ROOT = resolve(CACHE_DIR, "node_modules", "@rainbow-robotics", "preview-host");
13
15
 
14
- /**
15
- * Resolve preview-host root in order:
16
- * 1. Standard require (npm/yarn global install)
17
- * 2. Monorepo sibling (dev workspace)
18
- * 3. ~/.muscat-pk cache (previous auto-install)
19
- * 4. Auto-install to ~/.muscat-pk via npm (binary users who have Node.js)
20
- */
16
+ function findNodeBin() {
17
+ if (typeof Bun === "undefined") return process.execPath;
18
+ for (const c of ["node", "node20", "node18"]) {
19
+ if (hasBin(c)) return c;
20
+ }
21
+ return null;
22
+ }
23
+
21
24
  function findPreviewHostRoot() {
22
- // 1. npm/yarn 설치 경로
23
25
  try {
24
26
  const req = createRequire(import.meta.url);
25
27
  return dirname(req.resolve(`${PREVIEW_PKG}/package.json`));
26
28
  } catch {}
27
29
 
28
- // 2. 모노레포 sibling
29
30
  const sibling = resolve(kitRoot, "../preview-host");
30
31
  if (existsSync(resolve(sibling, "package.json"))) return sibling;
31
32
 
32
- // 3. 캐시
33
33
  if (existsSync(resolve(CACHE_ROOT, "package.json"))) return CACHE_ROOT;
34
34
 
35
- // 4. npm 자동 설치 → 캐시
36
35
  if (!hasBin("npm")) {
37
36
  throw new Error(
38
37
  `preview-host를 찾을 수 없습니다.\n` +
@@ -53,19 +52,78 @@ function findPreviewHostRoot() {
53
52
  return CACHE_ROOT;
54
53
  }
55
54
 
55
+ // Write sandbox.html + copy manifest into dist/ (vite doesn't generate these).
56
+ function stageDevAssets(pluginRoot) {
57
+ const distDir = resolve(pluginRoot, "dist");
58
+ mkdirSync(distDir, { recursive: true });
59
+ writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
60
+ const manifestSrc = resolve(pluginRoot, "manifest.json");
61
+ if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(distDir, "manifest.json"));
62
+ return distDir;
63
+ }
64
+
65
+ // Resolve vite bin path from the plugin root, hoisting-aware.
66
+ function findViteBin(pluginRoot) {
67
+ try {
68
+ const req = createRequire(resolve(pluginRoot, "package.json"));
69
+ const vitePkg = req.resolve("vite/package.json");
70
+ return resolve(dirname(vitePkg), "bin", "vite.js");
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ // Spawn vite build --watch as a background process.
77
+ // Returns the child process, or null if vite/node not available.
78
+ function spawnViteWatch(pluginRoot, node) {
79
+ const viteBin = findViteBin(pluginRoot);
80
+ if (!node || !viteBin || !existsSync(viteBin)) return null;
81
+
82
+ log.step("vite build --watch (ui/content.tsx → dist/content.js)");
83
+ return spawn(node, [viteBin, "build", "--watch", "--mode", "development"], {
84
+ cwd: pluginRoot,
85
+ stdio: "inherit",
86
+ });
87
+ }
88
+
89
+ // Wait until dist/content.js exists (vite initial build completes).
90
+ function waitForInitialBuild(distDir, timeoutMs = 30_000) {
91
+ return new Promise((res, rej) => {
92
+ const contentJs = resolve(distDir, "content.js");
93
+ const start = Date.now();
94
+ const check = () => {
95
+ if (existsSync(contentJs)) return res();
96
+ if (Date.now() - start > timeoutMs) return rej(new Error("vite initial build timed out"));
97
+ setTimeout(check, 300);
98
+ };
99
+ check();
100
+ });
101
+ }
102
+
56
103
  export async function runDevUi(args = []) {
57
104
  const portIdx = args.indexOf("--port");
58
105
  const port = portIdx !== -1 ? args[portIdx + 1] : "4180";
59
106
 
60
107
  await checkKitUpdate();
61
- const { distDir } = await buildPlugin();
108
+ const pluginRoot = findPluginRoot();
109
+ const distDir = stageDevAssets(pluginRoot);
110
+
111
+ const node = findNodeBin();
112
+ const viteProc = spawnViteWatch(pluginRoot, node);
113
+
114
+ if (viteProc) {
115
+ log.step("waiting for initial vite build…");
116
+ await waitForInitialBuild(distDir);
117
+ } else {
118
+ log.warn("vite not found — UI will not be bundled. Run `npm install` in the plugin project.");
119
+ }
62
120
 
63
121
  const previewRoot = findPreviewHostRoot();
64
122
  const serveEntry = resolve(previewRoot, "serve.mjs");
65
123
  if (!existsSync(resolve(previewRoot, "dist", "index.html"))) {
66
124
  log.step("building preview-host bundle (first run)…");
67
125
  await new Promise((res, rej) => {
68
- const p = spawn(process.execPath, [resolve(previewRoot, "build.mjs")], {
126
+ const p = spawn(node ?? process.execPath, [resolve(previewRoot, "build.mjs")], {
69
127
  stdio: "inherit",
70
128
  });
71
129
  p.on("exit", (c) => (c === 0 ? res() : rej(new Error("preview-host build failed"))));
@@ -74,10 +132,17 @@ export async function runDevUi(args = []) {
74
132
 
75
133
  log.step(`starting preview-host on :${port}`);
76
134
  const child = spawn(
77
- process.execPath,
135
+ node ?? process.execPath,
78
136
  [serveEntry, "--plugin", distDir, "--port", String(port)],
79
137
  { stdio: "inherit" },
80
138
  );
139
+
140
+ const cleanup = () => {
141
+ if (viteProc && !viteProc.killed) viteProc.kill();
142
+ if (!child.killed) child.kill();
143
+ };
144
+ process.once("SIGINT", cleanup);
145
+ process.once("SIGTERM", cleanup);
81
146
  child.on("exit", (code) => process.exit(code ?? 0));
82
147
  }
83
148
 
package/cli/src/new.mjs CHANGED
@@ -107,9 +107,21 @@ export async function runNew(args) {
107
107
  }
108
108
  type = TYPES.includes(type) ? type : "python";
109
109
 
110
+ // Auto-detect yarn workspace: if .yarnrc.yml exists anywhere up the tree, default to yarn.
111
+ function isInsideYarnWorkspace(dir) {
112
+ let cur = dir;
113
+ while (true) {
114
+ if (existsSync(resolve(cur, ".yarnrc.yml"))) return true;
115
+ const parent = resolve(cur, "..");
116
+ if (parent === cur) return false;
117
+ cur = parent;
118
+ }
119
+ }
120
+
110
121
  let pm = opts.pm;
111
122
  if (!pm && !opts.yes) {
112
- pm = await prompt(`Package manager (${PMS.join("/")}) [npm]: `, "npm");
123
+ const defaultPm = isInsideYarnWorkspace(process.cwd()) ? "yarn" : "npm";
124
+ pm = await prompt(`Package manager (${PMS.join("/")}) [${defaultPm}]: `, defaultPm);
113
125
  }
114
126
  pm = PMS.includes(pm) ? pm : "npm";
115
127
 
@@ -7,22 +7,22 @@ const DEFAULT_ACTION_TIMEOUT_MS = 10_000;
7
7
 
8
8
  export function startSocketBridge({ port = 10000, host = "0.0.0.0" } = {}) {
9
9
  const httpServer = createServer();
10
- const io = new Server(httpServer, {
11
- cors: { origin: "*" },
12
- });
10
+ const io = new Server(httpServer, { cors: { origin: "*" } });
11
+
12
+ // topic → socketId (serve_register로 등록된 queryable)
13
13
  const queryables = new Map();
14
- const pendingReplies = new Map();
14
+ // call_id → { timer, resolve } (action → serve_call → serve_reply 대기)
15
+ const pending = new Map();
15
16
 
16
17
  function cleanupSocket(socket) {
17
18
  for (const [topic, sid] of queryables) {
18
19
  if (sid === socket.id) queryables.delete(topic);
19
20
  }
20
-
21
- for (const [id, pending] of pendingReplies) {
22
- if (pending.sid !== socket.id) continue;
23
- clearTimeout(pending.timer);
24
- pending.resolve(undefined);
25
- pendingReplies.delete(id);
21
+ for (const [id, p] of pending) {
22
+ if (p.sid !== socket.id) continue;
23
+ clearTimeout(p.timer);
24
+ p.resolve(undefined);
25
+ pending.delete(id);
26
26
  }
27
27
  }
28
28
 
@@ -38,12 +38,9 @@ export function startSocketBridge({ port = 10000, host = "0.0.0.0" } = {}) {
38
38
  });
39
39
 
40
40
  socket.on("pub", (data = {}) => {
41
- const topic = data.topic;
41
+ const { topic, payload } = data;
42
42
  if (!topic) return;
43
- io.to(`t:${topic}`).emit("msg", {
44
- topic,
45
- payload: data.payload,
46
- });
43
+ io.to(`t:${topic}`).emit("msg", { topic, payload });
47
44
  });
48
45
 
49
46
  socket.on("serve_register", (data = {}) => {
@@ -52,71 +49,68 @@ export function startSocketBridge({ port = 10000, host = "0.0.0.0" } = {}) {
52
49
  });
53
50
 
54
51
  socket.on("serve_unregister", (data = {}) => {
55
- const topic = data.topic;
56
- if (!topic) return;
57
- if (queryables.get(topic) === socket.id) queryables.delete(topic);
52
+ if (queryables.get(data.topic) === socket.id) queryables.delete(data.topic);
58
53
  });
59
54
 
60
55
  socket.on("serve_reply", (data = {}) => {
61
- const pending = pendingReplies.get(data.id);
62
- if (!pending) return;
63
- clearTimeout(pending.timer);
64
- pending.resolve(data.payload);
65
- pendingReplies.delete(data.id);
56
+ const p = pending.get(data.id);
57
+ if (!p) return;
58
+ clearTimeout(p.timer);
59
+ p.resolve(data.payload);
60
+ pending.delete(data.id);
66
61
  });
67
62
 
68
63
  socket.on("action", (data = {}, ack) => {
69
- const topic = data.topic;
70
- const sid = topic ? queryables.get(topic) : undefined;
71
- const target = sid ? io.sockets.sockets.get(sid) : undefined;
72
-
64
+ const { topic, payload } = data;
65
+ const targetSid = queryables.get(topic);
66
+ const target = targetSid && io.sockets.sockets.get(targetSid);
73
67
  if (!target) {
74
68
  if (typeof ack === "function") ack(undefined);
75
69
  return;
76
70
  }
77
-
78
71
  const id = randomUUID();
79
- const result = new Promise((resolve) => {
80
- const timer = setTimeout(() => {
81
- pendingReplies.delete(id);
82
- resolve(undefined);
83
- }, DEFAULT_ACTION_TIMEOUT_MS);
84
-
85
- pendingReplies.set(id, {
86
- sid,
87
- timer,
88
- resolve,
89
- });
90
- });
91
-
92
- target.emit("serve_call", {
93
- id,
94
- topic,
95
- payload: data.payload,
96
- });
97
-
98
- result.then((payload) => {
99
- if (typeof ack === "function") ack(payload);
72
+ const timer = setTimeout(() => {
73
+ pending.delete(id);
74
+ if (typeof ack === "function") ack(undefined);
75
+ }, DEFAULT_ACTION_TIMEOUT_MS);
76
+ pending.set(id, {
77
+ sid: targetSid,
78
+ timer,
79
+ resolve: (v) => { if (typeof ack === "function") ack(v); },
100
80
  });
81
+ target.emit("serve_call", { id, topic, payload });
101
82
  });
102
83
 
103
84
  socket.on("disconnect", () => cleanupSocket(socket));
104
85
  });
105
86
 
106
- httpServer.listen(port, host, () => {
107
- log.step(`Socket.IO bridge listening on ws://127.0.0.1:${port}`);
108
- });
109
-
110
- const close = () => {
111
- for (const pending of pendingReplies.values()) {
112
- clearTimeout(pending.timer);
113
- pending.resolve(undefined);
114
- }
115
- pendingReplies.clear();
116
- queryables.clear();
117
- io.close();
118
- httpServer.close();
119
- };
87
+ return new Promise((resolve) => {
88
+ httpServer.once("error", (err) => {
89
+ if (err.code === "EADDRINUSE") {
90
+ log.warn(`Socket.IO bridge already running on :${port} — skipping`);
91
+ io.close();
92
+ httpServer.close();
93
+ resolve({ close: () => {} });
94
+ } else {
95
+ throw err;
96
+ }
97
+ });
120
98
 
121
- return { close };
99
+ httpServer.listen(port, host, () => {
100
+ log.step(`Socket.IO bridge listening on ws://127.0.0.1:${port}`);
101
+
102
+ const close = () => {
103
+ for (const p of pending.values()) {
104
+ clearTimeout(p.timer);
105
+ p.resolve(undefined);
106
+ }
107
+ pending.clear();
108
+ queryables.clear();
109
+ io.close();
110
+ httpServer.close();
111
+ };
112
+
113
+ resolve({ close });
114
+ });
115
+ });
122
116
  }
@@ -9,7 +9,8 @@
9
9
  "devDependencies": {
10
10
  "@rainbow-robotics/muscat-plugin-kit": "__RB_VERSION__",
11
11
  "@rainbow-robotics/preview-host": "__RB_VERSION__",
12
+ "@vitejs/plugin-react": "^4.0.0",
12
13
  "typescript": "^5.8.0",
13
- "vite": "^6.4.1"
14
+ "vite": "^6.0.0"
14
15
  }
15
16
  }
@@ -1,3 +1,4 @@
1
- rainbow-rb-sdk==0.0.9.dev40
2
- rainbow-rb-flat-buffers==0.0.9.dev40
3
- rainbow-rb-zenoh==0.0.9.dev40
1
+ rainbow-rb-sdk==0.0.9.dev42
2
+ rainbow-rb-flat-buffers==0.0.9.dev42
3
+ rainbow-rb-zenoh==0.0.9.dev42
4
+ requests==2.34.2
@@ -1,29 +1,38 @@
1
- import { defineConfig } from "vite";
1
+ import { readdirSync } from "fs";
2
+ import { basename, extname, resolve } from "path";
2
3
  import react from "@vitejs/plugin-react";
4
+ import { defineConfig } from "vite";
3
5
 
4
6
  // Plugin UI and React are provided by Muscat/preview-host via import map.
5
7
  // They must NOT be bundled into the plugin ZIP.
6
8
  const HOST_PROVIDED_EXTERNALS = [
7
- "@rainbow-robotics/plugin-ui",
8
- /^@rainbow\/plugin-ui\//,
9
- "react",
10
- "react/jsx-runtime",
11
- "react/jsx-dev-runtime",
12
- "react-dom",
13
- /^react-dom\//,
9
+ /^@rainbow-robotics\/plugin-ui(\/|$)/,
10
+ /^react(\/|$)/,
11
+ /^react-dom(\/|$)/,
14
12
  ];
15
13
 
14
+ // All top-level .tsx/.ts files in ui/ become separate entry points.
15
+ // Subdirectories (components/, hooks/, etc.) are imported by entries and bundled in.
16
+ const uiEntries = Object.fromEntries(
17
+ readdirSync("ui")
18
+ .filter((f) => /\.(tsx?|jsx?)$/.test(f))
19
+ .map((f) => [basename(f, extname(f)), resolve("ui", f)]),
20
+ );
21
+
16
22
  export default defineConfig({
17
23
  plugins: [react()],
18
24
  build: {
19
- lib: {
20
- entry: "ui/content.tsx",
21
- formats: ["es"],
22
- fileName: "content",
23
- },
24
25
  outDir: "dist",
26
+ emptyOutDir: false,
25
27
  rollupOptions: {
28
+ input: uiEntries,
26
29
  external: HOST_PROVIDED_EXTERNALS,
30
+ output: {
31
+ format: "es",
32
+ entryFileNames: "[name].js",
33
+ chunkFileNames: "chunks/[name]-[hash].js",
34
+ assetFileNames: "assets/[name]-[hash][extname]",
35
+ },
27
36
  },
28
37
  },
29
38
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/muscat-plugin-kit",
3
- "version": "0.1.2-dev.21",
3
+ "version": "0.1.2-dev.22",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -31,7 +31,7 @@
31
31
  "@types/react-dom": "^19.0.4"
32
32
  },
33
33
  "dependencies": {
34
- "@rainbow-robotics/preview-host": "0.1.2-dev.21",
34
+ "@rainbow-robotics/preview-host": "0.1.2-dev.22",
35
35
  "socket.io": "^4.8.1"
36
36
  },
37
37
  "optionalDependencies": {