@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.35 → 0.1.2-dev.37

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.
@@ -21,7 +21,7 @@ Usage: muscat-pk <command> [options]
21
21
  Commands:
22
22
  new [dir] Scaffold a new plugin project (prompts: author, email, description, type, front-package-manager)
23
23
  type change Switch backend type (python | cpp | ros2-python | ros2-cpp)
24
- build Build + lock deps + vendor wheels + zip (--no-zip: skip zip, --no-lock: skip python lock/vendor, --arch x86_64|aarch64|both)
24
+ build Build + lock deps + vendor wheels + zip (--no-zip: skip zip, --no-lock: skip python lock/vendor, --arch amd64|arm64|both)
25
25
  dev Run Socket.IO bridge, dev:ui, and dev:back together
26
26
  dev:ui Preview the plugin in a mini-Muscat (preview-host)
27
27
  dev:back Run the plugin back end (python: watchfiles reload / ros2: Docker)
package/cli/src/build.mjs CHANGED
@@ -14,8 +14,8 @@ import { createRequire } from "node:module";
14
14
  import { basename, dirname, resolve } from "node:path";
15
15
  import { createInterface } from "node:readline/promises";
16
16
  import { execSync, spawnSync } from "node:child_process";
17
- import { ensureDocker, findPluginRoot, hasBin, humanSize, log } from "./util.mjs";
18
- import { assertManifest } from "./manifest.mjs";
17
+ import { ensureDocker, findPluginRoot, hasBin, humanSize, log, parseArgs } from "./util.mjs";
18
+ import { assertManifest, manifestRuntime } from "./manifest.mjs";
19
19
 
20
20
  // Find a usable node binary (for running vite as subprocess).
21
21
  function findNodeBin() {
@@ -60,14 +60,6 @@ function isMainBranch() {
60
60
  return branch === "main" || branch === "master";
61
61
  }
62
62
 
63
- function manifestRuntime(root) {
64
- try {
65
- return JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.type ?? "python";
66
- } catch {
67
- return "python";
68
- }
69
- }
70
-
71
63
  // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
72
64
  // rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
73
65
  // rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
@@ -280,18 +272,7 @@ export async function buildPlugin({ root, minify = false } = {}) {
280
272
  return { pluginRoot, distDir };
281
273
  }
282
274
 
283
- function dirSize(dir) {
284
- let total = 0;
285
- for (const entry of readdirSync(dir)) {
286
- const full = resolve(dir, entry);
287
- const st = statSync(full);
288
- total += st.isDirectory() ? dirSize(full) : st.size;
289
- }
290
- return total;
291
- }
292
-
293
- // Recursively sum directory size, skipping subdirs in `skip` list (by name).
294
- function dirSizeExcluding(dir, skip = []) {
275
+ function dirSize(dir, skip = []) {
295
276
  if (!existsSync(dir)) return 0;
296
277
  let total = 0;
297
278
  for (const entry of readdirSync(dir)) {
@@ -313,7 +294,7 @@ function dirSizeExcluding(dir, skip = []) {
313
294
  function calcInstalledSize(pluginRoot) {
314
295
  const distDir = resolve(pluginRoot, "dist");
315
296
 
316
- const installedSize = dirSizeExcluding(distDir, ["wheels"]);
297
+ const installedSize = dirSize(distDir, ["wheels"]);
317
298
 
318
299
  // Write to root manifest.json
319
300
  const rootManifestPath = resolve(pluginRoot, "manifest.json");
@@ -369,9 +350,7 @@ function packZip(pluginRoot) {
369
350
  * @returns {Promise<string[]>} e.g. ['amd64'] | ['arm64'] | ['amd64','arm64']
370
351
  */
371
352
  async function promptArch(args = []) {
372
- // --arch x86_64 | --arch aarch64 | --arch both
373
- const archFlag = args.find((a) => a.startsWith("--arch="))?.slice(7)
374
- ?? (args.includes("--arch") ? args[args.indexOf("--arch") + 1] : null);
353
+ const archFlag = parseArgs(args).arch ?? null;
375
354
  if (archFlag) {
376
355
  if (archFlag === "both") return ["amd64", "arm64"];
377
356
  if (archFlag === "amd64" || archFlag === "x86_64") return ["amd64"];
@@ -1,29 +1,13 @@
1
- import { execFileSync } from "node:child_process";
2
- import { existsSync, readdirSync, statSync } from "node:fs";
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
7
- const roots = [resolve(root, "cli"), resolve(root, "preview-host")];
8
-
9
- function* mjsFiles(dir) {
10
- for (const entry of readdirSync(dir)) {
11
- const full = resolve(dir, entry);
12
- const stat = statSync(full);
13
- if (stat.isDirectory()) {
14
- if (entry === "dist" || entry === "runtime" || entry === "templates") {
15
- continue;
16
- }
17
- yield* mjsFiles(full);
18
- } else if (entry.endsWith(".mjs")) {
19
- yield full;
20
- }
21
- }
22
- }
23
-
24
- for (const dir of roots) {
25
- if (!existsSync(dir)) continue;
26
- for (const file of mjsFiles(dir)) {
27
- execFileSync(process.execPath, ["--check", file], { stdio: "inherit" });
28
- }
7
+ for (const dir of ["cli", "preview-host"]) {
8
+ if (!existsSync(resolve(root, dir))) continue;
9
+ execSync(
10
+ `find ${dir} -name '*.mjs' ! -path '*/dist/*' ! -path '*/runtime/*' ! -path '*/templates/*' -exec node --check {} +`,
11
+ { cwd: root, stdio: "inherit" },
12
+ );
29
13
  }
@@ -8,10 +8,10 @@
8
8
  // ros2 : Jazzy + Python 3.12 container, colcon build, run the node, restart
9
9
  // on source changes (rclcpp rebuilds on change).
10
10
  import { spawn, spawnSync } from "node:child_process";
11
- import { existsSync, readFileSync, watch } from "node:fs";
11
+ import { existsSync, watch } from "node:fs";
12
12
  import { resolve } from "node:path";
13
13
  import { ensureDocker, findPluginRoot, log } from "./util.mjs";
14
- import { readManifest as readManifestSchema, validateManifest } from "./manifest.mjs";
14
+ import { manifestRuntime, readManifest, validateManifest } from "./manifest.mjs";
15
15
  import {
16
16
  PYTHON_DEV_IMAGE,
17
17
  assertDockerReady as pyAssertDockerReady,
@@ -29,18 +29,6 @@ import {
29
29
  ensureImage as cppEnsureImage,
30
30
  } from "./cpp.mjs";
31
31
 
32
- function readManifest(root) {
33
- try {
34
- return JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
35
- } catch {
36
- return {};
37
- }
38
- }
39
-
40
- function manifestRuntime(root) {
41
- return readManifest(root).runtime?.type ?? "python";
42
- }
43
-
44
32
  // ── python ────────────────────────────────────────────────────────────────────
45
33
  // Runs in a warm Ubuntu 24.04 + Python 3.12 container (matches the robot).
46
34
  // Under `muscat-pk dev`, it shares the bridge container's netns, so the SDK
@@ -245,7 +233,7 @@ function runCpp(root) {
245
233
  }
246
234
 
247
235
  function warnManifest(root) {
248
- const manifest = readManifestSchema(root);
236
+ const manifest = readManifest(root);
249
237
  if (!manifest || Object.keys(manifest).length === 0) return;
250
238
  const errors = validateManifest(manifest);
251
239
  for (const e of errors) log.warn(`manifest.json: ${e}`);
@@ -36,6 +36,10 @@ export function readManifest(root) {
36
36
  }
37
37
  }
38
38
 
39
+ export function manifestRuntime(root) {
40
+ return readManifest(root)?.runtime?.type ?? "python";
41
+ }
42
+
39
43
  function validateIcons(icons, errors) {
40
44
  if (icons === undefined || icons === null) {
41
45
  errors.push("icons is required");
package/cli/src/new.mjs CHANGED
@@ -10,10 +10,9 @@ import {
10
10
  statSync,
11
11
  writeFileSync,
12
12
  } from "node:fs";
13
- import { createInterface } from "node:readline/promises";
14
13
  import { basename, resolve } from "node:path";
15
14
  import { rmSync } from "node:fs";
16
- import { hasBin, kitVersion, parseArgs, templatesDir, log } from "./util.mjs";
15
+ import { hasBin, kitVersion, parseArgs, prompt, templatesDir, log } from "./util.mjs";
17
16
 
18
17
  // `--type` values map to a runtime descriptor. ros2 has a python (rclpy) and a
19
18
  // C++ (rclcpp) flavor; we expose them as distinct type choices for clarity.
@@ -41,14 +40,6 @@ export function runtimeForChoice(choice, slug) {
41
40
  }
42
41
  }
43
42
 
44
- async function prompt(question, fallback) {
45
- if (!process.stdin.isTTY) return fallback;
46
- const rl = createInterface({ input: process.stdin, output: process.stdout });
47
- const answer = (await rl.question(question)).trim();
48
- rl.close();
49
- return answer || fallback;
50
- }
51
-
52
43
  export function applyTokens(dir, tokens) {
53
44
  for (const entry of readdirSync(dir)) {
54
45
  const full = resolve(dir, entry);
package/cli/src/ros2.mjs CHANGED
@@ -76,6 +76,8 @@ export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arc
76
76
  const script = [
77
77
  "pip install -r requirements.txt --break-system-packages --ignore-installed -q || true",
78
78
  "cd ros2_ws",
79
+ // ponytail: rm only on cross-arch; same-arch dev rebuilds keep the cache
80
+ ...(arch ? ["rm -rf build"] : []),
79
81
  `colcon build ${flag} --install-base ${installBase}`.trim(),
80
82
  ].join(" && ");
81
83
  const label = arch
package/cli/src/type.mjs CHANGED
@@ -3,9 +3,8 @@
3
3
  // Adds the matching backend overlay; never deletes existing user code
4
4
  // (e.g. ros2_ws/ or cpp/ are left in place when switching away).
5
5
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
- import { createInterface } from "node:readline/promises";
7
6
  import { basename, resolve } from "node:path";
8
- import { findPluginRoot, log } from "./util.mjs";
7
+ import { findPluginRoot, log, prompt } from "./util.mjs";
9
8
  import {
10
9
  TYPE_CHOICES,
11
10
  applyTokens,
@@ -17,14 +16,6 @@ import {
17
16
 
18
17
  const TYPES = Object.keys(TYPE_CHOICES);
19
18
 
20
- async function prompt(question, fallback) {
21
- if (!process.stdin.isTTY) return fallback;
22
- const rl = createInterface({ input: process.stdin, output: process.stdout });
23
- const answer = (await rl.question(question)).trim();
24
- rl.close();
25
- return answer || fallback;
26
- }
27
-
28
19
  function readManifest(root) {
29
20
  const path = resolve(root, "manifest.json");
30
21
  if (!existsSync(path)) throw new Error(`manifest.json not found in ${root}`);
@@ -10,12 +10,11 @@
10
10
  //
11
11
  // After updating the CLI itself, also bumps the plugin project's local deps
12
12
  // and migrates the project structure if it's outdated (src/ → ui/, root main.py → py/).
13
- import { createInterface } from "node:readline";
14
13
  import { execSync, spawn } from "node:child_process";
15
14
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
16
15
  import { tmpdir } from "node:os";
17
16
  import { join, resolve } from "node:path";
18
- import { findPluginRoot, KIT_PUBLISHED_NAME, kitVersion, log, parseArgs } from "./util.mjs";
17
+ import { findPluginRoot, KIT_PUBLISHED_NAME, kitVersion, log, parseArgs, prompt } from "./util.mjs";
19
18
 
20
19
  const PLUGIN_UI_PACKAGE = "@rainbow-robotics/plugin-ui";
21
20
  const S3_BASE = "https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit";
@@ -135,13 +134,6 @@ function detectOldStructure(root) {
135
134
  return issues;
136
135
  }
137
136
 
138
- function prompt(question) {
139
- return new Promise((resolve) => {
140
- const rl = createInterface({ input: process.stdin, output: process.stdout });
141
- rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
142
- });
143
- }
144
-
145
137
  async function migrateStructure(root, issues) {
146
138
  log.warn("This plugin project's structure is outdated:");
147
139
  for (const issue of issues) log.info(" • " + issue.desc);
package/cli/src/util.mjs CHANGED
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { createInterface } from "node:readline/promises";
6
7
 
7
8
  /** True if an executable is on PATH (cross-platform). */
8
9
  export function hasBin(bin) {
@@ -65,6 +66,15 @@ export function parseArgs(args, { booleans = [] } = {}) {
65
66
  return out;
66
67
  }
67
68
 
69
+ /** Prompt user for input. Returns trimmed answer or fallback (non-TTY or empty). */
70
+ export async function prompt(question, fallback = "") {
71
+ if (!process.stdin.isTTY) return fallback;
72
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
73
+ const answer = (await rl.question(question)).trim();
74
+ rl.close();
75
+ return answer || fallback;
76
+ }
77
+
68
78
  /** Walk up from cwd to find the plugin project root (has manifest.json). */
69
79
  export function findPluginRoot(start = process.cwd()) {
70
80
  let dir = resolve(start);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/muscat-plugin-kit",
3
- "version": "0.1.2-dev.35",
3
+ "version": "0.1.2-dev.37",
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.35",
34
+ "@rainbow-robotics/preview-host": "0.1.2-dev.37",
35
35
  "socket.io": "^4.8.1"
36
36
  },
37
37
  "optionalDependencies": {