@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.23 → 0.1.2-dev.24
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/README.md +2 -3
- package/cli/bin/muscat-pk.mjs +2 -6
- package/cli/src/build.mjs +51 -9
- package/cli/src/devUi.mjs +15 -11
- package/cli/src/manifest.mjs +26 -3
- package/cli/src/new.mjs +2 -2
- package/cli/templates/common/README.md +23 -14
- package/cli/templates/common/manifest.json +2 -0
- package/cli/templates/common/requirements.txt +3 -3
- package/package.json +2 -2
- package/cli/src/pack.mjs +0 -58
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
|
|
|
@@ -11,8 +11,7 @@ muscat-pk new my-plugin
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
muscat-pk new my-plugin [--type sdk|ros2|ui] [--pm npm|yarn|pnpm|bun] [--yes]
|
|
14
|
-
muscat-pk build #
|
|
15
|
-
muscat-pk pack # build (minified) + distributable ZIP with a size report
|
|
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
16
|
muscat-pk dev:back # run the back end (.venv + python main.py)
|
|
18
17
|
muscat-pk dev # dev:ui + dev:back
|
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
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, sdk.
|
|
4
4
|
import { log } from "../src/util.mjs";
|
|
5
5
|
import { runNew } from "../src/new.mjs";
|
|
6
6
|
import { runBuild } from "../src/build.mjs";
|
|
7
|
-
import { runPack } from "../src/pack.mjs";
|
|
8
7
|
import { runDevUi } from "../src/devUi.mjs";
|
|
9
8
|
import { runDevBack } from "../src/devBack.mjs";
|
|
10
9
|
import { runDev } from "../src/dev.mjs";
|
|
@@ -22,8 +21,7 @@ Usage: muscat-pk <command> [options]
|
|
|
22
21
|
Commands:
|
|
23
22
|
new [dir] Scaffold a new plugin project
|
|
24
23
|
type change Switch backend type (python <-> ros2)
|
|
25
|
-
build Build
|
|
26
|
-
pack Build then create a distributable ZIP (with size report)
|
|
24
|
+
build Build ui/content.tsx -> dist/ and zip into a distributable ZIP (--no-zip to skip)
|
|
27
25
|
dev Run Socket.IO bridge, dev:ui, and dev:back together
|
|
28
26
|
dev:ui Preview the plugin in a mini-Muscat (preview-host)
|
|
29
27
|
dev:back Run the plugin back end (python: watchfiles reload / ros2: Docker)
|
|
@@ -42,8 +40,6 @@ async function main() {
|
|
|
42
40
|
return await runType(args);
|
|
43
41
|
case "build":
|
|
44
42
|
return await runBuild(args);
|
|
45
|
-
case "pack":
|
|
46
|
-
return await runPack(args);
|
|
47
43
|
case "dev":
|
|
48
44
|
return await runDev(args);
|
|
49
45
|
case "dev:ui":
|
package/cli/src/build.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// muscat-pk build — bundle
|
|
2
|
-
// manifest.json and assets/ for distribution
|
|
1
|
+
// muscat-pk build — bundle ui/content.tsx into dist/content.js, stage
|
|
2
|
+
// manifest.json and assets/ for distribution, and zip dist/ into a
|
|
3
|
+
// distributable ZIP (skip with --no-zip).
|
|
3
4
|
//
|
|
4
5
|
// sandbox.html is generated by the CLI — plugin authors never manage it.
|
|
5
6
|
// The import map always reflects the current plugin-ui entry set, so users
|
|
@@ -8,11 +9,11 @@
|
|
|
8
9
|
// The plugin author's `@rainbow-robotics/plugin-ui` and React imports are left external.
|
|
9
10
|
// Muscat/robot and preview-host provide those modules through a sandbox import
|
|
10
11
|
// map, so plugin ZIPs do not carry the UI runtime.
|
|
11
|
-
import { cpSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { cpSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
12
13
|
import { createRequire } from "node:module";
|
|
13
|
-
import { dirname, resolve } from "node:path";
|
|
14
|
-
import { spawnSync } from "node:child_process";
|
|
15
|
-
import { findPluginRoot, hasBin, log } from "./util.mjs";
|
|
14
|
+
import { basename, dirname, resolve } from "node:path";
|
|
15
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
16
|
+
import { findPluginRoot, hasBin, humanSize, log } from "./util.mjs";
|
|
16
17
|
import { assertManifest } from "./manifest.mjs";
|
|
17
18
|
|
|
18
19
|
// Find a usable node binary (for running vite as subprocess).
|
|
@@ -117,7 +118,7 @@ async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// Pin unpinned (no version specifier) entries in requirements.txt to the
|
|
120
|
-
// latest PyPI release. Runs on explicit `build
|
|
121
|
+
// latest PyPI release. Runs on explicit `build` only — keeps the shipped
|
|
121
122
|
// plugin reproducible without churning the source file on every dev rebuild.
|
|
122
123
|
// stableOnly=true is used on main-branch builds to avoid shipping dev versions.
|
|
123
124
|
export async function pinRequirements(root, { stableOnly = false } = {}) {
|
|
@@ -219,14 +220,55 @@ export async function buildPlugin({ root, minify = false } = {}) {
|
|
|
219
220
|
return { pluginRoot, distDir };
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
function dirSize(dir) {
|
|
224
|
+
let total = 0;
|
|
225
|
+
for (const entry of readdirSync(dir)) {
|
|
226
|
+
const full = resolve(dir, entry);
|
|
227
|
+
const st = statSync(full);
|
|
228
|
+
total += st.isDirectory() ? dirSize(full) : st.size;
|
|
229
|
+
}
|
|
230
|
+
return total;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Zip dist/ (+ ros2_ws/install) into a distributable ZIP with a size report.
|
|
234
|
+
function packZip(pluginRoot) {
|
|
235
|
+
const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
|
|
236
|
+
const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
|
|
237
|
+
const zipPath = resolve(pluginRoot, zipName);
|
|
238
|
+
rmSync(zipPath, { force: true });
|
|
239
|
+
|
|
240
|
+
const entries = ["dist"];
|
|
241
|
+
if (existsSync(resolve(pluginRoot, "ros2_ws/install"))) entries.push("ros2_ws/install");
|
|
242
|
+
|
|
243
|
+
log.step(`packing ${entries.join(", ")} → ${zipName}`);
|
|
244
|
+
try {
|
|
245
|
+
execSync(`zip -r -q "${zipPath}" ${entries.map((e) => `"${e}"`).join(" ")}`, {
|
|
246
|
+
cwd: pluginRoot,
|
|
247
|
+
stdio: "inherit",
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
throw new Error("`zip` not found. Install zip or pack manually.");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const zipBytes = statSync(zipPath).size;
|
|
254
|
+
log.ok(`${zipName} ${humanSize(zipBytes)}`);
|
|
255
|
+
for (const e of entries) {
|
|
256
|
+
const full = resolve(pluginRoot, e);
|
|
257
|
+
const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
|
|
258
|
+
log.info(` ├─ ${e} ${humanSize(size)}`);
|
|
259
|
+
}
|
|
260
|
+
log.warn("robot install checks free space via GET /plugin/capacity (507 if low).");
|
|
261
|
+
}
|
|
262
|
+
|
|
222
263
|
export async function runBuild(args) {
|
|
223
|
-
const
|
|
264
|
+
const noZip = args.includes("--no-zip");
|
|
224
265
|
const root = findPluginRoot();
|
|
225
266
|
if (!args.includes("--no-pin")) {
|
|
226
267
|
const stableOnly = isMainBranch();
|
|
227
268
|
log.step(`pinning requirements.txt to latest ${stableOnly ? "stable " : ""}PyPI versions`);
|
|
228
269
|
await pinRequirements(root, { stableOnly });
|
|
229
270
|
}
|
|
230
|
-
await buildPlugin({ root, minify });
|
|
271
|
+
const { pluginRoot } = await buildPlugin({ root, minify: true });
|
|
231
272
|
await exportRos2Build(root);
|
|
273
|
+
if (!noZip) packZip(pluginRoot);
|
|
232
274
|
}
|
package/cli/src/devUi.mjs
CHANGED
|
@@ -13,6 +13,10 @@ const PREVIEW_PKG = "@rainbow-robotics/preview-host";
|
|
|
13
13
|
const CACHE_DIR = resolve(homedir(), ".muscat-pk");
|
|
14
14
|
const CACHE_ROOT = resolve(CACHE_DIR, "node_modules", "@rainbow-robotics", "preview-host");
|
|
15
15
|
|
|
16
|
+
// Dev builds go to a throwaway dir, not dist/ — dist/ is reserved for
|
|
17
|
+
// build/pack output (the shippable artifact).
|
|
18
|
+
const DEV_OUT = ".muscat-dev";
|
|
19
|
+
|
|
16
20
|
function findNodeBin() {
|
|
17
21
|
if (typeof Bun === "undefined") return process.execPath;
|
|
18
22
|
for (const c of ["node", "node20", "node18"]) {
|
|
@@ -52,14 +56,14 @@ function findPreviewHostRoot() {
|
|
|
52
56
|
return CACHE_ROOT;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
// Write sandbox.html + copy manifest into
|
|
59
|
+
// Write sandbox.html + copy manifest into the dev out dir (vite doesn't generate these).
|
|
56
60
|
function stageDevAssets(pluginRoot) {
|
|
57
|
-
const
|
|
58
|
-
mkdirSync(
|
|
59
|
-
writeFileSync(resolve(
|
|
61
|
+
const outDir = resolve(pluginRoot, DEV_OUT);
|
|
62
|
+
mkdirSync(outDir, { recursive: true });
|
|
63
|
+
writeFileSync(resolve(outDir, "sandbox.html"), SANDBOX_HTML, "utf8");
|
|
60
64
|
const manifestSrc = resolve(pluginRoot, "manifest.json");
|
|
61
|
-
if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(
|
|
62
|
-
return
|
|
65
|
+
if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(outDir, "manifest.json"));
|
|
66
|
+
return outDir;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
// Resolve vite bin path from the plugin root, hoisting-aware.
|
|
@@ -79,8 +83,8 @@ function spawnViteWatch(pluginRoot, node) {
|
|
|
79
83
|
const viteBin = findViteBin(pluginRoot);
|
|
80
84
|
if (!node || !viteBin || !existsSync(viteBin)) return null;
|
|
81
85
|
|
|
82
|
-
log.step(
|
|
83
|
-
return spawn(node, [viteBin, "build", "--watch", "--mode", "development"], {
|
|
86
|
+
log.step(`vite build --watch (ui/content.tsx → ${DEV_OUT}/content.js)`);
|
|
87
|
+
return spawn(node, [viteBin, "build", "--watch", "--mode", "development", "--outDir", DEV_OUT], {
|
|
84
88
|
cwd: pluginRoot,
|
|
85
89
|
stdio: "inherit",
|
|
86
90
|
});
|
|
@@ -106,14 +110,14 @@ export async function runDevUi(args = []) {
|
|
|
106
110
|
|
|
107
111
|
await checkKitUpdate();
|
|
108
112
|
const pluginRoot = findPluginRoot();
|
|
109
|
-
const
|
|
113
|
+
const outDir = stageDevAssets(pluginRoot);
|
|
110
114
|
|
|
111
115
|
const node = findNodeBin();
|
|
112
116
|
const viteProc = spawnViteWatch(pluginRoot, node);
|
|
113
117
|
|
|
114
118
|
if (viteProc) {
|
|
115
119
|
log.step("waiting for initial vite build…");
|
|
116
|
-
await waitForInitialBuild(
|
|
120
|
+
await waitForInitialBuild(outDir);
|
|
117
121
|
} else {
|
|
118
122
|
log.warn("vite not found — UI will not be bundled. Run `npm install` in the plugin project.");
|
|
119
123
|
}
|
|
@@ -133,7 +137,7 @@ export async function runDevUi(args = []) {
|
|
|
133
137
|
log.step(`starting preview-host on :${port}`);
|
|
134
138
|
const child = spawn(
|
|
135
139
|
node ?? process.execPath,
|
|
136
|
-
[serveEntry, "--plugin",
|
|
140
|
+
[serveEntry, "--plugin", outDir, "--port", String(port)],
|
|
137
141
|
{ stdio: "inherit" },
|
|
138
142
|
);
|
|
139
143
|
|
package/cli/src/manifest.mjs
CHANGED
|
@@ -7,6 +7,7 @@ const ICON_SIZES = ["16", "48", "128"];
|
|
|
7
7
|
const RUNTIME_TYPES = ["none", "python", "ros2"];
|
|
8
8
|
const ROS_CLIENTS = ["rclpy", "rclcpp"];
|
|
9
9
|
const I18N_LANGS = ["ko", "en", "ja", "zh", "vi", "th", "es"];
|
|
10
|
+
const TARGET_CATEGORIES = ["manipulate", "amr", "rby1", "vision"];
|
|
10
11
|
|
|
11
12
|
function validateI18nLabel(value, field, errors) {
|
|
12
13
|
if (value === undefined || value === null) {
|
|
@@ -15,7 +16,7 @@ function validateI18nLabel(value, field, errors) {
|
|
|
15
16
|
}
|
|
16
17
|
if (typeof value === "string") return;
|
|
17
18
|
if (typeof value === "object") {
|
|
18
|
-
if (typeof value.en !== "string"
|
|
19
|
+
if (typeof value.en !== "string")
|
|
19
20
|
errors.push(`${field}: "en" is required when using object form`);
|
|
20
21
|
const unknown = Object.keys(value).filter((k) => !I18N_LANGS.includes(k));
|
|
21
22
|
if (unknown.length > 0)
|
|
@@ -133,8 +134,30 @@ export function validateManifest(manifest) {
|
|
|
133
134
|
if (typeof manifest[key] !== "string" || manifest[key].length === 0)
|
|
134
135
|
errors.push(`"${key}" is required`);
|
|
135
136
|
}
|
|
136
|
-
if (typeof manifest.author !== "string")
|
|
137
|
-
errors.push(`"author" is required
|
|
137
|
+
if (typeof manifest.author !== "string" || manifest.author.length === 0)
|
|
138
|
+
errors.push(`"author" is required`);
|
|
139
|
+
|
|
140
|
+
const hasModels = manifest.targetModels !== undefined && manifest.targetModels !== null;
|
|
141
|
+
const hasCategorys = manifest.targetCategorys !== undefined && manifest.targetCategorys !== null;
|
|
142
|
+
if (!hasModels && !hasCategorys) {
|
|
143
|
+
errors.push(`"targetModels" or "targetCategorys" is required`);
|
|
144
|
+
} else {
|
|
145
|
+
if (hasModels) {
|
|
146
|
+
if (manifest.targetModels !== "all" && (!Array.isArray(manifest.targetModels) || manifest.targetModels.length === 0))
|
|
147
|
+
errors.push(`"targetModels" must be "all" or a non-empty array of model strings`);
|
|
148
|
+
else if (Array.isArray(manifest.targetModels) && manifest.targetModels.some((m) => typeof m !== "string"))
|
|
149
|
+
errors.push(`"targetModels": all entries must be strings`);
|
|
150
|
+
}
|
|
151
|
+
if (hasCategorys) {
|
|
152
|
+
if (manifest.targetCategorys !== "all" && (!Array.isArray(manifest.targetCategorys) || manifest.targetCategorys.length === 0))
|
|
153
|
+
errors.push(`"targetCategorys" must be "all" or a non-empty array`);
|
|
154
|
+
else if (Array.isArray(manifest.targetCategorys)) {
|
|
155
|
+
const invalid = manifest.targetCategorys.filter((c) => !TARGET_CATEGORIES.includes(c));
|
|
156
|
+
if (invalid.length > 0)
|
|
157
|
+
errors.push(`"targetCategorys": invalid value(s) ${invalid.map((c) => `"${c}"`).join(", ")} — allowed: ${TARGET_CATEGORIES.join(", ")}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
138
161
|
validateI18nLabel(manifest.description, "description", errors);
|
|
139
162
|
|
|
140
163
|
validateIcons(manifest.icons, errors);
|
package/cli/src/new.mjs
CHANGED
|
@@ -149,7 +149,7 @@ export async function runNew(args) {
|
|
|
149
149
|
// gitignore (templates can't ship a literal .gitignore via npm reliably)
|
|
150
150
|
writeFileSync(
|
|
151
151
|
resolve(target, ".gitignore"),
|
|
152
|
-
["node_modules", "dist", ".venv", "*.zip", "ros2_ws/install", ""].join("\n"),
|
|
152
|
+
["node_modules", "dist", ".muscat-dev", ".venv", "*.zip", "ros2_ws/install", ""].join("\n"),
|
|
153
153
|
);
|
|
154
154
|
|
|
155
155
|
if (!opts["no-install"]) {
|
|
@@ -197,7 +197,7 @@ export async function runNew(args) {
|
|
|
197
197
|
log.ok(`created ${targetName} (type: ${type}, pm: ${pm})`);
|
|
198
198
|
log.info(`\n cd ${targetName}`);
|
|
199
199
|
log.info(` muscat-pk dev # preview in mini-Muscat + back end`);
|
|
200
|
-
log.info(` muscat-pk
|
|
200
|
+
log.info(` muscat-pk build # build + distributable ZIP\n`);
|
|
201
201
|
|
|
202
202
|
// ── post-scaffold environment warnings ───────────────────────────────────
|
|
203
203
|
const noNode = !hasBin("node");
|
|
@@ -8,7 +8,7 @@ A Muscat plugin project. Build, develop, and ship with `muscat-pk` CLI.
|
|
|
8
8
|
muscat-pk dev # UI preview + backend together
|
|
9
9
|
muscat-pk dev:ui # UI preview only
|
|
10
10
|
muscat-pk dev:back # backend only (Python watchfiles / ROS2 Docker)
|
|
11
|
-
muscat-pk
|
|
11
|
+
muscat-pk build # build + distributable ZIP
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -25,7 +25,7 @@ __PLUGIN_DIR__/
|
|
|
25
25
|
│ └─ icon.svg # icon referenced from manifest
|
|
26
26
|
├─ manifest.json # metadata, contribution points, runtime config
|
|
27
27
|
├─ README.md # this file (copied to dist/ on build)
|
|
28
|
-
└─ requirements.txt # Python deps (auto-pinned to latest on
|
|
28
|
+
└─ requirements.txt # Python deps (auto-pinned to latest on build)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
> ROS2 plugins additionally include a `ros2_ws/` directory.
|
|
@@ -192,6 +192,13 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
192
192
|
"icons": "assets/icon.svg",
|
|
193
193
|
// "icons": { "16": "assets/icon-16.png", "48": "assets/icon-48.png", "128": "assets/icon-128.png" },
|
|
194
194
|
|
|
195
|
+
// targetModels and/or targetCategorys — at least one is required
|
|
196
|
+
// "all" = every target, or list specific values
|
|
197
|
+
"targetModels": "all",
|
|
198
|
+
// "targetModels": ["C500920", "C500930"],
|
|
199
|
+
"targetCategorys": "all",
|
|
200
|
+
// "targetCategorys": ["manipulate", "amr", "rby1", "vision"],
|
|
201
|
+
|
|
195
202
|
"contributes": [
|
|
196
203
|
// program.command — adds a step node to the program tree
|
|
197
204
|
{
|
|
@@ -258,9 +265,11 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
258
265
|
|
|
259
266
|
**Validation rules:**
|
|
260
267
|
- `id`, `name`, `version`, `minMuscatVersion` — required non-empty strings
|
|
261
|
-
- `author` — required
|
|
268
|
+
- `author` — required non-empty string
|
|
262
269
|
- `description` — required (`string` or `{ en (required), ko?, ja?, zh?, vi?, th?, es? }`)
|
|
263
270
|
- `icons` — SVG: single string path ending in `.svg`; PNG/JPG: object with `"16"`, `"48"`, `"128"` all present
|
|
271
|
+
- `targetModels` — `"all"` or a non-empty array of robot model ID strings (at least one of `targetModels` / `targetCategorys` required)
|
|
272
|
+
- `targetCategorys` — `"all"` or a non-empty array of `"manipulate" | "amr" | "rby1" | "vision"` (at least one of `targetModels` / `targetCategorys` required)
|
|
264
273
|
- `contributes` — required array; must contain at least one `toolbar` entry
|
|
265
274
|
- `ui` — each contribution must have a unique value
|
|
266
275
|
- `commandInfo` — required when `point` is `"program.command"`
|
|
@@ -281,7 +290,7 @@ README.ja.md # Japanese
|
|
|
281
290
|
```
|
|
282
291
|
|
|
283
292
|
All `README*.md` files found at the project root are automatically copied to `dist/` on
|
|
284
|
-
`muscat-pk build
|
|
293
|
+
`muscat-pk build`. Muscat displays the file matching the user's locale,
|
|
285
294
|
falling back to `README.md` if none matches.
|
|
286
295
|
|
|
287
296
|
---
|
|
@@ -302,7 +311,7 @@ Run `muscat-pk dev` or `muscat-pk dev:back` — dependencies are installed autom
|
|
|
302
311
|
- **Python plugin**: installed into `.venv` via `pip install -r requirements.txt`
|
|
303
312
|
- **ROS2 plugin**: installed inside the Docker container via `pip install -r requirements.txt`
|
|
304
313
|
|
|
305
|
-
> `muscat-pk build`
|
|
314
|
+
> `muscat-pk build` only **pins** versions in `requirements.txt` — it does not run `pip install`.
|
|
306
315
|
|
|
307
316
|
---
|
|
308
317
|
|
|
@@ -321,12 +330,13 @@ muscat-pk type change --to python # ros2 → python
|
|
|
321
330
|
## Shipping
|
|
322
331
|
|
|
323
332
|
```bash
|
|
324
|
-
muscat-pk
|
|
333
|
+
muscat-pk build # build + ZIP
|
|
334
|
+
muscat-pk build --no-zip # build only, no ZIP
|
|
325
335
|
```
|
|
326
336
|
|
|
327
|
-
- Builds `ui/content.tsx` → `dist/content.js`
|
|
337
|
+
- Builds `ui/content.tsx` → `dist/content.js` (minified)
|
|
328
338
|
- Pins `requirements.txt` to latest versions (stable-only on `main` branch)
|
|
329
|
-
- Copies `assets/`, `manifest.json`,
|
|
339
|
+
- Copies `assets/`, `manifest.json`, `README*.md`, `py/`, and `requirements.txt` into `dist/`
|
|
330
340
|
- ROS2: runs `colcon build` inside the Jazzy container; includes `ros2_ws/install/`
|
|
331
341
|
- Output: `__PLUGIN_DIR__-{version}.zip`
|
|
332
342
|
|
|
@@ -339,10 +349,10 @@ __PLUGIN_DIR__-1.0.0.zip
|
|
|
339
349
|
│ ├─ manifest.json
|
|
340
350
|
│ ├─ assets/
|
|
341
351
|
│ ├─ README.md
|
|
342
|
-
│
|
|
343
|
-
├─ py/
|
|
344
|
-
│ └─
|
|
345
|
-
└─
|
|
352
|
+
│ ├─ README.ko.md # if present
|
|
353
|
+
│ ├─ main.py # from py/
|
|
354
|
+
│ └─ requirements.txt # pinned
|
|
355
|
+
└─ ros2_ws/install/ # ROS2 only
|
|
346
356
|
```
|
|
347
357
|
|
|
348
358
|
---
|
|
@@ -353,8 +363,7 @@ __PLUGIN_DIR__-1.0.0.zip
|
|
|
353
363
|
muscat-pk dev # UI + backend
|
|
354
364
|
muscat-pk dev:ui # UI preview only (preview-host)
|
|
355
365
|
muscat-pk dev:back # backend only (Python: watchfiles / ROS2: Docker)
|
|
356
|
-
muscat-pk build #
|
|
357
|
-
muscat-pk pack # build + ZIP
|
|
366
|
+
muscat-pk build # build → dist/ + distributable ZIP (--no-zip to skip)
|
|
358
367
|
```
|
|
359
368
|
|
|
360
369
|
```bash
|
|
@@ -1,4 +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.dev43
|
|
2
|
+
rainbow-rb-flat-buffers==0.0.9.dev43
|
|
3
|
+
rainbow-rb-zenoh==0.0.9.dev43
|
|
4
4
|
requests==2.34.2
|
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.24",
|
|
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.
|
|
34
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.24",
|
|
35
35
|
"socket.io": "^4.8.1"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
package/cli/src/pack.mjs
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// muscat-pk pack — build, then create a distributable ZIP with a size report.
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
4
|
-
import { basename, resolve } from "node:path";
|
|
5
|
-
import { buildPlugin, exportRos2Build, pinRequirements } from "./build.mjs";
|
|
6
|
-
import { findPluginRoot, humanSize, log } from "./util.mjs";
|
|
7
|
-
|
|
8
|
-
function dirSize(dir) {
|
|
9
|
-
let total = 0;
|
|
10
|
-
for (const entry of readdirSync(dir)) {
|
|
11
|
-
const full = resolve(dir, entry);
|
|
12
|
-
const st = statSync(full);
|
|
13
|
-
total += st.isDirectory() ? dirSize(full) : st.size;
|
|
14
|
-
}
|
|
15
|
-
return total;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function runPack() {
|
|
19
|
-
const pluginRoot = findPluginRoot();
|
|
20
|
-
log.step("pinning requirements.txt to latest PyPI versions");
|
|
21
|
-
await pinRequirements(pluginRoot);
|
|
22
|
-
await buildPlugin({ root: pluginRoot, minify: true });
|
|
23
|
-
await exportRos2Build(pluginRoot);
|
|
24
|
-
|
|
25
|
-
const manifest = JSON.parse(
|
|
26
|
-
readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"),
|
|
27
|
-
);
|
|
28
|
-
const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
|
|
29
|
-
const zipPath = resolve(pluginRoot, zipName);
|
|
30
|
-
rmSync(zipPath, { force: true });
|
|
31
|
-
|
|
32
|
-
// dist/ already contains main.py, requirements.txt, manifest.json, assets/.
|
|
33
|
-
const entries = ["dist"];
|
|
34
|
-
if (existsSync(resolve(pluginRoot, "ros2_ws/install"))) {
|
|
35
|
-
entries.push("ros2_ws/install");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
log.step(`packing ${entries.join(", ")} → ${zipName}`);
|
|
39
|
-
try {
|
|
40
|
-
execSync(`zip -r -q "${zipPath}" ${entries.map((e) => `"${e}"`).join(" ")}`, {
|
|
41
|
-
cwd: pluginRoot,
|
|
42
|
-
stdio: "inherit",
|
|
43
|
-
});
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error("`zip` not found. Install zip or pack manually.");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const zipBytes = statSync(zipPath).size;
|
|
49
|
-
log.ok(`${zipName} ${humanSize(zipBytes)}`);
|
|
50
|
-
for (const e of entries) {
|
|
51
|
-
const full = resolve(pluginRoot, e);
|
|
52
|
-
const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
|
|
53
|
-
log.info(` ├─ ${e} ${humanSize(size)}`);
|
|
54
|
-
}
|
|
55
|
-
log.warn(
|
|
56
|
-
"robot install checks free space via GET /plugin/capacity (507 if low).",
|
|
57
|
-
);
|
|
58
|
-
}
|