@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.5 → 0.1.2-dev.51
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.md +82 -49
- package/cli/README.md +191 -5
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +25 -10
- package/cli/bundle-templates.mjs +28 -0
- package/cli/docker/Dockerfile.bridge +17 -0
- package/cli/docker/Dockerfile.cpp +22 -0
- package/cli/docker/Dockerfile.dev +1 -2
- package/cli/docker/Dockerfile.python +20 -0
- package/cli/docker/bridge-package.json +8 -0
- package/cli/prepack.mjs +10 -0
- package/cli/src/bridge.mjs +95 -0
- package/cli/src/build.mjs +377 -85
- package/cli/src/check-package.mjs +8 -23
- package/cli/src/cpp.mjs +61 -0
- package/cli/src/dev.mjs +49 -4
- package/cli/src/devBack.mjs +311 -49
- package/cli/src/devUi.mjs +198 -19
- package/cli/src/esbuild.mjs +27 -10
- package/cli/src/manifest.mjs +208 -0
- package/cli/src/mcp.mjs +592 -0
- package/cli/src/new.mjs +143 -43
- package/cli/src/pluginDevServer.mjs +112 -0
- package/cli/src/ports.mjs +160 -0
- package/cli/src/python.mjs +109 -0
- package/cli/src/ros2.mjs +43 -18
- package/cli/src/sdk.mjs +11 -9
- package/cli/src/socketBridge.mjs +133 -0
- package/cli/src/storybook.mjs +168 -0
- package/cli/src/templates-embedded.mjs +3 -0
- package/cli/src/type.mjs +52 -29
- package/cli/src/uninstall.mjs +98 -0
- package/cli/src/update.mjs +259 -30
- package/cli/src/util.mjs +114 -7
- package/cli/src/zenohDev.mjs +75 -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 +44 -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/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
- package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -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 +119 -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-BjuzF3Je.js +1 -0
- package/cli/storybook-dist/assets/Docs-BAFZYTMR.js +12 -0
- package/cli/storybook-dist/assets/Docs-BQesYW-Q.js +8 -0
- package/cli/storybook-dist/assets/Docs-BsMEe093.js +7 -0
- package/cli/storybook-dist/assets/Docs-By6k6vcM.js +20 -0
- package/cli/storybook-dist/assets/Docs-D1J63cJc.js +7 -0
- package/cli/storybook-dist/assets/Docs-GoNbntAU.js +8 -0
- package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-BwY_WiM7.js +2 -0
- package/cli/storybook-dist/assets/NxAvatar.stories-CvzBHfYa.js +13 -0
- package/cli/storybook-dist/assets/NxButton.stories-B83PesgN.js +20 -0
- package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
- package/cli/storybook-dist/assets/NxNumberInput.stories-DpNcCG6B.js +26 -0
- package/cli/storybook-dist/assets/NxPagination.stories-Xnvd3xhH.js +11 -0
- package/cli/storybook-dist/assets/NxPointerHoldButton.stories-C3YSIpM0.js +14 -0
- package/cli/storybook-dist/assets/NxRangeInput.stories-DIhaNZig.js +17 -0
- package/cli/storybook-dist/assets/NxSelectBox.stories-BGwO2mYJ.js +17 -0
- package/cli/storybook-dist/assets/NxSlideRange.stories-BXlt5Bgt.js +14 -0
- package/cli/storybook-dist/assets/NxSwitch.stories-C5ZqIRtw.js +11 -0
- package/cli/storybook-dist/assets/NxTextArea.stories-hm_ycALo.js +11 -0
- package/cli/storybook-dist/assets/NxTextInput.stories-DkZAySr_.js +11 -0
- package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
- package/cli/storybook-dist/assets/RBAccordion.stories--bY36GZY.js +17 -0
- package/cli/storybook-dist/assets/RBAccordionGroup.stories-BVr1ccI4.js +33 -0
- package/cli/storybook-dist/assets/RBAlert.stories-CkjFBEkv.js +17 -0
- package/cli/storybook-dist/assets/RBBadge.stories-CNXILDPh.js +1 -0
- package/cli/storybook-dist/assets/RBBattery.stories-AokMv8ZY.js +10 -0
- package/cli/storybook-dist/assets/RBCard.stories-CX1sLKB2.js +45 -0
- package/cli/storybook-dist/assets/RBCheckBox.stories-By3QHBFm.js +1 -0
- package/cli/storybook-dist/assets/RBCheckIconButton.stories-Dud1uK3X.js +1 -0
- package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C8iBkZxY.js +24 -0
- package/cli/storybook-dist/assets/RBChipTab.stories-DsgbiS2U.js +21 -0
- package/cli/storybook-dist/assets/RBChipsBoxTab.stories-C3DZGmq0.js +1 -0
- package/cli/storybook-dist/assets/RBColorIcons.stories-CusR20qk.js +3 -0
- package/cli/storybook-dist/assets/RBCommand.stories-DhJNgaLZ.js +73 -0
- package/cli/storybook-dist/assets/RBConfirm.stories-B6LKpCDa.js +20 -0
- package/cli/storybook-dist/assets/RBDateInput.stories-PEEhQ1wJ.js +15 -0
- package/cli/storybook-dist/assets/RBDialog.stories-CAtOdRen.js +38 -0
- package/cli/storybook-dist/assets/RBDivider.stories-0zpIUlXT.js +1 -0
- package/cli/storybook-dist/assets/RBDrawer.stories-CWMG4glY.js +14 -0
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-6fbEYZfk.js +86 -0
- package/cli/storybook-dist/assets/RBEmptyView.stories-BRDZHjn4.js +12 -0
- package/cli/storybook-dist/assets/RBIconButton.stories-sYrRLYob.js +1 -0
- package/cli/storybook-dist/assets/RBIcons.stories-DFe8O1_g.js +3 -0
- package/cli/storybook-dist/assets/RBInlineLabelButton.stories-DX4VdUfk.js +1 -0
- package/cli/storybook-dist/assets/RBJoyStick.stories-SA2Foelf.js +11 -0
- package/cli/storybook-dist/assets/RBLabelButton.stories-CicBns_Y.js +1 -0
- package/cli/storybook-dist/assets/RBLight.stories-WlP3oEmY.js +1 -0
- package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-CrWjPfxj.js +3 -0
- package/cli/storybook-dist/assets/RBMenubar.stories-Di4phWw7.js +3 -0
- package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DS3wrR3l.js +14 -0
- package/cli/storybook-dist/assets/RBPagination.stories-BDVPAUVD.js +17 -0
- package/cli/storybook-dist/assets/RBPopover.stories-DL9b_iX9.js +11 -0
- package/cli/storybook-dist/assets/RBProgramIcons.stories-ByRVx-on.js +3 -0
- package/cli/storybook-dist/assets/RBPrompt.stories-vTmKWyA5.js +20 -0
- package/cli/storybook-dist/assets/RBRadioBox.stories-C9WHt4jb.js +1 -0
- package/cli/storybook-dist/assets/RBRadioButton.stories-D8KYq63j.js +11 -0
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-Bvcf4Su7.js +15 -0
- package/cli/storybook-dist/assets/RBResizable.stories-3O2OMuH_.js +20 -0
- package/cli/storybook-dist/assets/RBSelectBox.stories-CKZLmPzX.js +6 -0
- package/cli/storybook-dist/assets/RBSheet.stories-Dko0It1W.js +39 -0
- package/cli/storybook-dist/assets/RBSideNumberInput.stories-CUvqVtId.js +5 -0
- package/cli/storybook-dist/assets/RBSlider.stories-CcbaKRYo.js +5 -0
- package/cli/storybook-dist/assets/RBSpinner.stories-D15e_JlE.js +14 -0
- package/cli/storybook-dist/assets/RBSwitch.stories-C4JRsYJ6.js +65 -0
- package/cli/storybook-dist/assets/RBTab.stories-BPvzQSM-.js +1 -0
- package/cli/storybook-dist/assets/RBTable.stories-nr8OS1mV.js +62 -0
- package/cli/storybook-dist/assets/RBTableRow.stories-rI0rK8Ci.js +11 -0
- package/cli/storybook-dist/assets/RBTabs.stories-CxMtHGWA.js +21 -0
- package/cli/storybook-dist/assets/RBTextArea.stories-Bru4AhJ3.js +5 -0
- package/cli/storybook-dist/assets/RBTextField.stories-Ah3D9lto.js +45 -0
- package/cli/storybook-dist/assets/RBTextInput.stories-C3qAVngJ.js +5 -0
- package/cli/storybook-dist/assets/RBToast.stories-C1occsvf.js +11 -0
- package/cli/storybook-dist/assets/RBToggle.stories-BNrNERc7.js +12 -0
- package/cli/storybook-dist/assets/RBToggleGroup.stories-D_RRBwxY.js +17 -0
- package/cli/storybook-dist/assets/RBTooltip.stories-DuyOQqrB.js +9 -0
- package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
- package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
- package/cli/storybook-dist/assets/browserUtils-DeopUpDy.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/createConveyorBeltLayer-Cn41pFRH.js +6210 -0
- package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
- package/cli/storybook-dist/assets/entry-preview-D-mLV0cS.js +2 -0
- package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
- package/cli/storybook-dist/assets/iframe-DNru2eN7.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-C1OzuK6-.js +240 -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-Cwr2bQOB.js +575 -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/keymapTileIndex.worker-BsNYNbqs.js +1 -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-BWzBA1C2.js +396 -0
- package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -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-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-DUTcCPBe.css +1 -0
- package/cli/storybook-dist/assets/preview-QAsoD6Sa.js +2 -0
- package/cli/storybook-dist/assets/preview-rcRMbKPr.js +3 -0
- package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -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/.mcp.json +8 -0
- package/cli/templates/common/README.md +817 -9
- package/cli/templates/common/assets/icon.svg +44 -2
- package/cli/templates/common/manifest.json +13 -13
- package/cli/templates/common/package.json +11 -11
- package/cli/templates/common/py/main.py +98 -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 +26 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +5 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -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 +22 -0
- package/cli/vite.d.mts +25 -0
- package/cli/vite.mjs +292 -0
- package/package.json +32 -5
- package/cli/src/pack.mjs +0 -61
- package/cli/templates/common/main.py +0 -21
- package/cli/templates/common/src/content.tsx +0 -32
package/cli/src/cpp.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Shared C++ Docker helpers (Ubuntu 24.04 + build-essential + cmake).
|
|
2
|
+
// Matches the robot OS so the compiled binary is relocatable to the robot.
|
|
3
|
+
// Used by dev:back (interactive dev loop) and build (export the binary into the ZIP).
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
7
|
+
|
|
8
|
+
export const CPP_DEV_IMAGE = "muscat-pk-cpp-dev";
|
|
9
|
+
|
|
10
|
+
export function assertDockerReady() {
|
|
11
|
+
if (!hasBin("docker")) {
|
|
12
|
+
throw new Error("Docker not found. C++ builds need Docker (Ubuntu 24.04 + cmake container).");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
17
|
+
export async function ensureImage(image = CPP_DEV_IMAGE, { platform = null } = {}) {
|
|
18
|
+
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
19
|
+
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
20
|
+
const platformFlags = platform ? ["--platform", platform] : [];
|
|
21
|
+
const r = spawnSync(
|
|
22
|
+
"docker",
|
|
23
|
+
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
24
|
+
{ stdio: "inherit" },
|
|
25
|
+
);
|
|
26
|
+
if (r.status !== 0) throw new Error("docker build failed.");
|
|
27
|
+
pruneMuskatImages();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Docker platform token for each target arch
|
|
31
|
+
export const ARCH_DOCKER_PLATFORM = {
|
|
32
|
+
amd64: "linux/amd64",
|
|
33
|
+
arm64: "linux/arm64",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configure + build cpp/ inside the container, producing cpp/build-{arch}/<entry>
|
|
38
|
+
* on the host (the workspace is mounted).
|
|
39
|
+
* - arch null → native (dev mode, uses cpp/build/)
|
|
40
|
+
* - arch "x86_64" / "aarch64" → cross via --platform (Release builds for ZIP)
|
|
41
|
+
* buildType "Debug" for dev, "Release" for export builds that ship in the ZIP.
|
|
42
|
+
*/
|
|
43
|
+
export function cmakeBuild(root, { image = CPP_DEV_IMAGE, buildType = "Debug", arch = null } = {}) {
|
|
44
|
+
const buildDir = arch ? `cpp/build-${arch}` : "cpp/build";
|
|
45
|
+
const dockerPlatform = arch ? ARCH_DOCKER_PLATFORM[arch] : null;
|
|
46
|
+
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
47
|
+
// arch 빌드 시 arch별 이미지 사용 (로컬 이미지와 platform 불일치 방지)
|
|
48
|
+
const actualImage = arch ? `${CPP_DEV_IMAGE}-${arch}` : image;
|
|
49
|
+
const script = [
|
|
50
|
+
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}`,
|
|
51
|
+
`cmake --build ${buildDir} -j`,
|
|
52
|
+
].join(" && ");
|
|
53
|
+
const label = arch ? `cmake build (${buildType}, ${arch}, in container)…` : `cmake build (${buildType}, in container)…`;
|
|
54
|
+
log.step(label);
|
|
55
|
+
const r = spawnSync(
|
|
56
|
+
"docker",
|
|
57
|
+
["run", "--rm", ...platformFlags, "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
|
|
58
|
+
{ cwd: root, stdio: "inherit" },
|
|
59
|
+
);
|
|
60
|
+
if (r.status !== 0) throw new Error(`cmake build failed${arch ? ` (${arch})` : ""}.`);
|
|
61
|
+
}
|
package/cli/src/dev.mjs
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
|
-
// muscat-pk dev — run dev:ui and dev:back together.
|
|
1
|
+
// muscat-pk dev — run the Socket.IO bridge (Docker), dev:ui, and dev:back together.
|
|
2
2
|
import { runDevUi } from "./devUi.mjs";
|
|
3
|
-
import { runDevBack } from "./devBack.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { runDevBack, stopDevBackContainers } from "./devBack.mjs";
|
|
4
|
+
import { startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
|
|
5
|
+
import { assertManifestPorts } from "./manifest.mjs";
|
|
6
|
+
import { formatPortRanges } from "./ports.mjs";
|
|
7
|
+
import { ensureDocker, findPluginRoot, log, prompt, promptRobotConnection } from "./util.mjs";
|
|
8
|
+
import { ensureRemoteZenohEnv, isRemoteRobot } from "./zenohDev.mjs";
|
|
5
9
|
|
|
6
10
|
export async function runDev(args = []) {
|
|
7
|
-
|
|
11
|
+
const root = findPluginRoot();
|
|
12
|
+
const portRanges = assertManifestPorts(root);
|
|
13
|
+
if (portRanges.length > 0) {
|
|
14
|
+
log.step(`plugin backend ports declared: ${formatPortRanges(portRanges)}`);
|
|
15
|
+
process.env.MUSCAT_PK_PORTS_PRECHECKED = "1";
|
|
16
|
+
}
|
|
17
|
+
ensureDocker();
|
|
18
|
+
const { robotIp, robotToken } = await promptRobotConnection(args);
|
|
19
|
+
process.env.MUSCAT_ROBOT_HOST = robotIp;
|
|
20
|
+
if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
|
|
21
|
+
|
|
22
|
+
const remoteRobot = isRemoteRobot(robotIp);
|
|
23
|
+
// Compute the Zenoh advertise host/port up front — startBridge() needs the
|
|
24
|
+
// port to publish it on the bridge container so the robot can reach back.
|
|
25
|
+
await ensureRemoteZenohEnv(robotIp, { log });
|
|
26
|
+
|
|
27
|
+
log.step("starting Socket.IO bridge (Docker) + dev:ui + dev:back");
|
|
28
|
+
const bridgeStarted = await startBridge({
|
|
29
|
+
zenohPort: remoteRobot ? Number(process.env.MUSCAT_ZENOH_PORT) : null,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!bridgeStarted) {
|
|
33
|
+
log.warn(`port ${BRIDGE_PORT} is already in use — can't start Socket.IO bridge.`);
|
|
34
|
+
const answer = await prompt(" Proceed without Socket.IO bridge? [y/N]: ");
|
|
35
|
+
if (answer.toLowerCase() !== "y") return;
|
|
36
|
+
log.info("proceeding without bridge — backend will skip network sharing.");
|
|
37
|
+
} else {
|
|
38
|
+
// Tell dev:back which bridge container to share a netns with, so the backend
|
|
39
|
+
// reaches the bridge at localhost:10000 (no host.docker.internal).
|
|
40
|
+
process.env.MUSCAT_PK_BRIDGE_CONTAINER = BRIDGE_CONTAINER;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|
|
44
|
+
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
stopDevBackContainers();
|
|
47
|
+
if (bridgeStarted) stopBridge();
|
|
48
|
+
};
|
|
49
|
+
process.once("SIGINT", cleanup);
|
|
50
|
+
process.once("SIGTERM", cleanup);
|
|
51
|
+
process.once("exit", cleanup);
|
|
52
|
+
|
|
8
53
|
// dev:back runs to completion / detaches on its own; dev:ui owns the process.
|
|
9
54
|
runDevBack().catch((e) => log.warn(`dev:back: ${e.message}`));
|
|
10
55
|
await runDevUi(args);
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -1,71 +1,235 @@
|
|
|
1
1
|
// muscat-pk dev:back — run the plugin back end during development.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// Every backend runs in a warm Docker container that matches the robot
|
|
3
|
+
// (Ubuntu 24.04), so dev behaves like production.
|
|
4
|
+
// python : Python 3.12 container, pip install, run main.py with watchfiles
|
|
5
|
+
// hot-reload. Shares the bridge container's netns (localhost:10000).
|
|
6
|
+
// cpp : Ubuntu + cmake container, build cpp/, run the binary, rebuild +
|
|
7
|
+
// restart on source changes.
|
|
8
|
+
// ros2 : Jazzy + Python 3.12 container, colcon build, run the node, restart
|
|
9
|
+
// on source changes (rclcpp rebuilds on change).
|
|
7
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
import { existsSync,
|
|
11
|
+
import { existsSync, watch } from "node:fs";
|
|
9
12
|
import { resolve } from "node:path";
|
|
10
|
-
import {
|
|
13
|
+
import { ensureDocker, ensureIdeVenv, findPluginRoot, log, promptRobotConnection } from "./util.mjs";
|
|
14
|
+
import { ensureRemoteZenohEnv } from "./zenohDev.mjs";
|
|
15
|
+
import { assertManifestPorts, manifestRuntime, readManifest, validateManifest } from "./manifest.mjs";
|
|
16
|
+
import {
|
|
17
|
+
formatPortRanges,
|
|
18
|
+
portEnvFlags,
|
|
19
|
+
startPortContractMonitor,
|
|
20
|
+
} from "./ports.mjs";
|
|
21
|
+
import {
|
|
22
|
+
PYTHON_DEV_IMAGE,
|
|
23
|
+
assertDockerReady as pyAssertDockerReady,
|
|
24
|
+
ensureImage as pyEnsureImage,
|
|
25
|
+
} from "./python.mjs";
|
|
11
26
|
import {
|
|
12
27
|
ROS2_DEV_IMAGE,
|
|
13
28
|
assertDockerReady,
|
|
14
29
|
ensureImage,
|
|
15
30
|
ros2PackageName,
|
|
16
31
|
} from "./ros2.mjs";
|
|
32
|
+
import {
|
|
33
|
+
CPP_DEV_IMAGE,
|
|
34
|
+
assertDockerReady as cppAssertDockerReady,
|
|
35
|
+
ensureImage as cppEnsureImage,
|
|
36
|
+
} from "./cpp.mjs";
|
|
37
|
+
|
|
38
|
+
const MANAGED_DEV_ENV = "MUSCAT_PK_MANAGED_DEV";
|
|
39
|
+
|
|
40
|
+
function exitAfterCleanup(code = 0) {
|
|
41
|
+
if (process.env[MANAGED_DEV_ENV] !== "1") process.exit(code ?? 0);
|
|
42
|
+
}
|
|
17
43
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
export function stopDevBackContainers() {
|
|
45
|
+
for (const container of [
|
|
46
|
+
`${PYTHON_DEV_IMAGE}-run`,
|
|
47
|
+
`${ROS2_DEV_IMAGE}-run`,
|
|
48
|
+
`${CPP_DEV_IMAGE}-run`,
|
|
49
|
+
]) {
|
|
50
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
24
51
|
}
|
|
25
52
|
}
|
|
26
53
|
|
|
54
|
+
// ── IDE tooling venv (best-effort, non-blocking) ────────────────────────────
|
|
55
|
+
// `muscat-pk new` already creates this up front (see new.mjs); this just
|
|
56
|
+
// refreshes it on every `dev`/`dev:back` in case requirements.txt changed.
|
|
57
|
+
// Docker remains the actual execution environment — this never affects it.
|
|
58
|
+
// Runs in the background so it never delays `dev:back` starting, and any
|
|
59
|
+
// failure here (no local Python toolchain, offline, ...) is silent.
|
|
60
|
+
function syncIdeVenv(root) {
|
|
61
|
+
setImmediate(() => {
|
|
62
|
+
try {
|
|
63
|
+
if (ensureIdeVenv(root)) {
|
|
64
|
+
log.info("IDE venv ready → .venv (point your editor's Python interpreter there for pylint/pyright)");
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
/* best-effort only */
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
27
72
|
// ── python ────────────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
73
|
+
// Runs in a warm Ubuntu 24.04 + Python 3.12 container (matches the robot).
|
|
74
|
+
// Under `muscat-pk dev`, it shares the bridge container's netns, so the SDK
|
|
75
|
+
// reaches the Socket.IO bridge at the default localhost:10000.
|
|
76
|
+
async function runPython(root, portRanges) {
|
|
77
|
+
// Independent of Docker readiness — must still happen if Docker isn't
|
|
78
|
+
// installed/running, since that's exactly when local IDE tooling matters most.
|
|
79
|
+
syncIdeVenv(root);
|
|
80
|
+
pyAssertDockerReady();
|
|
81
|
+
await pyEnsureImage();
|
|
38
82
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
const manifest = readManifest(root);
|
|
84
|
+
const pluginId = manifest.id ?? "";
|
|
85
|
+
const container = `${PYTHON_DEV_IMAGE}-run`;
|
|
86
|
+
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
87
|
+
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
|
88
|
+
const zenohAdvertiseHost = process.env.MUSCAT_ZENOH_ADVERTISE_HOST ?? "";
|
|
89
|
+
const zenohPort = process.env.MUSCAT_ZENOH_PORT ?? "";
|
|
90
|
+
// Share the bridge container's network namespace so localhost:10000 is the
|
|
91
|
+
// bridge (set by `muscat-pk dev`). Standalone dev:back has no bridge to join.
|
|
92
|
+
const bridgeContainer = process.env.MUSCAT_PK_BRIDGE_CONTAINER;
|
|
93
|
+
const netFlags = bridgeContainer ? ["--network", `container:${bridgeContainer}`] : [];
|
|
94
|
+
const standaloneZenohPortFlags = !bridgeContainer && zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : [];
|
|
95
|
+
const hostGatewayFlags = bridgeContainer
|
|
96
|
+
? []
|
|
97
|
+
: ["--add-host", "host.docker.internal:host-gateway"];
|
|
98
|
+
// Without the bridge's netns, this container is isolated — its own
|
|
99
|
+
// 127.0.0.1 (rainbow.rb_sdk's gateway default) is itself, not the host, so
|
|
100
|
+
// it can never see a gateway already running on the host (e.g. the user's
|
|
101
|
+
// own robot stack occupying port 10000, the reason the bridge start above
|
|
102
|
+
// was skipped in the first place). host.docker.internal reaches the host.
|
|
103
|
+
const gatewayFlags = bridgeContainer
|
|
104
|
+
? []
|
|
105
|
+
: ["-e", "RB_PLUGIN_GATEWAY_URL=http://host.docker.internal:10000"];
|
|
106
|
+
const envFlags = [
|
|
107
|
+
// docker exec below has no -t (no pseudo-TTY), so Python defaults to
|
|
108
|
+
// block-buffered stdout — print() output wouldn't show up until the
|
|
109
|
+
// buffer fills or the process exits. Force unbuffered.
|
|
110
|
+
"-e", "PYTHONUNBUFFERED=1",
|
|
111
|
+
...gatewayFlags,
|
|
112
|
+
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
113
|
+
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
114
|
+
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
115
|
+
...(zenohAdvertiseHost ? ["-e", `MUSCAT_ZENOH_ADVERTISE_HOST=${zenohAdvertiseHost}`] : []),
|
|
116
|
+
...(zenohPort ? ["-e", `MUSCAT_ZENOH_PORT=${zenohPort}`] : []),
|
|
117
|
+
...portEnvFlags(portRanges),
|
|
118
|
+
];
|
|
119
|
+
const docker = (args, opts = {}) =>
|
|
120
|
+
spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
|
|
44
121
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
// start a warm, long-lived container with the plugin mounted
|
|
123
|
+
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
124
|
+
log.step("starting Python dev container…");
|
|
125
|
+
const up = docker(
|
|
126
|
+
["run", "-d", "--name", container, ...netFlags, ...standaloneZenohPortFlags,
|
|
127
|
+
// host.docker.internal is automatic on Docker Desktop (macOS/Windows);
|
|
128
|
+
// this makes it resolve on Linux too, for the no-bridge gateway fallback above.
|
|
129
|
+
...hostGatewayFlags,
|
|
130
|
+
"-e", "PIP_ROOT_USER_ACTION=ignore",
|
|
131
|
+
"-v", `${root}:/plugin`, "-w", "/plugin", PYTHON_DEV_IMAGE, "sleep", "infinity"],
|
|
132
|
+
{ stdio: "inherit" },
|
|
133
|
+
);
|
|
134
|
+
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
135
|
+
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
|
|
50
136
|
|
|
51
|
-
log.step("
|
|
137
|
+
log.step("pip install requirements + watchfiles (in container)…");
|
|
138
|
+
docker(["exec", container, "bash", "-lc",
|
|
139
|
+
"pip install --break-system-packages --ignore-installed -q watchfiles" +
|
|
140
|
+
" && { [ -f requirements.txt ] && pip install --break-system-packages --ignore-installed -q -r requirements.txt || true; }"]);
|
|
141
|
+
|
|
142
|
+
docker(["exec", container, "bash", "-lc", `cat > /tmp/muscat-pk-python-dev-entry.py <<'PY'
|
|
143
|
+
import os
|
|
144
|
+
import runpy
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
from rainbow.rb_sdk.base import RBBaseSDK
|
|
148
|
+
|
|
149
|
+
advertise_host = os.getenv("MUSCAT_ZENOH_ADVERTISE_HOST")
|
|
150
|
+
zenoh_port = os.getenv("MUSCAT_ZENOH_PORT")
|
|
151
|
+
|
|
152
|
+
if advertise_host:
|
|
153
|
+
RBBaseSDK._detect_local_ip = staticmethod(lambda host: advertise_host)
|
|
154
|
+
|
|
155
|
+
if zenoh_port:
|
|
156
|
+
_orig_connect = RBBaseSDK.connect
|
|
157
|
+
|
|
158
|
+
async def _connect_with_dev_port(self, *args, **kwargs):
|
|
159
|
+
kwargs.setdefault("zenoh_port", int(zenoh_port))
|
|
160
|
+
return await _orig_connect(self, *args, **kwargs)
|
|
161
|
+
|
|
162
|
+
RBBaseSDK.connect = _connect_with_dev_port
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
print(f"[muscat-pk] SDK dev network patch skipped: {exc}", flush=True)
|
|
165
|
+
|
|
166
|
+
runpy.run_path("/plugin/py/main.py", run_name="__main__")
|
|
167
|
+
PY`]);
|
|
168
|
+
|
|
169
|
+
if (pluginId) log.step(`RB_PLUGIN_ID=${pluginId}`);
|
|
170
|
+
if (zenohAdvertiseHost && zenohPort) log.step(`Zenoh advertise → ${zenohAdvertiseHost}:${zenohPort}`);
|
|
171
|
+
log.step("watchfiles → py/main.py (reload on any .py change in py/)");
|
|
52
172
|
const child = spawn(
|
|
53
|
-
|
|
54
|
-
["
|
|
55
|
-
|
|
173
|
+
"docker",
|
|
174
|
+
["exec", ...envFlags, container, "bash", "-lc",
|
|
175
|
+
"python3 -m watchfiles --filter python --target-type command 'python3 /tmp/muscat-pk-python-dev-entry.py' py"],
|
|
176
|
+
{ cwd: root, stdio: ["inherit", "inherit", "pipe"] },
|
|
56
177
|
);
|
|
57
|
-
|
|
178
|
+
// watchfiles prints a KeyboardInterrupt traceback on SIGTERM — suppress it
|
|
179
|
+
child.stderr.on("data", (buf) => {
|
|
180
|
+
const text = buf.toString();
|
|
181
|
+
if (!text.includes("KeyboardInterrupt") && !text.includes("raise_keyboard_interrupt")) {
|
|
182
|
+
process.stderr.write(buf);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
let cleaned = false;
|
|
187
|
+
const cleanupResources = () => {
|
|
188
|
+
if (cleaned) return;
|
|
189
|
+
cleaned = true;
|
|
190
|
+
stopPortMonitor();
|
|
191
|
+
if (!child.killed && child.exitCode === null) child.kill();
|
|
192
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
193
|
+
};
|
|
194
|
+
const cleanup = () => {
|
|
195
|
+
cleanupResources();
|
|
196
|
+
exitAfterCleanup(0);
|
|
197
|
+
};
|
|
198
|
+
child.on("exit", (code) => {
|
|
199
|
+
cleanupResources();
|
|
200
|
+
exitAfterCleanup(code ?? 0);
|
|
201
|
+
});
|
|
202
|
+
process.once("SIGINT", cleanup);
|
|
203
|
+
process.once("SIGTERM", cleanup);
|
|
58
204
|
}
|
|
59
205
|
|
|
60
206
|
// ── ros2 ───────────────────────────────────────────────────────────────────────
|
|
61
|
-
function runRos2(root) {
|
|
207
|
+
async function runRos2(root, portRanges) {
|
|
208
|
+
const manifest = readManifest(root);
|
|
209
|
+
const isRclcpp = manifest.runtime?.rosClient === "rclcpp";
|
|
210
|
+
// Independent of Docker readiness — must still happen if Docker isn't
|
|
211
|
+
// installed/running, since that's exactly when local IDE tooling matters most.
|
|
212
|
+
if (!isRclcpp) syncIdeVenv(root); // rclpy is plain Python — rclcpp is C++, no venv needed
|
|
213
|
+
|
|
62
214
|
assertDockerReady();
|
|
63
215
|
const pkg = ros2PackageName(root);
|
|
64
216
|
if (!pkg) throw new Error("no ROS2 package found under ros2_ws/src.");
|
|
65
217
|
|
|
66
|
-
ensureImage();
|
|
218
|
+
await ensureImage();
|
|
67
219
|
|
|
220
|
+
const pluginId = manifest.id ?? "";
|
|
68
221
|
const container = `${ROS2_DEV_IMAGE}-run`;
|
|
222
|
+
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
223
|
+
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
|
224
|
+
const envFlags = [
|
|
225
|
+
// Same rationale as runPython(): no -t on docker exec below means
|
|
226
|
+
// rclpy's Python process stdout is block-buffered, not line-buffered.
|
|
227
|
+
"-e", "PYTHONUNBUFFERED=1",
|
|
228
|
+
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
229
|
+
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
230
|
+
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
231
|
+
...portEnvFlags(portRanges),
|
|
232
|
+
];
|
|
69
233
|
const docker = (args, opts = {}) =>
|
|
70
234
|
spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
|
|
71
235
|
|
|
@@ -73,10 +237,11 @@ function runRos2(root) {
|
|
|
73
237
|
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
74
238
|
log.step("starting ROS2 dev container…");
|
|
75
239
|
const up = docker(
|
|
76
|
-
["run", "-d", "--name", container, "-v", `${root}:/plugin`, "-w", "/plugin", ROS2_DEV_IMAGE, "sleep", "infinity"],
|
|
240
|
+
["run", "-d", "--name", container, "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", ROS2_DEV_IMAGE, "sleep", "infinity"],
|
|
77
241
|
{ stdio: "inherit" },
|
|
78
242
|
);
|
|
79
243
|
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
244
|
+
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
|
|
80
245
|
|
|
81
246
|
// one-time deps + symlink build (edits to .py reflect without rebuilding)
|
|
82
247
|
log.step("pip install + colcon build --symlink-install (once)…");
|
|
@@ -89,21 +254,22 @@ function runRos2(root) {
|
|
|
89
254
|
log.step(`ros2 run ${pkg} ${pkg}_node`);
|
|
90
255
|
child = spawn(
|
|
91
256
|
"docker",
|
|
92
|
-
["exec", container, "bash", "-lc",
|
|
257
|
+
["exec", ...envFlags, container, "bash", "-lc",
|
|
93
258
|
`cd ros2_ws && source install/setup.bash && ros2 run ${pkg} ${pkg}_node`],
|
|
94
259
|
{ cwd: root, stdio: "inherit" },
|
|
95
260
|
);
|
|
96
261
|
};
|
|
97
262
|
startNode();
|
|
98
263
|
|
|
99
|
-
// restart node on source change
|
|
264
|
+
// restart node on source change. rclpy reflects .py edits live (symlink-install);
|
|
265
|
+
// rclcpp (C++) and package-metadata changes require a colcon rebuild.
|
|
100
266
|
let timer = null;
|
|
101
267
|
const onChange = (_event, filename) => {
|
|
102
268
|
clearTimeout(timer);
|
|
103
269
|
timer = setTimeout(() => {
|
|
104
270
|
const structural = filename && /(setup\.py|package\.xml|CMakeLists\.txt)$/.test(String(filename));
|
|
105
|
-
if (structural) {
|
|
106
|
-
log.info("package metadata changed — colcon rebuild…");
|
|
271
|
+
if (structural || isRclcpp) {
|
|
272
|
+
log.info(structural ? "package metadata changed — colcon rebuild…" : "source changed — colcon rebuild…");
|
|
107
273
|
docker(["exec", container, "bash", "-lc", "cd ros2_ws && colcon build --symlink-install"]);
|
|
108
274
|
} else {
|
|
109
275
|
log.info("source changed — restarting node…");
|
|
@@ -117,17 +283,113 @@ function runRos2(root) {
|
|
|
117
283
|
}
|
|
118
284
|
|
|
119
285
|
const cleanup = () => {
|
|
286
|
+
stopPortMonitor();
|
|
120
287
|
if (child) child.kill();
|
|
121
288
|
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
122
|
-
|
|
289
|
+
exitAfterCleanup(0);
|
|
123
290
|
};
|
|
124
291
|
process.on("SIGINT", cleanup);
|
|
125
292
|
process.on("SIGTERM", cleanup);
|
|
126
293
|
}
|
|
127
294
|
|
|
128
|
-
|
|
295
|
+
// ── cpp ──────────────────────────────────────────────────────────────────────
|
|
296
|
+
async function runCpp(root, portRanges) {
|
|
297
|
+
cppAssertDockerReady();
|
|
298
|
+
const manifest = readManifest(root);
|
|
299
|
+
const entry = manifest.runtime?.entry;
|
|
300
|
+
if (!entry) throw new Error("runtime.entry (the built executable name) is required for cpp.");
|
|
301
|
+
|
|
302
|
+
await cppEnsureImage();
|
|
303
|
+
|
|
304
|
+
const pluginId = manifest.id ?? "";
|
|
305
|
+
const container = `${CPP_DEV_IMAGE}-run`;
|
|
306
|
+
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
307
|
+
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
|
308
|
+
const envFlags = [
|
|
309
|
+
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
310
|
+
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
311
|
+
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
312
|
+
...portEnvFlags(portRanges),
|
|
313
|
+
];
|
|
314
|
+
const docker = (args, opts = {}) =>
|
|
315
|
+
spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
|
|
316
|
+
|
|
317
|
+
// start a warm, long-lived container with the plugin mounted
|
|
318
|
+
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
319
|
+
log.step("starting C++ dev container…");
|
|
320
|
+
const up = docker(
|
|
321
|
+
["run", "-d", "--name", container, "-v", `${root}:/plugin`, "-w", "/plugin", CPP_DEV_IMAGE, "sleep", "infinity"],
|
|
322
|
+
{ stdio: "inherit" },
|
|
323
|
+
);
|
|
324
|
+
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
325
|
+
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
|
|
326
|
+
|
|
327
|
+
const build = () => {
|
|
328
|
+
log.step("cmake build…");
|
|
329
|
+
return docker(["exec", container, "bash", "-lc",
|
|
330
|
+
"cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Debug && cmake --build cpp/build -j"]);
|
|
331
|
+
};
|
|
332
|
+
build();
|
|
333
|
+
|
|
334
|
+
let child = null;
|
|
335
|
+
const startBin = () => {
|
|
336
|
+
if (child) child.kill();
|
|
337
|
+
log.step(`run cpp/build/${entry}`);
|
|
338
|
+
child = spawn(
|
|
339
|
+
"docker",
|
|
340
|
+
["exec", ...envFlags, container, "bash", "-lc", `./cpp/build/${entry}`],
|
|
341
|
+
{ cwd: root, stdio: "inherit" },
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
startBin();
|
|
345
|
+
|
|
346
|
+
// rebuild + restart on C++ source changes; ignore the build/ output dir.
|
|
347
|
+
let timer = null;
|
|
348
|
+
const onChange = (_event, filename) => {
|
|
349
|
+
if (filename && String(filename).startsWith("build")) return;
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
timer = setTimeout(() => {
|
|
352
|
+
log.info("source changed — rebuilding…");
|
|
353
|
+
const r = build();
|
|
354
|
+
if (r.status === 0) startBin();
|
|
355
|
+
else log.warn("build failed — keeping previous binary");
|
|
356
|
+
}, 300);
|
|
357
|
+
};
|
|
358
|
+
const cppDir = resolve(root, "cpp");
|
|
359
|
+
if (existsSync(cppDir)) watch(cppDir, { recursive: true }, onChange);
|
|
360
|
+
|
|
361
|
+
const cleanup = () => {
|
|
362
|
+
stopPortMonitor();
|
|
363
|
+
if (child) child.kill();
|
|
364
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
365
|
+
exitAfterCleanup(0);
|
|
366
|
+
};
|
|
367
|
+
process.on("SIGINT", cleanup);
|
|
368
|
+
process.on("SIGTERM", cleanup);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function warnManifest(root) {
|
|
372
|
+
const manifest = readManifest(root);
|
|
373
|
+
if (!manifest || Object.keys(manifest).length === 0) return;
|
|
374
|
+
const errors = validateManifest(manifest);
|
|
375
|
+
for (const e of errors) log.warn(`manifest.json: ${e}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function runDevBack(args = []) {
|
|
129
379
|
const root = findPluginRoot();
|
|
380
|
+
warnManifest(root);
|
|
381
|
+
const portRanges = assertManifestPorts(root);
|
|
382
|
+
if (portRanges.length > 0 && process.env.MUSCAT_PK_PORTS_PRECHECKED !== "1")
|
|
383
|
+
log.step(`plugin backend ports declared: ${formatPortRanges(portRanges)}`);
|
|
384
|
+
ensureDocker();
|
|
385
|
+
const { robotIp, robotToken } = await promptRobotConnection(args);
|
|
386
|
+
if (robotIp) process.env.MUSCAT_ROBOT_HOST = robotIp;
|
|
387
|
+
if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
|
|
388
|
+
// Standalone `dev:back` against a remote robot needs the Zenoh reverse-path
|
|
389
|
+
// advertise host/port too (idempotent — `muscat-pk dev` already set these).
|
|
390
|
+
await ensureRemoteZenohEnv(robotIp, { log });
|
|
130
391
|
const type = manifestRuntime(root);
|
|
131
|
-
if (type === "ros2") return runRos2(root);
|
|
132
|
-
return
|
|
392
|
+
if (type === "ros2") return await runRos2(root, portRanges);
|
|
393
|
+
if (type === "cpp") return await runCpp(root, portRanges);
|
|
394
|
+
return await runPython(root, portRanges);
|
|
133
395
|
}
|