@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.4 → 0.1.2-dev.40
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/README.md +116 -16
- package/cli/README.md +5 -5
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +20 -10
- package/cli/bundle-templates.mjs +28 -0
- package/cli/docker/Dockerfile.bridge +17 -0
- package/cli/docker/Dockerfile.cpp +22 -0
- package/cli/docker/Dockerfile.dev +14 -0
- package/cli/docker/Dockerfile.python +20 -0
- package/cli/docker/bridge-package.json +8 -0
- package/cli/src/bridge.mjs +92 -0
- package/cli/src/build.mjs +386 -36
- package/cli/src/check-package.mjs +8 -23
- package/cli/src/cpp.mjs +61 -0
- package/cli/src/dev.mjs +16 -3
- package/cli/src/devBack.mjs +239 -53
- package/cli/src/devUi.mjs +118 -15
- package/cli/src/esbuild.mjs +51 -10
- package/cli/src/manifest.mjs +193 -0
- package/cli/src/new.mjs +168 -37
- package/cli/src/preview.mjs +91 -0
- package/cli/src/python.mjs +122 -0
- package/cli/src/ros2.mjs +90 -0
- package/cli/src/socketBridge.mjs +129 -0
- package/cli/src/type.mjs +105 -0
- package/cli/src/uninstall.mjs +98 -0
- package/cli/src/update.mjs +259 -30
- package/cli/src/util.mjs +97 -7
- package/cli/templates/common/README.md +383 -9
- package/cli/templates/common/manifest.json +13 -13
- package/cli/templates/common/package.json +5 -11
- package/cli/templates/common/py/main.py +88 -0
- package/cli/templates/common/requirements.txt +4 -0
- package/cli/templates/common/tsconfig.json +1 -1
- package/cli/templates/common/{src → ui}/content.tsx +3 -2
- package/cli/templates/common/vite.config.ts +22 -13
- package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
- package/cli/templates/cpp/cpp/main.cpp +25 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__ +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
- package/package.json +4 -4
- package/cli/src/pack.mjs +0 -58
- package/cli/templates/sdk/main.py +0 -21
- package/cli/templates/sdk/requirements.txt +0 -1
package/README.md
CHANGED
|
@@ -1,27 +1,127 @@
|
|
|
1
1
|
# @rainbow-robotics/muscat-plugin-kit
|
|
2
2
|
|
|
3
|
-
Muscat
|
|
4
|
-
and preview-host.
|
|
3
|
+
Muscat 플러그인 개발 CLI. 스캐폴딩, 빌드/팩, 개발 서버(UI + 백엔드)를 제공합니다.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
### 방법 1 — npm (Node.js 20+ 필요)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 최신 stable
|
|
11
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit
|
|
12
|
+
|
|
13
|
+
# dev 채널 (최신 개발 빌드)
|
|
14
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@dev
|
|
15
|
+
|
|
16
|
+
# 특정 버전
|
|
17
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@1.2.3
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 방법 2 — S3 설치 스크립트 (Node.js 없어도 됨)
|
|
21
|
+
|
|
22
|
+
macOS / Linux / WSL:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 최신 stable
|
|
26
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | sh
|
|
27
|
+
|
|
28
|
+
# dev 채널
|
|
29
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | MUSCAT_PK_TAG=dev sh
|
|
30
|
+
|
|
31
|
+
# 특정 버전
|
|
32
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | MUSCAT_PK_TAG=1.2.3 sh
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Windows PowerShell:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
# 최신 stable
|
|
39
|
+
irm https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.ps1 | iex
|
|
40
|
+
|
|
41
|
+
# dev 채널
|
|
42
|
+
$env:MUSCAT_PK_TAG="dev"; irm https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.ps1 | iex
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> 설치 스크립트는 Node.js 20+가 있으면 npm을 우선 사용하고, 없으면 S3에서 self-contained 바이너리를 받습니다.
|
|
46
|
+
|
|
47
|
+
### 방법 3 — 바이너리 직접 다운로드
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/v{버전}/{바이너리}
|
|
51
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/stable/{바이너리}
|
|
52
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/dev/{바이너리}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| OS | 파일 |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Linux x64 | `muscat-pk-linux-x64` |
|
|
58
|
+
| macOS ARM64 | `muscat-pk-darwin-arm64` |
|
|
59
|
+
| macOS x64 | `muscat-pk-darwin-x64` |
|
|
60
|
+
| Windows x64 | `muscat-pk-win-x64.exe` |
|
|
61
|
+
|
|
62
|
+
채널 포인터:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
# 최신 stable 버전 문자열
|
|
66
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/latest-version.txt
|
|
67
|
+
|
|
68
|
+
# 최신 dev 버전 문자열
|
|
69
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/dev-version.txt
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 삭제
|
|
73
|
+
|
|
74
|
+
설치 방식과 관계없이 `muscat-pk`가 자기 자신을 삭제합니다.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
muscat-pk uninstall
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
수동 삭제:
|
|
9
81
|
|
|
10
82
|
```bash
|
|
11
|
-
npm
|
|
12
|
-
|
|
83
|
+
# npm으로 설치한 경우
|
|
84
|
+
npm uninstall -g @rainbow-robotics/muscat-plugin-kit
|
|
85
|
+
|
|
86
|
+
# S3 바이너리로 설치한 경우 (macOS/Linux 기본 경로)
|
|
87
|
+
sudo rm -f /usr/local/bin/muscat-pk
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 빠른 시작
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
muscat-pk new my-plugin # 플러그인 프로젝트 생성 (python 또는 ros2 선택)
|
|
13
96
|
cd my-plugin
|
|
14
|
-
|
|
15
|
-
muscat-pk dev
|
|
16
|
-
muscat-pk
|
|
97
|
+
|
|
98
|
+
muscat-pk dev # UI + 백엔드 동시 실행
|
|
99
|
+
muscat-pk dev:ui # UI 미리보기만
|
|
100
|
+
muscat-pk dev:back # 백엔드만 (Python watchfiles / ROS2 Docker)
|
|
101
|
+
|
|
102
|
+
muscat-pk build # dist/ 빌드
|
|
103
|
+
muscat-pk pack # 배포용 ZIP 생성
|
|
17
104
|
```
|
|
18
105
|
|
|
19
|
-
|
|
106
|
+
## 명령어 목록
|
|
107
|
+
|
|
108
|
+
| 명령 | 설명 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `muscat-pk new [dir]` | 플러그인 프로젝트 스캐폴딩 |
|
|
111
|
+
| `muscat-pk type change` | 백엔드 타입 전환 (python ↔ ros2) |
|
|
112
|
+
| `muscat-pk build` | `src/content.tsx` → `dist/content.js` 빌드 |
|
|
113
|
+
| `muscat-pk pack` | 빌드 후 배포용 ZIP 생성 |
|
|
114
|
+
| `muscat-pk dev` | UI + 백엔드 동시 개발 서버 |
|
|
115
|
+
| `muscat-pk dev:ui` | 미니 Muscat 미리보기 서버 |
|
|
116
|
+
| `muscat-pk dev:back` | 백엔드 개발 서버 (hot-reload) |
|
|
117
|
+
| `muscat-pk update` | kit + plugin-ui 최신 버전으로 업데이트 |
|
|
118
|
+
| `muscat-pk sdk <list\|update>` | Python rainbow-rb-sdk 버전 관리 |
|
|
119
|
+
|
|
120
|
+
## 패키지 역할
|
|
20
121
|
|
|
21
|
-
|
|
|
122
|
+
| 패키지 | 역할 |
|
|
22
123
|
|---|---|
|
|
23
|
-
| `@rainbow-robotics/plugin-ui` |
|
|
24
|
-
| `@rainbow-robotics/muscat-plugin-kit` | CLI,
|
|
124
|
+
| `@rainbow-robotics/plugin-ui` | UI 런타임 계약: rb-* 엘리먼트, React 바인딩, sandbox connect |
|
|
125
|
+
| `@rainbow-robotics/muscat-plugin-kit` | CLI, 템플릿, 빌드/팩, 미리보기 호스트 |
|
|
25
126
|
|
|
26
|
-
|
|
27
|
-
import map for `@rainbow-robotics/plugin-ui`, `react`, and `react-dom`.
|
|
127
|
+
플러그인 ZIP에는 플러그인 코드와 에셋만 포함됩니다. `@rainbow-robotics/plugin-ui`, `react`, `react-dom`은 호스트(Muscat/로봇)의 import map에서 제공됩니다.
|
package/cli/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rainbow-robotics/muscat-plugin-kit (`muscat-pk`)
|
|
2
2
|
|
|
3
|
-
CLI to scaffold, build,
|
|
3
|
+
CLI to scaffold, build, and preview Muscat plugins.
|
|
4
4
|
|
|
5
5
|
Install/use it the same way an external plugin author will:
|
|
6
6
|
|
|
@@ -10,13 +10,13 @@ muscat-pk new my-plugin
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
muscat-pk new my-plugin [--type
|
|
14
|
-
muscat-pk build #
|
|
15
|
-
muscat-pk pack # build (minified) + distributable ZIP with a size report
|
|
13
|
+
muscat-pk new my-plugin [--type python|cpp|ros2|ros2-cpp] [--pm npm|yarn|pnpm|bun] [--yes]
|
|
14
|
+
muscat-pk build # ui/content.tsx -> dist/ (minified) + distributable ZIP (--no-zip to skip)
|
|
16
15
|
muscat-pk dev:ui # preview the plugin in a mini-Muscat (preview-host)
|
|
17
|
-
muscat-pk dev:back # run the back end
|
|
16
|
+
muscat-pk dev:back # run the back end in a Docker container (python / cpp / ros2)
|
|
18
17
|
muscat-pk dev # dev:ui + dev:back
|
|
19
18
|
muscat-pk update # update @rainbow-robotics/muscat-plugin-kit and @rainbow-robotics/plugin-ui
|
|
19
|
+
muscat-pk uninstall # remove muscat-pk itself (npm global or S3 binary)
|
|
20
20
|
muscat-pk sdk list | sdk update [version] # manage rainbow-rb-sdk (python)
|
|
21
21
|
```
|
|
22
22
|
|
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// muscat-pk — Muscat plugin developer CLI.
|
|
3
|
-
// Subcommands: new, build,
|
|
3
|
+
// Subcommands: new, build, dev, dev:ui, dev:back, update, uninstall, sdk.
|
|
4
|
+
import { log } from "../src/util.mjs";
|
|
4
5
|
import { runNew } from "../src/new.mjs";
|
|
5
6
|
import { runBuild } from "../src/build.mjs";
|
|
6
|
-
import { runPack } from "../src/pack.mjs";
|
|
7
7
|
import { runDevUi } from "../src/devUi.mjs";
|
|
8
8
|
import { runDevBack } from "../src/devBack.mjs";
|
|
9
9
|
import { runDev } from "../src/dev.mjs";
|
|
10
10
|
import { runUpdate } from "../src/update.mjs";
|
|
11
|
+
import { runUninstall } from "../src/uninstall.mjs";
|
|
11
12
|
import { runSdk } from "../src/sdk.mjs";
|
|
13
|
+
import { runType } from "../src/type.mjs";
|
|
14
|
+
import { runPreview } from "../src/preview.mjs";
|
|
12
15
|
|
|
13
16
|
const [, , cmd, ...args] = process.argv;
|
|
14
17
|
|
|
@@ -17,13 +20,15 @@ const HELP = `muscat-pk — Muscat plugin kit
|
|
|
17
20
|
Usage: muscat-pk <command> [options]
|
|
18
21
|
|
|
19
22
|
Commands:
|
|
20
|
-
new [dir] Scaffold a new plugin project
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
dev Run dev:ui and dev:back together
|
|
23
|
+
new [dir] Scaffold a new plugin project (prompts: author, email, description, type, front-package-manager)
|
|
24
|
+
type change Switch backend type (python | cpp | ros2-python | ros2-cpp)
|
|
25
|
+
build Build + lock deps + vendor wheels + zip (--no-zip: skip zip, --no-lock: skip python lock/vendor, --arch amd64|arm64|both)
|
|
26
|
+
dev Run Socket.IO bridge, dev:ui, and dev:back together
|
|
24
27
|
dev:ui Preview the plugin in a mini-Muscat (preview-host)
|
|
25
|
-
dev:back Run the plugin back end (python
|
|
28
|
+
dev:back Run the plugin back end (python: watchfiles reload / ros2: Docker)
|
|
29
|
+
preview Preview against a real Muscat server (prompts for robot IP)
|
|
26
30
|
update Update @rainbow-robotics/muscat-plugin-kit and @rainbow-robotics/plugin-ui
|
|
31
|
+
uninstall Remove the installed muscat-pk CLI (npm global or S3 binary)
|
|
27
32
|
sdk <list|update [version]> Manage the python rainbow-rb-sdk version
|
|
28
33
|
|
|
29
34
|
Run "muscat-pk <command> --help" for details.`;
|
|
@@ -33,18 +38,22 @@ async function main() {
|
|
|
33
38
|
switch (cmd) {
|
|
34
39
|
case "new":
|
|
35
40
|
return await runNew(args);
|
|
41
|
+
case "type":
|
|
42
|
+
return await runType(args);
|
|
36
43
|
case "build":
|
|
37
44
|
return await runBuild(args);
|
|
38
|
-
case "pack":
|
|
39
|
-
return await runPack(args);
|
|
40
45
|
case "dev":
|
|
41
46
|
return await runDev(args);
|
|
42
47
|
case "dev:ui":
|
|
43
48
|
return await runDevUi(args);
|
|
44
49
|
case "dev:back":
|
|
45
50
|
return await runDevBack(args);
|
|
51
|
+
case "preview":
|
|
52
|
+
return await runPreview(args);
|
|
46
53
|
case "update":
|
|
47
54
|
return await runUpdate(args);
|
|
55
|
+
case "uninstall":
|
|
56
|
+
return await runUninstall(args);
|
|
48
57
|
case "sdk":
|
|
49
58
|
return await runSdk(args);
|
|
50
59
|
case undefined:
|
|
@@ -59,7 +68,8 @@ async function main() {
|
|
|
59
68
|
process.exitCode = 1;
|
|
60
69
|
}
|
|
61
70
|
} catch (err) {
|
|
62
|
-
console.error(
|
|
71
|
+
console.error();
|
|
72
|
+
log.error(err?.message || String(err));
|
|
63
73
|
process.exitCode = 1;
|
|
64
74
|
}
|
|
65
75
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Pre-build step: serialise cli/templates/** into cli/src/templates-embedded.mjs
|
|
3
|
+
// so that `bun build --compile` can embed them without needing the filesystem.
|
|
4
|
+
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
|
|
5
|
+
import { resolve, relative } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const root = resolve(fileURLToPath(import.meta.url), "..");
|
|
9
|
+
const out = resolve(root, "src/templates-embedded.mjs");
|
|
10
|
+
|
|
11
|
+
const files = {};
|
|
12
|
+
function walk(baseDir, prefix) {
|
|
13
|
+
for (const name of readdirSync(baseDir)) {
|
|
14
|
+
const full = resolve(baseDir, name);
|
|
15
|
+
const key = prefix + name;
|
|
16
|
+
if (statSync(full).isDirectory()) { walk(full + "/", key + "/"); continue; }
|
|
17
|
+
files[key] = readFileSync(full, "base64");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
walk(resolve(root, "templates") + "/", "");
|
|
21
|
+
walk(resolve(root, "docker") + "/", "docker/");
|
|
22
|
+
|
|
23
|
+
writeFileSync(out,
|
|
24
|
+
`// AUTO-GENERATED by cli/bundle-templates.mjs — do not edit.\n` +
|
|
25
|
+
`// ponytail: generated at publish time; not tracked in git.\n` +
|
|
26
|
+
`export const TEMPLATES = ${JSON.stringify(files)};\n`
|
|
27
|
+
);
|
|
28
|
+
console.log(`bundled ${Object.keys(files).length} template files → src/templates-embedded.mjs`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Socket.IO bridge container — the frontend (preview-host) and the backend
|
|
2
|
+
# container rendezvous here during `muscat-pk dev`.
|
|
3
|
+
# - frontend (host) → published port localhost:10000
|
|
4
|
+
# - backend container → shares this container's netns (--network container:),
|
|
5
|
+
# so it reaches the bridge at localhost:10000 too
|
|
6
|
+
# Build context is the `cli/` dir so it can copy src/socketBridge.mjs.
|
|
7
|
+
FROM node:20-slim
|
|
8
|
+
LABEL com.rainbow-robotics.muscat-pk="true"
|
|
9
|
+
|
|
10
|
+
WORKDIR /bridge
|
|
11
|
+
COPY docker/bridge-package.json package.json
|
|
12
|
+
RUN npm install --omit=dev --no-audit --no-fund
|
|
13
|
+
|
|
14
|
+
COPY src/socketBridge.mjs ./socketBridge.mjs
|
|
15
|
+
|
|
16
|
+
EXPOSE 10000
|
|
17
|
+
CMD ["node", "socketBridge.mjs"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Dev/build container for the pure C++ plugin back end.
|
|
2
|
+
# Matches the Muscat robot backend OS (Ubuntu 24.04) so the compiled binary
|
|
3
|
+
# is ABI-compatible with the robot. Used by `muscat-pk dev:back` and `build`
|
|
4
|
+
# — NOT shipped in the plugin ZIP.
|
|
5
|
+
FROM ubuntu:24.04
|
|
6
|
+
LABEL com.rainbow-robotics.muscat-pk="true"
|
|
7
|
+
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
9
|
+
build-essential \
|
|
10
|
+
cmake \
|
|
11
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
12
|
+
|
|
13
|
+
# TODO(cpp-sdk): install the rainbow cpp SDK (dev package) here, pinned to the
|
|
14
|
+
# robot's version, so plugins build/link against the same SDK as production.
|
|
15
|
+
# Plan: apt package + dynamic link (the robot provides the .so at runtime;
|
|
16
|
+
# we do NOT bundle it into the plugin binary). e.g.:
|
|
17
|
+
# RUN apt-get update && apt-get install -y rainbow-cpp-sdk-dev=<robot-version> \
|
|
18
|
+
# && rm -rf /var/lib/apt/lists/*
|
|
19
|
+
|
|
20
|
+
WORKDIR /plugin
|
|
21
|
+
|
|
22
|
+
CMD ["bash"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Dev-only container for the ROS2 plugin back end.
|
|
2
|
+
# Matches the Muscat robot backend: ROS2 Jazzy + Python 3.12 (Ubuntu 24.04).
|
|
3
|
+
# Used by `muscat-pk dev:back` — NOT shipped in the plugin ZIP.
|
|
4
|
+
FROM ros:jazzy-ros-base
|
|
5
|
+
LABEL com.rainbow-robotics.muscat-pk="true"
|
|
6
|
+
|
|
7
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
8
|
+
python3-pip \
|
|
9
|
+
python3-colcon-common-extensions \
|
|
10
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
WORKDIR /plugin
|
|
13
|
+
|
|
14
|
+
CMD ["bash"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Dev/build container for the Python plugin back end.
|
|
2
|
+
# Matches the Muscat robot backend: Ubuntu 24.04 + Python 3.12.
|
|
3
|
+
# Used by `muscat-pk dev:back` (hot-reload) and `muscat-pk build` (lock + wheel vendor).
|
|
4
|
+
# NOT shipped in the plugin ZIP — the robot installs deps from dist/wheels/ offline.
|
|
5
|
+
FROM ubuntu:24.04
|
|
6
|
+
LABEL com.rainbow-robotics.muscat-pk="true"
|
|
7
|
+
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
9
|
+
python3 \
|
|
10
|
+
python3-pip \
|
|
11
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
12
|
+
&& pip3 install uv==0.8.18 --break-system-packages
|
|
13
|
+
|
|
14
|
+
# TODO(cpp-sdk): the Python SDK is a pybind wrapper over the cpp SDK, so the
|
|
15
|
+
# cpp SDK shared lib must be present for the python module to load. Install the
|
|
16
|
+
# rainbow cpp SDK (apt, pinned to the robot's version) here once it exists.
|
|
17
|
+
|
|
18
|
+
WORKDIR /plugin
|
|
19
|
+
|
|
20
|
+
CMD ["bash"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Socket.IO bridge container lifecycle for `muscat-pk dev`.
|
|
2
|
+
// The bridge runs in Docker and publishes port 10000 to the host:
|
|
3
|
+
// - frontend (preview-host, on the host) connects to localhost:10000
|
|
4
|
+
// - the backend container joins via `--network container:<bridge>` and reaches
|
|
5
|
+
// it at localhost:10000 too — no host.docker.internal needed (robust cross-OS).
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { createServer, connect } from "node:net";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { hasBin, kitRoot, log, pruneMuskatImages } from "./util.mjs";
|
|
11
|
+
|
|
12
|
+
export const BRIDGE_IMAGE = "muscat-pk-bridge";
|
|
13
|
+
export const BRIDGE_CONTAINER = "muscat-pk-bridge-run";
|
|
14
|
+
export const BRIDGE_PORT = 10000;
|
|
15
|
+
|
|
16
|
+
const DOCKERFILE = resolve(kitRoot, "cli/docker/Dockerfile.bridge");
|
|
17
|
+
const BUILD_CONTEXT = resolve(kitRoot, "cli");
|
|
18
|
+
|
|
19
|
+
function assertDockerReady() {
|
|
20
|
+
if (!hasBin("docker")) {
|
|
21
|
+
throw new Error("Docker not found. `muscat-pk dev` runs the Socket.IO bridge in a container.");
|
|
22
|
+
}
|
|
23
|
+
if (!existsSync(DOCKERFILE)) {
|
|
24
|
+
throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Is a host TCP port already bound? (Catches another bridge or a stray process.)
|
|
29
|
+
function isPortInUse(port) {
|
|
30
|
+
return new Promise((res) => {
|
|
31
|
+
const srv = createServer();
|
|
32
|
+
srv.once("error", (err) => res(err.code === "EADDRINUSE"));
|
|
33
|
+
srv.once("listening", () => srv.close(() => res(false)));
|
|
34
|
+
srv.listen(port, "0.0.0.0");
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Wait until the published port accepts a TCP connection (bridge is ready).
|
|
39
|
+
function waitForPort(port, timeoutMs = 15_000) {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
return new Promise((res, rej) => {
|
|
42
|
+
const tryOnce = () => {
|
|
43
|
+
const sock = connect(port, "127.0.0.1");
|
|
44
|
+
sock.once("connect", () => { sock.destroy(); res(); });
|
|
45
|
+
sock.once("error", () => {
|
|
46
|
+
sock.destroy();
|
|
47
|
+
if (Date.now() - start > timeoutMs) rej(new Error("bridge container did not become ready in time"));
|
|
48
|
+
else setTimeout(tryOnce, 300);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
tryOnce();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ensureImage() {
|
|
56
|
+
log.step(`docker build -t ${BRIDGE_IMAGE} (first run is slow, then cached)…`);
|
|
57
|
+
const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f", DOCKERFILE, BUILD_CONTEXT], { stdio: "inherit" });
|
|
58
|
+
if (r.status !== 0) throw new Error("docker build failed (bridge image).");
|
|
59
|
+
pruneMuskatImages();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Build the image, preflight the port, and start the bridge container. */
|
|
63
|
+
export async function startBridge() {
|
|
64
|
+
assertDockerReady();
|
|
65
|
+
|
|
66
|
+
// remove our own stale container first, then check the host port is free
|
|
67
|
+
spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
|
|
68
|
+
if (await isPortInUse(BRIDGE_PORT)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`port ${BRIDGE_PORT} is already in use.\n` +
|
|
71
|
+
` Another 'muscat-pk dev' may be running, or a process is holding the port.\n` +
|
|
72
|
+
` Stop it, or free the port, then retry.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ensureImage();
|
|
77
|
+
|
|
78
|
+
log.step("starting Socket.IO bridge container…");
|
|
79
|
+
const up = spawnSync(
|
|
80
|
+
"docker",
|
|
81
|
+
["run", "-d", "--name", BRIDGE_CONTAINER, "-p", `${BRIDGE_PORT}:${BRIDGE_PORT}`, BRIDGE_IMAGE],
|
|
82
|
+
{ stdio: "inherit" },
|
|
83
|
+
);
|
|
84
|
+
if (up.status !== 0) throw new Error("failed to start bridge container.");
|
|
85
|
+
|
|
86
|
+
await waitForPort(BRIDGE_PORT);
|
|
87
|
+
log.step(`Socket.IO bridge listening on ws://127.0.0.1:${BRIDGE_PORT}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function stopBridge() {
|
|
91
|
+
spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
|
|
92
|
+
}
|