@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.5 → 0.1.2-dev.50

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 (269) hide show
  1. package/README.md +82 -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 -10
  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 -2
  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/bridge.mjs +90 -0
  13. package/cli/src/build.mjs +377 -85
  14. package/cli/src/check-package.mjs +8 -23
  15. package/cli/src/cpp.mjs +61 -0
  16. package/cli/src/dev.mjs +40 -4
  17. package/cli/src/devBack.mjs +274 -49
  18. package/cli/src/devUi.mjs +198 -19
  19. package/cli/src/esbuild.mjs +27 -10
  20. package/cli/src/manifest.mjs +208 -0
  21. package/cli/src/mcp.mjs +592 -0
  22. package/cli/src/new.mjs +143 -43
  23. package/cli/src/pluginDevServer.mjs +112 -0
  24. package/cli/src/ports.mjs +160 -0
  25. package/cli/src/python.mjs +109 -0
  26. package/cli/src/ros2.mjs +43 -18
  27. package/cli/src/sdk.mjs +11 -9
  28. package/cli/src/socketBridge.mjs +129 -0
  29. package/cli/src/storybook.mjs +168 -0
  30. package/cli/src/templates-embedded.mjs +3 -0
  31. package/cli/src/type.mjs +52 -29
  32. package/cli/src/uninstall.mjs +98 -0
  33. package/cli/src/update.mjs +259 -30
  34. package/cli/src/util.mjs +114 -7
  35. package/cli/storybook-dist/_design-tokens.css +4957 -0
  36. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  37. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  38. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  39. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  40. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  41. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  42. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  43. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  44. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  45. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  46. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  47. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  48. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +44 -0
  49. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  50. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  51. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  52. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  53. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  54. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  55. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  56. package/cli/storybook-dist/_stories-src/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
  57. package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -0
  58. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  59. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  60. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  61. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  62. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +119 -0
  63. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  64. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  65. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  66. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  67. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  68. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  69. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  70. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  71. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  72. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  73. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  74. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  75. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  76. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  77. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  84. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  85. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  86. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  87. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  88. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  89. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  90. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  91. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  92. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  93. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  94. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  95. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  96. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  97. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  98. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  99. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  100. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  101. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  102. package/cli/storybook-dist/assets/Color-YHDXOIA2-BjuzF3Je.js +1 -0
  103. package/cli/storybook-dist/assets/Docs-BAFZYTMR.js +12 -0
  104. package/cli/storybook-dist/assets/Docs-BQesYW-Q.js +8 -0
  105. package/cli/storybook-dist/assets/Docs-BsMEe093.js +7 -0
  106. package/cli/storybook-dist/assets/Docs-By6k6vcM.js +20 -0
  107. package/cli/storybook-dist/assets/Docs-D1J63cJc.js +7 -0
  108. package/cli/storybook-dist/assets/Docs-GoNbntAU.js +8 -0
  109. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-BwY_WiM7.js +2 -0
  110. package/cli/storybook-dist/assets/NxAvatar.stories-CvzBHfYa.js +13 -0
  111. package/cli/storybook-dist/assets/NxButton.stories-B83PesgN.js +20 -0
  112. package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
  113. package/cli/storybook-dist/assets/NxNumberInput.stories-DpNcCG6B.js +26 -0
  114. package/cli/storybook-dist/assets/NxPagination.stories-Xnvd3xhH.js +11 -0
  115. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-C3YSIpM0.js +14 -0
  116. package/cli/storybook-dist/assets/NxRangeInput.stories-DIhaNZig.js +17 -0
  117. package/cli/storybook-dist/assets/NxSelectBox.stories-BGwO2mYJ.js +17 -0
  118. package/cli/storybook-dist/assets/NxSlideRange.stories-BXlt5Bgt.js +14 -0
  119. package/cli/storybook-dist/assets/NxSwitch.stories-C5ZqIRtw.js +11 -0
  120. package/cli/storybook-dist/assets/NxTextArea.stories-hm_ycALo.js +11 -0
  121. package/cli/storybook-dist/assets/NxTextInput.stories-DkZAySr_.js +11 -0
  122. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  123. package/cli/storybook-dist/assets/RBAccordion.stories--bY36GZY.js +17 -0
  124. package/cli/storybook-dist/assets/RBAccordionGroup.stories-BVr1ccI4.js +33 -0
  125. package/cli/storybook-dist/assets/RBAlert.stories-CkjFBEkv.js +17 -0
  126. package/cli/storybook-dist/assets/RBBadge.stories-CNXILDPh.js +1 -0
  127. package/cli/storybook-dist/assets/RBBattery.stories-AokMv8ZY.js +10 -0
  128. package/cli/storybook-dist/assets/RBCard.stories-CX1sLKB2.js +45 -0
  129. package/cli/storybook-dist/assets/RBCheckBox.stories-By3QHBFm.js +1 -0
  130. package/cli/storybook-dist/assets/RBCheckIconButton.stories-Dud1uK3X.js +1 -0
  131. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C8iBkZxY.js +24 -0
  132. package/cli/storybook-dist/assets/RBChipTab.stories-DsgbiS2U.js +21 -0
  133. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-C3DZGmq0.js +1 -0
  134. package/cli/storybook-dist/assets/RBColorIcons.stories-CusR20qk.js +3 -0
  135. package/cli/storybook-dist/assets/RBCommand.stories-DhJNgaLZ.js +73 -0
  136. package/cli/storybook-dist/assets/RBConfirm.stories-B6LKpCDa.js +20 -0
  137. package/cli/storybook-dist/assets/RBDateInput.stories-PEEhQ1wJ.js +15 -0
  138. package/cli/storybook-dist/assets/RBDialog.stories-CAtOdRen.js +38 -0
  139. package/cli/storybook-dist/assets/RBDivider.stories-0zpIUlXT.js +1 -0
  140. package/cli/storybook-dist/assets/RBDrawer.stories-CWMG4glY.js +14 -0
  141. package/cli/storybook-dist/assets/RBDropdownMenu.stories-6fbEYZfk.js +86 -0
  142. package/cli/storybook-dist/assets/RBEmptyView.stories-BRDZHjn4.js +12 -0
  143. package/cli/storybook-dist/assets/RBIconButton.stories-sYrRLYob.js +1 -0
  144. package/cli/storybook-dist/assets/RBIcons.stories-DFe8O1_g.js +3 -0
  145. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-DX4VdUfk.js +1 -0
  146. package/cli/storybook-dist/assets/RBJoyStick.stories-SA2Foelf.js +11 -0
  147. package/cli/storybook-dist/assets/RBLabelButton.stories-CicBns_Y.js +1 -0
  148. package/cli/storybook-dist/assets/RBLight.stories-WlP3oEmY.js +1 -0
  149. package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-CrWjPfxj.js +3 -0
  150. package/cli/storybook-dist/assets/RBMenubar.stories-Di4phWw7.js +3 -0
  151. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DS3wrR3l.js +14 -0
  152. package/cli/storybook-dist/assets/RBPagination.stories-BDVPAUVD.js +17 -0
  153. package/cli/storybook-dist/assets/RBPopover.stories-DL9b_iX9.js +11 -0
  154. package/cli/storybook-dist/assets/RBProgramIcons.stories-ByRVx-on.js +3 -0
  155. package/cli/storybook-dist/assets/RBPrompt.stories-vTmKWyA5.js +20 -0
  156. package/cli/storybook-dist/assets/RBRadioBox.stories-C9WHt4jb.js +1 -0
  157. package/cli/storybook-dist/assets/RBRadioButton.stories-D8KYq63j.js +11 -0
  158. package/cli/storybook-dist/assets/RBRangeDateInput.stories-Bvcf4Su7.js +15 -0
  159. package/cli/storybook-dist/assets/RBResizable.stories-3O2OMuH_.js +20 -0
  160. package/cli/storybook-dist/assets/RBSelectBox.stories-CKZLmPzX.js +6 -0
  161. package/cli/storybook-dist/assets/RBSheet.stories-Dko0It1W.js +39 -0
  162. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CUvqVtId.js +5 -0
  163. package/cli/storybook-dist/assets/RBSlider.stories-CcbaKRYo.js +5 -0
  164. package/cli/storybook-dist/assets/RBSpinner.stories-D15e_JlE.js +14 -0
  165. package/cli/storybook-dist/assets/RBSwitch.stories-C4JRsYJ6.js +65 -0
  166. package/cli/storybook-dist/assets/RBTab.stories-BPvzQSM-.js +1 -0
  167. package/cli/storybook-dist/assets/RBTable.stories-nr8OS1mV.js +62 -0
  168. package/cli/storybook-dist/assets/RBTableRow.stories-rI0rK8Ci.js +11 -0
  169. package/cli/storybook-dist/assets/RBTabs.stories-CxMtHGWA.js +21 -0
  170. package/cli/storybook-dist/assets/RBTextArea.stories-Bru4AhJ3.js +5 -0
  171. package/cli/storybook-dist/assets/RBTextField.stories-Ah3D9lto.js +45 -0
  172. package/cli/storybook-dist/assets/RBTextInput.stories-C3qAVngJ.js +5 -0
  173. package/cli/storybook-dist/assets/RBToast.stories-C1occsvf.js +11 -0
  174. package/cli/storybook-dist/assets/RBToggle.stories-BNrNERc7.js +12 -0
  175. package/cli/storybook-dist/assets/RBToggleGroup.stories-D_RRBwxY.js +17 -0
  176. package/cli/storybook-dist/assets/RBTooltip.stories-DuyOQqrB.js +9 -0
  177. package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
  178. package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
  179. package/cli/storybook-dist/assets/browserUtils-DeopUpDy.js +1 -0
  180. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  181. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  182. package/cli/storybook-dist/assets/createConveyorBeltLayer-Cn41pFRH.js +6210 -0
  183. package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
  184. package/cli/storybook-dist/assets/entry-preview-D-mLV0cS.js +2 -0
  185. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  186. package/cli/storybook-dist/assets/iframe-DNru2eN7.js +211 -0
  187. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  188. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  189. package/cli/storybook-dist/assets/index-C1OzuK6-.js +240 -0
  190. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  191. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  192. package/cli/storybook-dist/assets/index-Cwr2bQOB.js +575 -0
  193. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  194. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  195. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  196. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  197. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  198. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +1 -0
  199. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  200. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  201. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  202. package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -0
  203. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  204. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  205. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  206. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  207. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  208. package/cli/storybook-dist/assets/preview-DUTcCPBe.css +1 -0
  209. package/cli/storybook-dist/assets/preview-QAsoD6Sa.js +2 -0
  210. package/cli/storybook-dist/assets/preview-rcRMbKPr.js +3 -0
  211. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  212. package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -0
  213. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  214. package/cli/storybook-dist/favicon.svg +1 -0
  215. package/cli/storybook-dist/iframe.html +666 -0
  216. package/cli/storybook-dist/index.html +190 -0
  217. package/cli/storybook-dist/index.json +1 -0
  218. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  219. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  220. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  221. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  222. package/cli/storybook-dist/project.json +1 -0
  223. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  224. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  225. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  226. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  227. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  228. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  229. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  230. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  231. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  233. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  234. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  235. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  237. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  239. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  240. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  241. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  242. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  243. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  244. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  245. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  246. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  247. package/cli/templates/common/.mcp.json +8 -0
  248. package/cli/templates/common/README.md +817 -9
  249. package/cli/templates/common/assets/icon.svg +44 -2
  250. package/cli/templates/common/manifest.json +13 -13
  251. package/cli/templates/common/package.json +9 -11
  252. package/cli/templates/common/py/main.py +98 -0
  253. package/cli/templates/common/requirements.txt +4 -3
  254. package/cli/templates/common/tsconfig.json +1 -1
  255. package/cli/templates/common/ui/content.tsx +216 -0
  256. package/cli/templates/common/ui/style.css +6 -0
  257. package/cli/templates/common/vite.config.ts +6 -25
  258. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  259. package/cli/templates/cpp/cpp/main.cpp +26 -0
  260. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +5 -0
  261. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  262. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  263. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  264. package/cli/vite.d.mts +25 -0
  265. package/cli/vite.mjs +281 -0
  266. package/package.json +32 -5
  267. package/cli/src/pack.mjs +0 -61
  268. package/cli/templates/common/main.py +0 -21
  269. package/cli/templates/common/src/content.tsx +0 -32
@@ -0,0 +1,90 @@
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() {
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 up = spawnSync(
77
+ "docker",
78
+ ["run", "-d", "--name", BRIDGE_CONTAINER, "-p", `127.0.0.1:${BRIDGE_PORT}:${BRIDGE_PORT}`, BRIDGE_IMAGE],
79
+ { stdio: "inherit" },
80
+ );
81
+ if (up.status !== 0) throw new Error("failed to start bridge container.");
82
+
83
+ await waitForPort(BRIDGE_PORT);
84
+ log.step(`Socket.IO bridge listening on ws://127.0.0.1:${BRIDGE_PORT}`);
85
+ return true;
86
+ }
87
+
88
+ export function stopBridge() {
89
+ spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
90
+ }
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,50 +9,194 @@
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 { buildWithFallback } from "./esbuild.mjs";
14
- 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";
15
19
 
16
- function manifestRuntime(root) {
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) {
17
33
  try {
18
- return JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.type ?? "python";
34
+ const req = createRequire(resolve(pluginRoot, "package.json"));
35
+ const vitePkg = req.resolve("vite/package.json");
36
+ return resolve(dirname(vitePkg), "bin", "vite.js");
19
37
  } catch {
20
- return "python";
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
+ );
21
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
+ }
67
+
68
+ function isMainBranch() {
69
+ const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
70
+ const branch = r.stdout?.trim();
71
+ return branch === "main" || branch === "master";
22
72
  }
23
73
 
24
- // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12
25
- // container so the shipped ZIP carries ros2_ws/install. Real (non-symlink) build
26
- // → relocatable on the robot. Skipped (with a warning) if Docker is unavailable.
27
- export async function exportRos2Build(root) {
28
- if (manifestRuntime(root) !== "ros2") return;
29
- const { assertDockerReady, ensureImage, colconBuild } = await import("./ros2.mjs");
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");
30
80
  try {
31
81
  assertDockerReady();
32
82
  } catch (e) {
33
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");
111
+ }
112
+ return skipped;
113
+ }
114
+
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");
122
+ try {
123
+ assertDockerReady();
124
+ } catch (e) {
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");
34
132
  return;
35
133
  }
36
- ensureImage();
37
- colconBuild(root, { symlink: false });
38
- 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;
39
160
  }
40
161
 
41
- // Resolve the latest released version of a PyPI package.
42
- async function latestPypiVersion(pkg) {
162
+ const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
163
+
164
+ function isPrerelease(version) {
165
+ return PRERELEASE_RE.test(version);
166
+ }
167
+
168
+ // Resolve the latest version of a PyPI package.
169
+ // stableOnly=true → skips pre-release (dev/a/b/rc) versions.
170
+ async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
43
171
  const res = await fetch(`https://pypi.org/pypi/${pkg}/json`, {
44
172
  signal: AbortSignal.timeout(5000),
45
173
  });
46
174
  if (!res.ok) throw new Error(`pypi ${pkg}: ${res.status}`);
47
175
  const data = await res.json();
176
+
177
+ if (!stableOnly) return data.info.version;
178
+
179
+ // data.info.version is latest stable when stable releases exist.
180
+ // If it's still a pre-release, scan all releases for the newest stable.
181
+ if (!isPrerelease(data.info.version)) return data.info.version;
182
+
183
+ const stable = Object.keys(data.releases)
184
+ .filter((v) => !isPrerelease(v))
185
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
186
+ .at(-1);
187
+
188
+ if (stable) return stable;
189
+
190
+ // No stable release exists — fall back with a warning.
191
+ log.warn(` ${pkg}: no stable release found, using ${data.info.version}`);
48
192
  return data.info.version;
49
193
  }
50
194
 
51
195
  // Pin unpinned (no version specifier) entries in requirements.txt to the
52
- // latest PyPI release. Runs on explicit `build`/`pack` only — keeps the shipped
196
+ // latest PyPI release. Runs on explicit `build` only — keeps the shipped
53
197
  // plugin reproducible without churning the source file on every dev rebuild.
54
- export async function pinRequirements(root) {
198
+ // stableOnly=true is used on main-branch builds to avoid shipping dev versions.
199
+ export async function pinRequirements(root, { stableOnly = false } = {}) {
55
200
  const reqPath = resolve(root, "requirements.txt");
56
201
  if (!existsSync(reqPath)) return;
57
202
 
@@ -66,7 +211,7 @@ export async function pinRequirements(root) {
66
211
  continue;
67
212
  }
68
213
  try {
69
- const v = await latestPypiVersion(name);
214
+ const v = await latestPypiVersion(name, { stableOnly });
70
215
  out.push(`${name}==${v}`);
71
216
  pinned++;
72
217
  log.info(` pinned ${name}==${v}`);
@@ -78,89 +223,236 @@ export async function pinRequirements(root) {
78
223
  if (pinned > 0) writeFileSync(reqPath, out.join("\n"));
79
224
  }
80
225
 
81
- const SANDBOX_HTML = `<!doctype html>
82
- <html lang="en">
83
- <head>
84
- <meta charset="UTF-8" />
85
- <title>plugin sandbox</title>
86
- <script type="importmap">
87
- {
88
- "imports": {
89
- "@rainbow-robotics/plugin-ui/connect": "/plugin-runtime/@rainbow-robotics/plugin-ui/connect.js",
90
- "@rainbow-robotics/plugin-ui/react": "/plugin-runtime/@rainbow-robotics/plugin-ui/react.js",
91
- "@rainbow-robotics/plugin-ui/sdk": "/plugin-runtime/@rainbow-robotics/plugin-ui/sdk.js",
92
- "@rainbow-robotics/plugin-ui/elements": "/plugin-runtime/@rainbow-robotics/plugin-ui/elements.js",
93
- "react": "/plugin-runtime/vendor/react.js",
94
- "react/jsx-runtime": "/plugin-runtime/vendor/react-jsx-runtime.js",
95
- "react/jsx-dev-runtime": "/plugin-runtime/vendor/react-jsx-dev-runtime.js",
96
- "react-dom/client": "/plugin-runtime/vendor/react-dom-client.js"
97
- }
98
- }
99
- </script>
100
- </head>
101
- <body>
102
- <script type="module" src="./content.js"></script>
103
- </body>
104
- </html>
105
- `;
106
-
107
- const HOST_PROVIDED_EXTERNALS = [
108
- "@rainbow-robotics/plugin-ui",
109
- "@rainbow-robotics/plugin-ui/*",
110
- "react",
111
- "react/jsx-runtime",
112
- "react/jsx-dev-runtime",
113
- "react-dom",
114
- "react-dom/*",
115
- ];
116
-
117
226
  export async function buildPlugin({ root, minify = false } = {}) {
118
227
  const pluginRoot = root ?? findPluginRoot();
119
- const entry = resolve(pluginRoot, "src/content.tsx");
120
- if (!existsSync(entry)) {
121
- throw new Error(`missing entry: ${entry}`);
122
- }
228
+ assertManifest(pluginRoot);
123
229
  const distDir = resolve(pluginRoot, "dist");
124
230
  rmSync(distDir, { recursive: true, force: true });
125
231
  mkdirSync(distDir, { recursive: true });
126
232
 
127
- log.step("bundling src/content.tsx → dist/content.js");
128
- await buildWithFallback({
129
- entryPoints: [entry],
130
- outfile: resolve(distDir, "content.js"),
131
- bundle: true,
132
- format: "esm",
133
- jsx: "automatic",
134
- minify,
135
- loader: { ".tsx": "tsx", ".ts": "ts", ".svg": "dataurl" },
136
- define: { "process.env.NODE_ENV": JSON.stringify(minify ? "production" : "development") },
137
- external: HOST_PROVIDED_EXTERNALS,
138
- logLevel: "warning",
139
- });
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
+ }
140
252
 
141
253
  // Stage distribution files.
142
254
  const manifestSrc = resolve(pluginRoot, "manifest.json");
143
255
  if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(distDir, "manifest.json"));
144
256
 
145
- // Generate sandbox.html — plugin authors don't manage this file.
146
- writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
147
- 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
+
148
260
  const assetsSrc = resolve(pluginRoot, "assets");
149
261
  if (existsSync(assetsSrc)) {
150
262
  cpSync(assetsSrc, resolve(distDir, "assets"), { recursive: true });
151
263
  }
152
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
+
153
282
  log.ok(`built → ${distDir}`);
154
283
  return { pluginRoot, distDir };
155
284
  }
156
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
+
157
408
  export async function runBuild(args) {
158
- const minify = args.includes("--minify");
409
+ const noZip = args.includes("--no-zip");
410
+ const noLock = args.includes("--no-lock");
159
411
  const root = findPluginRoot();
160
- if (!args.includes("--no-pin")) {
161
- log.step("pinning requirements.txt to latest PyPI versions");
162
- await pinRequirements(root);
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);
163
457
  }
164
- await buildPlugin({ root, minify });
165
- await exportRos2Build(root);
166
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
  }