@rainbow-robotics/muscat-plugin-kit 1.3.6-dev.6 → 1.3.6-dev.8

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.
Files changed (129) hide show
  1. package/cli/README.md +3 -8
  2. package/cli/bin/muscat-pk.mjs +0 -5
  3. package/cli/bundle-templates.mjs +1 -6
  4. package/cli/docker/Dockerfile.cpp +6 -28
  5. package/cli/docker/Dockerfile.dev +0 -32
  6. package/cli/docker/Dockerfile.python +0 -19
  7. package/cli/src/bridge.mjs +12 -45
  8. package/cli/src/build.mjs +79 -48
  9. package/cli/src/cpp.mjs +5 -11
  10. package/cli/src/dev.mjs +2 -7
  11. package/cli/src/devBack.mjs +40 -97
  12. package/cli/src/host_agent.py +83 -143
  13. package/cli/src/manifest.mjs +0 -11
  14. package/cli/src/mcp.mjs +54 -398
  15. package/cli/src/new.mjs +3 -13
  16. package/cli/src/ports.mjs +7 -21
  17. package/cli/src/python.mjs +20 -16
  18. package/cli/src/ros2.mjs +8 -16
  19. package/cli/src/sdk.mjs +21 -23
  20. package/cli/src/templates-embedded.mjs +4 -0
  21. package/cli/src/type.mjs +10 -44
  22. package/cli/src/uninstall.mjs +20 -3
  23. package/cli/src/update.mjs +8 -14
  24. package/cli/src/util.mjs +9 -70
  25. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +1 -13
  26. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +1 -98
  27. package/cli/storybook-dist/assets/{Color-YHDXOIA2-C5ttlJWI.js → Color-YHDXOIA2-Du8dJ-nM.js} +1 -1
  28. package/cli/storybook-dist/assets/{Docs-CCfiEyzU.js → Docs--Fm8069p.js} +1 -1
  29. package/cli/storybook-dist/assets/{Docs-B3AV-YvP.js → Docs-Cb72AJnj.js} +1 -1
  30. package/cli/storybook-dist/assets/{Docs-BN206lKl.js → Docs-LdjYwuG4.js} +1 -1
  31. package/cli/storybook-dist/assets/{Docs-BqH2prGO.js → Docs-Sy5RDbtH.js} +1 -1
  32. package/cli/storybook-dist/assets/{Docs-DhncK7Aw.js → Docs-guGflRzZ.js} +1 -1
  33. package/cli/storybook-dist/assets/{Docs-CGN1_Ua8.js → Docs-pwGqGTV_.js} +1 -1
  34. package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-D-w8Kq64.js → DocsRenderer-CFRXHY34-YGBLQ4BD.js} +1 -1
  35. package/cli/storybook-dist/assets/{NxAvatar.stories-oCcYtLwq.js → NxAvatar.stories-C1d4n8cq.js} +1 -1
  36. package/cli/storybook-dist/assets/{NxButton.stories-C5jUScIj.js → NxButton.stories-gCBoZdMq.js} +1 -1
  37. package/cli/storybook-dist/assets/{NxIcons.stories-DeqrVzVs.js → NxIcons.stories-Dg42riT5.js} +1 -1
  38. package/cli/storybook-dist/assets/{NxNumberInput.stories-gjsEulIN.js → NxNumberInput.stories-DB-vLM2J.js} +1 -1
  39. package/cli/storybook-dist/assets/{NxPagination.stories-BK6uSWIa.js → NxPagination.stories-CS8iMIak.js} +1 -1
  40. package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BDD5CvSZ.js → NxPointerHoldButton.stories-D66BopeS.js} +1 -1
  41. package/cli/storybook-dist/assets/{NxRangeInput.stories-C_nW26_x.js → NxRangeInput.stories-CZxddsJ2.js} +3 -3
  42. package/cli/storybook-dist/assets/{NxSelectBox.stories-DqLfKU9H.js → NxSelectBox.stories-CmezGGrm.js} +1 -1
  43. package/cli/storybook-dist/assets/{NxSlideRange.stories-CQjHA8yv.js → NxSlideRange.stories-CGAFMITi.js} +1 -1
  44. package/cli/storybook-dist/assets/{NxSwitch.stories-BMHCiPoL.js → NxSwitch.stories-B3QInkXX.js} +1 -1
  45. package/cli/storybook-dist/assets/{NxTextArea.stories-k7r1WVQh.js → NxTextArea.stories-BgJcgZNe.js} +1 -1
  46. package/cli/storybook-dist/assets/{NxTextInput.stories-Cqfe1I32.js → NxTextInput.stories-CwMQwI9L.js} +1 -1
  47. package/cli/storybook-dist/assets/{RBAccordion.stories-CJ3Ttuja.js → RBAccordion.stories-BuBVNzJT.js} +1 -1
  48. package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BThv7tM0.js → RBAccordionGroup.stories-Crdb6kFF.js} +1 -1
  49. package/cli/storybook-dist/assets/{RBAlert.stories-CSMP8Vun.js → RBAlert.stories-CqXgUvA2.js} +1 -1
  50. package/cli/storybook-dist/assets/{RBBadge.stories-cBMA2R6F.js → RBBadge.stories-BCw_ksML.js} +1 -1
  51. package/cli/storybook-dist/assets/{RBBattery.stories-BKVOskqs.js → RBBattery.stories-0id4XYoc.js} +1 -1
  52. package/cli/storybook-dist/assets/{RBCard.stories-BTOfK0HC.js → RBCard.stories-CgpG-7Lk.js} +1 -1
  53. package/cli/storybook-dist/assets/{RBCheckBox.stories-DyE912wG.js → RBCheckBox.stories-BG02BoGX.js} +1 -1
  54. package/cli/storybook-dist/assets/{RBCheckIconButton.stories-0sLFU0kM.js → RBCheckIconButton.stories-vrjD4GQ4.js} +1 -1
  55. package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-c8VecClE.js → RBCheckLabelButton.stories-POba8lU-.js} +1 -1
  56. package/cli/storybook-dist/assets/{RBChipTab.stories-CQ9Uav5_.js → RBChipTab.stories-XWJbFrHF.js} +1 -1
  57. package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-Ap4NlhAZ.js → RBChipsBoxTab.stories-BdU0Dzdp.js} +1 -1
  58. package/cli/storybook-dist/assets/{RBColorIcons.stories-BPPXjif2.js → RBColorIcons.stories-C_0dCNXG.js} +1 -1
  59. package/cli/storybook-dist/assets/{RBCommand.stories-PDpYOKlm.js → RBCommand.stories-Dvrko5No.js} +1 -1
  60. package/cli/storybook-dist/assets/{RBConfirm.stories-BEWvMAYP.js → RBConfirm.stories-CkTn0sHa.js} +1 -1
  61. package/cli/storybook-dist/assets/{RBDateInput.stories-DLdyNnVF.js → RBDateInput.stories-BH3D08cs.js} +1 -1
  62. package/cli/storybook-dist/assets/{RBDialog.stories-HsawMNOb.js → RBDialog.stories-C2nNX9fl.js} +1 -1
  63. package/cli/storybook-dist/assets/{RBDivider.stories-dlWwOxik.js → RBDivider.stories-D-GrJ0QZ.js} +1 -1
  64. package/cli/storybook-dist/assets/{RBDrawer.stories-C87iqGk0.js → RBDrawer.stories-XLOnLOnh.js} +1 -1
  65. package/cli/storybook-dist/assets/{RBDropdownMenu.stories-B8EQhA0z.js → RBDropdownMenu.stories-BjBSeoV2.js} +1 -1
  66. package/cli/storybook-dist/assets/{RBEmptyView.stories-SzpIhNDw.js → RBEmptyView.stories-C6U2zKNE.js} +1 -1
  67. package/cli/storybook-dist/assets/{RBIconButton.stories-BQ0nM1gb.js → RBIconButton.stories-MCJq5Hta.js} +1 -1
  68. package/cli/storybook-dist/assets/{RBIcons.stories-C19g5M5z.js → RBIcons.stories-DxOCiCe0.js} +1 -1
  69. package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-DbgI4-1P.js → RBInlineLabelButton.stories-BVZi1aEs.js} +1 -1
  70. package/cli/storybook-dist/assets/{RBJoyStick.stories-smF-vpGL.js → RBJoyStick.stories-BDThbEek.js} +1 -1
  71. package/cli/storybook-dist/assets/{RBLabelButton.stories-s53klwDv.js → RBLabelButton.stories-CaPEKUS6.js} +1 -1
  72. package/cli/storybook-dist/assets/{RBLight.stories-BnzkoR51.js → RBLight.stories-CihQzyEL.js} +1 -1
  73. package/cli/storybook-dist/assets/{RBMenubar.stories-CW014klu.js → RBMenubar.stories-J852XVFh.js} +1 -1
  74. package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-qa4tH_4Y.js → RBMultiRangeInput.stories-DXyX0cOZ.js} +1 -1
  75. package/cli/storybook-dist/assets/{RBPagination.stories-YoQNUpQH.js → RBPagination.stories-C9_49ymO.js} +1 -1
  76. package/cli/storybook-dist/assets/{RBPopover.stories-Bhpi0OaH.js → RBPopover.stories-DxLE912-.js} +1 -1
  77. package/cli/storybook-dist/assets/{RBPrompt.stories-iQM5iAka.js → RBPrompt.stories-DlsfGqWb.js} +1 -1
  78. package/cli/storybook-dist/assets/{RBRadioBox.stories-BGyq4rzx.js → RBRadioBox.stories-hb5cNWW1.js} +1 -1
  79. package/cli/storybook-dist/assets/{RBRadioButton.stories-DF6BcUcJ.js → RBRadioButton.stories-CSuOgeN-.js} +1 -1
  80. package/cli/storybook-dist/assets/RBRangeDateInput.stories-KRg2glX8.js +15 -0
  81. package/cli/storybook-dist/assets/{RBResizable.stories-DSVHvwjp.js → RBResizable.stories-BetjF9vG.js} +1 -1
  82. package/cli/storybook-dist/assets/RBSelectBox.stories-ILLQQ8M8.js +6 -0
  83. package/cli/storybook-dist/assets/{RBSheet.stories-CAK9hb-p.js → RBSheet.stories-D3QbcMKq.js} +1 -1
  84. package/cli/storybook-dist/assets/{RBSideNumberInput.stories-DUymnH-9.js → RBSideNumberInput.stories-DcTh0IjM.js} +1 -1
  85. package/cli/storybook-dist/assets/{RBSlider.stories-BwlDh9--.js → RBSlider.stories-B59We7ei.js} +1 -1
  86. package/cli/storybook-dist/assets/{RBSpinner.stories-VOlkuP6t.js → RBSpinner.stories-9u3_ETbi.js} +1 -1
  87. package/cli/storybook-dist/assets/{RBSwitch.stories-BiW2dQnT.js → RBSwitch.stories-DrVm1WGX.js} +1 -1
  88. package/cli/storybook-dist/assets/{RBTab.stories-DFKVsy0C.js → RBTab.stories-D8Dm7cRW.js} +1 -1
  89. package/cli/storybook-dist/assets/{RBTable.stories-BWG0ZSYI.js → RBTable.stories-dRdZ8g1j.js} +1 -1
  90. package/cli/storybook-dist/assets/{RBTableRow.stories-JVTD83Mh.js → RBTableRow.stories-rZeoGwHZ.js} +1 -1
  91. package/cli/storybook-dist/assets/{RBTabs.stories-D-aN385u.js → RBTabs.stories-STRV2lgO.js} +1 -1
  92. package/cli/storybook-dist/assets/{RBTextArea.stories-Dx5xkmZS.js → RBTextArea.stories-CfgUbrbJ.js} +1 -1
  93. package/cli/storybook-dist/assets/{RBTextField.stories-DmVciExd.js → RBTextField.stories-B5t8-Y97.js} +1 -1
  94. package/cli/storybook-dist/assets/{RBTextInput.stories-CCf0VGEv.js → RBTextInput.stories-Bf1edN6l.js} +1 -1
  95. package/cli/storybook-dist/assets/{RBToast.stories-DT0DGn05.js → RBToast.stories-Cnm8JwQ6.js} +1 -1
  96. package/cli/storybook-dist/assets/{RBToggle.stories-Ce_q3EtP.js → RBToggle.stories-B0OCo6zd.js} +1 -1
  97. package/cli/storybook-dist/assets/{RBToggleGroup.stories-CN40YHsR.js → RBToggleGroup.stories-GdYO2Fxg.js} +1 -1
  98. package/cli/storybook-dist/assets/{RBTooltip.stories-L7btDEpA.js → RBTooltip.stories-DXJTCScO.js} +1 -1
  99. package/cli/storybook-dist/assets/SideNumberInput-CrF-a1Nr.js +1114 -0
  100. package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +1 -0
  101. package/cli/storybook-dist/assets/{entry-preview-D_nGAXUk.js → entry-preview-DSRwIaVJ.js} +1 -1
  102. package/cli/storybook-dist/assets/{iframe-23VS_ZeI.js → iframe-Cj53Wn3d.js} +2 -2
  103. package/cli/storybook-dist/assets/{index-ClAc6FBW.js → index-C1OzuK6-.js} +1 -1
  104. package/cli/storybook-dist/assets/{index-Ckae_HWe.js → index-CkHtLEe8.js} +3 -3
  105. package/cli/storybook-dist/assets/preview-CEfMaO1t.css +1 -0
  106. package/cli/storybook-dist/assets/{preview-BVJwlGUF.js → preview-CILRoSnB.js} +1 -1
  107. package/cli/storybook-dist/assets/{preview-CD0LINug.js → preview-CgOtgtCD.js} +2 -2
  108. package/cli/storybook-dist/assets/preview-D_2um8Sd.js +3 -0
  109. package/cli/storybook-dist/assets/react-resizable-panels.browser-CtORL3tG.js +102 -0
  110. package/cli/storybook-dist/iframe.html +1 -1
  111. package/cli/storybook-dist/index.html +1 -1
  112. package/cli/storybook-dist/index.json +1 -1
  113. package/cli/storybook-dist/project.json +1 -1
  114. package/cli/templates/common/README.md +7 -211
  115. package/cli/templates/common/py/main.py +2 -29
  116. package/cli/templates/common/requirements.txt +3 -3
  117. package/cli/templates/cpp/cpp/CMakeLists.txt +7 -6
  118. package/cli/templates/cpp/cpp/main.cpp +10 -57
  119. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +0 -4
  120. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +4 -5
  121. package/package.json +3 -3
  122. package/cli/storybook-dist/assets/RBRangeDateInput.stories-COOQ_HNF.js +0 -15
  123. package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +0 -74
  124. package/cli/storybook-dist/assets/SideNumberInput-B5_xLK8z.js +0 -1109
  125. package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +0 -1
  126. package/cli/storybook-dist/assets/preview-P8yWWsME.js +0 -3
  127. package/cli/storybook-dist/assets/preview-zuAvMksL.css +0 -1
  128. package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +0 -120
  129. package/cli/templates/readme-cpp/README.md +0 -993
package/cli/src/ports.mjs CHANGED
@@ -4,15 +4,7 @@ import { usesHostNetwork } from "./zenohDev.mjs";
4
4
 
5
5
  export const PLUGIN_PORT_MIN = 28600;
6
6
  export const PLUGIN_PORT_MAX = 28999;
7
- export const MEDIA_PORTS = [8554, 8889, 8189]; // MediaMTX: RTSP publish, WebRTC, WebRTC media
8
- export const INTERNAL_DEV_PORTS = new Set([10000, ...MEDIA_PORTS]);
9
-
10
- export function mediaPortFlags() {
11
- return [
12
- "-p", "127.0.0.1:8554:8554", "-p", "127.0.0.1:8889:8889",
13
- "-p", "127.0.0.1:8189:8189/udp", "-p", "127.0.0.1:8189:8189/tcp",
14
- ];
15
- }
7
+ export const INTERNAL_DEV_PORTS = new Set([10000]);
16
8
  export const RESERVED_BACKEND_PORTS = new Map([
17
9
  [502, "Modbus TCP"],
18
10
  [4840, "OPC-UA"],
@@ -126,21 +118,14 @@ export function publishPortFlags(ranges = []) {
126
118
  return ranges.flatMap((port) => ["-p", `127.0.0.1:${port}:${port}`]);
127
119
  }
128
120
 
129
- // Skips OS-assigned loopback listeners (127.0.0.1:0, e.g. the SDK's Zenoh session).
130
121
  function listeningTcpPorts(container) {
131
122
  const script = `
132
- read -r eph_lo eph_hi < /proc/sys/net/ipv4/ip_local_port_range 2>/dev/null || { eph_lo=32768; eph_hi=60999; }
133
123
  for f in /proc/net/tcp /proc/net/tcp6; do
134
124
  [ -r "$f" ] || continue
135
125
  tail -n +2 "$f" | while read -r sl local_address rem_address st rest; do
136
126
  [ "$st" = "0A" ] || continue
137
- hex_ip="\${local_address%%:*}"
138
- port=$((0x\${local_address##*:}))
139
- case "$hex_ip" in
140
- 0100007F|00000000000000000000000001000000)
141
- [ "$port" -ge "$eph_lo" ] && [ "$port" -le "$eph_hi" ] && continue ;;
142
- esac
143
- echo "$port"
127
+ hex_port="\${local_address##*:}"
128
+ printf "%d\\n" "0x$hex_port"
144
129
  done
145
130
  done | sort -n -u
146
131
  `;
@@ -156,9 +141,10 @@ done | sort -n -u
156
141
  }
157
142
 
158
143
  export function startPortContractMonitor({ container, ranges, log, intervalMs = 1000, allow = [] }) {
159
- // Under --network host (or a local Muscat container's netns) /proc/net/tcp shows every
160
- // host/robot service port, not just the plugin's — the contract can't be enforced.
161
- if (usesHostNetwork() || process.env.MUSCAT_PK_MUSCAT_CONTAINER) return () => false;
144
+ // Under --network host the container shares the host's netns, so
145
+ // /proc/net/tcp shows every host/robot service port, not just the plugin's —
146
+ // the contract can't be enforced. Skip it (no isolated ports to police).
147
+ if (usesHostNetwork()) return () => false;
162
148
  const declared = ranges ?? [];
163
149
  // muscat-pk's own dev plumbing shares this netns — not the plugin's doing.
164
150
  const ignored = new Set([...INTERNAL_DEV_PORTS, ...allow.map(Number).filter(Number.isInteger)]);
@@ -1,9 +1,9 @@
1
1
  // Shared Python Docker helpers (Ubuntu 24.04 + Python 3.12 — matches the robot).
2
2
  // Used by dev:back (hot-reload) and build (lock + wheel vendor).
3
3
  import { spawnSync } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
4
+ import { existsSync } from "node:fs";
5
5
  import { dirname, resolve } from "node:path";
6
- import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
6
+ import { canSkipImageBuild, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
7
7
 
8
8
  export const PYTHON_DEV_IMAGE = "muscat-pk-python-dev";
9
9
 
@@ -15,17 +15,16 @@ export function assertDockerReady() {
15
15
 
16
16
  /** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
17
17
  export async function ensureImage(image = PYTHON_DEV_IMAGE, { platform = null } = {}) {
18
- const dockerfile = await resolveDockerfile("Dockerfile.python");
19
- const key = imageKey(dockerfile);
20
- if (canSkipImageBuild(image, key)) {
18
+ if (canSkipImageBuild(image)) {
21
19
  log.step(`${image} already built — skipping docker build`);
22
20
  return;
23
21
  }
22
+ const dockerfile = await resolveDockerfile("Dockerfile.python");
24
23
  log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
25
24
  const platformFlags = platform ? ["--platform", platform] : [];
26
25
  const r = spawnSync(
27
26
  "docker",
28
- ["build", ...platformFlags, ...imageKeyLabel(key), "-t", image, "-f", dockerfile, dirname(dockerfile)],
27
+ ["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
29
28
  { stdio: "inherit" },
30
29
  );
31
30
  if (r.status !== 0) throw new Error("docker build failed.");
@@ -66,11 +65,7 @@ export async function lockAndVendor(pluginRoot, archs = ["amd64", "arm64"]) {
66
65
 
67
66
  await ensureImage();
68
67
 
69
- // A pinned pre-release (e.g. rainbow-rb-sdk==0.1.4.dev1) pins its sibling rainbow-rb-*
70
- // pre-releases too; uv rejects those transitive pre-releases unless allowed.
71
- const reqText = readFileSync(resolve(pluginRoot, "requirements.txt"), "utf8");
72
- const prerelease = /==\s*\S*\d(a|b|rc|\.dev)\d*\s*$/im.test(reqText) ? " --prerelease=allow" : "";
73
- log.step(`generating lock (uv pip compile${prerelease})…`);
68
+ log.step("generating lock (uv pip compile)…");
74
69
  const lockResult = spawnSync(
75
70
  "docker",
76
71
  [
@@ -79,16 +74,17 @@ export async function lockAndVendor(pluginRoot, archs = ["amd64", "arm64"]) {
79
74
  "-v", `${pluginRoot}:/plugin`,
80
75
  PYTHON_DEV_IMAGE,
81
76
  "bash", "-c",
82
- "set -e && cd /plugin && mkdir -p dist && uv pip compile requirements.txt --python-version 3.12 -o dist/requirements.lock" + prerelease,
77
+ "set -e && cd /plugin && mkdir -p dist && uv pip compile requirements.txt --python-version 3.12 -o dist/requirements.lock",
83
78
  ],
84
79
  { stdio: "inherit" },
85
80
  );
86
81
  if (lockResult.status !== 0) throw new Error("lock generation failed (uv pip compile)");
87
82
  log.ok("dist/requirements.lock ready");
88
83
 
84
+ const failed = [];
89
85
  for (const arch of archs) {
90
86
  const pipPlatforms = PIP_PLATFORM[arch];
91
- if (!pipPlatforms) throw new Error(`unknown arch '${arch}'`);
87
+ if (!pipPlatforms) { log.warn(`unknown arch '${arch}' — skipped`); continue; }
92
88
  log.step(`vendoring wheels for ${arch}…`);
93
89
  const r = spawnSync(
94
90
  "docker",
@@ -106,8 +102,16 @@ export async function lockAndVendor(pluginRoot, archs = ["amd64", "arm64"]) {
106
102
  ],
107
103
  { stdio: "inherit" },
108
104
  );
109
- // A ZIP missing wheels fails to import on the robot — stop instead.
110
- if (r.status !== 0) throw new Error(`wheel download failed for ${arch} (see pip output above)`);
111
- log.ok(`dist/wheels/ ready (${arch})`);
105
+ if (r.status !== 0) {
106
+ log.warn(`wheel vendoring failed for ${arch}`);
107
+ failed.push(arch);
108
+ } else {
109
+ log.ok(`dist/wheels/ ready (${arch})`);
110
+ }
111
+ }
112
+
113
+ if (failed.length === archs.length) {
114
+ throw new Error(`wheel vendoring failed for all archs: ${failed.join(", ")}`);
112
115
  }
116
+ return failed;
113
117
  }
package/cli/src/ros2.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { existsSync, readdirSync } from "node:fs";
5
5
  import { dirname, resolve } from "node:path";
6
- import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
6
+ import { canSkipImageBuild, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
7
7
 
8
8
  export const ROS2_DEV_IMAGE = "muscat-pk-ros2-dev";
9
9
 
@@ -30,19 +30,16 @@ export const ARCH_DOCKER_PLATFORM = {
30
30
 
31
31
  /** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
32
32
  export async function ensureImage(image = ROS2_DEV_IMAGE, { platform = null } = {}) {
33
- const dockerfile = await resolveDockerfile("Dockerfile.dev");
34
- const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
35
- const key = imageKey(dockerfile, sdkVer ?? "");
36
- if (canSkipImageBuild(image, key)) {
33
+ if (canSkipImageBuild(image)) {
37
34
  log.step(`${image} already built — skipping docker build`);
38
35
  return;
39
36
  }
37
+ const dockerfile = await resolveDockerfile("Dockerfile.dev");
40
38
  log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
41
39
  const platformFlags = platform ? ["--platform", platform] : [];
42
- const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
43
40
  const r = spawnSync(
44
41
  "docker",
45
- ["build", ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
42
+ ["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
46
43
  { stdio: "inherit" },
47
44
  );
48
45
  if (r.status !== 0) throw new Error("docker build failed.");
@@ -74,16 +71,12 @@ export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arc
74
71
  installBase = "../dist/ros2_ws/install";
75
72
  }
76
73
  const flag = symlink ? "--symlink-install" : "";
77
- const buildBase = symlink ? "build" : arch ? `build-${arch}` : "build-dist";
78
74
  const script = [
79
75
  "pip install -r requirements.txt --break-system-packages --ignore-installed -q || true",
80
76
  "cd ros2_ws",
81
- // Export builds use their own build cache: sharing build/ with dev's --symlink-install
82
- // (or another arch) breaks the next colcon build.
83
- // rclcpp export: ship librb_sdk.so in <pkg>/lib/ (robot has no SDK) when the node links it.
84
- `colcon build ${flag} --build-base ${buildBase} --install-base ${installBase}`.replace(" ", " ") +
85
- (arch ? ` --cmake-args '-DCMAKE_INSTALL_RPATH=$ORIGIN/..'` : ""),
86
- ...(arch ? [`for d in ${installBase}/*/lib; do if readelf -d $(find "$d" -mindepth 2 -type f -executable) 2>/dev/null | grep -q librb_sdk; then cp -a $(dpkg -L rainbow-rb-sdk | grep -E '/librb_sdk[.]so[.][0-9]') "$d"/; fi; done`] : []),
77
+ // ponytail: rm only on cross-arch; same-arch dev rebuilds keep the cache
78
+ ...(arch ? ["rm -rf build"] : []),
79
+ `colcon build ${flag} --install-base ${installBase}`.trim(),
87
80
  ].join(" && ");
88
81
  const label = arch
89
82
  ? `colcon build (${arch}) → dist/ros2_ws/install-${arch}/…`
@@ -94,8 +87,7 @@ export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arc
94
87
  const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
95
88
  const r = spawnSync(
96
89
  "docker",
97
- // python/cpp 와 같이 호스트 uid 로 돌린다 — root 소유 산출물이 남으면 사용자가 지우지 못한다.
98
- ["run", "--rm", ...platformFlags, ...dockerUserFlags(), "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
90
+ ["run", "--rm", ...platformFlags, "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
99
91
  { cwd: root, stdio: "inherit" },
100
92
  );
101
93
  if (r.status !== 0) throw new Error(`colcon build failed${arch ? ` (${arch})` : ""}.`);
package/cli/src/sdk.mjs CHANGED
@@ -6,24 +6,24 @@
6
6
  // the new version immediately; this is best-effort only.
7
7
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { resolve } from "node:path";
9
- import { ensureIdeVenv, findPluginRoot, log, venvPython } from "./util.mjs";
9
+ import { spawnSync } from "node:child_process";
10
+ import { findPluginRoot, log, venvPython } from "./util.mjs";
10
11
 
11
12
  const PKG = "rainbow-rb-sdk";
12
13
 
13
- async function fetchPypi() {
14
+ async function fetchVersions() {
14
15
  try {
15
16
  const res = await fetch(`https://pypi.org/pypi/${PKG}/json`);
16
- return res.ok ? await res.json() : null;
17
+ if (!res.ok) return [];
18
+ const data = await res.json();
19
+ return Object.keys(data.releases || {}).sort((a, b) =>
20
+ b.localeCompare(a, undefined, { numeric: true }),
21
+ );
17
22
  } catch {
18
- return null;
23
+ return [];
19
24
  }
20
25
  }
21
26
 
22
- async function fetchVersions() {
23
- const data = await fetchPypi();
24
- return Object.keys(data?.releases || {}).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
25
- }
26
-
27
27
  export async function runSdk(args = []) {
28
28
  const sub = args[0];
29
29
  const root = findPluginRoot();
@@ -42,25 +42,23 @@ export async function runSdk(args = []) {
42
42
  if (sub === "update") {
43
43
  let version = args[1];
44
44
  if (!version) {
45
- // info.version = latest stable
46
- version = (await fetchPypi())?.info?.version;
47
- if (!version) throw new Error("could not fetch the latest version (offline?). Pass one: muscat-pk sdk update <version>");
45
+ const versions = await fetchVersions();
46
+ version = versions[0] || "latest";
48
47
  }
49
- // rainbow-rb-* pin each other exactly — bump them together.
50
48
  let content = existsSync(reqs) ? readFileSync(reqs, "utf8") : "";
51
- const pinned = /^(rainbow-rb-[\w-]+)\s*==.*$/gim;
52
- const bumped = [...content.matchAll(pinned)].map((m) => m[1]);
53
- content = content.replace(pinned, (_m, name) => `${name}==${version}`);
54
- if (!bumped.some((n) => n.toLowerCase() === PKG)) {
55
- content = (content ? content.trimEnd() + "\n" : "") + `${PKG}==${version}\n`;
56
- bumped.push(PKG);
49
+ const line = `${PKG}==${version}`;
50
+ if (new RegExp(`^${PKG}==`, "m").test(content)) {
51
+ content = content.replace(new RegExp(`^${PKG}==.*$`, "m"), line);
52
+ } else {
53
+ content = (content ? content.trimEnd() + "\n" : "") + line + "\n";
57
54
  }
58
55
  writeFileSync(reqs, content);
59
- log.ok(`requirements.txt → ${bumped.map((n) => `${n}==${version}`).join(", ")}`);
56
+ log.ok(`requirements.txt → ${line}`);
60
57
 
61
- if (existsSync(venvPython(resolve(root, ".venv")))) {
62
- log.step("refreshing .venv (for your editor)…");
63
- if (!ensureIdeVenv(root)) log.warn(".venv refresh failed — the Docker dev container is unaffected.");
58
+ const py = venvPython(resolve(root, ".venv"));
59
+ if (existsSync(py)) {
60
+ log.step("pip install -r requirements.txt (.venv, for your editor)…");
61
+ spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], { cwd: root, stdio: "inherit" });
64
62
  } else {
65
63
  log.info("Installed automatically on the next `muscat-pk dev` / `dev:back` (Docker container).");
66
64
  }