@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,107 @@
|
|
|
1
|
+
// Rules for AI coding agents working on a Muscat plugin. One source for both the MCP server's
|
|
2
|
+
// instructions (mcp.mjs) and the AGENTS.md / CLAUDE.md that `muscat-pk new` scaffolds, so the
|
|
3
|
+
// two never drift. Kept free of heavy imports: `new` loads it too.
|
|
4
|
+
|
|
5
|
+
// Native elements and the plugin-ui component to use instead.
|
|
6
|
+
export const NATIVE_TO_RB = [
|
|
7
|
+
["select / dropdown", "RBSelectBox + Option (children)"],
|
|
8
|
+
["input type=text", "RBTextInput (RBTextField for label + helper text)"],
|
|
9
|
+
["input type=number", "RBSideNumberInput"],
|
|
10
|
+
["input type=range", "RBSlider (RBMultiRangeInput for a range)"],
|
|
11
|
+
["input type=checkbox", "RBCheckBox"],
|
|
12
|
+
["input type=radio", "RBRadioButton / RBRadioBox"],
|
|
13
|
+
["toggle / switch", "RBSwitch (RBSwitchField with a label)"],
|
|
14
|
+
["input type=date", "RBDateInput / RBRangeDateInput"],
|
|
15
|
+
["textarea", "RBTextArea"],
|
|
16
|
+
["button", "RBLabelButton (text), RBIconButton (icon only)"],
|
|
17
|
+
["label", "RBTextLabel"],
|
|
18
|
+
["table", "RBTable + RBTableRow + RBTdColumn"],
|
|
19
|
+
["dialog / modal", "RBDialog… / RBModal; rb.confirm() / rb.alert() / rb.prompt() for simple prompts"],
|
|
20
|
+
["tabs", "RBTabs + RBTabsList + RBTabsTrigger + RBTabsContent"],
|
|
21
|
+
["tooltip (title=)", "RBTooltip"],
|
|
22
|
+
["hr", "RBDivider"],
|
|
23
|
+
["loading spinner", "RBSpinner"],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const UI_RULES = [
|
|
27
|
+
"Use plugin-ui components, never native form/control elements (<select>, <input>, <button>, <textarea>, <table>, <dialog>) or another UI library. Layout-only elements (<div>, <span>, <p>) are fine; prefer HStack/VStack for stacks.",
|
|
28
|
+
"Before writing a component, call get_component_example (working usage) or get_component_props (its type). Names are exact; when one is not found, use the suggestion returned, not a native element.",
|
|
29
|
+
"Colors and type come from tokens (bg-bg-*, text-fg-*, typo-*), not hex values or arbitrary Tailwind colors.",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// plugin-ui 1.x replaced the remote-dom components and the connect() bootstrap. The project check
|
|
33
|
+
// (update, MCP check_project) flags these; the migration rules below tell an agent how to fix them.
|
|
34
|
+
export const LEGACY_SUBPATHS = {
|
|
35
|
+
connect: "removed — drop `connect(App)` and `export default App` instead (the host renders the entry's default export)",
|
|
36
|
+
elements: "removed — use the RB* components from @rainbow-robotics/plugin-ui/react",
|
|
37
|
+
"dist-browser": "removed — import from @rainbow-robotics/plugin-ui/react",
|
|
38
|
+
};
|
|
39
|
+
export const LEGACY_COMPONENTS = {
|
|
40
|
+
Slider: "RBSlider",
|
|
41
|
+
Button: "RBLabelButton",
|
|
42
|
+
IconButton: "RBIconButton",
|
|
43
|
+
Text: "a plain element or RBTextLabel",
|
|
44
|
+
TextInput: "RBTextInput / RBTextField",
|
|
45
|
+
Switch: "RBSwitch",
|
|
46
|
+
Select: "RBSelectBox",
|
|
47
|
+
};
|
|
48
|
+
const MIGRATION_RULES = [
|
|
49
|
+
"After `muscat-pk update`, or when the UI fails to build or the preview says \"no default export\", call check_project and fix every item it lists. Call it again until it reports no issues.",
|
|
50
|
+
"Each top-level ui/*.tsx is an entry and ends in `export default App`. The old `connect(App)` bootstrap and @rainbow-robotics/plugin-ui/connect are gone.",
|
|
51
|
+
"vite.config.ts uses the muscat() preset (`plugins: [muscat()]` from @rainbow-robotics/muscat-plugin-kit/vite); keep any other plugins the project adds. A hand-written config drops the entry's default export from the build.",
|
|
52
|
+
"rb.toast takes one object: rb.toast({ message, type }).",
|
|
53
|
+
`Old components → new: ${Object.entries(LEGACY_COMPONENTS).map(([o, n]) => `${o} → ${n}`).join(", ")}. Their props differ (no event.detail) — call get_component_example for the new component before rewriting.`,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Claude Code reads this to start the MCP server; `muscat-pk` hands off to the project's local kit.
|
|
57
|
+
export const MCP_JSON = `${JSON.stringify({ mcpServers: { muscat: { command: "muscat-pk", args: ["mcp"] } } }, null, 2)}\n`;
|
|
58
|
+
|
|
59
|
+
const nativeTable = () => NATIVE_TO_RB.map(([el, rb]) => `- ${el}: ${rb}`).join("\n");
|
|
60
|
+
|
|
61
|
+
export const SERVER_INSTRUCTIONS = `Muscat plugin UI (ui/*.tsx) is built only from @rainbow-robotics/plugin-ui/react, styled with the design-token Tailwind classes (list_design_tokens).
|
|
62
|
+
|
|
63
|
+
Rules for UI work:
|
|
64
|
+
${UI_RULES.map((r) => `- ${r}`).join("\n")}
|
|
65
|
+
|
|
66
|
+
Native element -> plugin-ui component:
|
|
67
|
+
${nativeTable()}
|
|
68
|
+
|
|
69
|
+
Upgrading a project made with an older kit:
|
|
70
|
+
${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
|
|
71
|
+
|
|
72
|
+
Backend, manifest and SDK questions: search_rb_sdk, list_rb_sdk, get_rb_sdk_api.`;
|
|
73
|
+
|
|
74
|
+
// AGENTS.md (Codex, Cursor, Gemini CLI, Copilot …). CLAUDE.md imports it with `@AGENTS.md`.
|
|
75
|
+
// The backend type is not named: `muscat-pk type change` would leave it stale.
|
|
76
|
+
export function agentsMd({ pluginName }) {
|
|
77
|
+
return `# ${pluginName} — Muscat plugin
|
|
78
|
+
|
|
79
|
+
A Muscat plugin: \`manifest.json\`, a React UI in \`ui/*.tsx\`, and a backend whose type is \`runtime\` in \`manifest.json\`.
|
|
80
|
+
\`README.md\` is the full plugin-author guide. The \`muscat\` MCP server (\`.mcp.json\`, \`muscat-pk mcp\`) looks up
|
|
81
|
+
components, design tokens and the SDK: use it instead of guessing names or props.
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
- \`muscat-pk dev\`: UI preview + backend with hot reload (\`dev:ui\` / \`dev:back\` for one side)
|
|
86
|
+
- \`muscat-pk build\`: validates \`manifest.json\`, builds \`dist/\` and the distributable ZIP
|
|
87
|
+
|
|
88
|
+
## UI rules
|
|
89
|
+
|
|
90
|
+
UI is built only from \`@rainbow-robotics/plugin-ui/react\`, styled with design-token Tailwind classes.
|
|
91
|
+
|
|
92
|
+
${UI_RULES.map((r) => `- ${r}`).join("\n")}
|
|
93
|
+
|
|
94
|
+
Native element → plugin-ui component:
|
|
95
|
+
|
|
96
|
+
${nativeTable()}
|
|
97
|
+
|
|
98
|
+
## Upgrading from an older kit
|
|
99
|
+
|
|
100
|
+
\`muscat-pk update\` bumps the kit, plugin-ui and preview-host and lists what the project must change.
|
|
101
|
+
The MCP tool \`check_project\` runs the same check (known old APIs + \`tsc --noEmit\`).
|
|
102
|
+
|
|
103
|
+
${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const CLAUDE_MD = "@AGENTS.md\n";
|
package/cli/src/build.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// muscat-pk build — bundle
|
|
2
|
-
// manifest.json and assets/ for distribution
|
|
1
|
+
// muscat-pk build — bundle ui/content.tsx into dist/content.js, stage
|
|
2
|
+
// manifest.json and assets/ for distribution, and zip dist/ into a
|
|
3
|
+
// distributable ZIP.
|
|
3
4
|
//
|
|
4
5
|
// sandbox.html is generated by the CLI — plugin authors never manage it.
|
|
5
6
|
// The import map always reflects the current plugin-ui entry set, so users
|
|
@@ -8,41 +9,143 @@
|
|
|
8
9
|
// The plugin author's `@rainbow-robotics/plugin-ui` and React imports are left external.
|
|
9
10
|
// Muscat/robot and preview-host provide those modules through a sandbox import
|
|
10
11
|
// map, so plugin ZIPs do not carry the UI runtime.
|
|
11
|
-
import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
-
import {
|
|
12
|
+
import { chmodSync, cpSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { basename, dirname, resolve } from "node:path";
|
|
15
|
+
import { createInterface } from "node:readline/promises";
|
|
13
16
|
import { spawnSync } from "node:child_process";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
17
|
+
import { ensureDocker, findPluginRoot, hasBin, humanSize, log, parseArgs, prompt } from "./util.mjs";
|
|
18
|
+
import { assertManifest, manifestRuntime } from "./manifest.mjs";
|
|
19
|
+
import { writeZip } from "./zip.mjs";
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
// Find a usable JS runtime for running vite as a subprocess.
|
|
22
|
+
// In binary (Bun) mode, try `bun` first (it can run vite directly), then node.
|
|
23
|
+
// In node mode, use the current process.
|
|
24
|
+
function findJsRuntime() {
|
|
25
|
+
if (typeof Bun === "undefined") return { bin: process.execPath, isBun: false };
|
|
26
|
+
for (const c of ["bun", "node", "nodejs"]) {
|
|
27
|
+
if (hasBin(c)) return { bin: c, isBun: c === "bun" };
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
// Resolve vite bin path from the plugin root, hoisting-aware.
|
|
33
|
+
function findViteBin(pluginRoot) {
|
|
24
34
|
try {
|
|
25
|
-
|
|
35
|
+
const req = createRequire(resolve(pluginRoot, "package.json"));
|
|
36
|
+
const vitePkg = req.resolve("vite/package.json");
|
|
37
|
+
return resolve(dirname(vitePkg), "bin", "vite.js");
|
|
26
38
|
} catch {
|
|
27
|
-
return
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Run vite build in the plugin project. Without a JS runtime (binary CLI, no Node.js) it asks
|
|
44
|
+
// whether to build the plugin without its UI and returns false on yes; throws on no or if vite is missing.
|
|
45
|
+
async function runViteBuild(pluginRoot, { mode = "production" } = {}) {
|
|
46
|
+
const runtime = findJsRuntime();
|
|
47
|
+
if (!runtime) {
|
|
48
|
+
log.warn("Node.js 20+ (or Bun) not found — the UI cannot be built and will not be in the plugin.");
|
|
49
|
+
const answer = await prompt(" Build the plugin without its UI? [y/N]: ", "n");
|
|
50
|
+
if (answer.toLowerCase() !== "y")
|
|
51
|
+
throw new Error("build cancelled. Install Node.js 20+ (https://nodejs.org), run `npm install` in the plugin, then build again.");
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const viteBin = findViteBin(pluginRoot);
|
|
55
|
+
if (!viteBin || !existsSync(viteBin)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"vite not found in this plugin's node_modules.\n" +
|
|
58
|
+
` Run: npm install (inside ${pluginRoot})`,
|
|
59
|
+
);
|
|
28
60
|
}
|
|
61
|
+
|
|
62
|
+
log.step(`vite build (mode: ${mode})`);
|
|
63
|
+
// bun can run vite directly; node needs the explicit bin path
|
|
64
|
+
const args = runtime.isBun ? ["run", viteBin, "build", "--mode", mode]
|
|
65
|
+
: [viteBin, "build", "--mode", mode];
|
|
66
|
+
const r = spawnSync(runtime.bin, args, { cwd: pluginRoot, stdio: "inherit" });
|
|
67
|
+
if (r.status !== 0) throw new Error("vite build failed");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// function isMainBranch() {
|
|
72
|
+
// const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
|
|
73
|
+
// const branch = r.stdout?.trim();
|
|
74
|
+
// return branch === "main" || branch === "master";
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
// For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
|
|
78
|
+
// rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
|
|
79
|
+
// rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
|
|
80
|
+
// Cross-arch C++ builds run inside an emulated container. Docker Desktop ships the
|
|
81
|
+
// emulation; native Linux needs binfmt/QEMU — check up front so the error says so.
|
|
82
|
+
function assertCanBuildArch(arch, dockerPlatform) {
|
|
83
|
+
const info = spawnSync("docker", ["info", "--format", "{{.Architecture}}"], { encoding: "utf8" });
|
|
84
|
+
const host = { x86_64: "amd64", aarch64: "arm64", arm64: "arm64" }[(info.stdout || "").trim()] ?? "";
|
|
85
|
+
if (arch === host) return;
|
|
86
|
+
const r = spawnSync("docker", ["run", "--rm", "--platform", dockerPlatform, "ubuntu:24.04", "true"], { stdio: "ignore" });
|
|
87
|
+
if (r.status === 0) return;
|
|
88
|
+
throw new Error(
|
|
89
|
+
`can't build ${arch} on this ${host || "machine"}: Docker has no ${arch} emulation.\n` +
|
|
90
|
+
` enable it once: docker run --privileged --rm tonistiigi/binfmt --install ${arch}\n` +
|
|
91
|
+
` or build only this machine's arch: muscat-pk build --arch ${host || "<arch>"}`,
|
|
92
|
+
);
|
|
29
93
|
}
|
|
30
94
|
|
|
31
|
-
|
|
32
|
-
// container so the shipped ZIP carries ros2_ws/install. Real (non-symlink) build
|
|
33
|
-
// → relocatable on the robot. Skipped (with a warning) if Docker is unavailable.
|
|
34
|
-
export async function exportRos2Build(root) {
|
|
95
|
+
export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
35
96
|
if (manifestRuntime(root) !== "ros2") return;
|
|
36
|
-
const { assertDockerReady, ensureImage, colconBuild } = await import("./ros2.mjs");
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
97
|
+
const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
|
|
98
|
+
assertDockerReady();
|
|
99
|
+
|
|
100
|
+
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
|
|
101
|
+
if (manifest.runtime?.rosClient === "rclcpp") {
|
|
102
|
+
// C++ ROS2: compile per arch so each robot picks the right install-{arch}/
|
|
103
|
+
for (const arch of archs) {
|
|
104
|
+
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
105
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
106
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
107
|
+
await ensureImage(`${ROS2_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
108
|
+
colconBuild(root, { symlink: false, arch });
|
|
109
|
+
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// rclpy / pure Python: arch-agnostic, build once
|
|
113
|
+
await ensureImage();
|
|
114
|
+
colconBuild(root, { symlink: false });
|
|
115
|
+
log.ok("dist/ros2_ws/install/ ready");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
|
|
120
|
+
// and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
|
|
121
|
+
// robot-compatible executables.
|
|
122
|
+
export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"]) {
|
|
123
|
+
if (manifestRuntime(root) !== "cpp") return;
|
|
124
|
+
const { assertDockerReady, ensureImage, cmakeBuild, CPP_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./cpp.mjs");
|
|
125
|
+
assertDockerReady();
|
|
126
|
+
|
|
127
|
+
const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
|
|
128
|
+
if (!entry) throw new Error("manifest.json runtime.entry is required for cpp (the built executable name)");
|
|
129
|
+
|
|
130
|
+
for (const arch of archs) {
|
|
131
|
+
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
132
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
133
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
134
|
+
await ensureImage(`${CPP_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
135
|
+
cmakeBuild(root, { buildType: "Release", arch });
|
|
136
|
+
const built = resolve(root, `cpp/build-${arch}`, entry);
|
|
137
|
+
if (!existsSync(built)) {
|
|
138
|
+
throw new Error(`cpp/build-${arch}/${entry} not found — the CMake target name must equal runtime.entry`);
|
|
139
|
+
}
|
|
140
|
+
const archDir = resolve(distDir, arch);
|
|
141
|
+
mkdirSync(archDir, { recursive: true });
|
|
142
|
+
const dest = resolve(archDir, entry);
|
|
143
|
+
copyFileSync(built, dest);
|
|
144
|
+
chmodSync(dest, 0o755);
|
|
145
|
+
const libDir = resolve(root, `cpp/build-${arch}`, "lib");
|
|
146
|
+
if (existsSync(libDir)) cpSync(libDir, resolve(archDir, "lib"), { recursive: true, verbatimSymlinks: true });
|
|
147
|
+
log.ok(`cpp binary → dist/${arch}/${entry}${existsSync(libDir) ? " (+ lib/)" : ""}`);
|
|
42
148
|
}
|
|
43
|
-
ensureImage();
|
|
44
|
-
colconBuild(root, { symlink: false });
|
|
45
|
-
log.ok("ros2_ws/install ready (will be included in the ZIP)");
|
|
46
149
|
}
|
|
47
150
|
|
|
48
151
|
const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
|
|
@@ -55,7 +158,7 @@ function isPrerelease(version) {
|
|
|
55
158
|
// stableOnly=true → skips pre-release (dev/a/b/rc) versions.
|
|
56
159
|
async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
|
|
57
160
|
const res = await fetch(`https://pypi.org/pypi/${pkg}/json`, {
|
|
58
|
-
signal:
|
|
161
|
+
signal: new AbortController().signal.timeout(5000),
|
|
59
162
|
});
|
|
60
163
|
if (!res.ok) throw new Error(`pypi ${pkg}: ${res.status}`);
|
|
61
164
|
const data = await res.json();
|
|
@@ -79,7 +182,7 @@ async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
|
|
|
79
182
|
}
|
|
80
183
|
|
|
81
184
|
// Pin unpinned (no version specifier) entries in requirements.txt to the
|
|
82
|
-
// latest PyPI release. Runs on explicit `build
|
|
185
|
+
// latest PyPI release. Runs on explicit `build` only — keeps the shipped
|
|
83
186
|
// plugin reproducible without churning the source file on every dev rebuild.
|
|
84
187
|
// stableOnly=true is used on main-branch builds to avoid shipping dev versions.
|
|
85
188
|
export async function pinRequirements(root, { stableOnly = false } = {}) {
|
|
@@ -109,90 +212,227 @@ export async function pinRequirements(root, { stableOnly = false } = {}) {
|
|
|
109
212
|
if (pinned > 0) writeFileSync(reqPath, out.join("\n"));
|
|
110
213
|
}
|
|
111
214
|
|
|
112
|
-
const SANDBOX_HTML = `<!doctype html>
|
|
113
|
-
<html lang="en">
|
|
114
|
-
<head>
|
|
115
|
-
<meta charset="UTF-8" />
|
|
116
|
-
<title>plugin sandbox</title>
|
|
117
|
-
<script type="importmap">
|
|
118
|
-
{
|
|
119
|
-
"imports": {
|
|
120
|
-
"@rainbow-robotics/plugin-ui/connect": "/plugin-runtime/@rainbow-robotics/plugin-ui/connect.js",
|
|
121
|
-
"@rainbow-robotics/plugin-ui/react": "/plugin-runtime/@rainbow-robotics/plugin-ui/react.js",
|
|
122
|
-
"@rainbow-robotics/plugin-ui/sdk": "/plugin-runtime/@rainbow-robotics/plugin-ui/sdk.js",
|
|
123
|
-
"@rainbow-robotics/plugin-ui/elements": "/plugin-runtime/@rainbow-robotics/plugin-ui/elements.js",
|
|
124
|
-
"react": "/plugin-runtime/vendor/react.js",
|
|
125
|
-
"react/jsx-runtime": "/plugin-runtime/vendor/react-jsx-runtime.js",
|
|
126
|
-
"react/jsx-dev-runtime": "/plugin-runtime/vendor/react-jsx-dev-runtime.js",
|
|
127
|
-
"react-dom/client": "/plugin-runtime/vendor/react-dom-client.js"
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
</script>
|
|
131
|
-
</head>
|
|
132
|
-
<body>
|
|
133
|
-
<script type="module" src="./content.js"></script>
|
|
134
|
-
</body>
|
|
135
|
-
</html>
|
|
136
|
-
`;
|
|
137
|
-
|
|
138
|
-
const HOST_PROVIDED_EXTERNALS = [
|
|
139
|
-
"@rainbow-robotics/plugin-ui",
|
|
140
|
-
"@rainbow-robotics/plugin-ui/*",
|
|
141
|
-
"react",
|
|
142
|
-
"react/jsx-runtime",
|
|
143
|
-
"react/jsx-dev-runtime",
|
|
144
|
-
"react-dom",
|
|
145
|
-
"react-dom/*",
|
|
146
|
-
];
|
|
147
|
-
|
|
148
215
|
export async function buildPlugin({ root, minify = false } = {}) {
|
|
149
216
|
const pluginRoot = root ?? findPluginRoot();
|
|
150
|
-
|
|
151
|
-
if (!existsSync(entry)) {
|
|
152
|
-
throw new Error(`missing entry: ${entry}`);
|
|
153
|
-
}
|
|
217
|
+
assertManifest(pluginRoot);
|
|
154
218
|
const distDir = resolve(pluginRoot, "dist");
|
|
155
|
-
|
|
219
|
+
try {
|
|
220
|
+
rmSync(distDir, { recursive: true, force: true });
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (err?.code !== "EACCES" && err?.code !== "EPERM") throw err;
|
|
223
|
+
// 이전 버전이 컨테이너를 root로 돌려 남긴 dist/ — 한 번만 정리하면 된다
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Permission denied when deleting dist/ (${err.path ?? distDir}).\n` +
|
|
226
|
+
`The previous build left files owned by root. Please clean it up once:\n` +
|
|
227
|
+
` sudo rm -rf ${distDir}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
156
230
|
mkdirSync(distDir, { recursive: true });
|
|
157
231
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
232
|
+
const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
|
|
233
|
+
const uiDir = resolve(pluginRoot, "ui");
|
|
234
|
+
const declaredUiEntries = [...new Set(
|
|
235
|
+
(manifest.contributes ?? []).map((c) => c.ui).filter(Boolean)
|
|
236
|
+
)];
|
|
237
|
+
// Validate each declared UI entry has a matching source file
|
|
238
|
+
for (const entry of declaredUiEntries) {
|
|
239
|
+
const exists = [".tsx", ".ts", ".jsx", ".js"].some(
|
|
240
|
+
(ext) => existsSync(resolve(uiDir, entry + ext))
|
|
241
|
+
);
|
|
242
|
+
if (!exists)
|
|
243
|
+
throw new Error(
|
|
244
|
+
`contributes declares ui "${entry}" but ui/${entry}.tsx (or .ts/.jsx/.js) does not exist.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const hasUiFiles = existsSync(uiDir) && readdirSync(uiDir).some((f) => /\.[tj]sx?$/.test(f));
|
|
248
|
+
// A backend-only plugin (no contributes) ships no UI, so leftover ui/ files are not built either.
|
|
249
|
+
let uiBuilt = true;
|
|
250
|
+
if (declaredUiEntries.length > 0 || (hasUiFiles && manifest.contributes?.length > 0)) {
|
|
251
|
+
uiBuilt = await runViteBuild(pluginRoot, { mode: minify ? "production" : "development" });
|
|
252
|
+
}
|
|
171
253
|
|
|
172
254
|
// Stage distribution files.
|
|
173
255
|
const manifestSrc = resolve(pluginRoot, "manifest.json");
|
|
174
256
|
if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(distDir, "manifest.json"));
|
|
257
|
+
// Built without the UI: ship it as a backend-only plugin so the host lists no screen it cannot load.
|
|
258
|
+
if (!uiBuilt) {
|
|
259
|
+
const { contributes, ...backendOnly } = manifest;
|
|
260
|
+
writeFileSync(resolve(distDir, "manifest.json"), JSON.stringify(backendOnly, null, 2) + "\n");
|
|
261
|
+
log.warn(`packaged without UI: dropped ${contributes.length} screen area(s) from dist/manifest.json`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// No sandbox.html: the host dynamically imports dist/{ui}.js directly as a
|
|
265
|
+
// plain ESM module (see @rainbow-robotics/plugin-ui/host) — no iframe.
|
|
175
266
|
|
|
176
|
-
// Generate sandbox.html — plugin authors don't manage this file.
|
|
177
|
-
writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
|
|
178
|
-
log.step("generated dist/sandbox.html");
|
|
179
267
|
const assetsSrc = resolve(pluginRoot, "assets");
|
|
180
268
|
if (existsSync(assetsSrc)) {
|
|
181
269
|
cpSync(assetsSrc, resolve(distDir, "assets"), { recursive: true });
|
|
182
270
|
}
|
|
183
271
|
|
|
272
|
+
// README.md, README.[lang].md, CHANGELOG.md → dist/
|
|
273
|
+
for (const f of readdirSync(pluginRoot)) {
|
|
274
|
+
if (/^README(\.[a-z]{2}(-[A-Z]{2})?)?\.md$/i.test(f) || /^CHANGELOG\.md$/i.test(f)) {
|
|
275
|
+
copyFileSync(resolve(pluginRoot, f), resolve(distDir, f));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Backend runtime files — copy entire py/ into dist/, then requirements.txt from root.
|
|
280
|
+
const pySrc = resolve(pluginRoot, "py");
|
|
281
|
+
if (existsSync(pySrc)) {
|
|
282
|
+
cpSync(pySrc, distDir, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
const rootReqs = resolve(pluginRoot, "requirements.txt");
|
|
285
|
+
if (existsSync(rootReqs)) {
|
|
286
|
+
copyFileSync(rootReqs, resolve(distDir, "requirements.txt"));
|
|
287
|
+
}
|
|
288
|
+
|
|
184
289
|
log.ok(`built → ${distDir}`);
|
|
185
|
-
return { pluginRoot, distDir };
|
|
290
|
+
return { pluginRoot, distDir, uiBuilt };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function dirSize(dir, skip = []) {
|
|
294
|
+
if (!existsSync(dir)) return 0;
|
|
295
|
+
let total = 0;
|
|
296
|
+
for (const entry of readdirSync(dir)) {
|
|
297
|
+
if (skip.includes(entry)) continue;
|
|
298
|
+
const full = resolve(dir, entry);
|
|
299
|
+
const st = statSync(full);
|
|
300
|
+
total += st.isDirectory() ? dirSize(full) : st.size;
|
|
301
|
+
}
|
|
302
|
+
return total;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Read the uncompressed byte total of a .whl (zip) file by parsing its central
|
|
306
|
+
// directory — no external library needed, no actual extraction.
|
|
307
|
+
|
|
308
|
+
// installedSize = "이 플러그인 하나를 지우면 되찾는 용량"
|
|
309
|
+
// = dist/(wheels/ 제외)
|
|
310
|
+
// ros2_ws/install* 은 이제 dist/ 안에 있으므로 별도 합산 불필요.
|
|
311
|
+
// 휠은 로봇 공유 스토어(store/py/)에 언팩되므로 포함하지 않는다.
|
|
312
|
+
function calcInstalledSize(pluginRoot) {
|
|
313
|
+
const distDir = resolve(pluginRoot, "dist");
|
|
314
|
+
|
|
315
|
+
const installedSize = dirSize(distDir, ["wheels"]);
|
|
316
|
+
|
|
317
|
+
// Write to root manifest.json
|
|
318
|
+
const rootManifestPath = resolve(pluginRoot, "manifest.json");
|
|
319
|
+
if (existsSync(rootManifestPath)) {
|
|
320
|
+
const m = JSON.parse(readFileSync(rootManifestPath, "utf8"));
|
|
321
|
+
m.installedSize = installedSize;
|
|
322
|
+
writeFileSync(rootManifestPath, JSON.stringify(m, null, 2) + "\n");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Re-stage dist/manifest.json so the ZIP carries the updated value
|
|
326
|
+
const distManifestPath = resolve(distDir, "manifest.json");
|
|
327
|
+
if (existsSync(distManifestPath)) {
|
|
328
|
+
const m = JSON.parse(readFileSync(distManifestPath, "utf8"));
|
|
329
|
+
m.installedSize = installedSize;
|
|
330
|
+
writeFileSync(distManifestPath, JSON.stringify(m, null, 2) + "\n");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
log.ok(`installedSize → ${humanSize(installedSize)}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Zip dist/ into a distributable ZIP with a size report.
|
|
337
|
+
// ros2_ws/install* is now inside dist/, so only "dist" is needed.
|
|
338
|
+
function packZip(pluginRoot) {
|
|
339
|
+
const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
|
|
340
|
+
const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
|
|
341
|
+
const zipPath = resolve(pluginRoot, zipName);
|
|
342
|
+
rmSync(zipPath, { force: true });
|
|
343
|
+
|
|
344
|
+
const entries = ["dist"];
|
|
345
|
+
|
|
346
|
+
log.step(`packing ${entries.join(", ")} → ${zipName}`);
|
|
347
|
+
// In-process, not the `zip` CLI: Windows has none, and it keeps the robot's executable bits (zip.mjs).
|
|
348
|
+
writeZip(zipPath, entries, pluginRoot);
|
|
349
|
+
|
|
350
|
+
const zipBytes = statSync(zipPath).size;
|
|
351
|
+
log.ok(`${zipName} ${humanSize(zipBytes)}`);
|
|
352
|
+
for (const e of entries) {
|
|
353
|
+
const full = resolve(pluginRoot, e);
|
|
354
|
+
const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
|
|
355
|
+
log.info(` ├─ ${e} ${humanSize(size)}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Ask which target arch(es) to build for.
|
|
361
|
+
* Non-TTY (CI): both, same as the prompt default.
|
|
362
|
+
* @returns {Promise<string[]>} e.g. ['amd64'] | ['arm64'] | ['amd64','arm64']
|
|
363
|
+
*/
|
|
364
|
+
async function promptArch(args = []) {
|
|
365
|
+
const archFlag = parseArgs(args).arch ?? null;
|
|
366
|
+
if (archFlag) {
|
|
367
|
+
if (archFlag === "both") return ["amd64", "arm64"];
|
|
368
|
+
if (archFlag === "amd64" || archFlag === "x86_64") return ["amd64"];
|
|
369
|
+
if (archFlag === "arm64" || archFlag === "aarch64") return ["arm64"];
|
|
370
|
+
throw new Error(`unknown --arch value '${archFlag}' (valid: amd64, arm64, both)`);
|
|
371
|
+
}
|
|
372
|
+
// Same default as the prompt: the target is the robot, not this machine
|
|
373
|
+
// (node's os.arch() is x64 under Rosetta on an arm64 Mac anyway).
|
|
374
|
+
if (!process.stdin.isTTY) {
|
|
375
|
+
log.info("arch auto-selected (non-TTY): both");
|
|
376
|
+
return ["amd64", "arm64"];
|
|
377
|
+
}
|
|
378
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
379
|
+
process.stdout.write(
|
|
380
|
+
"\n Target arch:\n" +
|
|
381
|
+
" 1) amd64\n" +
|
|
382
|
+
" 2) arm64\n" +
|
|
383
|
+
" 3) Both (fat ZIP)\n",
|
|
384
|
+
);
|
|
385
|
+
const answer = (await rl.question(" Select [1/2/3, default: 3]: ")).trim() || "3";
|
|
386
|
+
rl.close();
|
|
387
|
+
if (answer === "1") return ["amd64"];
|
|
388
|
+
if (answer === "2") return ["arm64"];
|
|
389
|
+
return ["amd64", "arm64"];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function promptVersion(root) {
|
|
393
|
+
if (!process.stdin.isTTY) return;
|
|
394
|
+
const manifestPath = resolve(root, "manifest.json");
|
|
395
|
+
if (!existsSync(manifestPath)) return;
|
|
396
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
397
|
+
const current = manifest.version ?? "1.0.0";
|
|
398
|
+
|
|
399
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
400
|
+
const input = (await rl.question(` Version (current: ${current}, leave blank to keep): `)).trim();
|
|
401
|
+
rl.close();
|
|
402
|
+
|
|
403
|
+
if (!input) return;
|
|
404
|
+
manifest.version = input;
|
|
405
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
406
|
+
log.ok(`manifest.json version → ${input}`);
|
|
186
407
|
}
|
|
187
408
|
|
|
188
409
|
export async function runBuild(args) {
|
|
189
|
-
const minify = args.includes("--minify");
|
|
190
410
|
const root = findPluginRoot();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
411
|
+
const rt = manifestRuntime(root);
|
|
412
|
+
|
|
413
|
+
// All runtimes except "none" need Docker
|
|
414
|
+
if (rt !== "none") ensureDocker();
|
|
415
|
+
|
|
416
|
+
await promptVersion(root);
|
|
417
|
+
const archs = await promptArch(args);
|
|
418
|
+
// Drop a previous ZIP of this version so a failed build can't leave a stale one to upload.
|
|
419
|
+
const version = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).version || "0.0.0";
|
|
420
|
+
rmSync(resolve(root, `${basename(root)}-${version}.zip`), { force: true });
|
|
421
|
+
|
|
422
|
+
const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
|
|
423
|
+
|
|
424
|
+
// python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
|
|
425
|
+
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
|
|
426
|
+
const needsLock =
|
|
427
|
+
manifest.type === "python" ||
|
|
428
|
+
(manifest.type === "ros2" && manifest.rosClient === "rclpy");
|
|
429
|
+
if (needsLock) {
|
|
430
|
+
const { lockAndVendor } = await import("./python.mjs");
|
|
431
|
+
await lockAndVendor(pluginRoot, archs);
|
|
195
432
|
}
|
|
196
|
-
|
|
197
|
-
await exportRos2Build(root);
|
|
433
|
+
|
|
434
|
+
await exportRos2Build(root, archs);
|
|
435
|
+
await exportCppBuild(root, distDir, archs);
|
|
436
|
+
calcInstalledSize(pluginRoot);
|
|
437
|
+
packZip(pluginRoot);
|
|
198
438
|
}
|
|
@@ -1,28 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (stat.isDirectory()) {
|
|
14
|
-
if (entry === "dist" || entry === "runtime" || entry === "templates") {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
yield* mjsFiles(full);
|
|
18
|
-
} else if (entry.endsWith(".mjs")) {
|
|
19
|
-
yield full;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
for (const dir of roots) {
|
|
25
|
-
for (const file of mjsFiles(dir)) {
|
|
26
|
-
execFileSync(process.execPath, ["--check", file], { stdio: "inherit" });
|
|
27
|
-
}
|
|
7
|
+
for (const dir of ["cli", "preview-host"]) {
|
|
8
|
+
if (!existsSync(resolve(root, dir))) continue;
|
|
9
|
+
execSync(
|
|
10
|
+
`find ${dir} -name '*.mjs' ! -path '*/dist/*' ! -path '*/runtime/*' ! -path '*/templates/*' -exec node --check {} +`,
|
|
11
|
+
{ cwd: root, stdio: "inherit" },
|
|
12
|
+
);
|
|
28
13
|
}
|