@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.8 → 1.0.0

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 (272) hide show
  1. package/README.md +113 -49
  2. package/cli/README.md +191 -5
  3. package/cli/bin/create-muscat-plugin.mjs +1 -1
  4. package/cli/bin/muscat-pk.mjs +25 -14
  5. package/cli/bundle-templates.mjs +28 -0
  6. package/cli/docker/Dockerfile.bridge +17 -0
  7. package/cli/docker/Dockerfile.cpp +22 -0
  8. package/cli/docker/Dockerfile.dev +1 -0
  9. package/cli/docker/Dockerfile.python +20 -0
  10. package/cli/docker/bridge-package.json +8 -0
  11. package/cli/prepack.mjs +10 -0
  12. package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
  13. package/cli/src/bridge.mjs +95 -0
  14. package/cli/src/build.mjs +345 -85
  15. package/cli/src/check-package.mjs +8 -23
  16. package/cli/src/cpp.mjs +61 -0
  17. package/cli/src/dev.mjs +156 -4
  18. package/cli/src/devBack.mjs +505 -57
  19. package/cli/src/devUi.mjs +167 -22
  20. package/cli/src/esbuild.mjs +27 -10
  21. package/cli/src/host_agent.py +162 -0
  22. package/cli/src/manifest.mjs +208 -0
  23. package/cli/src/mcp.mjs +617 -0
  24. package/cli/src/new.mjs +170 -67
  25. package/cli/src/pluginDevServer.mjs +112 -0
  26. package/cli/src/ports.mjs +160 -0
  27. package/cli/src/python.mjs +109 -0
  28. package/cli/src/ros2.mjs +43 -18
  29. package/cli/src/sdk.mjs +11 -9
  30. package/cli/src/socketBridge.mjs +133 -0
  31. package/cli/src/storybook.mjs +168 -0
  32. package/cli/src/type.mjs +52 -29
  33. package/cli/src/uninstall.mjs +98 -0
  34. package/cli/src/update.mjs +259 -30
  35. package/cli/src/util.mjs +123 -9
  36. package/cli/src/zenohDev.mjs +75 -0
  37. package/cli/storybook-dist/_design-tokens.css +4957 -0
  38. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  39. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  40. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  41. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  42. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  43. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  44. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  45. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  46. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  47. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  48. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  49. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  50. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +44 -0
  51. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  52. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  53. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  54. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  55. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  56. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  57. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  58. package/cli/storybook-dist/_stories-src/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
  59. package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -0
  60. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  61. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  62. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  63. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  64. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +119 -0
  65. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  66. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  67. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  68. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  69. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  70. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  71. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  72. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  73. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  74. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  75. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  76. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  77. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  84. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  85. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  86. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  87. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  88. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  89. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  90. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  91. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  92. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  93. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  94. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  95. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  96. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  97. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  98. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  99. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  100. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  101. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  102. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  103. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  104. package/cli/storybook-dist/assets/Color-YHDXOIA2-ttZJDaKn.js +1 -0
  105. package/cli/storybook-dist/assets/Docs-B1jnlXHd.js +12 -0
  106. package/cli/storybook-dist/assets/Docs-B7dRsHh7.js +8 -0
  107. package/cli/storybook-dist/assets/Docs-C68t3mnn.js +20 -0
  108. package/cli/storybook-dist/assets/Docs-D7rPMMhK.js +7 -0
  109. package/cli/storybook-dist/assets/Docs-DM98U-ZG.js +7 -0
  110. package/cli/storybook-dist/assets/Docs-guWsANnn.js +8 -0
  111. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-t12O9QfY.js +2 -0
  112. package/cli/storybook-dist/assets/NxAvatar.stories-ikdLTtys.js +13 -0
  113. package/cli/storybook-dist/assets/NxButton.stories-DL3S2KEV.js +20 -0
  114. package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
  115. package/cli/storybook-dist/assets/NxNumberInput.stories-D6NVMTos.js +26 -0
  116. package/cli/storybook-dist/assets/NxPagination.stories-BDKSlHiQ.js +11 -0
  117. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-BI544zqB.js +14 -0
  118. package/cli/storybook-dist/assets/NxRangeInput.stories-BigBBkE7.js +17 -0
  119. package/cli/storybook-dist/assets/NxSelectBox.stories-Ci0IyUWd.js +17 -0
  120. package/cli/storybook-dist/assets/NxSlideRange.stories-sjnPAjFy.js +14 -0
  121. package/cli/storybook-dist/assets/NxSwitch.stories-CRxPDlJm.js +11 -0
  122. package/cli/storybook-dist/assets/NxTextArea.stories-Jh09n24O.js +11 -0
  123. package/cli/storybook-dist/assets/NxTextInput.stories-BeGCjxES.js +11 -0
  124. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  125. package/cli/storybook-dist/assets/RBAccordion.stories-CnC95OwM.js +17 -0
  126. package/cli/storybook-dist/assets/RBAccordionGroup.stories-og-6ePox.js +33 -0
  127. package/cli/storybook-dist/assets/RBAlert.stories-BcE9-UOT.js +17 -0
  128. package/cli/storybook-dist/assets/RBBadge.stories-DYZMXb-e.js +1 -0
  129. package/cli/storybook-dist/assets/RBBattery.stories-B7YAUvlM.js +10 -0
  130. package/cli/storybook-dist/assets/RBCard.stories-DTL_7n6I.js +45 -0
  131. package/cli/storybook-dist/assets/RBCheckBox.stories-D9KC6hTg.js +1 -0
  132. package/cli/storybook-dist/assets/RBCheckIconButton.stories-BR8aIkOC.js +1 -0
  133. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C1ABYhEr.js +24 -0
  134. package/cli/storybook-dist/assets/RBChipTab.stories-Ceo_vnNi.js +21 -0
  135. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-CB_bfK8M.js +1 -0
  136. package/cli/storybook-dist/assets/RBColorIcons.stories-DHZKezld.js +3 -0
  137. package/cli/storybook-dist/assets/RBCommand.stories-DjuCDn5h.js +73 -0
  138. package/cli/storybook-dist/assets/RBConfirm.stories-HMmUPkB1.js +20 -0
  139. package/cli/storybook-dist/assets/RBDateInput.stories-BRfpYtJ4.js +15 -0
  140. package/cli/storybook-dist/assets/RBDialog.stories-Ln23Gyhk.js +38 -0
  141. package/cli/storybook-dist/assets/RBDivider.stories-Bhc_D5Cm.js +1 -0
  142. package/cli/storybook-dist/assets/RBDrawer.stories-CEEhamr_.js +14 -0
  143. package/cli/storybook-dist/assets/RBDropdownMenu.stories-DBvwDCnf.js +86 -0
  144. package/cli/storybook-dist/assets/RBEmptyView.stories-BY5QtXVc.js +12 -0
  145. package/cli/storybook-dist/assets/RBIconButton.stories-DyMFWpIV.js +1 -0
  146. package/cli/storybook-dist/assets/RBIcons.stories-DisU8G0d.js +3 -0
  147. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-Cx0KJqxD.js +1 -0
  148. package/cli/storybook-dist/assets/RBJoyStick.stories-D1THM1Du.js +11 -0
  149. package/cli/storybook-dist/assets/RBLabelButton.stories-BDktlyzc.js +1 -0
  150. package/cli/storybook-dist/assets/RBLight.stories-C5qpnM6J.js +1 -0
  151. package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-DUCeQPKJ.js +3 -0
  152. package/cli/storybook-dist/assets/RBMenubar.stories-ClyM9v37.js +3 -0
  153. package/cli/storybook-dist/assets/RBMultiRangeInput.stories--xSn64ww.js +14 -0
  154. package/cli/storybook-dist/assets/RBPagination.stories-C_uTj83z.js +17 -0
  155. package/cli/storybook-dist/assets/RBPopover.stories-DWmb9Por.js +11 -0
  156. package/cli/storybook-dist/assets/RBProgramIcons.stories-B8ZjEtvw.js +3 -0
  157. package/cli/storybook-dist/assets/RBPrompt.stories-BFKfyYtl.js +20 -0
  158. package/cli/storybook-dist/assets/RBRadioBox.stories-DUz8a27t.js +1 -0
  159. package/cli/storybook-dist/assets/RBRadioButton.stories-DKF61xvC.js +11 -0
  160. package/cli/storybook-dist/assets/RBRangeDateInput.stories-GhMVDyMr.js +15 -0
  161. package/cli/storybook-dist/assets/RBResizable.stories-V0iisR-x.js +20 -0
  162. package/cli/storybook-dist/assets/RBSelectBox.stories-DhvooAcg.js +6 -0
  163. package/cli/storybook-dist/assets/RBSheet.stories-C93Hpmvn.js +39 -0
  164. package/cli/storybook-dist/assets/RBSideNumberInput.stories-Y547mUsB.js +5 -0
  165. package/cli/storybook-dist/assets/RBSlider.stories-BJZyHiLO.js +5 -0
  166. package/cli/storybook-dist/assets/RBSpinner.stories-6wkbu-bU.js +14 -0
  167. package/cli/storybook-dist/assets/RBSwitch.stories-T9InolEP.js +65 -0
  168. package/cli/storybook-dist/assets/RBTab.stories-CjGzZODx.js +1 -0
  169. package/cli/storybook-dist/assets/RBTable.stories-CaolKsVG.js +62 -0
  170. package/cli/storybook-dist/assets/RBTableRow.stories-D13urGfs.js +11 -0
  171. package/cli/storybook-dist/assets/RBTabs.stories-Cv2iIxvP.js +21 -0
  172. package/cli/storybook-dist/assets/RBTextArea.stories-CmrPeit2.js +5 -0
  173. package/cli/storybook-dist/assets/RBTextField.stories-ByW-EKCD.js +45 -0
  174. package/cli/storybook-dist/assets/RBTextInput.stories-DSPlr3uh.js +5 -0
  175. package/cli/storybook-dist/assets/RBToast.stories-DJNZ9tku.js +11 -0
  176. package/cli/storybook-dist/assets/RBToggle.stories-BDQW2WO1.js +12 -0
  177. package/cli/storybook-dist/assets/RBToggleGroup.stories-CjwH6YbI.js +17 -0
  178. package/cli/storybook-dist/assets/RBTooltip.stories-Cp1oL68L.js +9 -0
  179. package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
  180. package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
  181. package/cli/storybook-dist/assets/browserUtils-DeopUpDy.js +1 -0
  182. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  183. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  184. package/cli/storybook-dist/assets/createConveyorBeltLayer-769P8deX.js +6257 -0
  185. package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
  186. package/cli/storybook-dist/assets/entry-preview-CNdFC92L.js +2 -0
  187. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  188. package/cli/storybook-dist/assets/iframe-CLUgOMp4.js +211 -0
  189. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  190. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  191. package/cli/storybook-dist/assets/index-C1OzuK6-.js +240 -0
  192. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  193. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  194. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  195. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  196. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  197. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  198. package/cli/storybook-dist/assets/index-mT46TdHs.js +575 -0
  199. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  200. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +1 -0
  201. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  202. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  203. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  204. package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -0
  205. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  206. package/cli/storybook-dist/assets/preview-Com4scch.css +1 -0
  207. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  208. package/cli/storybook-dist/assets/preview-CvycU0Br.js +2 -0
  209. package/cli/storybook-dist/assets/preview-CzhIqee1.js +3 -0
  210. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  211. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  212. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  213. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  214. package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -0
  215. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  216. package/cli/storybook-dist/favicon.svg +1 -0
  217. package/cli/storybook-dist/iframe.html +666 -0
  218. package/cli/storybook-dist/index.html +190 -0
  219. package/cli/storybook-dist/index.json +1 -0
  220. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  221. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  222. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  223. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  224. package/cli/storybook-dist/project.json +1 -0
  225. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  226. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  227. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  228. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  229. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  230. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  231. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  233. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  234. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  235. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  237. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  239. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  240. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  241. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  242. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  243. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  244. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  245. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  246. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  247. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  248. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  249. package/cli/templates/common/.mcp.json +8 -0
  250. package/cli/templates/common/README.md +644 -81
  251. package/cli/templates/common/assets/icon.svg +44 -2
  252. package/cli/templates/common/manifest.json +13 -13
  253. package/cli/templates/common/package.json +11 -4
  254. package/cli/templates/common/py/main.py +98 -0
  255. package/cli/templates/common/requirements.txt +4 -3
  256. package/cli/templates/common/tsconfig.json +1 -1
  257. package/cli/templates/common/ui/content.tsx +216 -0
  258. package/cli/templates/common/ui/style.css +6 -0
  259. package/cli/templates/common/vite.config.ts +6 -25
  260. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  261. package/cli/templates/cpp/cpp/main.cpp +26 -0
  262. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +1 -1
  263. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  264. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  265. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  266. package/cli/vite.d.mts +25 -0
  267. package/cli/vite.mjs +292 -0
  268. package/package.json +32 -5
  269. package/cli/src/pack.mjs +0 -61
  270. package/cli/src/preview.mjs +0 -91
  271. package/cli/templates/common/main.py +0 -114
  272. package/cli/templates/common/src/content.tsx +0 -33
@@ -0,0 +1,95 @@
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
+ /**
63
+ * Build the image, preflight the port, and start the bridge container.
64
+ * Returns true if bridge started, false if port was already in use (caller decides).
65
+ */
66
+ export async function startBridge({ zenohPort = null } = {}) {
67
+ assertDockerReady();
68
+
69
+ // remove our own stale container first, then check the host port is free
70
+ spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
71
+ if (await isPortInUse(BRIDGE_PORT)) return false;
72
+
73
+ ensureImage();
74
+
75
+ log.step("starting Socket.IO bridge container…");
76
+ const portFlags = [
77
+ "-p", `127.0.0.1:${BRIDGE_PORT}:${BRIDGE_PORT}`,
78
+ ...(zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : []),
79
+ ];
80
+ const up = spawnSync(
81
+ "docker",
82
+ ["run", "-d", "--name", BRIDGE_CONTAINER, ...portFlags, BRIDGE_IMAGE],
83
+ { stdio: "inherit" },
84
+ );
85
+ if (up.status !== 0) throw new Error("failed to start bridge container.");
86
+
87
+ await waitForPort(BRIDGE_PORT);
88
+ log.step(`Socket.IO bridge listening on ws://127.0.0.1:${BRIDGE_PORT}`);
89
+ if (zenohPort) log.step(`Zenoh dev peer port published on :${zenohPort}`);
90
+ return true;
91
+ }
92
+
93
+ export function stopBridge() {
94
+ spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
95
+ }
package/cli/src/build.mjs CHANGED
@@ -1,5 +1,6 @@
1
- // muscat-pk build — bundle src/content.tsx into dist/content.js and stage
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,61 @@
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, readFileSync, rmSync, writeFileSync } from "node:fs";
12
- import { resolve } from "node:path";
13
- import { spawnSync } from "node:child_process";
14
- import { buildWithFallback } from "./esbuild.mjs";
15
- import { findPluginRoot, log } from "./util.mjs";
12
+ import { chmodSync, cpSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
13
+ import { createRequire } from "node:module";
14
+ import { basename, dirname, resolve } from "node:path";
15
+ import { createInterface } from "node:readline/promises";
16
+ import { execSync, spawnSync } from "node:child_process";
17
+ import { ensureDocker, findPluginRoot, hasBin, humanSize, log, parseArgs } from "./util.mjs";
18
+ import { assertManifest, manifestRuntime } from "./manifest.mjs";
19
+
20
+ // Find a usable JS runtime for running vite as a subprocess.
21
+ // In binary (Bun) mode, try `bun` first (it can run vite directly), then node.
22
+ // In node mode, use the current process.
23
+ function findJsRuntime() {
24
+ if (typeof Bun === "undefined") return { bin: process.execPath, isBun: false };
25
+ for (const c of ["bun", "node", "nodejs"]) {
26
+ if (hasBin(c)) return { bin: c, isBun: c === "bun" };
27
+ }
28
+ return null;
29
+ }
30
+
31
+ // Resolve vite bin path from the plugin root, hoisting-aware.
32
+ function findViteBin(pluginRoot) {
33
+ try {
34
+ const req = createRequire(resolve(pluginRoot, "package.json"));
35
+ const vitePkg = req.resolve("vite/package.json");
36
+ return resolve(dirname(vitePkg), "bin", "vite.js");
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ // Run vite build in the plugin project. Throws if runtime or vite not found.
43
+ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
44
+ const runtime = findJsRuntime();
45
+ if (!runtime) {
46
+ throw new Error(
47
+ "Node.js 20+ (or Bun) is required to build the plugin UI.\n" +
48
+ " Install Node.js: https://nodejs.org\n" +
49
+ " Then run: npm install (inside the plugin directory)",
50
+ );
51
+ }
52
+ const viteBin = findViteBin(pluginRoot);
53
+ if (!viteBin || !existsSync(viteBin)) {
54
+ throw new Error(
55
+ "vite not found in this plugin's node_modules.\n" +
56
+ ` Run: npm install (inside ${pluginRoot})`,
57
+ );
58
+ }
59
+
60
+ log.step(`vite build (mode: ${mode})`);
61
+ // bun can run vite directly; node needs the explicit bin path
62
+ const args = runtime.isBun ? ["run", viteBin, "build", "--mode", mode]
63
+ : [viteBin, "build", "--mode", mode];
64
+ const r = spawnSync(runtime.bin, args, { cwd: pluginRoot, stdio: "inherit" });
65
+ if (r.status !== 0) throw new Error("vite build failed");
66
+ }
16
67
 
17
68
  function isMainBranch() {
18
69
  const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
@@ -20,29 +71,92 @@ function isMainBranch() {
20
71
  return branch === "main" || branch === "master";
21
72
  }
22
73
 
23
- function manifestRuntime(root) {
74
+ // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
75
+ // rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
76
+ // rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
77
+ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
78
+ if (manifestRuntime(root) !== "ros2") return [];
79
+ const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
24
80
  try {
25
- return JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.type ?? "python";
26
- } catch {
27
- return "python";
81
+ assertDockerReady();
82
+ } catch (e) {
83
+ log.warn(`ros2 build skipped: ${e.message}`);
84
+ return [];
85
+ }
86
+
87
+ const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
88
+ const rosClient = manifest.runtime?.rosClient;
89
+
90
+ const skipped = [];
91
+ if (rosClient === "rclcpp") {
92
+ // C++ ROS2: compile per arch so each robot picks the right install-{arch}/
93
+ for (const arch of archs) {
94
+ const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
95
+ if (!dockerPlatform) { log.warn(`unknown arch '${arch}' — skipped`); skipped.push(arch); continue; }
96
+ const archImage = `${ROS2_DEV_IMAGE}-${arch}`;
97
+ try {
98
+ await ensureImage(archImage, { platform: dockerPlatform });
99
+ colconBuild(root, { symlink: false, arch });
100
+ log.ok(`dist/ros2_ws/install-${arch}/ ready`);
101
+ } catch {
102
+ log.warn(`${arch} ros2 build skipped — QEMU not available`);
103
+ skipped.push(arch);
104
+ }
105
+ }
106
+ } else {
107
+ // rclpy / pure Python: arch-agnostic, build once
108
+ await ensureImage();
109
+ colconBuild(root, { symlink: false });
110
+ log.ok("dist/ros2_ws/install/ ready");
28
111
  }
112
+ return skipped;
29
113
  }
30
114
 
31
- // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12
32
- // container so the shipped ZIP carries ros2_ws/install. Real (non-symlink) build
33
- // → relocatable on the robot. Skipped (with a warning) if Docker is unavailable.
34
- export async function exportRos2Build(root) {
35
- if (manifestRuntime(root) !== "ros2") return;
36
- const { assertDockerReady, ensureImage, colconBuild } = await import("./ros2.mjs");
115
+ // For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
116
+ // and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
117
+ // robot-compatible executables. rb-runtime prefers dist/{arch}/ and falls back
118
+ // to dist/{entry} (legacy). Skipped (with a warning) if Docker is unavailable.
119
+ export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"]) {
120
+ if (manifestRuntime(root) !== "cpp") return [];
121
+ const { assertDockerReady, ensureImage, cmakeBuild, CPP_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./cpp.mjs");
37
122
  try {
38
123
  assertDockerReady();
39
124
  } catch (e) {
40
- log.warn(`ros2 build skipped: ${e.message}`);
125
+ log.warn(`cpp build skipped: ${e.message}`);
126
+ return [];
127
+ }
128
+
129
+ const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
130
+ if (!entry) {
131
+ log.warn("manifest.json runtime.entry not set — cpp build skipped");
41
132
  return;
42
133
  }
43
- ensureImage();
44
- colconBuild(root, { symlink: false });
45
- log.ok("ros2_ws/install ready (will be included in the ZIP)");
134
+
135
+ const skipped = [];
136
+ for (const arch of archs) {
137
+ const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
138
+ if (!dockerPlatform) { log.warn(`unknown arch '${arch}' — skipped`); skipped.push(arch); continue; }
139
+ const archImage = `${CPP_DEV_IMAGE}-${arch}`;
140
+ try {
141
+ await ensureImage(archImage, { platform: dockerPlatform });
142
+ cmakeBuild(root, { buildType: "Release", arch });
143
+ const built = resolve(root, `cpp/build-${arch}`, entry);
144
+ if (existsSync(built)) {
145
+ const archDir = resolve(distDir, arch);
146
+ mkdirSync(archDir, { recursive: true });
147
+ const dest = resolve(archDir, entry);
148
+ copyFileSync(built, dest);
149
+ chmodSync(dest, 0o755);
150
+ log.ok(`cpp binary → dist/${arch}/${entry}`);
151
+ } else {
152
+ log.warn(`cpp binary not found at cpp/build-${arch}/${entry}`);
153
+ }
154
+ } catch {
155
+ log.warn(`${arch} cpp build skipped — QEMU not available`);
156
+ skipped.push(arch);
157
+ }
158
+ }
159
+ return skipped;
46
160
  }
47
161
 
48
162
  const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
@@ -79,7 +193,7 @@ async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
79
193
  }
80
194
 
81
195
  // Pin unpinned (no version specifier) entries in requirements.txt to the
82
- // latest PyPI release. Runs on explicit `build`/`pack` only — keeps the shipped
196
+ // latest PyPI release. Runs on explicit `build` only — keeps the shipped
83
197
  // plugin reproducible without churning the source file on every dev rebuild.
84
198
  // stableOnly=true is used on main-branch builds to avoid shipping dev versions.
85
199
  export async function pinRequirements(root, { stableOnly = false } = {}) {
@@ -109,90 +223,236 @@ export async function pinRequirements(root, { stableOnly = false } = {}) {
109
223
  if (pinned > 0) writeFileSync(reqPath, out.join("\n"));
110
224
  }
111
225
 
112
- const SANDBOX_HTML = `<!doctype html>
113
- <html lang="en">
114
- <head>
115
- <meta charset="UTF-8" />
116
- <title>plugin sandbox</title>
117
- <script type="importmap">
118
- {
119
- "imports": {
120
- "@rainbow-robotics/plugin-ui/connect": "/plugin-runtime/@rainbow-robotics/plugin-ui/connect.js",
121
- "@rainbow-robotics/plugin-ui/react": "/plugin-runtime/@rainbow-robotics/plugin-ui/react.js",
122
- "@rainbow-robotics/plugin-ui/sdk": "/plugin-runtime/@rainbow-robotics/plugin-ui/sdk.js",
123
- "@rainbow-robotics/plugin-ui/elements": "/plugin-runtime/@rainbow-robotics/plugin-ui/elements.js",
124
- "react": "/plugin-runtime/vendor/react.js",
125
- "react/jsx-runtime": "/plugin-runtime/vendor/react-jsx-runtime.js",
126
- "react/jsx-dev-runtime": "/plugin-runtime/vendor/react-jsx-dev-runtime.js",
127
- "react-dom/client": "/plugin-runtime/vendor/react-dom-client.js"
128
- }
129
- }
130
- </script>
131
- </head>
132
- <body>
133
- <script type="module" src="./content.js"></script>
134
- </body>
135
- </html>
136
- `;
137
-
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
226
  export async function buildPlugin({ root, minify = false } = {}) {
149
227
  const pluginRoot = root ?? findPluginRoot();
150
- const entry = resolve(pluginRoot, "src/content.tsx");
151
- if (!existsSync(entry)) {
152
- throw new Error(`missing entry: ${entry}`);
153
- }
228
+ assertManifest(pluginRoot);
154
229
  const distDir = resolve(pluginRoot, "dist");
155
230
  rmSync(distDir, { recursive: true, force: true });
156
231
  mkdirSync(distDir, { recursive: true });
157
232
 
158
- log.step("bundling src/content.tsx → dist/content.js");
159
- await buildWithFallback({
160
- entryPoints: [entry],
161
- outfile: resolve(distDir, "content.js"),
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
- });
233
+ const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
234
+ const uiDir = resolve(pluginRoot, "ui");
235
+ const declaredUiEntries = [...new Set(
236
+ (manifest.contributes ?? []).map((c) => c.ui).filter(Boolean)
237
+ )];
238
+ // Validate each declared UI entry has a matching source file
239
+ for (const entry of declaredUiEntries) {
240
+ const exists = [".tsx", ".ts", ".jsx", ".js"].some(
241
+ (ext) => existsSync(resolve(uiDir, entry + ext))
242
+ );
243
+ if (!exists)
244
+ throw new Error(
245
+ `contributes declares ui "${entry}" but ui/${entry}.tsx (or .ts/.jsx/.js) does not exist.`
246
+ );
247
+ }
248
+ const hasUiFiles = existsSync(uiDir) && readdirSync(uiDir).some((f) => /\.[tj]sx?$/.test(f));
249
+ if (declaredUiEntries.length > 0 || hasUiFiles) {
250
+ runViteBuild(pluginRoot, { mode: minify ? "production" : "development" });
251
+ }
171
252
 
172
253
  // Stage distribution files.
173
254
  const manifestSrc = resolve(pluginRoot, "manifest.json");
174
255
  if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(distDir, "manifest.json"));
175
256
 
176
- // Generate sandbox.html — plugin authors don't manage this file.
177
- writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
178
- log.step("generated dist/sandbox.html");
257
+ // No sandbox.html: the host dynamically imports dist/{ui}.js directly as a
258
+ // plain ESM module (see @rainbow-robotics/plugin-ui/host) — no iframe.
259
+
179
260
  const assetsSrc = resolve(pluginRoot, "assets");
180
261
  if (existsSync(assetsSrc)) {
181
262
  cpSync(assetsSrc, resolve(distDir, "assets"), { recursive: true });
182
263
  }
183
264
 
265
+ // README.md, README.[lang].md, CHANGELOG.md → dist/
266
+ for (const f of readdirSync(pluginRoot)) {
267
+ if (/^README(\.[a-z]{2}(-[A-Z]{2})?)?\.md$/i.test(f) || /^CHANGELOG\.md$/i.test(f)) {
268
+ copyFileSync(resolve(pluginRoot, f), resolve(distDir, f));
269
+ }
270
+ }
271
+
272
+ // Backend runtime files — copy entire py/ into dist/, then requirements.txt from root.
273
+ const pySrc = resolve(pluginRoot, "py");
274
+ if (existsSync(pySrc)) {
275
+ cpSync(pySrc, distDir, { recursive: true });
276
+ }
277
+ const rootReqs = resolve(pluginRoot, "requirements.txt");
278
+ if (existsSync(rootReqs)) {
279
+ copyFileSync(rootReqs, resolve(distDir, "requirements.txt"));
280
+ }
281
+
184
282
  log.ok(`built → ${distDir}`);
185
283
  return { pluginRoot, distDir };
186
284
  }
187
285
 
286
+ function dirSize(dir, skip = []) {
287
+ if (!existsSync(dir)) return 0;
288
+ let total = 0;
289
+ for (const entry of readdirSync(dir)) {
290
+ if (skip.includes(entry)) continue;
291
+ const full = resolve(dir, entry);
292
+ const st = statSync(full);
293
+ total += st.isDirectory() ? dirSize(full) : st.size;
294
+ }
295
+ return total;
296
+ }
297
+
298
+ // Read the uncompressed byte total of a .whl (zip) file by parsing its central
299
+ // directory — no external library needed, no actual extraction.
300
+
301
+ // installedSize = "이 플러그인 하나를 지우면 되찾는 용량"
302
+ // = dist/(wheels/ 제외)
303
+ // ros2_ws/install* 은 이제 dist/ 안에 있으므로 별도 합산 불필요.
304
+ // 휠은 로봇 공유 스토어(store/py/)에 언팩되므로 포함하지 않는다.
305
+ function calcInstalledSize(pluginRoot) {
306
+ const distDir = resolve(pluginRoot, "dist");
307
+
308
+ const installedSize = dirSize(distDir, ["wheels"]);
309
+
310
+ // Write to root manifest.json
311
+ const rootManifestPath = resolve(pluginRoot, "manifest.json");
312
+ if (existsSync(rootManifestPath)) {
313
+ const m = JSON.parse(readFileSync(rootManifestPath, "utf8"));
314
+ m.installedSize = installedSize;
315
+ writeFileSync(rootManifestPath, JSON.stringify(m, null, 2) + "\n");
316
+ }
317
+
318
+ // Re-stage dist/manifest.json so the ZIP carries the updated value
319
+ const distManifestPath = resolve(distDir, "manifest.json");
320
+ if (existsSync(distManifestPath)) {
321
+ const m = JSON.parse(readFileSync(distManifestPath, "utf8"));
322
+ m.installedSize = installedSize;
323
+ writeFileSync(distManifestPath, JSON.stringify(m, null, 2) + "\n");
324
+ }
325
+
326
+ log.ok(`installedSize → ${humanSize(installedSize)}`);
327
+ }
328
+
329
+ // Zip dist/ into a distributable ZIP with a size report.
330
+ // ros2_ws/install* is now inside dist/, so only "dist" is needed.
331
+ function packZip(pluginRoot) {
332
+ const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
333
+ const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
334
+ const zipPath = resolve(pluginRoot, zipName);
335
+ rmSync(zipPath, { force: true });
336
+
337
+ const entries = ["dist"];
338
+
339
+ log.step(`packing ${entries.join(", ")} → ${zipName}`);
340
+ try {
341
+ execSync(`zip -r -q "${zipPath}" ${entries.map((e) => `"${e}"`).join(" ")}`, {
342
+ cwd: pluginRoot,
343
+ stdio: "inherit",
344
+ });
345
+ } catch {
346
+ throw new Error("`zip` not found. Install zip or pack manually.");
347
+ }
348
+
349
+ const zipBytes = statSync(zipPath).size;
350
+ log.ok(`${zipName} ${humanSize(zipBytes)}`);
351
+ for (const e of entries) {
352
+ const full = resolve(pluginRoot, e);
353
+ const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
354
+ log.info(` ├─ ${e} ${humanSize(size)}`);
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Ask which target arch(es) to build for.
360
+ * Non-TTY (CI): auto-selects current machine arch.
361
+ * @returns {Promise<string[]>} e.g. ['amd64'] | ['arm64'] | ['amd64','arm64']
362
+ */
363
+ async function promptArch(args = []) {
364
+ const archFlag = parseArgs(args).arch ?? null;
365
+ if (archFlag) {
366
+ if (archFlag === "both") return ["amd64", "arm64"];
367
+ if (archFlag === "amd64" || archFlag === "x86_64") return ["amd64"];
368
+ if (archFlag === "arm64" || archFlag === "aarch64") return ["arm64"];
369
+ throw new Error(`unknown --arch value '${archFlag}' (valid: amd64, arm64, both)`);
370
+ }
371
+ if (!process.stdin.isTTY) {
372
+ const { arch } = await import("node:os");
373
+ const machine = arch() === "arm64" ? "arm64" : "amd64";
374
+ log.info(`arch auto-selected (non-TTY): ${machine}`);
375
+ return [machine];
376
+ }
377
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
378
+ process.stdout.write(
379
+ "\n Target arch:\n" +
380
+ " 1) amd64\n" +
381
+ " 2) arm64\n" +
382
+ " 3) Both (fat ZIP)\n",
383
+ );
384
+ const answer = (await rl.question(" Select [1/2/3, default: 3]: ")).trim() || "3";
385
+ rl.close();
386
+ if (answer === "1") return ["amd64"];
387
+ if (answer === "2") return ["arm64"];
388
+ return ["amd64", "arm64"];
389
+ }
390
+
391
+ async function promptVersion(root) {
392
+ if (!process.stdin.isTTY) return;
393
+ const manifestPath = resolve(root, "manifest.json");
394
+ if (!existsSync(manifestPath)) return;
395
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
396
+ const current = manifest.version ?? "1.0.0";
397
+
398
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
399
+ const input = (await rl.question(` Version (current: ${current}, leave blank to keep): `)).trim();
400
+ rl.close();
401
+
402
+ if (!input) return;
403
+ manifest.version = input;
404
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
405
+ log.ok(`manifest.json version → ${input}`);
406
+ }
407
+
188
408
  export async function runBuild(args) {
189
- const minify = args.includes("--minify");
409
+ const noZip = args.includes("--no-zip");
410
+ const noLock = args.includes("--no-lock");
190
411
  const root = findPluginRoot();
191
- if (!args.includes("--no-pin")) {
192
- const stableOnly = isMainBranch();
193
- log.step(`pinning requirements.txt to latest ${stableOnly ? "stable " : ""}PyPI versions`);
194
- await pinRequirements(root, { stableOnly });
412
+ const rt = manifestRuntime(root);
413
+
414
+ // All runtimes except "none" need Docker
415
+ if (rt !== "none") ensureDocker();
416
+
417
+ await promptVersion(root);
418
+ const archs = await promptArch(args);
419
+
420
+ const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
421
+
422
+ const skippedArches = { wheels: [], ros2: [], cpp: [] };
423
+
424
+ // python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
425
+ if (!noLock) {
426
+ const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
427
+ const needsLock =
428
+ manifest.type === "python" ||
429
+ (manifest.type === "ros2" && manifest.rosClient === "rclpy");
430
+ if (needsLock) {
431
+ const { lockAndVendor } = await import("./python.mjs");
432
+ try {
433
+ skippedArches.wheels = await lockAndVendor(pluginRoot, archs);
434
+ } catch (e) {
435
+ log.warn(`python lock/vendor skipped: ${e.message}`);
436
+ skippedArches.wheels = archs;
437
+ }
438
+ }
439
+ }
440
+
441
+ skippedArches.ros2 = await exportRos2Build(root, archs) ?? [];
442
+ skippedArches.cpp = await exportCppBuild(root, distDir, archs) ?? [];
443
+ calcInstalledSize(pluginRoot);
444
+ if (!noZip) packZip(pluginRoot);
445
+
446
+ // Build summary
447
+ const allSkipped = [...new Set([...skippedArches.wheels, ...skippedArches.ros2, ...skippedArches.cpp])];
448
+ if (allSkipped.length > 0) {
449
+ const line = "─".repeat(58);
450
+ console.log(`\n${line}`);
451
+ log.warn(`Cross-arch builds skipped (QEMU not available): ${allSkipped.join(", ")}`);
452
+ if (skippedArches.wheels.length) log.warn(` wheels: ${skippedArches.wheels.join(", ")}`);
453
+ if (skippedArches.ros2.length) log.warn(` ros2: ${skippedArches.ros2.join(", ")}`);
454
+ if (skippedArches.cpp.length) log.warn(` cpp: ${skippedArches.cpp.join(", ")}`);
455
+ log.warn(` Enable: docker run --privileged --rm tonistiigi/binfmt --install all`);
456
+ console.log(line);
195
457
  }
196
- await buildPlugin({ root, minify });
197
- await exportRos2Build(root);
198
458
  }
@@ -1,28 +1,13 @@
1
- import { execFileSync } from "node:child_process";
2
- import { 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
- for (const file of mjsFiles(dir)) {
26
- execFileSync(process.execPath, ["--check", file], { stdio: "inherit" });
27
- }
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
+ );
28
13
  }