@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.9 → 0.10.1-dev.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.
- package/README.ko.md +1186 -0
- package/README.md +1137 -57
- package/cli/README.md +239 -7
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +123 -27
- package/cli/bundle-templates.mjs +31 -0
- package/cli/docker/Dockerfile.cpp +28 -0
- package/cli/docker/Dockerfile.dev +17 -0
- package/cli/docker/Dockerfile.python +21 -0
- package/cli/prepack.mjs +10 -0
- package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
- package/cli/src/ai-rules.mjs +107 -0
- package/cli/src/build.mjs +333 -93
- package/cli/src/check-package.mjs +8 -23
- package/cli/src/completion.mjs +168 -0
- package/cli/src/cpp.mjs +78 -0
- package/cli/src/cppIde.mjs +76 -0
- package/cli/src/dev.mjs +195 -6
- package/cli/src/devBack.mjs +671 -60
- package/cli/src/devId.mjs +54 -0
- package/cli/src/devSession.mjs +67 -0
- package/cli/src/devUi.mjs +214 -25
- package/cli/src/esbuild.mjs +27 -10
- package/cli/src/host_agent.py +223 -0
- package/cli/src/manifest.mjs +288 -0
- package/cli/src/mcp.mjs +1153 -0
- package/cli/src/mcpProxy.mjs +110 -0
- package/cli/src/muscatLocal.mjs +292 -0
- package/cli/src/new.mjs +205 -68
- package/cli/src/pluginDevServer.mjs +111 -0
- package/cli/src/ports.mjs +193 -0
- package/cli/src/projectCheck.mjs +102 -0
- package/cli/src/python.mjs +112 -0
- package/cli/src/ros2.mjs +62 -18
- package/cli/src/sdk.mjs +74 -24
- package/cli/src/sim.mjs +177 -0
- package/cli/src/storybook.mjs +150 -0
- package/cli/src/templates-embedded.mjs +4 -0
- package/cli/src/type.mjs +93 -34
- package/cli/src/uninstall.mjs +81 -0
- package/cli/src/update.mjs +364 -35
- package/cli/src/util.mjs +341 -11
- package/cli/src/zenohDev.mjs +83 -0
- package/cli/src/zip.mjs +121 -0
- package/cli/storybook-dist/_design-tokens.css +4957 -0
- package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
- package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
- package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
- package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
- package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
- package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
- package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
- package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
- package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
- package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
- package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
- package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
- package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
- package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
- package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
- package/cli/storybook-dist/assets/Color-YHDXOIA2-pCKrjUal.js +1 -0
- package/cli/storybook-dist/assets/Docs-B-Y7I1GA.js +7 -0
- package/cli/storybook-dist/assets/Docs-BFJcE_VS.js +8 -0
- package/cli/storybook-dist/assets/Docs-B_QaTtz8.js +8 -0
- package/cli/storybook-dist/assets/Docs-CryDGmfh.js +20 -0
- package/cli/storybook-dist/assets/Docs-Cw2AVNKx.js +7 -0
- package/cli/storybook-dist/assets/Docs-Q6r1fBeL.js +12 -0
- package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-_OmZBCn8.js +2 -0
- package/cli/storybook-dist/assets/NxAvatar.stories-Ctf5G2FQ.js +13 -0
- package/cli/storybook-dist/assets/NxButton.stories-DeLObVSj.js +20 -0
- package/cli/storybook-dist/assets/NxIcons.stories-DeqrVzVs.js +3 -0
- package/cli/storybook-dist/assets/NxNumberInput.stories-LqoY0ed1.js +26 -0
- package/cli/storybook-dist/assets/NxPagination.stories-DI6bXuJM.js +11 -0
- package/cli/storybook-dist/assets/NxPointerHoldButton.stories-3HSV1XmN.js +14 -0
- package/cli/storybook-dist/assets/NxRangeInput.stories-1pjgQVaG.js +17 -0
- package/cli/storybook-dist/assets/NxSelectBox.stories-bcQPZEuF.js +17 -0
- package/cli/storybook-dist/assets/NxSlideRange.stories-BosX4axk.js +14 -0
- package/cli/storybook-dist/assets/NxSwitch.stories-DxV6dS33.js +11 -0
- package/cli/storybook-dist/assets/NxTextArea.stories-CO545-LP.js +11 -0
- package/cli/storybook-dist/assets/NxTextInput.stories-BP0npai1.js +11 -0
- package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
- package/cli/storybook-dist/assets/RBAccordion.stories-fVR4Va2a.js +17 -0
- package/cli/storybook-dist/assets/RBAccordionGroup.stories-Du5bsckD.js +33 -0
- package/cli/storybook-dist/assets/RBAlert.stories-DNkMXUu6.js +17 -0
- package/cli/storybook-dist/assets/RBBadge.stories-C44pXt8D.js +1 -0
- package/cli/storybook-dist/assets/RBBattery.stories-DE2JHvfL.js +10 -0
- package/cli/storybook-dist/assets/RBCard.stories-CuIda7NI.js +45 -0
- package/cli/storybook-dist/assets/RBCheckBox.stories-D1qM1ayr.js +1 -0
- package/cli/storybook-dist/assets/RBCheckIconButton.stories-e4gkliBO.js +1 -0
- package/cli/storybook-dist/assets/RBCheckLabelButton.stories-CDzCtsMI.js +24 -0
- package/cli/storybook-dist/assets/RBChipTab.stories-DBrc_3Mp.js +21 -0
- package/cli/storybook-dist/assets/RBChipsBoxTab.stories-hlrsHjKY.js +1 -0
- package/cli/storybook-dist/assets/RBColorIcons.stories-BHUvpkdu.js +3 -0
- package/cli/storybook-dist/assets/RBCommand.stories-azAF41NP.js +73 -0
- package/cli/storybook-dist/assets/RBConfirm.stories-BMNjwbmT.js +20 -0
- package/cli/storybook-dist/assets/RBDateInput.stories-D0VezDzv.js +15 -0
- package/cli/storybook-dist/assets/RBDialog.stories-AOJ975Fy.js +38 -0
- package/cli/storybook-dist/assets/RBDivider.stories-NSvnj43Z.js +1 -0
- package/cli/storybook-dist/assets/RBDrawer.stories-Du0oTvD1.js +14 -0
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-Hsxve4PW.js +86 -0
- package/cli/storybook-dist/assets/RBEmptyView.stories-BlwHx3zp.js +12 -0
- package/cli/storybook-dist/assets/RBIconButton.stories-DAPDY63x.js +1 -0
- package/cli/storybook-dist/assets/RBIcons.stories-W5ZUV2L1.js +3 -0
- package/cli/storybook-dist/assets/RBInlineLabelButton.stories-C99j0Y3g.js +1 -0
- package/cli/storybook-dist/assets/RBJoyStick.stories-Du0H1iAS.js +11 -0
- package/cli/storybook-dist/assets/RBLabelButton.stories-BlvHaU_Z.js +1 -0
- package/cli/storybook-dist/assets/RBLight.stories-BY6DKrre.js +1 -0
- package/cli/storybook-dist/assets/RBMenubar.stories-D2PEGpM9.js +3 -0
- package/cli/storybook-dist/assets/RBMultiRangeInput.stories-Bm2qSmjq.js +14 -0
- package/cli/storybook-dist/assets/RBPagination.stories-DDleJa5N.js +17 -0
- package/cli/storybook-dist/assets/RBPopover.stories-27YxPSFG.js +11 -0
- package/cli/storybook-dist/assets/RBPrompt.stories-BFaIOctr.js +20 -0
- package/cli/storybook-dist/assets/RBRadioBox.stories-BroDar0t.js +1 -0
- package/cli/storybook-dist/assets/RBRadioButton.stories-M3O-wX7V.js +11 -0
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-CWlM_D5X.js +15 -0
- package/cli/storybook-dist/assets/RBResizable.stories-ui8X1qnc.js +20 -0
- package/cli/storybook-dist/assets/RBSelectBox.stories-C8T6cfA0.js +74 -0
- package/cli/storybook-dist/assets/RBSheet.stories-BCOo_Zcc.js +39 -0
- package/cli/storybook-dist/assets/RBSideNumberInput.stories-CU2_MVlJ.js +5 -0
- package/cli/storybook-dist/assets/RBSlider.stories-DgOp1TFc.js +5 -0
- package/cli/storybook-dist/assets/RBSpinner.stories-qLScY3zZ.js +14 -0
- package/cli/storybook-dist/assets/RBSwitch.stories-SXSY1QMb.js +65 -0
- package/cli/storybook-dist/assets/RBTab.stories-BzXXTsXP.js +1 -0
- package/cli/storybook-dist/assets/RBTable.stories-DHTeRNVk.js +62 -0
- package/cli/storybook-dist/assets/RBTableRow.stories-DRNRsd15.js +11 -0
- package/cli/storybook-dist/assets/RBTabs.stories-DP0noBLp.js +21 -0
- package/cli/storybook-dist/assets/RBTextArea.stories-TkaejfxJ.js +5 -0
- package/cli/storybook-dist/assets/RBTextField.stories-B6VpCGyt.js +45 -0
- package/cli/storybook-dist/assets/RBTextInput.stories-D3hZ08e-.js +5 -0
- package/cli/storybook-dist/assets/RBToast.stories-BQ2wS2U0.js +11 -0
- package/cli/storybook-dist/assets/RBToggle.stories-GVOjGqo3.js +12 -0
- package/cli/storybook-dist/assets/RBToggleGroup.stories-pzx0btOD.js +17 -0
- package/cli/storybook-dist/assets/RBTooltip.stories-jufB6fa1.js +9 -0
- package/cli/storybook-dist/assets/SideNumberInput-BYlmNHEw.js +1109 -0
- package/cli/storybook-dist/assets/SideNumberInput-SAkhydsn.css +21 -0
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
- package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
- package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
- package/cli/storybook-dist/assets/entry-preview-CAkU-e0D.js +2 -0
- package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
- package/cli/storybook-dist/assets/iframe-B-BmTsxV.js +211 -0
- package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
- package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
- package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
- package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
- package/cli/storybook-dist/assets/index-CkB0GfaI.js +575 -0
- package/cli/storybook-dist/assets/index-ClAc6FBW.js +240 -0
- package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
- package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
- package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
- package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
- package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
- package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
- package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
- package/cli/storybook-dist/assets/preview-BVJwlGUF.js +1 -0
- package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
- package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
- package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
- package/cli/storybook-dist/assets/preview-CznQwwbo.js +3 -0
- package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
- package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
- package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
- package/cli/storybook-dist/assets/preview-M77JRg1A.js +2 -0
- package/cli/storybook-dist/assets/preview-kTZPgP6D.css +1 -0
- package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels-HsFbxPbn.css +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-DuxpZLkF.js +120 -0
- package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
- package/cli/storybook-dist/favicon.svg +1 -0
- package/cli/storybook-dist/iframe.html +666 -0
- package/cli/storybook-dist/index.html +190 -0
- package/cli/storybook-dist/index.json +1 -0
- package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
- package/cli/storybook-dist/project.json +1 -0
- package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
- package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
- package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
- package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
- package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
- package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
- package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
- package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
- package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
- package/cli/storybook-dist/sb-manager/globals.js +48 -0
- package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
- package/cli/templates/common/README.md +1070 -83
- package/cli/templates/common/assets/icon.svg +44 -2
- package/cli/templates/common/manifest.json +14 -13
- package/cli/templates/common/package.json +11 -4
- package/cli/templates/common/py/main.py +125 -0
- package/cli/templates/common/requirements.txt +4 -3
- package/cli/templates/common/tsconfig.json +1 -1
- package/cli/templates/common/ui/content.tsx +216 -0
- package/cli/templates/common/ui/style.css +6 -0
- package/cli/templates/common/vite.config.ts +6 -25
- package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
- package/cli/templates/cpp/cpp/main.cpp +73 -0
- package/cli/templates/readme-cpp/README.md +1012 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +12 -1
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +22 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +41 -0
- package/cli/vite.d.mts +25 -0
- package/cli/vite.mjs +292 -0
- package/package.json +34 -7
- package/cli/src/pack.mjs +0 -61
- package/cli/src/preview.mjs +0 -91
- package/cli/templates/common/main.py +0 -114
- package/cli/templates/common/src/content.tsx +0 -33
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Port contract helpers for plugin back ends.
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { usesHostNetwork } from "./zenohDev.mjs";
|
|
4
|
+
|
|
5
|
+
export const PLUGIN_PORT_MIN = 28600;
|
|
6
|
+
export const PLUGIN_PORT_MAX = 28999;
|
|
7
|
+
export const MEDIA_PORTS = [8554, 8889, 8189]; // MediaMTX: RTSP publish, WebRTC, WebRTC media
|
|
8
|
+
export const INTERNAL_DEV_PORTS = new Set([10000, ...MEDIA_PORTS]);
|
|
9
|
+
|
|
10
|
+
export function mediaPortFlags() {
|
|
11
|
+
return [
|
|
12
|
+
"-p", "127.0.0.1:8554:8554", "-p", "127.0.0.1:8889:8889",
|
|
13
|
+
"-p", "127.0.0.1:8189:8189/udp", "-p", "127.0.0.1:8189:8189/tcp",
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
export const RESERVED_BACKEND_PORTS = new Map([
|
|
17
|
+
[502, "Modbus TCP"],
|
|
18
|
+
[4840, "OPC-UA"],
|
|
19
|
+
[10000, "Muscat plugin gateway"],
|
|
20
|
+
[2222, "EtherNet/IP Implicit"],
|
|
21
|
+
[30004, "RTDE"],
|
|
22
|
+
[34964, "ProfiNET"],
|
|
23
|
+
[44818, "EtherNet/IP Explicit"],
|
|
24
|
+
[50051, "gRPC"],
|
|
25
|
+
]);
|
|
26
|
+
export const RESERVED_BACKEND_PORT_RANGES = [
|
|
27
|
+
{ from: 5170, to: 5179, name: "UI 517x servers" },
|
|
28
|
+
{ from: 8000, to: 8999, name: "Muscat/internal 8000 range" },
|
|
29
|
+
{ from: 50000, to: 50005, name: "robot TCP/UDP ASCII and REST interfaces" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function reservedPortReason(port) {
|
|
33
|
+
if (RESERVED_BACKEND_PORTS.has(port)) return RESERVED_BACKEND_PORTS.get(port);
|
|
34
|
+
const range = RESERVED_BACKEND_PORT_RANGES.find((item) => port >= item.from && port <= item.to);
|
|
35
|
+
if (!range) return null;
|
|
36
|
+
return `${range.name} (${range.from}-${range.to})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// function isPluginManagedPort(port) {
|
|
40
|
+
// return reservedPortReason(port) || (port >= PLUGIN_PORT_MIN && port <= PLUGIN_PORT_MAX);
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
function isInt(value) {
|
|
44
|
+
return Number.isInteger(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validatePort(port, field, errors) {
|
|
48
|
+
if (!isInt(port)) {
|
|
49
|
+
errors.push(`${field}: must be a port number`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (port < 1 || port > 65535) {
|
|
53
|
+
errors.push(`${field}: port must be between 1 and 65535`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const reserved = reservedPortReason(port);
|
|
57
|
+
if (reserved) {
|
|
58
|
+
errors.push(`${field}: ${port} is reserved for ${reserved}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (port < PLUGIN_PORT_MIN || port > PLUGIN_PORT_MAX) {
|
|
62
|
+
errors.push(
|
|
63
|
+
`${field}: external plugin backend ports must be within ${PLUGIN_PORT_MIN}-${PLUGIN_PORT_MAX}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function normalizePorts(ports, errors = []) {
|
|
69
|
+
if (ports === undefined || ports === null) return [];
|
|
70
|
+
if (!Array.isArray(ports)) {
|
|
71
|
+
errors.push(`"ports" must be an array`);
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
if (ports.length === 0) {
|
|
75
|
+
errors.push(`"ports" must not be empty when provided`);
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const normalized = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
ports.forEach((port, i) => {
|
|
82
|
+
validatePort(port, `ports[${i}]`, errors);
|
|
83
|
+
if (!isInt(port)) return;
|
|
84
|
+
if (seen.has(port)) {
|
|
85
|
+
errors.push(`"ports" entries must be unique (duplicate: ${port})`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
seen.add(port);
|
|
89
|
+
normalized.push(port);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function validatePorts(ports, errors) {
|
|
96
|
+
normalizePorts(ports, errors);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function manifestPortRanges(manifest) {
|
|
100
|
+
return normalizePorts(manifest?.ports, []);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatPortRange(range) {
|
|
104
|
+
return String(range);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function formatPortRanges(ranges) {
|
|
108
|
+
return ranges.map(formatPortRange).join(", ");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function portInRanges(port, ranges) {
|
|
112
|
+
return ranges.includes(port);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function portEnvFlags(ranges) {
|
|
116
|
+
if (ranges.length === 0) return [];
|
|
117
|
+
return ["-e", `RB_PLUGIN_PORTS=${ranges.map(formatPortRange).join(",")}`];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `docker run -p` flags for the plugin's declared ports, TCP and UDP on every host interface like
|
|
122
|
+
* on the robot, so LAN devices (e.g. a Meta Quest streaming UDP) reach the backend. Native Linux
|
|
123
|
+
* without a local Muscat uses --network host instead. Only valid on the container that owns the netns.
|
|
124
|
+
*/
|
|
125
|
+
export function publishPortFlags(ranges = []) {
|
|
126
|
+
return ranges.flatMap((port) => ["-p", `${port}:${port}`, "-p", `${port}:${port}/udp`]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Skips OS-assigned loopback listeners (127.0.0.1:0, e.g. the SDK's Zenoh session).
|
|
130
|
+
function listeningTcpPorts(container) {
|
|
131
|
+
const script = `
|
|
132
|
+
read -r eph_lo eph_hi < /proc/sys/net/ipv4/ip_local_port_range 2>/dev/null || { eph_lo=32768; eph_hi=60999; }
|
|
133
|
+
for f in /proc/net/tcp /proc/net/tcp6; do
|
|
134
|
+
[ -r "$f" ] || continue
|
|
135
|
+
tail -n +2 "$f" | while read -r sl local_address rem_address st rest; do
|
|
136
|
+
[ "$st" = "0A" ] || continue
|
|
137
|
+
hex_ip="\${local_address%%:*}"
|
|
138
|
+
port=$((0x\${local_address##*:}))
|
|
139
|
+
case "$hex_ip" in
|
|
140
|
+
0100007F|00000000000000000000000001000000)
|
|
141
|
+
[ "$port" -ge "$eph_lo" ] && [ "$port" -le "$eph_hi" ] && continue ;;
|
|
142
|
+
esac
|
|
143
|
+
echo "$port"
|
|
144
|
+
done
|
|
145
|
+
done | sort -n -u
|
|
146
|
+
`;
|
|
147
|
+
const r = spawnSync("docker", ["exec", container, "bash", "-lc", script], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
});
|
|
150
|
+
if (r.status !== 0) return [];
|
|
151
|
+
return String(r.stdout || "")
|
|
152
|
+
.split(/\s+/)
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.map((v) => Number(v))
|
|
155
|
+
.filter((v) => Number.isInteger(v));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function startPortContractMonitor({ container, ranges, log, intervalMs = 1000, allow = [] }) {
|
|
159
|
+
// Under --network host (or a local Muscat container's netns) /proc/net/tcp shows every
|
|
160
|
+
// host/robot service port, not just the plugin's — the contract can't be enforced.
|
|
161
|
+
if (usesHostNetwork() || process.env.MUSCAT_PK_MUSCAT_CONTAINER) return () => false;
|
|
162
|
+
const declared = ranges ?? [];
|
|
163
|
+
// muscat-pk's own dev plumbing shares this netns — not the plugin's doing.
|
|
164
|
+
const ignored = new Set([...INTERNAL_DEV_PORTS, ...allow.map(Number).filter(Number.isInteger)]);
|
|
165
|
+
let reported = false;
|
|
166
|
+
const timer = setInterval(() => {
|
|
167
|
+
// Every listening port that isn't declared is a violation — a port outside
|
|
168
|
+
// 28600-28999 can't be declared at all, so it must not be opened either.
|
|
169
|
+
// Otherwise it silently escapes both this check and rby2's install-time
|
|
170
|
+
// conflict check, and only collides once it's on a robot.
|
|
171
|
+
const unexpected = listeningTcpPorts(container)
|
|
172
|
+
.filter((port) => !ignored.has(port))
|
|
173
|
+
.filter((port) => !portInRanges(port, declared));
|
|
174
|
+
if (unexpected.length === 0) return;
|
|
175
|
+
|
|
176
|
+
clearInterval(timer);
|
|
177
|
+
reported = true;
|
|
178
|
+
const declaredText = declared.length > 0 ? formatPortRanges(declared) : "(none)";
|
|
179
|
+
log.error(
|
|
180
|
+
`backend opened undeclared TCP port(s): ${unexpected.join(", ")}. ` +
|
|
181
|
+
`Every listening port must be in manifest.json "ports" within ${PLUGIN_PORT_MIN}-${PLUGIN_PORT_MAX}. ` +
|
|
182
|
+
`Declared: ${declaredText}`,
|
|
183
|
+
);
|
|
184
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}, intervalMs);
|
|
187
|
+
timer.unref?.();
|
|
188
|
+
|
|
189
|
+
return () => {
|
|
190
|
+
clearInterval(timer);
|
|
191
|
+
return reported;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// What a project must change for the plugin-ui it has installed: known old APIs (ai-rules.mjs) plus
|
|
2
|
+
// `tsc --noEmit`. Shared by `muscat-pk update` and the MCP tool check_project, so a user and an
|
|
3
|
+
// agent see the same list.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, relative, resolve } from "node:path";
|
|
7
|
+
import { LEGACY_COMPONENTS, LEGACY_SUBPATHS } from "./ai-rules.mjs";
|
|
8
|
+
|
|
9
|
+
const isBinaryMode = typeof Bun !== "undefined";
|
|
10
|
+
|
|
11
|
+
// The clause can't hold quotes or `;`, so a match can't start at an earlier import statement.
|
|
12
|
+
const PLUGIN_UI_IMPORT = /import\s+([^;'"]*?)\s+from\s+["']@rainbow-robotics\/plugin-ui\/([\w-]+)["']/g;
|
|
13
|
+
|
|
14
|
+
function listUiFiles(dir) {
|
|
15
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
16
|
+
e.isDirectory() ? listUiFiles(join(dir, e.name)) : /\.[jt]sx?$/.test(e.name) ? [join(dir, e.name)] : [],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// rb.toast takes one { message, type } object now; flag a string or multi-argument call.
|
|
21
|
+
const TOAST_CALL = /\.toast\(([^()]*)\)/g;
|
|
22
|
+
|
|
23
|
+
/** Uses of the pre-1.x plugin-ui API in the project (ui/ and vite.config), as { file, line, text }. */
|
|
24
|
+
function findLegacyUiUsage(root) {
|
|
25
|
+
const uiDir = resolve(root, "ui");
|
|
26
|
+
if (!existsSync(uiDir)) return [];
|
|
27
|
+
const found = [];
|
|
28
|
+
// The old hand-written config builds entries without their exports (connect() rendered as a side
|
|
29
|
+
// effect), so `export default App` is dropped from dist. The muscat() preset keeps it.
|
|
30
|
+
const viteConfig = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"].find((f) => existsSync(resolve(root, f)));
|
|
31
|
+
if (viteConfig && !readFileSync(resolve(root, viteConfig), "utf8").includes("@rainbow-robotics/muscat-plugin-kit/vite")) {
|
|
32
|
+
found.push({
|
|
33
|
+
file: viteConfig,
|
|
34
|
+
line: 1,
|
|
35
|
+
text: 'not using the muscat() preset — the build drops the entry\'s default export. Use: import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite"; defineConfig({ plugins: [muscat()] })',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const file of listUiFiles(uiDir)) {
|
|
39
|
+
const src = readFileSync(file, "utf8");
|
|
40
|
+
const rel = relative(root, file);
|
|
41
|
+
const lineOf = (index) => src.slice(0, index).split("\n").length;
|
|
42
|
+
for (const m of src.matchAll(PLUGIN_UI_IMPORT)) {
|
|
43
|
+
const [, clause, subpath] = m;
|
|
44
|
+
if (LEGACY_SUBPATHS[subpath]) {
|
|
45
|
+
found.push({ file: rel, line: lineOf(m.index), text: `@rainbow-robotics/plugin-ui/${subpath} ${LEGACY_SUBPATHS[subpath]}` });
|
|
46
|
+
} else if (subpath === "react") {
|
|
47
|
+
const named = clause.match(/\{([\s\S]*)\}/)?.[1] ?? "";
|
|
48
|
+
for (const spec of named.split(",")) {
|
|
49
|
+
const name = spec.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
|
|
50
|
+
if (LEGACY_COMPONENTS[name]) {
|
|
51
|
+
found.push({ file: rel, line: lineOf(m.index), text: `${name} → ${LEGACY_COMPONENTS[name]}` });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const m of src.matchAll(TOAST_CALL)) {
|
|
57
|
+
const arg = m[1].trim();
|
|
58
|
+
if (!arg.startsWith("{") && (/^["'`]/.test(arg) || arg.includes(","))) {
|
|
59
|
+
found.push({ file: rel, line: lineOf(m.index), text: "rb.toast(message, type) → rb.toast({ message, type })" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Top-level ui/ files are the entries; the host mounts their default export.
|
|
63
|
+
if (relative(uiDir, file).split(/[\\/]/).length === 1 && !/export\s+default\b/.test(src)) {
|
|
64
|
+
found.push({ file: rel, line: 1, text: "no default export — the host renders the entry's default export" });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The known-API list above only knows past changes. Type-checking against the plugin-ui that was
|
|
71
|
+
// just installed catches whatever a later release removes or reshapes, with no list to keep up.
|
|
72
|
+
function typeScriptErrors(root) {
|
|
73
|
+
const tsc = resolve(root, "node_modules/typescript/bin/tsc");
|
|
74
|
+
if (!existsSync(tsc) || !existsSync(resolve(root, "tsconfig.json"))) return [];
|
|
75
|
+
const runtime = isBinaryMode ? "node" : process.execPath;
|
|
76
|
+
const r = spawnSync(runtime, [tsc, "--noEmit", "-p", root, "--pretty", "false"], { cwd: root, encoding: "utf8" });
|
|
77
|
+
return `${r.stdout ?? ""}`
|
|
78
|
+
.split("\n")
|
|
79
|
+
.map((l) => /^(.+?)\((\d+),\d+\): error (TS\d+): (.*)$/.exec(l))
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map(([, file, line, code, text]) => ({ file: relative(root, resolve(root, file)), line: Number(line), text: `${code} ${text}` }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Everything the project must change, as { file, line, text, source: "known" | "tsc" }. */
|
|
85
|
+
export function checkProject(root) {
|
|
86
|
+
return [
|
|
87
|
+
...findLegacyUiUsage(root).map((f) => ({ ...f, source: "known" })),
|
|
88
|
+
...typeScriptErrors(root).map((e) => ({ ...e, source: "tsc" })),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Plain-text lines for a checkProject() result (empty when there is nothing to fix). */
|
|
93
|
+
export function formatProjectCheck(items) {
|
|
94
|
+
if (!items.length) return [];
|
|
95
|
+
const line = (i) => ` ${i.file}:${i.line} ${i.text}`;
|
|
96
|
+
const tsc = items.filter((i) => i.source === "tsc");
|
|
97
|
+
return [
|
|
98
|
+
...items.filter((i) => i.source !== "tsc").map(line),
|
|
99
|
+
...(tsc.length ? [" TypeScript (tsc --noEmit):", ...tsc.map(line)] : []),
|
|
100
|
+
" For working examples, generate a fresh project and read its ui/content.tsx: muscat-pk new <dir>",
|
|
101
|
+
];
|
|
102
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Shared Python Docker helpers (Ubuntu 24.04 + Python 3.12 — matches the robot).
|
|
2
|
+
// Used by dev:back (hot-reload) and build (lock + wheel vendor).
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile, buildCacheFlags } from "./util.mjs";
|
|
7
|
+
|
|
8
|
+
export const PYTHON_DEV_IMAGE = "muscat-pk-python-dev";
|
|
9
|
+
|
|
10
|
+
export function assertDockerReady() {
|
|
11
|
+
if (!hasBin("docker")) {
|
|
12
|
+
throw new Error("Docker not found. The Python backend runs in a container (Ubuntu 24.04 + Python 3.12).");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
17
|
+
export async function ensureImage(image = PYTHON_DEV_IMAGE, { platform = null } = {}) {
|
|
18
|
+
const dockerfile = await resolveDockerfile("Dockerfile.python");
|
|
19
|
+
const key = imageKey(dockerfile);
|
|
20
|
+
if (canSkipImageBuild(image, key)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
24
|
+
const platformFlags = platform ? ["--platform", platform] : [];
|
|
25
|
+
const r = spawnSync(
|
|
26
|
+
"docker",
|
|
27
|
+
["build", ...buildCacheFlags(), ...platformFlags, ...imageKeyLabel(key), "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
28
|
+
{ stdio: "inherit" },
|
|
29
|
+
);
|
|
30
|
+
if (r.status !== 0) throw new Error("docker build failed.");
|
|
31
|
+
pruneMuskatImages();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// pip --platform does NOT auto-expand manylinux tags (unlike sys_tags()) — exact match only.
|
|
35
|
+
// Specifying all common glibc milestones ensures packages built for any Ubuntu era are found.
|
|
36
|
+
const PIP_PLATFORM = {
|
|
37
|
+
amd64: [
|
|
38
|
+
"manylinux_2_5_x86_64",
|
|
39
|
+
"manylinux_2_17_x86_64",
|
|
40
|
+
"manylinux_2_27_x86_64",
|
|
41
|
+
"manylinux_2_28_x86_64",
|
|
42
|
+
"manylinux_2_34_x86_64",
|
|
43
|
+
"manylinux_2_35_x86_64",
|
|
44
|
+
"manylinux_2_39_x86_64",
|
|
45
|
+
"linux_x86_64",
|
|
46
|
+
],
|
|
47
|
+
arm64: [
|
|
48
|
+
"manylinux_2_17_aarch64",
|
|
49
|
+
"manylinux_2_27_aarch64",
|
|
50
|
+
"manylinux_2_28_aarch64",
|
|
51
|
+
"manylinux_2_34_aarch64",
|
|
52
|
+
"manylinux_2_35_aarch64",
|
|
53
|
+
"manylinux_2_39_aarch64",
|
|
54
|
+
"linux_aarch64",
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export async function lockAndVendor(pluginRoot, archs = ["amd64", "arm64"]) {
|
|
59
|
+
// ponytail: pip --platform used intentionally (not QEMU) — downloads wheels without running target arch code.
|
|
60
|
+
const reqTxt = resolve(pluginRoot, "requirements.txt");
|
|
61
|
+
if (!existsSync(reqTxt)) {
|
|
62
|
+
log.warn("requirements.txt not found — lock/vendor skipped");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await ensureImage();
|
|
67
|
+
|
|
68
|
+
// A pinned pre-release (e.g. rainbow-rb-sdk==0.1.4.dev1) pins its sibling rainbow-rb-*
|
|
69
|
+
// pre-releases too; uv rejects those transitive pre-releases unless allowed.
|
|
70
|
+
const reqText = readFileSync(resolve(pluginRoot, "requirements.txt"), "utf8");
|
|
71
|
+
const prerelease = /==\s*\S*\d(a|b|rc|\.dev)\d*\s*$/im.test(reqText) ? " --prerelease=allow" : "";
|
|
72
|
+
log.step(`generating lock (uv pip compile${prerelease})…`);
|
|
73
|
+
const lockResult = spawnSync(
|
|
74
|
+
"docker",
|
|
75
|
+
[
|
|
76
|
+
"run", "--rm",
|
|
77
|
+
...dockerUserFlags(),
|
|
78
|
+
"-v", `${pluginRoot}:/plugin`,
|
|
79
|
+
PYTHON_DEV_IMAGE,
|
|
80
|
+
"bash", "-c",
|
|
81
|
+
"set -e && cd /plugin && mkdir -p dist && uv pip compile requirements.txt --python-version 3.12 -o dist/requirements.lock" + prerelease,
|
|
82
|
+
],
|
|
83
|
+
{ stdio: "inherit" },
|
|
84
|
+
);
|
|
85
|
+
if (lockResult.status !== 0) throw new Error("lock generation failed (uv pip compile)");
|
|
86
|
+
log.ok("dist/requirements.lock ready");
|
|
87
|
+
|
|
88
|
+
for (const arch of archs) {
|
|
89
|
+
const pipPlatforms = PIP_PLATFORM[arch];
|
|
90
|
+
if (!pipPlatforms) throw new Error(`unknown arch '${arch}'`);
|
|
91
|
+
log.step(`vendoring wheels for ${arch}…`);
|
|
92
|
+
const r = spawnSync(
|
|
93
|
+
"docker",
|
|
94
|
+
[
|
|
95
|
+
"run", "--rm",
|
|
96
|
+
...dockerUserFlags(),
|
|
97
|
+
"-e", "PIP_ROOT_USER_ACTION=ignore",
|
|
98
|
+
"-v", `${pluginRoot}:/plugin`,
|
|
99
|
+
"-w", "/plugin",
|
|
100
|
+
PYTHON_DEV_IMAGE,
|
|
101
|
+
"bash", "-c",
|
|
102
|
+
"mkdir -p dist/wheels && pip3 download -r dist/requirements.lock --no-deps --only-binary=:all: -d dist/wheels -q " +
|
|
103
|
+
"--python-version 312 --implementation cp --abi cp312 " +
|
|
104
|
+
pipPlatforms.map((p) => `--platform ${p}`).join(" "),
|
|
105
|
+
],
|
|
106
|
+
{ stdio: "inherit" },
|
|
107
|
+
);
|
|
108
|
+
// A ZIP missing wheels fails to import on the robot — stop instead.
|
|
109
|
+
if (r.status !== 0) throw new Error(`wheel download failed for ${arch} (see pip output above)`);
|
|
110
|
+
log.ok(`dist/wheels/ ready (${arch})`);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/cli/src/ros2.mjs
CHANGED
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync, readdirSync } from "node:fs";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { canSkipImageBuild, cppSdkVersion, pluginRootOrNull, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile, buildCacheFlags } from "./util.mjs";
|
|
7
|
+
import { syncCppIde } from "./cppIde.mjs";
|
|
7
8
|
|
|
8
9
|
export const ROS2_DEV_IMAGE = "muscat-pk-ros2-dev";
|
|
9
10
|
|
|
10
|
-
// Dockerfile lives inside the kit — plugin authors never see or edit it.
|
|
11
|
-
// It has no COPY/ADD, so the build context is irrelevant (its own dir is used).
|
|
12
|
-
const DOCKERFILE = resolve(kitRoot, "cli/docker/Dockerfile.dev");
|
|
13
|
-
|
|
14
11
|
/** First package directory under ros2_ws/src (the colcon package name). */
|
|
15
12
|
export function ros2PackageName(root) {
|
|
16
13
|
const srcDir = resolve(root, "ros2_ws/src");
|
|
@@ -25,20 +22,38 @@ export function assertDockerReady() {
|
|
|
25
22
|
if (!hasBin("docker")) {
|
|
26
23
|
throw new Error("Docker not found. ROS2 needs Docker (Jazzy + Python 3.12 container).");
|
|
27
24
|
}
|
|
28
|
-
if (!existsSync(DOCKERFILE)) {
|
|
29
|
-
throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
|
|
30
|
-
}
|
|
31
25
|
}
|
|
32
26
|
|
|
27
|
+
export const ARCH_DOCKER_PLATFORM = {
|
|
28
|
+
amd64: "linux/amd64",
|
|
29
|
+
arm64: "linux/arm64",
|
|
30
|
+
};
|
|
31
|
+
|
|
33
32
|
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
34
|
-
export function ensureImage(image = ROS2_DEV_IMAGE) {
|
|
35
|
-
|
|
33
|
+
export async function ensureImage(image = ROS2_DEV_IMAGE, { platform = null, root = pluginRootOrNull() } = {}) {
|
|
34
|
+
await buildImage(image, platform, root);
|
|
35
|
+
// Dev images feed the editor's SDK headers: the native one (dev) or the host-arch one (build).
|
|
36
|
+
const hostArch = process.arch === "arm64" ? "arm64" : "amd64";
|
|
37
|
+
if (root && (image === ROS2_DEV_IMAGE || image === `${ROS2_DEV_IMAGE}-${hostArch}`)) syncCppIde(root, image);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function buildImage(image, platform, root) {
|
|
41
|
+
const dockerfile = await resolveDockerfile("Dockerfile.dev");
|
|
42
|
+
const sdkVer = cppSdkVersion(root);
|
|
43
|
+
const key = imageKey(dockerfile, sdkVer ?? "");
|
|
44
|
+
if (canSkipImageBuild(image, key)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
48
|
+
const platformFlags = platform ? ["--platform", platform] : [];
|
|
49
|
+
const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
|
|
36
50
|
const r = spawnSync(
|
|
37
51
|
"docker",
|
|
38
|
-
["build", "-t", image, "-f",
|
|
52
|
+
["build", ...buildCacheFlags(), ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
39
53
|
{ stdio: "inherit" },
|
|
40
54
|
);
|
|
41
55
|
if (r.status !== 0) throw new Error("docker build failed.");
|
|
56
|
+
pruneMuskatImages();
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
/**
|
|
@@ -46,20 +61,49 @@ export function ensureImage(image = ROS2_DEV_IMAGE) {
|
|
|
46
61
|
* (the workspace is mounted). symlink=true for dev (fast, edit-in-place);
|
|
47
62
|
* symlink=false for export builds that ship in the ZIP (real copies, relocatable).
|
|
48
63
|
*/
|
|
49
|
-
|
|
64
|
+
/**
|
|
65
|
+
* arch=null → dev mode, builds to ros2_ws/install/ (native)
|
|
66
|
+
* arch="amd64"|"arm64" → export mode, builds to ros2_ws/install-{arch}/ using arch-specific image
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* symlink=true (dev): ros2_ws/install/ — local dev workspace, not for ZIP
|
|
70
|
+
* symlink=false, arch=null (rclpy export): dist/ros2_ws/install/
|
|
71
|
+
* symlink=false, arch="amd64"|"arm64" (rclcpp export): dist/ros2_ws/install-{arch}/
|
|
72
|
+
*/
|
|
73
|
+
export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arch = null } = {}) {
|
|
74
|
+
// paths are relative to ros2_ws/ inside the container
|
|
75
|
+
let installBase;
|
|
76
|
+
if (symlink) {
|
|
77
|
+
installBase = "install";
|
|
78
|
+
} else if (arch) {
|
|
79
|
+
installBase = `../dist/ros2_ws/install-${arch}`;
|
|
80
|
+
} else {
|
|
81
|
+
installBase = "../dist/ros2_ws/install";
|
|
82
|
+
}
|
|
50
83
|
const flag = symlink ? "--symlink-install" : "";
|
|
84
|
+
const buildBase = symlink ? "build" : arch ? `build-${arch}` : "build-dist";
|
|
51
85
|
const script = [
|
|
52
|
-
// --ignore-installed: don't try to uninstall debian-managed packages
|
|
53
|
-
// (e.g. psutil), which fails with "RECORD file not found".
|
|
54
86
|
"pip install -r requirements.txt --break-system-packages --ignore-installed -q || true",
|
|
55
87
|
"cd ros2_ws",
|
|
56
|
-
|
|
88
|
+
// Export builds use their own build cache: sharing build/ with dev's --symlink-install
|
|
89
|
+
// (or another arch) breaks the next colcon build.
|
|
90
|
+
// rclcpp export: ship librb_sdk.so in <pkg>/lib/ (robot has no SDK) when the node links it.
|
|
91
|
+
`colcon build ${flag} --build-base ${buildBase} --install-base ${installBase}`.replace(" ", " ") +
|
|
92
|
+
(arch ? ` --cmake-args '-DCMAKE_INSTALL_RPATH=$ORIGIN/..'` : ""),
|
|
93
|
+
...(arch ? [`for d in ${installBase}/*/lib; do if readelf -d $(find "$d" -mindepth 2 -type f -executable) 2>/dev/null | grep -q librb_sdk; then cp -a $(dpkg -L rainbow-rb-sdk | grep -E '/librb_sdk[.]so[.][0-9]') "$d"/; fi; done`] : []),
|
|
57
94
|
].join(" && ");
|
|
58
|
-
|
|
95
|
+
const label = arch
|
|
96
|
+
? `colcon build (${arch}) → dist/ros2_ws/install-${arch}/…`
|
|
97
|
+
: `colcon build${symlink ? " --symlink-install" : ""} (in container)…`;
|
|
98
|
+
log.step(label);
|
|
99
|
+
const actualImage = arch ? `${ROS2_DEV_IMAGE}-${arch}` : image;
|
|
100
|
+
const dockerPlatform = arch ? ARCH_DOCKER_PLATFORM[arch] : null;
|
|
101
|
+
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
59
102
|
const r = spawnSync(
|
|
60
103
|
"docker",
|
|
61
|
-
|
|
104
|
+
// python/cpp 와 같이 호스트 uid 로 돌린다 — root 소유 산출물이 남으면 사용자가 지우지 못한다.
|
|
105
|
+
["run", "--rm", ...platformFlags, ...dockerUserFlags(), "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
|
|
62
106
|
{ cwd: root, stdio: "inherit" },
|
|
63
107
|
);
|
|
64
|
-
if (r.status !== 0) throw new Error(
|
|
108
|
+
if (r.status !== 0) throw new Error(`colcon build failed${arch ? ` (${arch})` : ""}.`);
|
|
65
109
|
}
|
package/cli/src/sdk.mjs
CHANGED
|
@@ -1,27 +1,78 @@
|
|
|
1
|
-
// muscat-pk sdk <list|update [version]> — manage the
|
|
2
|
-
|
|
1
|
+
// muscat-pk sdk <list|update [version]> — manage the rainbow-rb-sdk version.
|
|
2
|
+
//
|
|
3
|
+
// C++ backends (cpp, ros2 rclcpp) pin it in CMakeLists.txt (find_package(rb_sdk <ver> EXACT)); the
|
|
4
|
+
// versions come from the rb-sdk apt repository and the dev image installs the pinned one.
|
|
5
|
+
//
|
|
6
|
+
// Python backends:
|
|
7
|
+
// Pins requirements.txt — the real install happens in the `dev`/`dev:back`
|
|
8
|
+
// Docker container. If a local .venv exists (see devBack.mjs's syncIdeVenv,
|
|
9
|
+
// for editor/linter support), also update it in place so pylint/pyright see
|
|
10
|
+
// the new version immediately; this is best-effort only.
|
|
3
11
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
12
|
import { resolve } from "node:path";
|
|
5
|
-
import { findPluginRoot, log } from "./util.mjs";
|
|
13
|
+
import { cppSdkFiles, cppSdkPin, ensureIdeVenv, findPluginRoot, log, setCppSdkPin, venvPython } from "./util.mjs";
|
|
14
|
+
import { readManifest } from "./manifest.mjs";
|
|
6
15
|
|
|
7
16
|
const PKG = "rainbow-rb-sdk";
|
|
8
17
|
|
|
9
|
-
async function
|
|
18
|
+
async function fetchPypi() {
|
|
10
19
|
try {
|
|
11
20
|
const res = await fetch(`https://pypi.org/pypi/${PKG}/json`);
|
|
21
|
+
return res.ok ? await res.json() : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchVersions() {
|
|
28
|
+
const data = await fetchPypi();
|
|
29
|
+
return Object.keys(data?.releases || {}).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Stable releases of rainbow-rb-sdk-dev, newest first. Pre-releases (0.1.4~dev1) are skipped: CMake
|
|
33
|
+
// versions are numeric only.
|
|
34
|
+
const APT_PACKAGES = "https://rainbowrobotics.github.io/rb-sdk/dists/stable/main/binary-amd64/Packages";
|
|
35
|
+
|
|
36
|
+
export async function fetchAptVersions() {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(APT_PACKAGES);
|
|
12
39
|
if (!res.ok) return [];
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
40
|
+
const text = await res.text();
|
|
41
|
+
const versions = [...text.matchAll(/^Package: rainbow-rb-sdk-dev\n(?:.*\n)*?Version: (\S+)/gm)].map((m) => m[1]);
|
|
42
|
+
return [...new Set(versions)].filter((v) => /^[0-9]+(\.[0-9]+)*$/.test(v)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
17
43
|
} catch {
|
|
18
44
|
return [];
|
|
19
45
|
}
|
|
20
46
|
}
|
|
21
47
|
|
|
48
|
+
async function runCppSdk(sub, version, root) {
|
|
49
|
+
if (sub === "list") {
|
|
50
|
+
const versions = await fetchAptVersions();
|
|
51
|
+
if (!versions.length) return log.warn("could not fetch versions (offline?).");
|
|
52
|
+
log.info(`rainbow-rb-sdk-dev versions:\n ${versions.slice(0, 12).join("\n ")}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (sub === "update") {
|
|
56
|
+
if (!cppSdkFiles(root).length) throw new Error("no find_package(rb_sdk ...) in cpp/CMakeLists.txt or ros2_ws/src/*/CMakeLists.txt");
|
|
57
|
+
if (!version) {
|
|
58
|
+
version = (await fetchAptVersions())[0];
|
|
59
|
+
if (!version) throw new Error("could not fetch the latest version (offline?). Pass one: muscat-pk sdk update <version>");
|
|
60
|
+
}
|
|
61
|
+
if (!/^[0-9]+(\.[0-9]+)*$/.test(version)) throw new Error(`"${version}" is not a CMake version (digits and dots only)`);
|
|
62
|
+
const before = cppSdkPin(root);
|
|
63
|
+
const files = setCppSdkPin(root, version);
|
|
64
|
+
log.ok(`rb_sdk ${before ?? "(unpinned)"} → ${version} in ${files.map((f) => f.slice(root.length + 1)).join(", ")}`);
|
|
65
|
+
log.info("The dev image is rebuilt with this SDK on the next `muscat-pk build` / `dev:back`.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
log.info("Usage: muscat-pk sdk <list|update [version]>");
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
export async function runSdk(args = []) {
|
|
23
72
|
const sub = args[0];
|
|
24
73
|
const root = findPluginRoot();
|
|
74
|
+
const runtime = readManifest(root)?.runtime ?? {};
|
|
75
|
+
if (runtime.type === "cpp" || (runtime.type === "ros2" && runtime.rosClient === "rclcpp")) return runCppSdk(sub, args[1], root);
|
|
25
76
|
const reqs = resolve(root, "requirements.txt");
|
|
26
77
|
|
|
27
78
|
if (sub === "list") {
|
|
@@ -37,28 +88,27 @@ export async function runSdk(args = []) {
|
|
|
37
88
|
if (sub === "update") {
|
|
38
89
|
let version = args[1];
|
|
39
90
|
if (!version) {
|
|
40
|
-
|
|
41
|
-
version =
|
|
91
|
+
// info.version = latest stable
|
|
92
|
+
version = (await fetchPypi())?.info?.version;
|
|
93
|
+
if (!version) throw new Error("could not fetch the latest version (offline?). Pass one: muscat-pk sdk update <version>");
|
|
42
94
|
}
|
|
95
|
+
// rainbow-rb-* pin each other exactly — bump them together.
|
|
43
96
|
let content = existsSync(reqs) ? readFileSync(reqs, "utf8") : "";
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
content = (content ? content.trimEnd() + "\n" : "") +
|
|
97
|
+
const pinned = /^(rainbow-rb-[\w-]+)\s*==.*$/gim;
|
|
98
|
+
const bumped = [...content.matchAll(pinned)].map((m) => m[1]);
|
|
99
|
+
content = content.replace(pinned, (_m, name) => `${name}==${version}`);
|
|
100
|
+
if (!bumped.some((n) => n.toLowerCase() === PKG)) {
|
|
101
|
+
content = (content ? content.trimEnd() + "\n" : "") + `${PKG}==${version}\n`;
|
|
102
|
+
bumped.push(PKG);
|
|
49
103
|
}
|
|
50
104
|
writeFileSync(reqs, content);
|
|
51
|
-
log.ok(`requirements.txt → ${
|
|
105
|
+
log.ok(`requirements.txt → ${bumped.map((n) => `${n}==${version}`).join(", ")}`);
|
|
52
106
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
log.
|
|
56
|
-
spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], {
|
|
57
|
-
cwd: root,
|
|
58
|
-
stdio: "inherit",
|
|
59
|
-
});
|
|
107
|
+
if (existsSync(venvPython(resolve(root, ".venv")))) {
|
|
108
|
+
log.step("refreshing .venv (for your editor)…");
|
|
109
|
+
if (!ensureIdeVenv(root)) log.warn(".venv refresh failed — the Docker dev container is unaffected.");
|
|
60
110
|
} else {
|
|
61
|
-
log.
|
|
111
|
+
log.info("Installed automatically on the next `muscat-pk dev` / `dev:back` (Docker container).");
|
|
62
112
|
}
|
|
63
113
|
return;
|
|
64
114
|
}
|