@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.19 → 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 +45 -31
- package/cli/src/dev.mjs +1 -1
- package/cli/src/devBack.mjs +11 -8
- package/cli/src/devUi.mjs +81 -16
- package/cli/src/new.mjs +13 -1
- package/cli/src/socketBridge.mjs +58 -64
- package/cli/src/update.mjs +8 -5
- package/cli/templates/common/package.json +2 -1
- package/cli/templates/common/requirements.txt +4 -3
- package/cli/templates/common/vite.config.ts +22 -13
- package/package.json +2 -2
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 {
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { dirname, resolve } from "node:path";
|
|
13
14
|
import { spawnSync } from "node:child_process";
|
|
14
|
-
import {
|
|
15
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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);
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -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")
|
|
43
|
-
|
|
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(
|
|
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 →
|
|
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",
|
|
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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
package/cli/src/socketBridge.mjs
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
10
|
+
const io = new Server(httpServer, { cors: { origin: "*" } });
|
|
11
|
+
|
|
12
|
+
// topic → socketId (serve_register로 등록된 queryable)
|
|
13
13
|
const queryables = new Map();
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
pending.
|
|
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
|
|
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
|
-
|
|
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
|
|
62
|
-
if (!
|
|
63
|
-
clearTimeout(
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
70
|
-
const
|
|
71
|
-
const target =
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
}
|
package/cli/src/update.mjs
CHANGED
|
@@ -90,8 +90,7 @@ async function updateBinary(tag) {
|
|
|
90
90
|
stdio: "ignore",
|
|
91
91
|
}).unref();
|
|
92
92
|
|
|
93
|
-
log.ok(`v${version}
|
|
94
|
-
process.exit(0);
|
|
93
|
+
log.ok(`v${version} will be applied on next run.`);
|
|
95
94
|
} else {
|
|
96
95
|
// macOS / Linux: can replace the file while it's mapped into memory
|
|
97
96
|
const tmp = join(tmpdir(), `muscat-pk-${version}`);
|
|
@@ -249,9 +248,13 @@ export async function runUpdate(args = []) {
|
|
|
249
248
|
const isGlobal = (() => {
|
|
250
249
|
try {
|
|
251
250
|
const prefix = execSync("npm prefix -g", { encoding: "utf8" }).trim();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
return (
|
|
252
|
+
// Unix: {prefix}/bin/muscat-pk
|
|
253
|
+
existsSync(join(prefix, "bin", "muscat-pk")) ||
|
|
254
|
+
// Windows npm: {prefix}/muscat-pk.cmd (no bin/ subdir)
|
|
255
|
+
existsSync(join(prefix, "muscat-pk.cmd")) ||
|
|
256
|
+
existsSync(join(prefix, "bin", "muscat-pk.cmd"))
|
|
257
|
+
);
|
|
255
258
|
} catch { return false; }
|
|
256
259
|
})();
|
|
257
260
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
rainbow-rb-sdk==0.0.9.
|
|
2
|
-
rainbow-rb-flat-buffers==0.0.9.
|
|
3
|
-
rainbow-rb-zenoh==0.0.9.
|
|
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 {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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.
|
|
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": "
|
|
34
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.22",
|
|
35
35
|
"socket.io": "^4.8.1"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|