From 8092c4b1f89a17b4355e12288b1b7973d93a4f55 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 1 Jun 2026 13:43:09 +0800 Subject: [PATCH] =?UTF-8?q?"=E6=B7=BB=E5=8A=A0=E5=89=8D=E7=AB=AF=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=92=8C=E8=BF=90=E8=A1=8C=E4=BB=A3=E7=A0=81=E6=A8=A1?= =?UTF-8?q?=E5=9D=97"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../.cursor/rules/code-execution.mdc | 109 + .../.cursor/rules/dependencies.mdc | 72 + .../.cursor/rules/error-handling.mdc | 101 + .../.cursor/rules/examples.mdc | 138 + .../.cursor/rules/fixtures-guide.mdc | 98 + .../.cursor/rules/mcp-integration.mdc | 146 + .../.cursor/rules/project-overview.mdc | 50 + .../.cursor/rules/python-execution.mdc | 143 + .../.devcontainer/devcontainer.json | 43 + qiming-run_code_rmcp/.gitignore | 6 + qiming-run_code_rmcp/CLAUDE.md | 108 + qiming-run_code_rmcp/Cargo.lock | 1429 +++ qiming-run_code_rmcp/Cargo.toml | 56 + qiming-run_code_rmcp/Dockerfile | 80 + qiming-run_code_rmcp/LICENSE | 202 + qiming-run_code_rmcp/LICENSE-APACHE | 202 + qiming-run_code_rmcp/NOTICE | 16 + qiming-run_code_rmcp/README.md | 335 + qiming-run_code_rmcp/README_zh.md | 335 + .../examples/cursor_mcp_config.json | 6 + qiming-run_code_rmcp/examples/mcp_client.js | 151 + .../examples/mcp_client_example.js | 78 + qiming-run_code_rmcp/examples/test_mcp.sh | 21 + .../fixtures/cow_say_hello.js | 24 + .../fixtures/import_axios_example.js | 30 + .../fixtures/import_deno_std_example.js | 79 + .../fixtures/import_esm_module_example.js | 59 + .../fixtures/import_jsr_example.js | 79 + .../fixtures/import_local_module_example.js | 74 + .../fixtures/import_lodash_example.js | 43 + .../fixtures/rfunction_js_test3.js | 13 + .../fixtures/rfunction_python.py | 26 + .../fixtures/rfunction_test1.js | 20 + .../fixtures/rfunction_test2.py | 40 + qiming-run_code_rmcp/fixtures/test_js.js | 25 + .../fixtures/test_js_large_params.js | 75 + .../fixtures/test_js_params.js | 39 + qiming-run_code_rmcp/fixtures/test_python.py | 41 + .../fixtures/test_python_large_params.py | 70 + .../fixtures/test_python_logging.py | 36 + .../fixtures/test_python_params.py | 33 + .../fixtures/test_python_simple.py | 23 + .../fixtures/test_python_types.py | 46 + qiming-run_code_rmcp/fixtures/test_ts.ts | 41 + .../fixtures/test_ts_params.ts | 46 + qiming-run_code_rmcp/src/app_error.rs | 10 + .../src/cache/code_file_cache.rs | 131 + qiming-run_code_rmcp/src/cache/mod.rs | 3 + .../src/deno_runner/common_runner.rs | 117 + .../src/deno_runner/js_runner.rs | 61 + qiming-run_code_rmcp/src/deno_runner/mod.rs | 6 + .../src/deno_runner/ts_runner.rs | 37 + qiming-run_code_rmcp/src/lib.rs | 19 + qiming-run_code_rmcp/src/main.rs | 194 + qiming-run_code_rmcp/src/mcp/mcp_server.rs | 164 + qiming-run_code_rmcp/src/mcp/mod.rs | 3 + .../src/model/code_run_model.rs | 268 + qiming-run_code_rmcp/src/model/mod.rs | 8 + qiming-run_code_rmcp/src/model/tool_params.rs | 50 + qiming-run_code_rmcp/src/python_imports.pest | 25 + .../src/python_runner/dependencies/mod.rs | 3 + .../dependencies/python_dependencies.rs | 406 + qiming-run_code_rmcp/src/python_runner/mod.rs | 5 + .../src/python_runner/python_runner.rs | 200 + qiming-run_code_rmcp/src/script_runner.rs | 166 + .../src/templates/js_template_es.js | 92 + .../src/templates/js_template_normal.js | 93 + .../src/templates/python_template.py | 134 + .../src/templates/ts_template.ts | 96 + qiming-run_code_rmcp/src/tests/js_tests.rs | 740 ++ qiming-run_code_rmcp/src/tests/mod.rs | 90 + .../src/tests/python_tests.rs | 591 + qiming-run_code_rmcp/src/tests/ts_tests.rs | 202 + qiming-run_code_rmcp/src/warm_up.rs | 366 + qiming-vite-plugin-design-mode/.gitignore | 137 + qiming-vite-plugin-design-mode/.npmignore | 38 + qiming-vite-plugin-design-mode/CHANGELOG.md | 67 + qiming-vite-plugin-design-mode/FEATURES.md | 84 + qiming-vite-plugin-design-mode/README.md | 627 ++ qiming-vite-plugin-design-mode/TESTING_CLI.md | 277 + .../examples/advanced/index.html | 14 + .../examples/advanced/package-lock.json | 3148 ++++++ .../examples/advanced/package.json | 34 + .../examples/advanced/postcss.config.js | 6 + .../examples/advanced/src/App.tsx | 119 + .../advanced/src/components/DemoElement.tsx | 38 + .../examples/advanced/src/index.css | 957 ++ .../examples/advanced/src/main.tsx | 62 + .../advanced/src/pages/ComponentShowcase.tsx | 197 + .../advanced/src/pages/ConfigurationGuide.tsx | 292 + .../examples/advanced/src/pages/Dashboard.tsx | 242 + .../advanced/src/pages/InteractiveDemo.tsx | 74 + .../examples/advanced/src/store/appStore.ts | 173 + .../examples/advanced/src/utils/cn.ts | 6 + .../examples/advanced/tailwind.config.js | 12 + .../examples/advanced/tsconfig.json | 30 + .../examples/advanced/tsconfig.node.json | 10 + .../examples/advanced/vite.config.ts | 77 + .../examples/basic/index.html | 78 + .../examples/basic/main.js | 171 + .../examples/basic/package.json | 13 + .../examples/basic/vite.config.ts | 13 + .../examples/demo/.gitignore | 24 + .../examples/demo/README.md | 61 + .../examples/demo/README_CUSTOMIZABLE.md | 580 + .../examples/demo/eslint.config.js | 23 + .../examples/demo/index.html | 32 + .../examples/demo/package-lock.json | 2097 ++++ .../examples/demo/package.json | 28 + .../examples/demo/postcss.config.js | 5 + .../examples/demo/public/vite.svg | 1 + .../examples/demo/src/App.css | 42 + .../examples/demo/src/App.tsx | 64 + .../examples/demo/src/assets/react.svg | 1 + .../demo/src/external-panel/ContentPanel.tsx | 612 + .../demo/src/external-panel/PropertyPanel.tsx | 856 ++ .../demo/src/external-panel/StylePanel.tsx | 567 + .../examples/demo/src/index.css | 1 + .../examples/demo/src/main.tsx | 10 + .../demo/src/pages/CustomizableDemoPage.tsx | 499 + .../examples/demo/src/pages/FeaturesPage.tsx | 107 + .../examples/demo/src/pages/HomePage.tsx | 114 + .../demo/src/pages/IframeDemoPage.tsx | 515 + .../examples/demo/src/react.ts | 10 + .../examples/demo/tsconfig.app.json | 40 + .../examples/demo/tsconfig.json | 7 + .../examples/demo/tsconfig.node.json | 26 + .../examples/demo/vite.config.ts | 44 + .../examples/full-featured/.gitignore | 146 + .../examples/full-featured/README.md | 449 + .../examples/full-featured/index.html | 180 + .../examples/full-featured/package.json | 77 + .../examples/full-featured/pnpm-lock.yaml | 3265 ++++++ .../examples/full-featured/postcss.config.js | 5 + .../examples/full-featured/src/App.tsx | 318 + .../components/design-system/ColorPalette.tsx | 484 + .../components/design-system/TokenCard.tsx | 395 + .../src/components/layout/AppLayout.tsx | 283 + .../src/components/ui/Button.tsx | 274 + .../full-featured/src/components/ui/Card.tsx | 421 + .../src/components/ui/LoadingSpinner.tsx | 297 + .../examples/full-featured/src/index.css | 388 + .../examples/full-featured/src/main.tsx | 215 + .../src/pages/ComponentEditor.tsx | 711 ++ .../full-featured/src/pages/Dashboard.tsx | 527 + .../full-featured/src/pages/DesignSystem.tsx | 496 + .../full-featured/src/pages/LivePreview.tsx | 566 + .../src/store/design-system-store.ts | 599 + .../full-featured/src/types/design-system.ts | 364 + .../examples/full-featured/src/utils/index.ts | 395 + .../examples/full-featured/tailwind.config.js | 139 + .../examples/full-featured/tsconfig.json | 89 + .../examples/full-featured/tsconfig.node.json | 15 + .../examples/full-featured/vite.config.ts | 50 + .../examples/react-tsx/debug_app.tsx | 602 + .../examples/react-tsx/index.html | 16 + .../examples/react-tsx/package-lock.json | 1681 +++ .../examples/react-tsx/package.json | 21 + .../examples/react-tsx/src/App.css | 473 + .../examples/react-tsx/src/App.tsx | 310 + .../examples/react-tsx/src/main.tsx | 9 + .../examples/react-tsx/vite.config.ts | 16 + .../examples/react-vite-md2/.npmrc | 6 + .../examples/react-vite-md2/biome.json | 21 + .../examples/react-vite-md2/components.json | 21 + .../examples/react-vite-md2/cpage_config.json | 12 + .../examples/react-vite-md2/index.html | 14 + .../examples/react-vite-md2/package-lock.json | 9904 +++++++++++++++++ .../examples/react-vite-md2/package.json | 90 + .../react-vite-md2/pnpm-workspace.yaml | 3 + .../examples/react-vite-md2/postcss.config.js | 6 + .../react-vite-md2/public/favicon.png | Bin 0 -> 5560 bytes .../public/images/error/404-dark.svg | 20 + .../public/images/error/404.svg | 20 + .../public/images/error/500-dark.svg | 24 + .../public/images/error/500.svg | 24 + .../public/images/error/503-dark.svg | 26 + .../public/images/error/503.svg | 26 + .../react-vite-md2/public/images/favicon.ico | Bin 0 -> 15406 bytes .../public/images/logo/auth-logo.svg | 53 + .../public/images/logo/logo-dark.svg | 53 + .../public/images/logo/logo-icon.svg | 44 + .../public/images/shape/grid-01.svg | 71 + .../examples/react-vite-md2/sgconfig.yml | 5 + .../examples/react-vite-md2/src/App.tsx | 29 + .../src/components/common/Footer.tsx | 71 + .../src/components/common/Header.tsx | 51 + .../src/components/common/PageMeta.tsx | 20 + .../src/components/dropzone.tsx | 227 + .../src/components/editor/RichTextEditor.tsx | 128 + .../src/components/ui/accordion.tsx | 64 + .../src/components/ui/alert-dialog.tsx | 155 + .../src/components/ui/alert.tsx | 66 + .../src/components/ui/aspect-ratio.tsx | 9 + .../src/components/ui/avatar.tsx | 51 + .../src/components/ui/badge.tsx | 46 + .../src/components/ui/breadcrumb.tsx | 109 + .../src/components/ui/button.tsx | 60 + .../src/components/ui/calendar.tsx | 73 + .../react-vite-md2/src/components/ui/card.tsx | 92 + .../src/components/ui/carousel.tsx | 239 + .../src/components/ui/chart.tsx | 351 + .../src/components/ui/checkbox.tsx | 30 + .../src/components/ui/collapsible.tsx | 31 + .../src/components/ui/command.tsx | 175 + .../src/components/ui/dialog.tsx | 135 + .../src/components/ui/drawer.tsx | 130 + .../src/components/ui/dropdown-menu.tsx | 201 + .../react-vite-md2/src/components/ui/form.tsx | 166 + .../src/components/ui/input-otp.tsx | 75 + .../src/components/ui/input.tsx | 21 + .../src/components/ui/label.tsx | 24 + .../react-vite-md2/src/components/ui/map.tsx | 196 + .../src/components/ui/menubar.tsx | 274 + .../src/components/ui/multi-select.tsx | 196 + .../src/components/ui/navigation-menu.tsx | 168 + .../src/components/ui/pagination.tsx | 127 + .../src/components/ui/popover.tsx | 46 + .../src/components/ui/progress.tsx | 29 + .../src/components/ui/qrcodedataurl.tsx | 97 + .../src/components/ui/radio-group.tsx | 43 + .../src/components/ui/resizable.tsx | 54 + .../src/components/ui/scroll-area.tsx | 56 + .../src/components/ui/select.tsx | 159 + .../src/components/ui/separator.tsx | 28 + .../src/components/ui/sheet.tsx | 140 + .../src/components/ui/sidebar.tsx | 724 ++ .../src/components/ui/skeleton.tsx | 13 + .../src/components/ui/slider.tsx | 61 + .../src/components/ui/sonner.tsx | 23 + .../src/components/ui/switch.tsx | 29 + .../src/components/ui/table.tsx | 114 + .../react-vite-md2/src/components/ui/tabs.tsx | 64 + .../src/components/ui/textarea.tsx | 16 + .../src/components/ui/toast.tsx | 129 + .../src/components/ui/toaster.tsx | 33 + .../src/components/ui/toggle-group.tsx | 71 + .../src/components/ui/toggle.tsx | 45 + .../src/components/ui/tooltip.tsx | 61 + .../src/components/ui/video.tsx | 111 + .../examples/react-vite-md2/src/global.d.ts | 1 + .../react-vite-md2/src/hooks/use-debounce.ts | 15 + .../react-vite-md2/src/hooks/use-go-back.ts | 17 + .../react-vite-md2/src/hooks/use-mobile.ts | 19 + .../src/hooks/use-supabase-upload.ts | 197 + .../react-vite-md2/src/hooks/use-toast.tsx | 188 + .../examples/react-vite-md2/src/index.css | 93 + .../examples/react-vite-md2/src/lib/utils.ts | 39 + .../examples/react-vite-md2/src/main.tsx | 13 + .../src/pages/ArticleGenerator.tsx | 242 + .../react-vite-md2/src/pages/Home.tsx | 116 + .../src/pages/IframeDemoPage.tsx | 567 + .../react-vite-md2/src/pages/ImageCaption.tsx | 318 + .../react-vite-md2/src/pages/NotFound.tsx | 39 + .../react-vite-md2/src/pages/SamplePage.tsx | 16 + .../react-vite-md2/src/pages/UserSet.tsx | 245 + .../react-vite-md2/src/pages/VideoScript.tsx | 243 + .../examples/react-vite-md2/src/routes.tsx | 49 + .../react-vite-md2/src/services/chatStream.ts | 150 + .../src/services/imageGeneration.ts | 100 + .../examples/react-vite-md2/src/svg.d.ts | 6 + .../react-vite-md2/src/types/article.ts | 20 + .../react-vite-md2/src/types/index.ts | 6 + .../react-vite-md2/src/types/messages.ts | 29 + .../examples/react-vite-md2/src/vite-env.d.ts | 1 + .../react-vite-md2/tailwind.config.js | 159 + .../examples/react-vite-md2/tsconfig.app.json | 33 + .../react-vite-md2/tsconfig.check.json | 17 + .../examples/react-vite-md2/tsconfig.json | 18 + .../react-vite-md2/tsconfig.node.json | 24 + .../examples/react-vite-md2/vite.config.ts | 49 + qiming-vite-plugin-design-mode/package.json | 55 + .../packages/client-react/package.json | 26 + .../client-react/src/DesignModeBridge.tsx | 209 + .../client-react/src/DesignModeContext.tsx | 1074 ++ .../client-react/src/DesignModeManager.tsx | 224 + .../client-react/src/DesignModeUI.tsx | 280 + .../client-react/src/SelectionManager.tsx | 596 + .../client-react/src/UpdateManager.tsx | 816 ++ .../packages/client-react/src/bridge.ts | 651 ++ .../packages/client-react/src/index.tsx | 38 + .../client-react/src/managers/EditManager.ts | 364 + .../src/managers/ObserverManager.ts | 80 + .../client-react/src/types/UpdateTypes.ts | 58 + .../client-react/src/ui/ContextMenu.ts | 231 + .../client-react/src/ui/EditModeUI.ts | 76 + .../packages/client-react/src/ui/Toast.ts | 98 + .../packages/client-react/tsconfig.json | 21 + .../packages/client-shared/package.json | 31 + .../client-shared/src/HistoryManager.ts | 72 + .../client-shared/src/UpdateService.ts | 221 + .../client-shared/src/attributeNames.d.ts | 37 + .../client-shared/src/attributeNames.d.ts.map | 1 + .../client-shared/src/attributeNames.js | 97 + .../client-shared/src/attributeNames.js.map | 1 + .../client-shared/src/attributeNames.ts | 107 + .../packages/client-shared/src/bridge.d.ts | 97 + .../client-shared/src/bridge.d.ts.map | 1 + .../packages/client-shared/src/bridge.js | 503 + .../packages/client-shared/src/bridge.js.map | 1 + .../packages/client-shared/src/bridge.ts | 651 ++ .../client-shared/src/elementUtils.d.ts | 5 + .../client-shared/src/elementUtils.d.ts.map | 1 + .../client-shared/src/elementUtils.js | 16 + .../client-shared/src/elementUtils.js.map | 1 + .../client-shared/src/elementUtils.ts | 17 + .../packages/client-shared/src/index.ts | 20 + .../packages/client-shared/src/messages.d.ts | 265 + .../client-shared/src/messages.d.ts.map | 1 + .../packages/client-shared/src/messages.js | 3 + .../client-shared/src/messages.js.map | 1 + .../packages/client-shared/src/messages.ts | 340 + .../client-shared/src/sourceInfo.d.ts | 10 + .../client-shared/src/sourceInfo.d.ts.map | 1 + .../packages/client-shared/src/sourceInfo.js | 69 + .../client-shared/src/sourceInfo.js.map | 1 + .../packages/client-shared/src/sourceInfo.ts | 77 + .../client-shared/src/sourceInfoResolver.d.ts | 13 + .../src/sourceInfoResolver.d.ts.map | 1 + .../client-shared/src/sourceInfoResolver.js | 157 + .../src/sourceInfoResolver.js.map | 1 + .../client-shared/src/sourceInfoResolver.ts | 190 + .../packages/client-shared/src/types.d.ts | 50 + .../packages/client-shared/src/types.d.ts.map | 1 + .../packages/client-shared/src/types.js | 2 + .../packages/client-shared/src/types.js.map | 1 + .../packages/client-shared/src/types.ts | 62 + .../packages/client-shared/tsconfig.json | 10 + .../packages/client-vue/package.json | 24 + .../client-vue/scripts/copy-vue-to-dist.mjs | 47 + .../packages/client-vue/src/DesignModeApp.vue | 170 + .../packages/client-vue/src/README.md | 296 + .../client-vue/src/components/ContextMenu.vue | 175 + .../src/components/DesignModeUI.vue | 344 + .../client-vue/src/components/Toast.vue | 101 + .../src/components/ToastContainer.vue | 31 + .../client-vue/src/composables/README.md | 238 + .../client-vue/src/composables/index.ts | 16 + .../src/composables/useDesignMode.ts | 915 ++ .../src/composables/useEditManager.ts | 367 + .../src/composables/useObserverManager.ts | 98 + .../src/composables/useSelectionManager.ts | 539 + .../client-vue/src/composables/useToast.ts | 50 + .../packages/client-vue/src/exports.ts | 7 + .../packages/client-vue/src/index.ts | 32 + .../packages/client-vue/src/shims-vue.d.ts | 5 + .../packages/client-vue/tsconfig.json | 15 + .../packages/plugin/package.json | 43 + .../packages/plugin/src/cli/index.ts | 51 + .../packages/plugin/src/cli/install.ts | 336 + .../packages/plugin/src/cli/uninstall.ts | 138 + .../plugin/src/core/astTransformer.ts | 47 + .../packages/plugin/src/core/batchUpdater.ts | 101 + .../packages/plugin/src/core/codeUpdater.ts | 160 + .../plugin/src/core/serverMiddleware.ts | 974 ++ .../packages/plugin/src/core/sourceMapper.ts | 444 + .../plugin/src/core/vueSfcTransformer.ts | 184 + .../packages/plugin/src/core/vueSfcUpdater.ts | 176 + .../packages/plugin/src/index.ts | 569 + .../packages/plugin/src/types/index.ts | 53 + .../packages/plugin/src/types/messages.ts | 340 + .../packages/plugin/src/utils/babelHelpers.ts | 87 + .../packages/plugin/tsconfig.json | 19 + qiming-vite-plugin-design-mode/pnpm-lock.yaml | 1620 +++ .../pnpm-workspace.yaml | 2 + .../scripts/release-dry-run-beta.sh | 40 + .../scripts/release-dry-run.sh | 40 + .../scripts/release-preflight.sh | 62 + .../scripts/release-publish-beta.sh | 42 + .../scripts/release-publish-next.sh | 47 + .../scripts/release-sync-npmmirror.sh | 198 + .../scripts/release-verify-beta.sh | 38 + .../scripts/release-verify-next.sh | 64 + .../scripts/release-version-sync.sh | 73 + .../scripts/test-cli.sh | 83 + qiming-vite-plugin-design-mode/test/README.md | 185 + .../test/core/astTransformer.test.ts | 214 + .../test/core/codeUpdater.test.ts | 346 + .../test/core/serverMiddleware.test.ts | 296 + .../test/core/sourceMapper.test.ts | 262 + .../test/core/vueSfcTransformer.test.ts | 63 + .../test/core/vueSfcUpdater.test.ts | 60 + .../test/index.test.ts | 284 + .../test/utils/babelHelpers.test.ts | 211 + qiming-vite-plugin-design-mode/tsconfig.json | 31 + .../vitest.config.ts | 42 + .../plans/---radix-ui----5c033265.plan.md | 251 + qiming-xagi-frontend-templates/.env.example | 5 + .../.github/workflows/release.yml | 87 + qiming-xagi-frontend-templates/.gitignore | 37 + qiming-xagi-frontend-templates/.npmrc | 24 + qiming-xagi-frontend-templates/.prettierrc | 10 + .../INTEGRATION_SPEC.md | 1042 ++ qiming-xagi-frontend-templates/MONOREPO.md | 215 + qiming-xagi-frontend-templates/README.md | 233 + qiming-xagi-frontend-templates/latest.json | 8 + qiming-xagi-frontend-templates/package.json | 57 + .../packages/react-next/.cursorrules | 155 + .../packages/react-next/.editorconfig | 42 + .../packages/react-next/.eslintignore | 44 + .../packages/react-next/.eslintrc.json | 1 + .../packages/react-next/.gitignore | 130 + .../packages/react-next/.prettierrc | 15 + .../packages/react-next/AGENTS.md | 216 + .../packages/react-next/CLAUDE.md | 307 + .../packages/react-next/biome.json | 23 + .../packages/react-next/components.json | 21 + .../packages/react-next/env.example | 27 + .../packages/react-next/meta.json | 106 + .../packages/react-next/next-env.d.ts | 5 + .../packages/react-next/next.config.js | 70 + .../packages/react-next/package.json | 102 + .../packages/react-next/postcss.config.js | 6 + .../packages/react-next/src/app/globals.css | 271 + .../packages/react-next/src/app/layout.tsx | 69 + .../packages/react-next/src/app/page.tsx | 262 + .../src/components/ui/accordion.tsx | 56 + .../src/components/ui/alert-dialog.tsx | 139 + .../src/components/ui/aspect-ratio.tsx | 18 + .../react-next/src/components/ui/avatar.tsx | 48 + .../react-next/src/components/ui/button.tsx | 54 + .../react-next/src/components/ui/card.tsx | 82 + .../react-next/src/components/ui/checkbox.tsx | 28 + .../src/components/ui/collapsible.tsx | 10 + .../react-next/src/components/ui/dialog.tsx | 120 + .../src/components/ui/dropdown-menu.tsx | 198 + .../react-next/src/components/ui/form.tsx | 169 + .../react-next/src/components/ui/input.tsx | 25 + .../react-next/src/components/ui/label.tsx | 24 + .../react-next/src/components/ui/menubar.tsx | 231 + .../src/components/ui/navigation-menu.tsx | 128 + .../react-next/src/components/ui/popover.tsx | 29 + .../react-next/src/components/ui/progress.tsx | 26 + .../src/components/ui/radio-group.tsx | 42 + .../src/components/ui/scroll-area.tsx | 46 + .../react-next/src/components/ui/select.tsx | 158 + .../src/components/ui/separator.tsx | 29 + .../react-next/src/components/ui/slider.tsx | 26 + .../react-next/src/components/ui/switch.tsx | 27 + .../react-next/src/components/ui/tabs.tsx | 53 + .../react-next/src/components/ui/textarea.tsx | 24 + .../src/components/ui/toggle-group.tsx | 61 + .../react-next/src/components/ui/toggle.tsx | 43 + .../react-next/src/components/ui/tooltip.tsx | 28 + .../packages/react-next/src/lib/api.ts | 106 + .../packages/react-next/src/lib/utils.ts | 134 + .../packages/react-next/tailwind.config.js | 99 + .../packages/react-next/tsconfig.json | 28 + .../packages/react-vite/.editorconfig | 35 + .../packages/react-vite/.eslintignore | 41 + .../packages/react-vite/.eslintrc.json | 34 + .../packages/react-vite/.gitignore | 124 + .../packages/react-vite/.prettierrc | 15 + .../packages/react-vite/AGENTS.md | 84 + .../packages/react-vite/CLAUDE.md | 1 + .../packages/react-vite/README.md | 106 + .../packages/react-vite/biome.json | 21 + .../packages/react-vite/components.json | 21 + .../packages/react-vite/index.html | 14 + .../packages/react-vite/meta.json | 12 + .../packages/react-vite/package.json | 94 + .../packages/react-vite/pnpm-workspace.yaml | 2 + .../packages/react-vite/postcss.config.js | 6 + .../packages/react-vite/src/App.css | 157 + .../packages/react-vite/src/App.tsx | 11 + .../src/components/ui/accordion.tsx | 56 + .../src/components/ui/alert-dialog.tsx | 139 + .../src/components/ui/aspect-ratio.tsx | 18 + .../react-vite/src/components/ui/avatar.tsx | 48 + .../react-vite/src/components/ui/button.tsx | 55 + .../react-vite/src/components/ui/card.tsx | 102 + .../react-vite/src/components/ui/checkbox.tsx | 28 + .../src/components/ui/collapsible.tsx | 9 + .../react-vite/src/components/ui/dialog.tsx | 120 + .../src/components/ui/dropdown-menu.tsx | 198 + .../react-vite/src/components/ui/form.tsx | 169 + .../react-vite/src/components/ui/input.tsx | 24 + .../react-vite/src/components/ui/label.tsx | 24 + .../react-vite/src/components/ui/menubar.tsx | 231 + .../src/components/ui/navigation-menu.tsx | 128 + .../react-vite/src/components/ui/popover.tsx | 29 + .../react-vite/src/components/ui/progress.tsx | 26 + .../src/components/ui/radio-group.tsx | 42 + .../src/components/ui/scroll-area.tsx | 46 + .../react-vite/src/components/ui/select.tsx | 158 + .../src/components/ui/separator.tsx | 29 + .../react-vite/src/components/ui/slider.tsx | 26 + .../react-vite/src/components/ui/switch.tsx | 27 + .../react-vite/src/components/ui/tabs.tsx | 53 + .../react-vite/src/components/ui/textarea.tsx | 23 + .../src/components/ui/toggle-group.tsx | 61 + .../react-vite/src/components/ui/toggle.tsx | 43 + .../react-vite/src/components/ui/tooltip.tsx | 28 + .../react-vite/src/examples/api-example.ts | 126 + .../react-vite/src/examples/form-example.tsx | 166 + .../src/examples/list-page-example.tsx | 205 + .../packages/react-vite/src/index.css | 254 + .../packages/react-vite/src/lib/api.ts | 115 + .../packages/react-vite/src/lib/services.ts | 103 + .../packages/react-vite/src/lib/utils.ts | 119 + .../packages/react-vite/src/main.tsx | 10 + .../react-vite/src/pages/ExamplesPage.tsx | 36 + .../packages/react-vite/src/pages/Home.tsx | 83 + .../react-vite/src/pages/NotFound.tsx | 26 + .../packages/react-vite/src/router/index.tsx | 34 + .../packages/react-vite/src/vite-env.d.ts | 1 + .../packages/react-vite/tailwind.config.js | 506 + .../packages/react-vite/tsconfig.json | 31 + .../packages/react-vite/tsconfig.node.json | 10 + .../packages/react-vite/vite.config.ts | 13 + .../packages/vue3-vite/.editorconfig | 42 + .../packages/vue3-vite/.eslintignore | 41 + .../packages/vue3-vite/.eslintrc.cjs | 36 + .../packages/vue3-vite/.gitignore | 124 + .../packages/vue3-vite/.prettierrc | 15 + .../packages/vue3-vite/AGENTS.md | 84 + .../packages/vue3-vite/CLAUDE.md | 1 + .../packages/vue3-vite/biome.json | 26 + .../packages/vue3-vite/components.json | 19 + .../packages/vue3-vite/index.html | 13 + .../packages/vue3-vite/meta.json | 20 + .../packages/vue3-vite/package.json | 65 + .../packages/vue3-vite/pnpm-workspace.yaml | 3 + .../packages/vue3-vite/postcss.config.js | 6 + .../packages/vue3-vite/src/App.vue | 4 + .../vue3-vite/src/components/ui/accordion.vue | 42 + .../src/components/ui/alert-dialog.vue | 74 + .../src/components/ui/aspect-ratio.vue | 22 + .../vue3-vite/src/components/ui/avatar.vue | 39 + .../vue3-vite/src/components/ui/button.vue | 60 + .../vue3-vite/src/components/ui/card.vue | 22 + .../vue3-vite/src/components/ui/checkbox.vue | 37 + .../src/components/ui/collapsible.vue | 28 + .../vue3-vite/src/components/ui/dialog.vue | 56 + .../src/components/ui/dropdown-menu.vue | 61 + .../vue3-vite/src/components/ui/form.ts | 110 + .../vue3-vite/src/components/ui/index.ts | 37 + .../vue3-vite/src/components/ui/input.vue | 41 + .../vue3-vite/src/components/ui/label.vue | 21 + .../vue3-vite/src/components/ui/menubar.vue | 29 + .../src/components/ui/navigation-menu.vue | 27 + .../vue3-vite/src/components/ui/popover.vue | 40 + .../vue3-vite/src/components/ui/progress.vue | 31 + .../src/components/ui/radio-group.vue | 46 + .../src/components/ui/scroll-area.vue | 23 + .../vue3-vite/src/components/ui/select.vue | 83 + .../vue3-vite/src/components/ui/separator.vue | 26 + .../vue3-vite/src/components/ui/slider.vue | 39 + .../vue3-vite/src/components/ui/switch.vue | 40 + .../vue3-vite/src/components/ui/tabs.vue | 56 + .../vue3-vite/src/components/ui/textarea.vue | 41 + .../src/components/ui/toggle-group.vue | 46 + .../vue3-vite/src/components/ui/toggle.vue | 35 + .../vue3-vite/src/components/ui/tooltip.vue | 40 + .../vue3-vite/src/examples/api-example.ts | 67 + .../vue3-vite/src/examples/form-example.vue | 109 + .../src/examples/list-page-example.vue | 147 + .../packages/vue3-vite/src/lib/api.ts | 132 + .../packages/vue3-vite/src/lib/services.ts | 164 + .../packages/vue3-vite/src/lib/utils.ts | 59 + .../packages/vue3-vite/src/main.ts | 9 + .../vue3-vite/src/pages/ExamplesPage.vue | 34 + .../packages/vue3-vite/src/pages/Home.vue | 101 + .../packages/vue3-vite/src/pages/NotFound.vue | 11 + .../packages/vue3-vite/src/router/index.ts | 32 + .../packages/vue3-vite/src/style.css | 32 + .../packages/vue3-vite/tailwind.config.js | 507 + .../packages/vue3-vite/tsconfig.json | 29 + .../packages/vue3-vite/tsconfig.node.json | 10 + .../packages/vue3-vite/vite.config.ts | 15 + qiming-xagi-frontend-templates/pnpm-lock.yaml | 8359 ++++++++++++++ .../pnpm-workspace.yaml | 17 + .../scripts/README.md | 210 + .../scripts/build-all.js | 33 + .../scripts/check-versions.js | 82 + .../scripts/deploy-templates.sh | 47 + .../scripts/generate-latest-json.js | 77 + .../scripts/list-templates.js | 38 + .../scripts/pack-template.js | 211 + .../scripts/release.js | 191 + .../scripts/restart-file-server.sh | 203 + .../scripts/restart-rcoder.sh | 93 + .../scripts/test-all.js | 29 + .../scripts/upload-templates.sh | 104 + qiming-xagi-frontend-templates/templates.json | 352 + .../test-templates.sh | 76 + 587 files changed, 99761 insertions(+) create mode 100644 qiming-run_code_rmcp/.cursor/rules/code-execution.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/dependencies.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/error-handling.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/examples.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/project-overview.mdc create mode 100644 qiming-run_code_rmcp/.cursor/rules/python-execution.mdc create mode 100644 qiming-run_code_rmcp/.devcontainer/devcontainer.json create mode 100644 qiming-run_code_rmcp/.gitignore create mode 100644 qiming-run_code_rmcp/CLAUDE.md create mode 100644 qiming-run_code_rmcp/Cargo.lock create mode 100644 qiming-run_code_rmcp/Cargo.toml create mode 100644 qiming-run_code_rmcp/Dockerfile create mode 100644 qiming-run_code_rmcp/LICENSE create mode 100644 qiming-run_code_rmcp/LICENSE-APACHE create mode 100644 qiming-run_code_rmcp/NOTICE create mode 100644 qiming-run_code_rmcp/README.md create mode 100644 qiming-run_code_rmcp/README_zh.md create mode 100644 qiming-run_code_rmcp/examples/cursor_mcp_config.json create mode 100644 qiming-run_code_rmcp/examples/mcp_client.js create mode 100644 qiming-run_code_rmcp/examples/mcp_client_example.js create mode 100644 qiming-run_code_rmcp/examples/test_mcp.sh create mode 100644 qiming-run_code_rmcp/fixtures/cow_say_hello.js create mode 100644 qiming-run_code_rmcp/fixtures/import_axios_example.js create mode 100644 qiming-run_code_rmcp/fixtures/import_deno_std_example.js create mode 100644 qiming-run_code_rmcp/fixtures/import_esm_module_example.js create mode 100644 qiming-run_code_rmcp/fixtures/import_jsr_example.js create mode 100644 qiming-run_code_rmcp/fixtures/import_local_module_example.js create mode 100644 qiming-run_code_rmcp/fixtures/import_lodash_example.js create mode 100644 qiming-run_code_rmcp/fixtures/rfunction_js_test3.js create mode 100644 qiming-run_code_rmcp/fixtures/rfunction_python.py create mode 100644 qiming-run_code_rmcp/fixtures/rfunction_test1.js create mode 100644 qiming-run_code_rmcp/fixtures/rfunction_test2.py create mode 100644 qiming-run_code_rmcp/fixtures/test_js.js create mode 100644 qiming-run_code_rmcp/fixtures/test_js_large_params.js create mode 100644 qiming-run_code_rmcp/fixtures/test_js_params.js create mode 100644 qiming-run_code_rmcp/fixtures/test_python.py create mode 100644 qiming-run_code_rmcp/fixtures/test_python_large_params.py create mode 100644 qiming-run_code_rmcp/fixtures/test_python_logging.py create mode 100644 qiming-run_code_rmcp/fixtures/test_python_params.py create mode 100644 qiming-run_code_rmcp/fixtures/test_python_simple.py create mode 100644 qiming-run_code_rmcp/fixtures/test_python_types.py create mode 100644 qiming-run_code_rmcp/fixtures/test_ts.ts create mode 100644 qiming-run_code_rmcp/fixtures/test_ts_params.ts create mode 100644 qiming-run_code_rmcp/src/app_error.rs create mode 100644 qiming-run_code_rmcp/src/cache/code_file_cache.rs create mode 100644 qiming-run_code_rmcp/src/cache/mod.rs create mode 100644 qiming-run_code_rmcp/src/deno_runner/common_runner.rs create mode 100644 qiming-run_code_rmcp/src/deno_runner/js_runner.rs create mode 100644 qiming-run_code_rmcp/src/deno_runner/mod.rs create mode 100644 qiming-run_code_rmcp/src/deno_runner/ts_runner.rs create mode 100644 qiming-run_code_rmcp/src/lib.rs create mode 100644 qiming-run_code_rmcp/src/main.rs create mode 100644 qiming-run_code_rmcp/src/mcp/mcp_server.rs create mode 100644 qiming-run_code_rmcp/src/mcp/mod.rs create mode 100644 qiming-run_code_rmcp/src/model/code_run_model.rs create mode 100644 qiming-run_code_rmcp/src/model/mod.rs create mode 100644 qiming-run_code_rmcp/src/model/tool_params.rs create mode 100644 qiming-run_code_rmcp/src/python_imports.pest create mode 100644 qiming-run_code_rmcp/src/python_runner/dependencies/mod.rs create mode 100644 qiming-run_code_rmcp/src/python_runner/dependencies/python_dependencies.rs create mode 100644 qiming-run_code_rmcp/src/python_runner/mod.rs create mode 100644 qiming-run_code_rmcp/src/python_runner/python_runner.rs create mode 100644 qiming-run_code_rmcp/src/script_runner.rs create mode 100644 qiming-run_code_rmcp/src/templates/js_template_es.js create mode 100644 qiming-run_code_rmcp/src/templates/js_template_normal.js create mode 100644 qiming-run_code_rmcp/src/templates/python_template.py create mode 100644 qiming-run_code_rmcp/src/templates/ts_template.ts create mode 100644 qiming-run_code_rmcp/src/tests/js_tests.rs create mode 100644 qiming-run_code_rmcp/src/tests/mod.rs create mode 100644 qiming-run_code_rmcp/src/tests/python_tests.rs create mode 100644 qiming-run_code_rmcp/src/tests/ts_tests.rs create mode 100644 qiming-run_code_rmcp/src/warm_up.rs create mode 100644 qiming-vite-plugin-design-mode/.gitignore create mode 100644 qiming-vite-plugin-design-mode/.npmignore create mode 100644 qiming-vite-plugin-design-mode/CHANGELOG.md create mode 100644 qiming-vite-plugin-design-mode/FEATURES.md create mode 100644 qiming-vite-plugin-design-mode/README.md create mode 100644 qiming-vite-plugin-design-mode/TESTING_CLI.md create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/package-lock.json create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/postcss.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/index.css create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json create mode 100644 qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/examples/basic/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/basic/main.js create mode 100644 qiming-vite-plugin-design-mode/examples/basic/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/basic/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/examples/demo/.gitignore create mode 100644 qiming-vite-plugin-design-mode/examples/demo/README.md create mode 100644 qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md create mode 100644 qiming-vite-plugin-design-mode/examples/demo/eslint.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/demo/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/demo/package-lock.json create mode 100644 qiming-vite-plugin-design-mode/examples/demo/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/demo/postcss.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/demo/public/vite.svg create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/App.css create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/App.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/external-panel/PropertyPanel.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/external-panel/StylePanel.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/index.css create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/main.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/pages/CustomizableDemoPage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/pages/FeaturesPage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/pages/HomePage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/pages/IframeDemoPage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/demo/src/react.ts create mode 100644 qiming-vite-plugin-design-mode/examples/demo/tsconfig.app.json create mode 100644 qiming-vite-plugin-design-mode/examples/demo/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/examples/demo/tsconfig.node.json create mode 100644 qiming-vite-plugin-design-mode/examples/demo/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/.gitignore create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/README.md create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/pnpm-lock.yaml create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/postcss.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/App.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/ColorPalette.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/TokenCard.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/layout/AppLayout.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Button.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Card.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/LoadingSpinner.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/index.css create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/main.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/pages/ComponentEditor.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/pages/Dashboard.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/pages/DesignSystem.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/pages/LivePreview.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/store/design-system-store.ts create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/types/design-system.ts create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/src/utils/index.ts create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/tailwind.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.node.json create mode 100644 qiming-vite-plugin-design-mode/examples/full-featured/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/debug_app.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/package-lock.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/src/App.css create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/src/App.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/src/main.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-tsx/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/.npmrc create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/biome.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/components.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/cpage_config.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/index.html create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/package-lock.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/package.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/pnpm-workspace.yaml create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/postcss.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/favicon.png create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404-dark.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500-dark.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503-dark.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/favicon.ico create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/auth-logo.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-dark.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-icon.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/shape/grid-01.svg create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/sgconfig.yml create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/App.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Footer.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Header.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/PageMeta.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/dropzone.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/editor/RichTextEditor.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/accordion.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert-dialog.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/aspect-ratio.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/avatar.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/badge.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/breadcrumb.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/button.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/calendar.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/card.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/carousel.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/chart.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/checkbox.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/collapsible.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/command.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/dialog.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/drawer.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/dropdown-menu.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/form.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/input-otp.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/input.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/label.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/map.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/menubar.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/multi-select.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/navigation-menu.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/pagination.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/popover.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/progress.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/qrcodedataurl.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/radio-group.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/resizable.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/scroll-area.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/select.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/separator.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/sheet.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/sidebar.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/skeleton.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/slider.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/sonner.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/switch.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/table.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/tabs.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/textarea.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/toast.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/toaster.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/toggle-group.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/toggle.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/tooltip.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/video.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/global.d.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-debounce.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-go-back.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-mobile.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-supabase-upload.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-toast.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/index.css create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/lib/utils.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/main.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ArticleGenerator.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/Home.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/IframeDemoPage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ImageCaption.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/NotFound.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/SamplePage.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/UserSet.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/VideoScript.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/routes.tsx create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/chatStream.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/imageGeneration.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/svg.d.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/article.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/index.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/messages.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/src/vite-env.d.ts create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/tailwind.config.js create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.app.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.check.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.node.json create mode 100644 qiming-vite-plugin-design-mode/examples/react-vite-md2/vite.config.ts create mode 100644 qiming-vite-plugin-design-mode/package.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/package.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeBridge.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeContext.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeManager.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeUI.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/SelectionManager.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/UpdateManager.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/bridge.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/index.tsx create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/managers/EditManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/managers/ObserverManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/types/UpdateTypes.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/ui/ContextMenu.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/ui/EditModeUI.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/src/ui/Toast.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-react/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/package.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/HistoryManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/UpdateService.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/messages.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/types.js create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/types.js.map create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/src/types.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-shared/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/package.json create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/scripts/copy-vue-to-dist.mjs create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/DesignModeApp.vue create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/README.md create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/components/ContextMenu.vue create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/components/DesignModeUI.vue create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/components/Toast.vue create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/components/ToastContainer.vue create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/README.md create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useDesignMode.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useEditManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useObserverManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useSelectionManager.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useToast.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/exports.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/src/shims-vue.d.ts create mode 100644 qiming-vite-plugin-design-mode/packages/client-vue/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/package.json create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/cli/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/cli/install.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/cli/uninstall.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/astTransformer.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/batchUpdater.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/codeUpdater.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/serverMiddleware.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/sourceMapper.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcTransformer.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcUpdater.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/types/index.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/types/messages.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/src/utils/babelHelpers.ts create mode 100644 qiming-vite-plugin-design-mode/packages/plugin/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/pnpm-lock.yaml create mode 100644 qiming-vite-plugin-design-mode/pnpm-workspace.yaml create mode 100644 qiming-vite-plugin-design-mode/scripts/release-dry-run-beta.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-dry-run.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-preflight.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-publish-beta.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-publish-next.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-sync-npmmirror.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-verify-beta.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-verify-next.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/release-version-sync.sh create mode 100644 qiming-vite-plugin-design-mode/scripts/test-cli.sh create mode 100644 qiming-vite-plugin-design-mode/test/README.md create mode 100644 qiming-vite-plugin-design-mode/test/core/astTransformer.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/core/codeUpdater.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/core/serverMiddleware.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/core/sourceMapper.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/core/vueSfcTransformer.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/core/vueSfcUpdater.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/index.test.ts create mode 100644 qiming-vite-plugin-design-mode/test/utils/babelHelpers.test.ts create mode 100644 qiming-vite-plugin-design-mode/tsconfig.json create mode 100644 qiming-vite-plugin-design-mode/vitest.config.ts create mode 100644 qiming-xagi-frontend-templates/.cursor/plans/---radix-ui----5c033265.plan.md create mode 100644 qiming-xagi-frontend-templates/.env.example create mode 100644 qiming-xagi-frontend-templates/.github/workflows/release.yml create mode 100644 qiming-xagi-frontend-templates/.gitignore create mode 100644 qiming-xagi-frontend-templates/.npmrc create mode 100644 qiming-xagi-frontend-templates/.prettierrc create mode 100644 qiming-xagi-frontend-templates/INTEGRATION_SPEC.md create mode 100644 qiming-xagi-frontend-templates/MONOREPO.md create mode 100644 qiming-xagi-frontend-templates/README.md create mode 100644 qiming-xagi-frontend-templates/latest.json create mode 100644 qiming-xagi-frontend-templates/package.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.cursorrules create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.editorconfig create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.eslintignore create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.eslintrc.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.gitignore create mode 100644 qiming-xagi-frontend-templates/packages/react-next/.prettierrc create mode 100644 qiming-xagi-frontend-templates/packages/react-next/AGENTS.md create mode 100644 qiming-xagi-frontend-templates/packages/react-next/CLAUDE.md create mode 100644 qiming-xagi-frontend-templates/packages/react-next/biome.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/components.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/env.example create mode 100644 qiming-xagi-frontend-templates/packages/react-next/meta.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/next-env.d.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-next/next.config.js create mode 100644 qiming-xagi-frontend-templates/packages/react-next/package.json create mode 100644 qiming-xagi-frontend-templates/packages/react-next/postcss.config.js create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/app/globals.css create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/app/layout.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/app/page.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/accordion.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/alert-dialog.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/aspect-ratio.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/avatar.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/button.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/card.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/checkbox.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/collapsible.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/dialog.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/dropdown-menu.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/form.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/input.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/label.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/menubar.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/navigation-menu.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/popover.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/progress.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/radio-group.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/scroll-area.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/select.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/separator.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/slider.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/switch.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/tabs.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/textarea.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/toggle-group.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/toggle.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/components/ui/tooltip.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/lib/api.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-next/src/lib/utils.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-next/tailwind.config.js create mode 100644 qiming-xagi-frontend-templates/packages/react-next/tsconfig.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/.editorconfig create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/.eslintignore create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/.eslintrc.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/.gitignore create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/.prettierrc create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/AGENTS.md create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/CLAUDE.md create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/README.md create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/biome.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/components.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/index.html create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/meta.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/package.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/pnpm-workspace.yaml create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/postcss.config.js create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/App.css create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/App.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/accordion.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/alert-dialog.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/aspect-ratio.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/avatar.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/button.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/card.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/checkbox.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/collapsible.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/dialog.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/dropdown-menu.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/form.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/input.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/label.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/menubar.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/navigation-menu.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/popover.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/progress.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/radio-group.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/scroll-area.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/select.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/separator.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/slider.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/switch.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/tabs.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/textarea.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/toggle-group.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/toggle.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/components/ui/tooltip.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/examples/api-example.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/examples/form-example.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/examples/list-page-example.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/index.css create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/lib/api.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/lib/services.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/lib/utils.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/main.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/pages/ExamplesPage.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/pages/Home.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/pages/NotFound.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/router/index.tsx create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/src/vite-env.d.ts create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/tailwind.config.js create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/tsconfig.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/tsconfig.node.json create mode 100644 qiming-xagi-frontend-templates/packages/react-vite/vite.config.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/.editorconfig create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/.eslintignore create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/.eslintrc.cjs create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/.gitignore create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/.prettierrc create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/AGENTS.md create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/CLAUDE.md create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/biome.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/components.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/index.html create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/meta.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/package.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/pnpm-workspace.yaml create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/postcss.config.js create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/App.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/accordion.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/alert-dialog.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/aspect-ratio.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/avatar.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/button.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/card.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/checkbox.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/collapsible.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/dialog.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/dropdown-menu.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/form.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/index.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/input.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/label.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/menubar.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/navigation-menu.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/popover.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/progress.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/radio-group.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/scroll-area.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/select.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/separator.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/slider.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/switch.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/tabs.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/textarea.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/toggle-group.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/toggle.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/components/ui/tooltip.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/examples/api-example.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/examples/form-example.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/examples/list-page-example.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/lib/api.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/lib/services.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/lib/utils.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/main.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/pages/ExamplesPage.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/pages/Home.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/pages/NotFound.vue create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/router/index.ts create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/src/style.css create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/tailwind.config.js create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/tsconfig.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/tsconfig.node.json create mode 100644 qiming-xagi-frontend-templates/packages/vue3-vite/vite.config.ts create mode 100644 qiming-xagi-frontend-templates/pnpm-lock.yaml create mode 100644 qiming-xagi-frontend-templates/pnpm-workspace.yaml create mode 100644 qiming-xagi-frontend-templates/scripts/README.md create mode 100644 qiming-xagi-frontend-templates/scripts/build-all.js create mode 100644 qiming-xagi-frontend-templates/scripts/check-versions.js create mode 100644 qiming-xagi-frontend-templates/scripts/deploy-templates.sh create mode 100644 qiming-xagi-frontend-templates/scripts/generate-latest-json.js create mode 100644 qiming-xagi-frontend-templates/scripts/list-templates.js create mode 100644 qiming-xagi-frontend-templates/scripts/pack-template.js create mode 100644 qiming-xagi-frontend-templates/scripts/release.js create mode 100644 qiming-xagi-frontend-templates/scripts/restart-file-server.sh create mode 100644 qiming-xagi-frontend-templates/scripts/restart-rcoder.sh create mode 100644 qiming-xagi-frontend-templates/scripts/test-all.js create mode 100644 qiming-xagi-frontend-templates/scripts/upload-templates.sh create mode 100644 qiming-xagi-frontend-templates/templates.json create mode 100644 qiming-xagi-frontend-templates/test-templates.sh diff --git a/.gitignore b/.gitignore index 676d6bda..e7e4ce2c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ !/qiming-dev-inject/ !/qiming-mcp-proxy/ !/qiming-noVNC/ +!/qiming-run_code_rmcp/ +!/qiming-vite-plugin-design-mode/ +!/qiming-xagi-frontend-templates/ !/qimingclaw/ !/qimingcode/ diff --git a/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc b/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc new file mode 100644 index 00000000..6771acee --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc @@ -0,0 +1,109 @@ +--- +description: +globs: +alwaysApply: false +--- +# 代码执行流程 + +本项目的核心功能是执行JavaScript、TypeScript和Python代码,并捕获执行结果。以下是代码执行的主要流程: + +## 代码执行模型 + +项目使用模块化的代码执行模型,定义在[src/model/code_run_model.rs](mdc:src/model/code_run_model.rs)中: + +```rust +/// 执行结果,包含js/python 执行结果,和打印的log日志 +#[derive(Debug, Serialize, Deserialize)] +pub struct CodeScriptExecutionResult { + //js/python 执行结果 + pub result: Option, + //js/python 打印的log日志 + pub logs: Vec, + // 是否执行成功,ture:默认值,执行成功 + #[serde(skip_serializing)] + pub success: bool, + //如果执行错误的话,错误信息 + #[serde(skip_serializing)] + pub error: Option, +} + +/// 代码执行器 +pub struct CodeExecutor; + +impl CodeExecutor { + /// 执行代码 + pub async fn execute( + code: &str, + language: LanguageScript, + ) -> Result { + Self::execute_with_params(code, language, None).await + } + + /// 执行代码并传递参数 + pub async fn execute_with_params( + code: &str, + language: LanguageScript, + params: Option, + ) -> Result { + match language { + LanguageScript::Js => JsRunner::new().run_with_params(code, params).await, + LanguageScript::Ts => TsRunner::new().run_with_params(code, params).await, + LanguageScript::Python => PythonRunner::new().run_with_params(code, params).await, + } + } +} +``` + +## 语言特定执行器 + +### JavaScript执行器 + +JavaScript代码通过[JsRunner](mdc:src/deno_runner/js_runner.rs)执行: + +1. 准备JavaScript代码,添加日志捕获和handler函数执行逻辑 +2. 使用代码哈希值缓存处理过的代码 +3. 使用Deno执行代码 +4. 解析执行结果 + +### TypeScript执行器 + +TypeScript代码通过[TsRunner](mdc:src/deno_runner/ts_runner.rs)执行: + +1. 准备TypeScript代码,添加日志捕获和handler函数执行逻辑 +2. 使用代码哈希值缓存处理过的代码 +3. 使用Deno执行代码 +4. 解析执行结果 + +### Python执行器 + +Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行: + +1. 分析Python代码的依赖 +2. 准备Python代码,添加日志捕获和handler/main函数执行逻辑 +3. 使用代码哈希值缓存处理过的代码 +4. 使用uv在隔离环境中执行Python代码 +5. 解析执行结果 + +## 参数传递 + +参数通过JSON格式传递给脚本: + +1. JavaScript/TypeScript:参数直接传递给handler函数 +2. Python:参数通过环境变量`INPUT_JSON`传递,然后在脚本中解析并传递给handler或main函数 + +## 结果处理 + +执行结果通过[CodeScriptExecutionResult](mdc:src/model/code_run_model.rs)结构体表示,包含: + +- result: 执行结果(通过handler/main函数返回) +- logs: 执行过程中的日志 +- success: 执行是否成功 +- error: 执行错误(如果有) + +## 缓存机制 + +为了提高性能,项目使用[CodeFileCache](mdc:src/cache)缓存处理过的代码: + +1. 计算代码的哈希值 +2. 检查是否存在对应的缓存 +3. 如果存在,直接使用缓存;否则处理代码并缓存结果 diff --git a/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc b/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc new file mode 100644 index 00000000..2a94ea5c --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc @@ -0,0 +1,72 @@ +--- +description: +globs: +alwaysApply: false +--- +# 项目依赖 + +本项目使用多个Rust库来实现其功能。以下是主要依赖项及其用途: + +## Cargo.toml + +```toml +[dependencies] +tokio = { version = "1.32", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +log = "0.4" +env_logger = "0.10" +anyhow = "1.0" +tempfile = "3.8" +regex = "1.9" +clap = { version = "4.4", features = ["derive"] } +thiserror = "1.0" +``` + +## 主要依赖项 + +### 异步运行时 + +- [tokio](https://crates.io/crates/tokio): 异步运行时,用于支持异步/await语法和并发执行 + +### 序列化/反序列化 + +- [serde](https://crates.io/crates/serde): 通用序列化/反序列化框架 +- [serde_json](https://crates.io/crates/serde_json): JSON序列化/反序列化支持 + +### 错误处理 + +- [anyhow](https://crates.io/crates/anyhow): 简化错误处理的库 +- [thiserror](https://crates.io/crates/thiserror): 用于定义自定义错误类型 + +### 日志记录 + +- [log](https://crates.io/crates/log): 日志记录接口 +- [env_logger](https://crates.io/crates/env_logger): 基于环境变量的日志记录器 + +### 命令行参数解析 + +- [clap](https://crates.io/crates/clap): 命令行参数解析库,支持派生宏 + +### 其他工具 + +- [rmcp](https://crates.io/crates/rmcp): Model Context Protocol (MCP) SDK +- [tempfile](https://crates.io/crates/tempfile): 临时文件创建和管理 +- [regex](https://crates.io/crates/regex): 正则表达式支持 + +## 外部依赖 + +除了Rust库外,本项目还依赖以下外部工具: + +1. [Deno](https://deno.land/): 现代JavaScript和TypeScript运行时,用于执行JavaScript代码 +2. [uv](https://github.com/astral-sh/uv): Python包管理器和隔离环境工具,用于安全执行Python代码 + +## 安装外部依赖 + +```bash +# 安装Deno +curl -fsSL https://deno.land/x/install/install.sh | sh + +# 安装uv +curl -fsSL https://astral.sh/uv/install.sh | sh +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc b/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc new file mode 100644 index 00000000..73812684 --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc @@ -0,0 +1,101 @@ +--- +description: +globs: +alwaysApply: false +--- +# 错误处理 + +本项目使用[anyhow](mdc:https:/crates.io/crates/anyhow)和[thiserror](mdc:https:/crates.io/crates/thiserror)库进行错误处理,错误类型定义在[error.rs](mdc:src/error.rs)中。 + +## 错误类型 + +```rust +#[derive(Error, Debug)] +pub enum AppError { + #[error("Failed to execute code: {0}")] + ExecutionError(String), + + #[error("Failed to parse output: {0}")] + ParseError(String), + + #[error("Failed to create temporary file: {0}")] + IoError(#[from] std::io::Error), + + #[error("Failed to parse JSON: {0}")] + JsonError(#[from] serde_json::Error), + + #[error("Missing required field: {0}")] + MissingField(String), + + #[error("Unsupported language: {0}")] + UnsupportedLanguage(String), + + #[error("Handler function not found")] + HandlerNotFound, + + #[error("MCP error: {0}")] + McpError(String), + + #[error("{0}")] + Other(String), +} + +pub type AppResult = Result; +``` + +## 错误处理流程 + +1. 在代码执行过程中使用`anyhow::Result`类型传递错误 +2. 使用`?`操作符传播错误 +3. 使用`context`方法添加错误上下文 +4. 在`main`函数中处理所有未捕获的错误 + +## 示例 + +```rust +// 读取代码文件 +fn get_code(args: &CodeArgs) -> Result { + if let Some(file) = &args.file { + fs::read_to_string(file).context("Failed to read code file") + } else if let Some(code) = &args.code { + Ok(code.clone()) + } else { + anyhow::bail!("Either file or code must be provided") + } +} + +// 执行JavaScript代码 +fn execute_js(code: &str, show_logs: bool) -> Result { + // Prepare the JavaScript code with wrapped handler + let wrapped_code = prepare_js_code(code, show_logs); + + // Create a temporary file + let mut temp_file = NamedTempFile::new()?; + write!(temp_file.as_file_mut(), "{}", wrapped_code)?; + + // Execute with Deno + let output = Command::new("deno") + .arg("run") + .arg("--allow-all") + .arg(temp_file.path()) + .output() + .context("Failed to execute Deno")?; + + // Parse the output + parse_execution_output(&output.stdout, &output.stderr) +} +``` + +## 错误输出 + +执行结果中的错误通过`ExecutionResult`结构体的`error`字段返回,并在`print_result`函数中输出: + +```rust +fn print_result(result: ExecutionResult) { + // ... + + if let Some(error) = result.error { + eprintln!("Error: {}", error); + } +} +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/examples.mdc b/qiming-run_code_rmcp/.cursor/rules/examples.mdc new file mode 100644 index 00000000..9e8a1966 --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/examples.mdc @@ -0,0 +1,138 @@ +--- +description: +globs: +alwaysApply: false +--- +# 示例代码 + +本项目包含JavaScript和Python的示例代码,用于测试代码执行功能。 + +## JavaScript示例 + +[examples/test_js.js](mdc:examples/test_js.js)是一个简单的JavaScript示例,包含: + +```javascript +// Sample JavaScript file with handler function + +// Log some debug information +console.log("Initializing JavaScript test..."); + +// Define a simple add function +function add(a, b) { + console.log(`Adding ${a} + ${b}`); + return a + b; +} + +// Some processing +const numbers = [1, 2, 3, 4, 5]; +console.log("Processing numbers:", numbers); + +// Handler function that will be called to get the result +function handler() { + console.log("Handler function called"); + + // Calculate sum + const sum = numbers.reduce((acc, num) => add(acc, num), 0); + console.log("Final calculation completed"); + + return `The sum of [${numbers.join(", ")}] is ${sum}`; +} +``` + +执行这个示例将输出: + +``` +--- Logs --- +Initializing JavaScript test... +Processing numbers: [1,2,3,4,5] +Handler function called +Adding 0 + 1 +Adding 1 + 2 +Adding 3 + 3 +Adding 6 + 4 +Adding 10 + 5 +Final calculation completed +------------ +Result: The sum of [1, 2, 3, 4, 5] is 15 +``` + +## Python示例 + +[examples/test_python.py](mdc:examples/test_python.py)是一个简单的Python示例,包含: + +```python +#!/usr/bin/env python3 +# Sample Python file with handler function + +import time + +# Log some debug information +print("Initializing Python test...") + +# Define a simple function +def multiply(a, b): + print(f"Multiplying {a} * {b}") + return a * b + +# Some processing +numbers = [1, 2, 3, 4, 5] +print(f"Processing numbers: {numbers}") + +# Handler function that will be called to get the result +def handler(): + print("Handler function called") + + # Calculate product + product = 1 + for num in numbers: + product = multiply(product, num) + + print("Final calculation completed") + + return f"The product of {numbers} is {product}" + +# This part won't be executed when the code is run through the MCP runner +if __name__ == "__main__": + print("Running directly, calling handler...") + result = handler() + print(f"Result: {result}") +``` + +执行这个示例将输出: + +``` +--- Logs --- +Handler function called +Multiplying 1 * 1 +Multiplying 1 * 2 +Multiplying 2 * 3 +Multiplying 6 * 4 +Multiplying 24 * 5 +Final calculation completed +------------ +Result: The product of [1, 2, 3, 4, 5] is 120 +``` + +## 如何创建自己的示例 + +创建自己的示例代码时,需要遵循以下规则: + +1. 必须包含一个名为`handler`的函数,作为执行结果的返回点 +2. `handler`函数的返回值将作为执行结果 +3. 使用`console.log`(JavaScript)或`print`(Python)输出日志 +4. 可以定义其他函数和变量,它们将在执行过程中被使用 + +示例: + +```javascript +// JavaScript示例 +function handler() { + return "Hello from JavaScript!"; +} +``` + +```python +# Python示例 +def handler(): + return "Hello from Python!" +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc b/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc new file mode 100644 index 00000000..e8f6a68c --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc @@ -0,0 +1,98 @@ +--- +description: +globs: +alwaysApply: false +--- +# 测试脚本示例指南 + +本项目包含多种语言的测试脚本示例,位于`fixtures`目录下。这些示例展示了如何编写可被本工具执行的JavaScript、TypeScript和Python脚本。 + +## JavaScript示例 + +### 基本执行 + +[test_js.js](mdc:fixtures/test_js.js) - 基本的JavaScript执行示例: +- 展示了如何使用`console.log`输出日志 +- 定义了`handler`函数作为执行结果的返回点 + +### 参数传递 + +[test_js_params.js](mdc:fixtures/test_js_params.js) - 带参数的JavaScript执行示例: +- 展示了如何接收和处理传入的参数 +- 演示了如何根据参数执行不同的逻辑 + +### 函数示例 + +[rfunction_test1.js](mdc:fixtures/rfunction_test1.js) - 函数式JavaScript示例: +- 展示了如何定义和使用函数 +- 演示了如何处理和返回复杂数据结构 + +## TypeScript示例 + +### 基本执行 + +[test_ts.ts](mdc:fixtures/test_ts.ts) - 基本的TypeScript执行示例: +- 展示了TypeScript的类型定义 +- 定义了带类型的`handler`函数 + +### 参数传递 + +[test_ts_params.ts](mdc:fixtures/test_ts_params.ts) - 带参数的TypeScript执行示例: +- 展示了如何定义参数类型 +- 演示了TypeScript中的类型安全参数处理 + +## Python示例 + +### 基本执行 + +[test_python.py](mdc:fixtures/test_python.py) - 基本的Python执行示例: +- 使用`handler`函数作为执行结果的返回点 +- 展示了如何使用Python进行基本计算和日志输出 + +### 参数传递 + +[test_python_params.py](mdc:fixtures/test_python_params.py) - 带参数的Python执行示例: +- 展示了如何接收和处理传入的参数 +- 演示了如何返回包含多个字段的结果 + +### 类型示例 + +[test_python_types.py](mdc:fixtures/test_python_types.py) - 不同返回类型的Python示例: +- 展示了如何返回不同类型的值(字符串、数字、布尔值、列表、字典等) +- 演示了不同类型在JSON序列化中的处理方式 + +### 函数示例 + +[rfunction_python.py](mdc:fixtures/rfunction_python.py) - 函数式Python示例: +- 展示了如何定义和使用Python函数 +- 演示了如何处理和返回复杂数据结构 + +## 使用示例 + +以下是如何使用这些示例脚本的命令: + +```bash +# 执行基本JavaScript示例 +cargo run -- --show-logs js -f fixtures/test_js.js + +# 执行带参数的JavaScript示例 +cargo run -- --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}' + +# 执行基本TypeScript示例 +cargo run -- --show-logs ts -f fixtures/test_ts.ts + +# 执行带参数的TypeScript示例 +cargo run -- --show-logs ts -f fixtures/test_ts_params.ts -p '{"a":10, "b":20, "name":"User"}' + +# 执行基本Python示例 +cargo run -- --show-logs python -f fixtures/test_python.py + +# 执行带参数的Python示例 +cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}' + +# 执行不同类型的Python示例 +cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"string"}' +cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"number"}' +cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"list"}' +cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"dict"}' +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc b/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc new file mode 100644 index 00000000..c3cd4b97 --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc @@ -0,0 +1,146 @@ +--- +description: +globs: +alwaysApply: false +--- +# MCP SDK集成 + +本项目支持Model Context Protocol (MCP) SDK集成,允许通过标准化协议执行代码。MCP相关功能主要在[mcp.rs](mdc:src/mcp.rs)中实现。 + +## 核心数据结构 + +```rust +#[derive(Serialize, Deserialize, Debug)] +pub struct CodeExecutionParams { + pub code: String, + pub language: String, + pub show_logs: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CodeExecutionResult { + pub logs: Vec, + pub result: Option, + pub error: Option, +} +``` + +## JavaScript执行 + +MCP中的JavaScript执行使用以下包装代码: + +```javascript +// MCP SDK integration for JavaScript +// Note: MCP SDK is not required for basic functionality + +// Log capture setup +const logs = []; +const originalConsoleLog = console.log; +console.log = function() { + const message = Array.from(arguments).map(arg => + typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg) + ).join(' '); + logs.push(message); + if (show_logs) { + originalConsoleLog.apply(console, arguments); + } +}; + +async function mcpExecute() { + try { + // User code here + + // Execute handler + let result = null; + if (typeof handler === 'function') { + result = await handler(); + } + + return { + logs: logs, + result: result !== undefined ? String(result) : null, + error: null + }; + } catch (error) { + return { + logs: logs, + result: null, + error: error.toString() + }; + } +} + +// Execute and return results +mcpExecute().then(result => { + originalConsoleLog(JSON.stringify(result)); +}); +``` + +## Python执行 + +MCP中的Python执行使用以下包装代码: + +```python +# MCP SDK integration for Python +# Note: MCP SDK is not required for basic functionality +import sys +import json +import io +from contextlib import redirect_stdout + +# Store logs +logs = [] + +# Create custom stdout to capture logs +class LogCapture(io.StringIO): + def write(self, text): + if text.strip(): + logs.append(text.rstrip()) + if show_logs: + sys.__stdout__.write(text) + +def mcp_execute(): + try: + // User code here + + // Execute handler function and capture result + result = None + with redirect_stdout(LogCapture()): + if 'handler' in globals() and callable(handler): + result = handler() + + return { + "logs": logs, + "result": str(result) if result is not None else None, + "error": None + } + except Exception as e: + return { + "logs": logs, + "result": None, + "error": str(e) + } + +# Execute and print results +result = mcp_execute() +print(json.dumps(result)) +``` + +## 执行流程 + +MCP执行流程由[execute_with_mcp](mdc:src/mcp.rs#L138)函数处理: + +1. 创建临时文件并写入包装后的代码 +2. 根据语言类型选择执行命令(Deno或uv) +3. 执行代码并捕获输出 +4. 解析JSON格式的执行结果 +5. 如果无法解析JSON,则返回原始输出 + +## 使用方法 + +在命令行中使用`--use-mcp`参数启用MCP SDK集成: + +```bash +cargo run -- --use-mcp js -c "function handler() { return 'Hello from MCP JS'; }" +cargo run -- --use-mcp python -f examples/test_python.py +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc b/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc new file mode 100644 index 00000000..aba9dab5 --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc @@ -0,0 +1,50 @@ +--- +description: +globs: +alwaysApply: false +--- +# 项目概述 + +这是一个使用Rust开发的命令行工具,用于执行JavaScript、TypeScript和Python代码。该工具支持以下功能: + +1. 执行JavaScript代码(使用Deno运行时) +2. 执行TypeScript代码(使用Deno运行时) +3. 执行Python代码(使用uv提供隔离环境) +4. 捕获并区分脚本中的日志输出和执行结果 +5. 支持JavaScript/TypeScript的handler函数和Python的handler/main函数作为执行结果的返回点 +6. 通过环境变量传递参数给脚本 +7. 可以通过参数控制是否显示日志输出 +8. 支持MCP SDK集成 + +## 主要文件 + +- [src/main.rs](mdc:src/main.rs) - 应用程序入口点,处理命令行参数和执行流程 +- [src/lib.rs](mdc:src/lib.rs) - 库入口点,导出模块和类型 +- [src/model/code_run_model.rs](mdc:src/model/code_run_model.rs) - 代码执行模型和结果处理 +- [src/deno_runner/js_runner.rs](mdc:src/deno_runner/js_runner.rs) - JavaScript代码执行器 +- [src/deno_runner/ts_runner.rs](mdc:src/deno_runner/ts_runner.rs) - TypeScript代码执行器 +- [src/python_runner/python_runner.rs](mdc:src/python_runner/python_runner.rs) - Python代码执行器 +- [src/cache/](mdc:src/cache) - 代码缓存管理 +- [fixtures/](mdc:fixtures) - 测试脚本示例 + +## 使用示例 + +```bash +# 执行JavaScript文件 +cargo run -- --show-logs js -f fixtures/test_js.js + +# 执行TypeScript文件 +cargo run -- --show-logs ts -f fixtures/test_ts.ts + +# 执行Python文件 +cargo run -- --show-logs python -f fixtures/test_python.py + +# 带参数执行Python文件 +cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}' + +# 直接执行JavaScript代码 +cargo run -- js -c "function handler(input) { return 'Hello from JS: ' + input.name; }" -p '{"name":"User"}' + +# 直接执行Python代码 +cargo run -- python -c "def handler(args): return 'Hello from Python: ' + args.get('name', 'Guest')" -p '{"name":"User"}' +``` diff --git a/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc b/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc new file mode 100644 index 00000000..850496be --- /dev/null +++ b/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc @@ -0,0 +1,143 @@ +--- +description: +globs: +alwaysApply: false +--- +# Python代码执行 + +本项目使用`uv`在隔离环境中执行Python代码,提供安全可靠的执行环境。以下是Python代码执行的详细流程: + +## Python运行器 + +Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行,实现了`RunCode`特性: + +```rust +pub struct PythonRunner; + +impl RunCode for PythonRunner { + async fn run(&self, code: &str) -> Result { + // 实现代码... + } + + async fn run_with_params(&self, code: &str, params: Option) -> Result { + // 实现代码... + } +} +``` + +## 依赖解析 + +Python代码的依赖通过[python_dependencies.rs](mdc:src/python_runner/dependencies/python_dependencies.rs)解析: + +1. 使用Pest解析器分析Python代码中的import语句 +2. 支持常规import语句和importlib动态导入 +3. 提取所有导入的模块名称 + +```rust +pub fn parse_import(python_code: &str) -> Result> { + // 使用Pest解析器分析Python代码 + let pairs = ImportParser::parse(Rule::file, &input)?; + + // 提取模块名称 + let mut imported_modules = Vec::new(); + // 处理各种import语句... + + Ok(imported_modules) +} +``` + +## 代码准备 + +Python代码在执行前需要进行包装,添加日志捕获和函数执行逻辑: + +```rust +fn prepare_python_code(&self, code: &str, show_logs: bool) -> String { + // 创建包装代码,包括: + // 1. 日志捕获 + // 2. 参数解析 + // 3. 执行handler或main函数 + // 4. 结果格式化 +} +``` + +包装代码主要功能: +1. 捕获标准输出作为日志 +2. 从环境变量`INPUT_JSON`解析参数 +3. 优先执行`handler`函数,如果不存在则执行`main`函数 +4. 将执行结果和日志格式化为JSON + +## 参数传递 + +参数通过环境变量`INPUT_JSON`传递给Python脚本: + +```rust +// 将参数序列化为JSON字符串 +let params_json = match params { + Some(p) => serde_json::to_string(&p)?, + None => "{}".to_string(), +}; + +// 通过环境变量传递参数 +execute_command.env("INPUT_JSON", ¶ms_json); +``` + +在Python脚本中,参数通过以下方式获取: + +```python +# 从环境变量获取输入参数 +args = {} +try: + input_json = os.environ.get('INPUT_JSON') + if input_json: + args = json.loads(input_json) +except Exception as e: + print(f"解析输入参数失败: {e}") +``` + +## 执行流程 + +1. 计算代码哈希值,检查缓存 +2. 如果缓存存在,直接使用;否则: + - 分析代码依赖 + - 准备包装后的代码 + - 保存到缓存 +3. 使用`uv`命令在隔离环境中执行代码: + ```rust + let mut execute_command = Command::new("uv"); + execute_command + .arg("run") + .arg("-s") // 明确指定作为脚本运行 + .arg("--isolated") // 在隔离环境中运行 + .arg("-p") + .arg("python3") // 指定Python解释器 + .env("INPUT_JSON", ¶ms_json) // 通过环境变量传递参数 + .arg(&temp_path) + ``` +4. 解析执行结果,提取日志和返回值 + +## 结果处理 + +执行结果通过JSON格式返回,包含三个字段: +- `logs`: 执行过程中的日志数组 +- `result`: 执行结果(可能是字符串、数字、布尔值、对象等) +- `error`: 执行错误(如果有) + +```json +{ + "logs": ["日志1", "日志2", ...], + "result": "执行结果", + "error": null +} +``` + +## 支持的返回类型 + +Python脚本可以返回多种类型的值: +1. 字符串 +2. 数字 +3. 布尔值 +4. 列表/数组 +5. 字典/对象 +6. None/null + +所有复杂类型(列表、字典)都会被序列化为JSON字符串。 diff --git a/qiming-run_code_rmcp/.devcontainer/devcontainer.json b/qiming-run_code_rmcp/.devcontainer/devcontainer.json new file mode 100644 index 00000000..afab381e --- /dev/null +++ b/qiming-run_code_rmcp/.devcontainer/devcontainer.json @@ -0,0 +1,43 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/rust +{ + "name": "Rust", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + // "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", + // 使用你自定义的Dockerfile + "build": { + "dockerfile": "../Dockerfile", + "context": ".." + }, + "features": { + "ghcr.io/devcontainers/features/go:1": {}, + "ghcr.io/devcontainers-community/features/deno:1": {}, + "ghcr.io/va-h/devcontainers-features/uv:1": {}, + "ghcr.io/devcontainers-extra/features/curl-apt-get:1": {}, + "ghcr.io/devcontainers-extra/features/wget-apt-get:1": {} + } + + // Use 'mounts' to make the cargo cache persistent in a Docker Volume. + // "mounts": [ + // { + // "source": "devcontainer-cargo-cache-${devcontainerId}", + // "target": "/usr/local/cargo", + // "type": "volume" + // } + // ] + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "rustc --version", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/qiming-run_code_rmcp/.gitignore b/qiming-run_code_rmcp/.gitignore new file mode 100644 index 00000000..3187ccbd --- /dev/null +++ b/qiming-run_code_rmcp/.gitignore @@ -0,0 +1,6 @@ +/target +.idea +.vscode +.DS_Store + +temp/ \ No newline at end of file diff --git a/qiming-run_code_rmcp/CLAUDE.md b/qiming-run_code_rmcp/CLAUDE.md new file mode 100644 index 00000000..2a0663ec --- /dev/null +++ b/qiming-run_code_rmcp/CLAUDE.md @@ -0,0 +1,108 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Rust-based MCP (Model Context Protocol) server that executes JavaScript, TypeScript, and Python code in isolated environments. The project provides both a command-line tool and a library for code execution with proper sandboxing and result capture. + +## Key Commands + +### Building and Running +```bash +# Build the project +cargo build + +# Run with cargo +cargo run -- js -f fixtures/test_js.js +cargo run -- ts -f fixtures/test_ts.ts +cargo run -- python -f fixtures/test_python.py + +# Install as binary +cargo install --path . --bin script_runner + +# Run tests +cargo test + +# Clear cache +cargo run -- clear-cache all +``` + +### Common Development Commands +```bash +# Execute JavaScript with logs +cargo run -- --show-logs js -f fixtures/test_js.js + +# Execute with parameters +cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}' + +# Execute inline code +cargo run -- js -c "function handler(input) { return 'Hello: ' + input.name; }" -p '{"name":"User"}' + +# Use MCP integration +cargo run -- --use-mcp python -f fixtures/test_python.py +``` + +## Architecture + +### Core Components +- **src/main.rs** - CLI entry point with command parsing using clap +- **src/lib.rs** - Library interface exporting public APIs +- **src/model/code_run_model.rs** - Core execution models and `CodeExecutor` struct +- **src/cache/** - Code caching system using blake3 hashing +- **src/mcp/** - MCP protocol server implementation + +### Language Runners +- **src/deno_runner/js_runner.rs** - JavaScript execution via Deno +- **src/deno_runner/ts_runner.rs** - TypeScript execution via Deno +- **src/python_runner/python_runner.rs** - Python execution via uv with dependency analysis +- **src/python_runner/dependencies/** - Python dependency parsing and management + +### Execution Flow +1. Code is processed and cached using blake3 hashing +2. Language-specific runner prepares the execution environment +3. Code is wrapped with logging capture and handler function execution +4. Results are captured and returned as `CodeScriptExecutionResult` + +## Important Patterns + +### Handler Function Convention +Scripts must expose a `handler` function (JS/TS) or `handler`/`main` function (Python) that returns the final result: +```javascript +function handler(input) { + return "Result: " + input.param; +} +``` + +### Parameter Passing +- JS/TS: Parameters passed directly to handler function +- Python: Parameters passed via `INPUT_JSON` environment variable + +### Error Handling +- Uses `anyhow::Result` for error propagation +- Custom `app_error` module for application-specific errors +- Execution results include error information in `CodeScriptExecutionResult` + +### Dependencies +- **Deno** for JavaScript/TypeScript execution +- **uv** for Python environment isolation +- **rmcp** crate for MCP protocol implementation +- **tokio** for async runtime +- **serde** for JSON serialization + +## Development Notes + +### Testing +- Test fixtures in `fixtures/` directory for all supported languages +- Integration tests in `src/tests/` +- Use `--show-logs` flag during development to see execution output + +### Caching +- Code processing results are cached based on content hash +- Use `--clear-cache` to invalidate cache when debugging +- Cache files stored in system temp directory + +### MCP Integration +- Can run as standalone MCP server via `script_runner` binary +- Supports SSE and HTTP transports +- Tools: `run_javascript`, `run_typescript`, `run_python` \ No newline at end of file diff --git a/qiming-run_code_rmcp/Cargo.lock b/qiming-run_code_rmcp/Cargo.lock new file mode 100644 index 00000000..c78f31c9 --- /dev/null +++ b/qiming-run_code_rmcp/Cargo.lock @@ -0,0 +1,1429 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + +[[package]] +name = "pest" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +dependencies = [ + "memchr", + "serde", + "serde_json", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rmcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa" +dependencies = [ + "async-trait", + "base64", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "run_code_rmcp" +version = "0.0.35" +dependencies = [ + "anyhow", + "async-trait", + "blake3", + "clap", + "env_logger", + "libc", + "log", + "once_cell", + "pest", + "pest_derive", + "pin-project", + "regex", + "rmcp", + "schemars", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/qiming-run_code_rmcp/Cargo.toml b/qiming-run_code_rmcp/Cargo.toml new file mode 100644 index 00000000..dd059115 --- /dev/null +++ b/qiming-run_code_rmcp/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "run_code_rmcp" +description = "云函数服务,执行JS/TS/Python语言代码,脚本必须有约定的函数名称(handler/main),会调用约定的函数名称结果和日志返回." +version = "0.0.35" +edition = "2024" +license = "Apache-2.0" +repository = "https://github.com/nuwax-ai/run_code_rmcp" +homepage = "https://nuwax.com/" +readme = "README.md" +default-run = "run_code_rmcp" + +[features] +default = [] +mcp = ["dep:rmcp"] + +[dependencies] +tokio = { version = "1.47", features = [ + "fs", + "macros", + "net", + "rt", + "rt-multi-thread", + "signal", + "io-util", + "process", + "time", +] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +log = "0.4" +env_logger = "0.11" +anyhow = "1.0" +rmcp = { version = "0.12", features = [ + "server", + "client", + "transport-io", + "transport-async-rw", + "macros", +], optional = true } +tempfile = "3.21" +regex = "1.11" +clap = { version = "4.5", features = ["derive"] } +thiserror = "2.0" +pest = { version = "2.8", features = ["pretty-print"] } +pest_derive = "2.8" +blake3 = "1.8" +pin-project = "1.1" +libc = "0.2" +once_cell = "1.21" +schemars = "1.0" +async-trait = "0.1" + +[[bin]] +name = "script_runner" +path = "src/script_runner.rs" +required-features = ["mcp"] diff --git a/qiming-run_code_rmcp/Dockerfile b/qiming-run_code_rmcp/Dockerfile new file mode 100644 index 00000000..eaf2e471 --- /dev/null +++ b/qiming-run_code_rmcp/Dockerfile @@ -0,0 +1,80 @@ +FROM rust:1.85 + +# 设置环境变量 +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Asia/Shanghai +ENV PATH="/root/.deno/bin:/root/.cargo/bin:${PATH}" + +# 安装基础依赖 +RUN apt-get update && apt-get install -y \ + python3.11 \ + python3.11-venv \ + python3.11-distutils \ + python3.11-dev \ + python3-pip \ + curl \ + vim \ + net-tools \ + gettext-base \ + telnet \ + wget \ + && rm -rf /var/lib/apt/lists/* \ + && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ + && echo $TZ > /etc/timezone + +# 安装 Node.js +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && npm install -g npm@latest + +# 安装 go +# 设置 Go 环境变量 +ENV GOPATH=/root/go +ENV PATH=$PATH:/usr/local/go/bin:/root/go/bin + +# 创建 Go 工作目录 +RUN mkdir -p $GOPATH/bin $GOPATH/src $GOPATH/pkg +# 安装 Go 1.24(根据架构自动选择下载链接) +ARG TARGETARCH +RUN echo "Target Architecture: $TARGETARCH" \ + && if [ "$TARGETARCH" = "amd64" ]; then \ + GO_URL="https://go.dev/dl/go1.24.3.linux-amd64.tar.gz"; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + GO_URL="https://go.dev/dl/go1.24.3.linux-arm64.tar.gz"; \ + else \ + echo "Unsupported architecture: $TARGETARCH"; \ + exit 1; \ + fi \ + && echo "Downloading Go from: $GO_URL" \ + && curl -fsSL "$GO_URL" -o go.tar.gz \ + && tar -C /usr/local -xzf go.tar.gz \ + && rm go.tar.gz \ + && ln -s /usr/local/go/bin/go /usr/local/bin/go \ + && ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt + + +RUN go version + +# 测试mcp用 go-mcp-mysql@latest +RUN go install -v github.com/Zhwt/go-mcp-mysql@latest + + +# 安装 Deno +RUN curl -fsSL https://deno.land/install.sh | sh + + +# 安装 uv +RUN curl -LsSf https://astral.sh/uv/install.sh | sh + +# 添加uv到PATH +ENV PATH="/root/.local/bin:${PATH}" + +# 创建虚拟环境 +RUN uv venv + +# 设置工作目录 +WORKDIR /app + +# 暴露端口,实际端口一般是8080 +EXPOSE 8080 + diff --git a/qiming-run_code_rmcp/LICENSE b/qiming-run_code_rmcp/LICENSE new file mode 100644 index 00000000..79d46eaa --- /dev/null +++ b/qiming-run_code_rmcp/LICENSE @@ -0,0 +1,202 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but not +limited to compiled object code, generated documentation, and +conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2024-2025 Nuwax AI and contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/qiming-run_code_rmcp/LICENSE-APACHE b/qiming-run_code_rmcp/LICENSE-APACHE new file mode 100644 index 00000000..79d46eaa --- /dev/null +++ b/qiming-run_code_rmcp/LICENSE-APACHE @@ -0,0 +1,202 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but not +limited to compiled object code, generated documentation, and +conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2024-2025 Nuwax AI and contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/qiming-run_code_rmcp/NOTICE b/qiming-run_code_rmcp/NOTICE new file mode 100644 index 00000000..ee1c0605 --- /dev/null +++ b/qiming-run_code_rmcp/NOTICE @@ -0,0 +1,16 @@ +Copyright 2024-2025 Nuwax AI + +This product includes software developed by Nuwax AI and contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/qiming-run_code_rmcp/README.md b/qiming-run_code_rmcp/README.md new file mode 100644 index 00000000..4e60c1ac --- /dev/null +++ b/qiming-run_code_rmcp/README.md @@ -0,0 +1,335 @@ +# MCP Code Execution Tool + +A Rust-based tool that provides both a command-line interface and an MCP (Model Context Protocol) server for executing JavaScript, TypeScript, and Python code in isolated environments. + +**Repository:** + +English | [中文](./README_zh.md) + +## Features + +- Execute JavaScript, TypeScript, and Python code in isolated environments +- **JavaScript/TypeScript**: Powered by Deno runtime +- **Python**: Powered by `uv` with automatic dependency management +- Automatic code caching using blake3 hashing for faster repeated executions +- Support for ESM and CommonJS modules in JavaScript +- Automatic Python dependency parsing and installation +- Separate log capture and execution result handling +- Available as both CLI tool (`run_code_rmcp`) and MCP server (`script_runner`) + +## Installation + +### Install CLI Tool + +```bash +# Clone the repository +git clone https://github.com/nuwax-ai/run_code_rmcp.git +cd run_code_rmcp + +# Install the CLI tool (run_code_rmcp) +cargo install --path . + +# Or install both CLI tool and MCP server +cargo install --path . --features mcp +cargo install --path . --bin script_runner --features mcp +``` + +### System Requirements + +- **Rust**: 1.85 or higher (for building) +- **Deno**: Required for JavaScript/TypeScript execution +- **uv**: Required for Python execution and dependency management + +Install Deno: +```bash +curl -fsSL https://deno.land/install.sh | sh +``` + +Install uv: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +## Usage + +### CLI Tool: run_code_rmcp + +The `run_code_rmcp` command allows you to execute code directly from the command line. + +#### Basic Syntax + +```bash +run_code_rmcp [OPTIONS] +``` + +**Options:** +- `--show-logs`: Display execution logs +- `--clear-cache`: Clear cache before execution +- `--use-mcp`: Use MCP SDK integration (requires mcp feature) + +**Commands:** +- `js`: Execute JavaScript code +- `ts`: Execute TypeScript code +- `python`: Execute Python code +- `clear-cache`: Clear cached files + +#### Execute JavaScript/TypeScript + +```bash +# Execute a JavaScript file +run_code_rmcp --show-logs js -f fixtures/test_js.js + +# Execute with parameters +run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}' + +# Execute TypeScript +run_code_rmcp --show-logs ts -f fixtures/test_ts.ts + +# Execute inline code +run_code_rmcp js -c "function handler() { return 'Hello!'; }" +``` + +#### Execute Python + +```bash +# Execute a Python file +run_code_rmcp --show-logs python -f fixtures/test_python.py + +# Execute with parameters +run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}' + +# Execute inline code +run_code_rmcp python -c "def handler(): return 'Hello from Python!'" +``` + +#### Cache Management + +```bash +# Clear all cache +run_code_rmcp clear-cache --language all + +# Clear specific language cache +run_code_rmcp clear-cache --language python +run_code_rmcp clear-cache --language js +run_code_rmcp clear-cache --language ts + +# Clear cache before execution +run_code_rmcp --clear-cache js -f fixtures/test_js.js +``` + +### Using as a Rust Library + +```toml +[dependencies] +run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" } +``` + +```rust +use run_code_rmcp::{CodeExecutor, LanguageScript}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Execute JavaScript + let js_result = CodeExecutor::execute_with_params_compat( + "function handler() { return {success: true}; }", + LanguageScript::Js, + None + ).await?; + + // Execute Python + let py_result = CodeExecutor::execute_with_params_compat( + "def handler(): return {'success': True}", + LanguageScript::Python, + None + ).await?; + + Ok(()) +} +``` + +### MCP Server: script_runner + +The `script_runner` is an MCP server that provides tools for AI assistants. + +#### Starting the Server + +```bash +# Start MCP server +script_runner + +# Start with verbose logging +script_runner --verbose +``` + +The server communicates via stdio and waits for MCP protocol JSON requests. + +#### Available Tools + +The MCP server provides the following tools: + +1. **run_javascript** - Execute JavaScript code +2. **run_typescript** - Execute TypeScript code +3. **run_python** - Execute Python code + +Each tool accepts: +- `code` (string): The code to execute +- `params` (object, optional): Parameters to pass to the handler function + +#### Integration with AI Assistants + +Add to your MCP client configuration (e.g., Claude Desktop): + +```json +{ + "name": "script-runner", + "command": "script_runner", + "transport": "stdio" +} +``` + +## Code Structure + +### Handler Functions + +Your scripts should define a handler function that returns the result. + +#### JavaScript/TypeScript + +Supports both `main` and `handler` functions (main takes priority): + +```javascript +// This function takes priority +function main(input) { + console.log("Processing with main:", input); + return { result: "Hello from main!" }; +} + +// This function is used if main is not defined +function handler(input) { + console.log("Processing with handler:", input); + return { result: "Hello from handler!" }; +} +``` + +ESM modules are also supported: + +```javascript +import { serve } from "https://deno.land/std/http/server.ts"; + +function handler(input) { + return { message: "ESM works!" }; +} +``` + +#### Python + +Supports both `handler` and `main` functions (handler takes priority): + +```python +import pandas as pd + +# This function takes priority +def handler(args): + print(f"Processing: {args}") + data = pd.DataFrame({"a": [1, 2, 3]}) + return {"result": data.to_dict()} + +# This function is used if handler is not defined +def main(args): + return {"result": "Using main instead"} +``` + +### Parameter Passing + +Parameters are passed to your handler function: + +```bash +run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}' +``` + +In your code: +```javascript +function handler(input) { + // input = { name: "User", count: 42 } + return `Hello ${input.name}, count: ${input.count}`; +} +``` + +### Python Dependencies + +Python dependencies are automatically detected and installed: + +```python +import requests +import pandas as pd + +def handler(args): + # Dependencies are automatically installed via uv + response = requests.get("https://api.example.com") + data = pd.DataFrame(response.json()) + return data.to_dict() +``` + +### Log Capture + +All console output is captured separately from the return value: + +```javascript +console.log("This goes to logs"); +console.log("So does this"); + +function handler() { + return "This is the result"; +} +``` + +Result: +```json +{ + "logs": ["This goes to logs", "So does this"], + "result": "This is the result", + "error": null +} +``` + +## Development + +```bash +# Build +cargo build + +# Run with cargo +cargo run -- --show-logs js -f fixtures/test_js.js + +# Run tests +cargo test + +# Build with MCP feature +cargo build --features mcp + +# Run MCP server with cargo +cargo run --bin script_runner --features mcp +``` + +## Environment Pre-warming + +The project includes a warm-up function to cache common dependencies: + +```rust +use run_code_rmcp::warm_up_all_envs; + +#[tokio::main] +async fn main() -> Result<()> { + // Pre-install common Python and JS/TS dependencies + warm_up_all_envs(None, None, None, None).await?; + Ok(()) +} +``` + +## TODO + +- [ ] TTS (Text-to-Speech) functionality - Currently has known issues + +## License + +This project is licensed under the Apache License 2.0. See `LICENSE` and `NOTICE` files in the root directory for details. diff --git a/qiming-run_code_rmcp/README_zh.md b/qiming-run_code_rmcp/README_zh.md new file mode 100644 index 00000000..9bed86bd --- /dev/null +++ b/qiming-run_code_rmcp/README_zh.md @@ -0,0 +1,335 @@ +# MCP 代码执行工具 + +一个基于 Rust 的工具,提供命令行界面和 MCP(Model Context Protocol)服务器,用于在隔离环境中执行 JavaScript、TypeScript 和 Python 代码。 + +**项目仓库:** + +[English](./README.md) | 中文 + +## 功能特点 + +- 在隔离环境中执行 JavaScript、TypeScript 和 Python 代码 +- **JavaScript/TypeScript**:由 Deno 运行时驱动 +- **Python**:由 `uv` 驱动,支持自动依赖管理 +- 使用 blake3 哈希自动缓存代码,加快重复执行速度 +- 支持 JavaScript 的 ESM 和 CommonJS 模块 +- 自动解析和安装 Python 依赖 +- 独立的日志捕获和执行结果处理 +- 提供命令行工具(`run_code_rmcp`)和 MCP 服务器(`script_runner`)两种使用方式 + +## 安装方法 + +### 安装命令行工具 + +```bash +# 克隆仓库 +git clone https://github.com/nuwax-ai/run_code_rmcp.git +cd run_code_rmcp + +# 安装命令行工具 (run_code_rmcp) +cargo install --path . + +# 或同时安装命令行工具和 MCP 服务器 +cargo install --path . --features mcp +cargo install --path . --bin script_runner --features mcp +``` + +### 系统要求 + +- **Rust**:1.85 或更高版本(用于构建) +- **Deno**:JavaScript/TypeScript 执行必需 +- **uv**:Python 执行和依赖管理必需 + +安装 Deno: +```bash +curl -fsSL https://deno.land/install.sh | sh +``` + +安装 uv: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +## 使用方法 + +### 命令行工具:run_code_rmcp + +`run_code_rmcp` 命令允许直接从命令行执行代码。 + +#### 基本语法 + +```bash +run_code_rmcp [选项] <命令> +``` + +**选项:** +- `--show-logs`:显示执行日志 +- `--clear-cache`:执行前清除缓存 +- `--use-mcp`:使用 MCP SDK 集成(需要 mcp feature) + +**命令:** +- `js`:执行 JavaScript 代码 +- `ts`:执行 TypeScript 代码 +- `python`:执行 Python 代码 +- `clear-cache`:清除缓存文件 + +#### 执行 JavaScript/TypeScript + +```bash +# 执行 JavaScript 文件 +run_code_rmcp --show-logs js -f fixtures/test_js.js + +# 带参数执行 +run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}' + +# 执行 TypeScript +run_code_rmcp --show-logs ts -f fixtures/test_ts.ts + +# 执行内联代码 +run_code_rmcp js -c "function handler() { return 'Hello!'; }" +``` + +#### 执行 Python + +```bash +# 执行 Python 文件 +run_code_rmcp --show-logs python -f fixtures/test_python.py + +# 带参数执行 +run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}' + +# 执行内联代码 +run_code_rmcp python -c "def handler(): return 'Hello from Python!'" +``` + +#### 缓存管理 + +```bash +# 清除所有缓存 +run_code_rmcp clear-cache --language all + +# 清除特定语言的缓存 +run_code_rmcp clear-cache --language python +run_code_rmcp clear-cache --language js +run_code_rmcp clear-cache --language ts + +# 执行前清除缓存 +run_code_rmcp --clear-cache js -f fixtures/test_js.js +``` + +### 作为 Rust 库使用 + +```toml +[dependencies] +run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" } +``` + +```rust +use run_code_rmcp::{CodeExecutor, LanguageScript}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 执行 JavaScript + let js_result = CodeExecutor::execute_with_params_compat( + "function handler() { return {success: true}; }", + LanguageScript::Js, + None + ).await?; + + // 执行 Python + let py_result = CodeExecutor::execute_with_params_compat( + "def handler(): return {'success': True}", + LanguageScript::Python, + None + ).await?; + + Ok(()) +} +``` + +### MCP 服务器:script_runner + +`script_runner` 是一个 MCP 服务器,为 AI 助手提供代码执行工具。 + +#### 启动服务器 + +```bash +# 启动 MCP 服务器 +script_runner + +# 启用详细日志 +script_runner --verbose +``` + +服务器通过 stdio 通信,等待 MCP 协议的 JSON 请求。 + +#### 可用工具 + +MCP 服务器提供以下工具: + +1. **run_javascript** - 执行 JavaScript 代码 +2. **run_typescript** - 执行 TypeScript 代码 +3. **run_python** - 执行 Python 代码 + +每个工具接受: +- `code`(字符串):要执行的代码 +- `params`(对象,可选):传递给 handler 函数的参数 + +#### 与 AI 助手集成 + +添加到你的 MCP 客户端配置(如 Claude Desktop): + +```json +{ + "name": "script-runner", + "command": "script_runner", + "transport": "stdio" +} +``` + +## 代码结构 + +### Handler 函数 + +脚本应定义一个返回结果的 handler 函数。 + +#### JavaScript/TypeScript + +支持 `main` 和 `handler` 函数(main 优先): + +```javascript +// 这个函数优先级更高 +function main(input) { + console.log("使用 main 处理:", input); + return { result: "Hello from main!" }; +} + +// 如果没有定义 main,则使用这个函数 +function handler(input) { + console.log("使用 handler 处理:", input); + return { result: "Hello from handler!" }; +} +``` + +也支持 ESM 模块: + +```javascript +import { serve } from "https://deno.land/std/http/server.ts"; + +function handler(input) { + return { message: "ESM works!" }; +} +``` + +#### Python + +支持 `handler` 和 `main` 函数(handler 优先): + +```python +import pandas as pd + +# 这个函数优先级更高 +def handler(args): + print(f"处理中: {args}") + data = pd.DataFrame({"a": [1, 2, 3]}) + return {"result": data.to_dict()} + +# 如果没有定义 handler,则使用这个函数 +def main(args): + return {"result": "使用 main 代替"} +``` + +### 参数传递 + +参数会传递给 handler 函数: + +```bash +run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}' +``` + +在代码中: +```javascript +function handler(input) { + // input = { name: "User", count: 42 } + return `Hello ${input.name}, count: ${input.count}`; +} +``` + +### Python 依赖 + +Python 依赖会被自动检测并安装: + +```python +import requests +import pandas as pd + +def handler(args): + # 依赖会通过 uv 自动安装 + response = requests.get("https://api.example.com") + data = pd.DataFrame(response.json()) + return data.to_dict() +``` + +### 日志捕获 + +所有控制台输出都会与返回值分开捕获: + +```javascript +console.log("这是日志"); +console.log("这也是日志"); + +function handler() { + return "这是结果"; +} +``` + +结果: +```json +{ + "logs": ["这是日志", "这也是日志"], + "result": "这是结果", + "error": null +} +``` + +## 开发 + +```bash +# 构建 +cargo build + +# 使用 cargo 运行 +cargo run -- --show-logs js -f fixtures/test_js.js + +# 运行测试 +cargo test + +# 使用 mcp feature 构建 +cargo build --features mcp + +# 使用 cargo 运行 MCP 服务器 +cargo run --bin script_runner --features mcp +``` + +## 环境预热 + +项目包含预热函数来缓存常用依赖: + +```rust +use run_code_rmcp::warm_up_all_envs; + +#[tokio::main] +async fn main() -> Result<()> { + // 预安装常用的 Python 和 JS/TS 依赖 + warm_up_all_envs(None, None, None, None).await?; + Ok(()) +} +``` + +## 待办事项 + +- [ ] TTS(文本转语音)功能 - 目前存在已知问题 + +## 许可证 + +本项目采用 Apache License 2.0 发布。详见根目录的 `LICENSE` 与 `NOTICE` 文件。 diff --git a/qiming-run_code_rmcp/examples/cursor_mcp_config.json b/qiming-run_code_rmcp/examples/cursor_mcp_config.json new file mode 100644 index 00000000..64b47458 --- /dev/null +++ b/qiming-run_code_rmcp/examples/cursor_mcp_config.json @@ -0,0 +1,6 @@ +{ + "name": "Script Runner", + "description": "执行JavaScript、TypeScript和Python代码", + "path": "/path/to/script_runner", + "transport": "stdio" +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/examples/mcp_client.js b/qiming-run_code_rmcp/examples/mcp_client.js new file mode 100644 index 00000000..936d4bb2 --- /dev/null +++ b/qiming-run_code_rmcp/examples/mcp_client.js @@ -0,0 +1,151 @@ +// MCP客户端示例,用于与script_runner交互 +// 使用方法:node mcp_client.js + +const { spawn } = require('node:child_process'); +const readline = require('node:readline'); + +// 启动script_runner进程 +const scriptRunner = spawn('script_runner', ['--verbose']); + +// 创建读取标准输入的接口 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 处理script_runner的输出 +scriptRunner.stdout.on('data', (data) => { + console.log(`接收到MCP服务器响应: ${data}`); + try { + const response = JSON.parse(data); + console.log('解析后的响应:', JSON.stringify(response, null, 2)); + } catch (e) { + console.log('无法解析为JSON,显示原始输出'); + } +}); + +// 处理script_runner的错误 +scriptRunner.stderr.on('data', (data) => { + console.error(`MCP服务器错误: ${data}`); +}); + +// 处理script_runner进程结束 +scriptRunner.on('close', (code) => { + console.log(`MCP服务器进程退出,退出码: ${code}`); + rl.close(); +}); + +// 初始化MCP连接 +const initializeRequest = { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + client_info: { + name: 'mcp_client_example' + } + } +}; + +// 发送初始化请求 +console.log('发送初始化请求...'); +scriptRunner.stdin.write(JSON.stringify(initializeRequest) + '\n'); + +// 等待初始化响应后,发送工具列表请求 +setTimeout(() => { + const listToolsRequest = { + jsonrpc: '2.0', + id: 2, + method: 'listTools' + }; + console.log('发送工具列表请求...'); + scriptRunner.stdin.write(JSON.stringify(listToolsRequest) + '\n'); +}, 1000); + +// 等待工具列表响应后,执行JavaScript代码 +setTimeout(() => { + const callToolRequest = { + jsonrpc: '2.0', + id: 3, + method: 'callTool', + params: { + name: 'run_javascript', + arguments: { + code: 'console.log("Hello from JavaScript!"); return { message: "Hello, World!", value: 42 };' + } + } + }; + console.log('发送JavaScript代码执行请求...'); + scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n'); +}, 2000); + +// 等待JavaScript执行响应后,执行TypeScript代码 +setTimeout(() => { + const callToolRequest = { + jsonrpc: '2.0', + id: 4, + method: 'callTool', + params: { + name: 'run_typescript', + arguments: { + code: 'const greeting: string = "Hello from TypeScript!"; console.log(greeting); return { message: greeting, timestamp: new Date().toISOString() };' + } + } + }; + console.log('发送TypeScript代码执行请求...'); + scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n'); +}, 3000); + +// 等待TypeScript执行响应后,执行Python代码 +setTimeout(() => { + const callToolRequest = { + jsonrpc: '2.0', + id: 5, + method: 'callTool', + params: { + name: 'run_python', + arguments: { + code: 'import math\nprint("Hello from Python!")\nresult = math.sqrt(16)\nprint(f"Square root of 16 is {result}")\n{"message": "Hello from Python", "result": result}' + } + } + }; + console.log('发送Python代码执行请求...'); + scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n'); +}, 4000); + +// 处理用户输入,允许用户发送自定义请求 +rl.on('line', (input) => { + if (input.toLowerCase() === 'exit') { + console.log('退出程序...'); + scriptRunner.kill(); + rl.close(); + return; + } + + try { + // 尝试解析用户输入为JSON + const request = JSON.parse(input); + console.log('发送自定义请求:', JSON.stringify(request, null, 2)); + scriptRunner.stdin.write(JSON.stringify(request) + '\n'); + } catch (e) { + console.error('输入不是有效的JSON:', e.message); + } +}); + +// 显示帮助信息 +console.log('\n=== MCP客户端示例 ==='); +console.log('已自动发送初始化、工具列表和代码执行请求'); +console.log('您可以输入自定义JSON请求,或输入"exit"退出程序'); +console.log('示例请求:'); +console.log(JSON.stringify({ + jsonrpc: '2.0', + id: 100, + method: 'callTool', + params: { + name: 'run_javascript', + arguments: { + code: 'return "Hello, " + new Date().toISOString();' + } + } +}, null, 2)); +console.log('====================\n'); \ No newline at end of file diff --git a/qiming-run_code_rmcp/examples/mcp_client_example.js b/qiming-run_code_rmcp/examples/mcp_client_example.js new file mode 100644 index 00000000..621a7f5a --- /dev/null +++ b/qiming-run_code_rmcp/examples/mcp_client_example.js @@ -0,0 +1,78 @@ +// 这是一个使用JavaScript调用MCP服务的示例 +// 你可以使用任何支持MCP协议的客户端来调用我们的服务 +// 以下是一个简单的示例,展示如何调用我们的服务 + +// 假设我们已经连接到了MCP服务 + +// 执行JavaScript代码示例 +async function testRunJavaScript() { + const code = ` + // 一些处理代码 + console.log("Processing JavaScript..."); + + function handler(input) { + // 输入包含通过参数传递的数据 + console.log("Received input:", input); + + // 返回最终结果 + return "Hello from JavaScript! Input: " + JSON.stringify(input); + } + `; + + const params = { name: "JavaScript User", data: [1, 2, 3] }; + + // 在实际的MCP客户端中,你会使用类似以下的代码调用服务 + // const result = await client.run_javascript({ code, params }); + // console.log(result); +} + +// 执行TypeScript代码示例 +async function testRunTypeScript() { + const code = ` + // 一些处理代码 + console.log("Processing TypeScript..."); + + function handler(input: any): string { + // 输入包含通过参数传递的数据 + console.log("Received input:", input); + + // 返回最终结果 + return \`Hello from TypeScript! Input: \${JSON.stringify(input)}\`; + } + `; + + const params = { name: "TypeScript User", count: 42 }; + + // 在实际的MCP客户端中,你会使用类似以下的代码调用服务 + // const result = await client.run_typescript({ code, params }); + // console.log(result); +} + +// 执行Python代码示例 +async function testRunPython() { + const code = ` +# 一些处理代码 +print("Processing Python...") + +def handler(args): + # args 包含通过参数传递的数据 + print(f"Received args: {args}") + + # 返回最终结果 + return f"Hello from Python! Args: {args}" + `; + + const params = { name: "Python User", values: [10, 20, 30] }; + + // 在实际的MCP客户端中,你会使用类似以下的代码调用服务 + // const result = await client.run_python({ code, params }); + // console.log(result); +} + +// 在实际的MCP客户端中,你可以按顺序调用这些函数 +// await testRunJavaScript(); +// await testRunTypeScript(); +// await testRunPython(); + +// 注意:这个文件只是一个示例,不能直接运行 +// 你需要使用支持MCP协议的客户端(如Cursor)来调用我们的服务 \ No newline at end of file diff --git a/qiming-run_code_rmcp/examples/test_mcp.sh b/qiming-run_code_rmcp/examples/test_mcp.sh new file mode 100644 index 00000000..4b12c562 --- /dev/null +++ b/qiming-run_code_rmcp/examples/test_mcp.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 测试script_runner的简单bash脚本 + +# 初始化请求 +echo "发送初始化请求..." +echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"client_info":{"name":"test_client"}}}' | script_runner + +# 等待一下 +sleep 1 + +# 列出工具请求 +echo -e "\n发送列出工具请求..." +echo '{"jsonrpc":"2.0","id":2,"method":"listTools"}' | script_runner + +# 等待一下 +sleep 1 + +# 执行JavaScript代码 +echo -e "\n发送JavaScript代码执行请求..." +echo '{"jsonrpc":"2.0","id":3,"method":"callTool","params":{"name":"run_javascript","arguments":{"code":"console.log(\"Hello from JavaScript!\"); return { message: \"Hello, World!\", value: 42 };"}}}' | script_runner \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/cow_say_hello.js b/qiming-run_code_rmcp/fixtures/cow_say_hello.js new file mode 100644 index 00000000..408eff61 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/cow_say_hello.js @@ -0,0 +1,24 @@ +import * as o from 'https://deno.land/x/cowsay/mod.ts' + +// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript) +function add(input) { + console.log("test Add", input.a, "+", input.b); + + let m = o.say({ + text: 'hello every one' + }) + console.log(m) + + return input.a + input.b; +} + + +export default async function handler(input) { + + let a = add(input) + console.log("计算结果=" + a) + + return { + message: a, + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_axios_example.js b/qiming-run_code_rmcp/fixtures/import_axios_example.js new file mode 100644 index 00000000..9edc8330 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_axios_example.js @@ -0,0 +1,30 @@ +import axios from 'npm:axios'; + +// 使用axios发起HTTP请求的函数 +async function fetchData(url) { + console.log(`正在请求: ${url}`); + try { + const response = await axios.get(url); + console.log(`请求成功,状态码: ${response.status}`); + return response.data; + } catch (error) { + console.error(`请求失败: ${error.message}`); + return { error: error.message }; + } +} + +export default async function handler(input) { + // 默认URL或从输入参数获取 + const url = input.url || 'https://jsonplaceholder.typicode.com/todos/1'; + + console.log(`开始处理请求,URL: ${url}`); + + // 发起请求并获取数据 + const data = await fetchData(url); + + return { + message: '请求完成', + data: data, + timestamp: new Date().toISOString() + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_deno_std_example.js b/qiming-run_code_rmcp/fixtures/import_deno_std_example.js new file mode 100644 index 00000000..9ad808c1 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_deno_std_example.js @@ -0,0 +1,79 @@ +import { join, basename } from "https://deno.land/std/path/mod.ts"; +import { readLines } from "https://deno.land/std/io/mod.ts"; +import { format } from "https://deno.land/std/datetime/mod.ts"; + +// 使用Deno标准库处理路径和文件 +async function processPath(path) { + console.log(`处理路径: ${path}`); + + // 使用path模块处理路径 + const fileName = basename(path); + console.log(`文件名: ${fileName}`); + + const fullPath = join("/tmp", fileName); + console.log(`完整路径: ${fullPath}`); + + // 获取当前时间并格式化 + const now = new Date(); + const formattedDate = format(now, "yyyy-MM-dd HH:mm:ss"); + console.log(`当前时间: ${formattedDate}`); + + return { + originalPath: path, + fileName: fileName, + fullPath: fullPath, + timestamp: formattedDate + }; +} + +// 模拟读取文件内容 +async function simulateFileReading() { + const text = `这是第一行 +这是第二行 +这是第三行 +这是最后一行`; + + // 使用TextEncoder和StringReader代替Deno.Buffer + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + const data = encoder.encode(text); + + // 创建一个可读流 + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(data); + controller.close(); + } + }); + + // 将流转换为Reader + const reader = stream.getReader(); + + console.log("开始读取文件内容:"); + + // 手动处理文本行 + const lines = text.split('\n'); + for (let i = 0; i < lines.length; i++) { + console.log(`第${i+1}行: ${lines[i]}`); + } + + return lines; +} + +export default async function handler(input) { + console.log("开始处理"); + + // 处理路径 + const path = input.path || "example.txt"; + const pathInfo = await processPath(path); + + // 模拟读取文件 + const fileLines = await simulateFileReading(); + + return { + message: '处理完成', + pathInfo: pathInfo, + fileContent: fileLines, + linesCount: fileLines.length + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_esm_module_example.js b/qiming-run_code_rmcp/fixtures/import_esm_module_example.js new file mode 100644 index 00000000..ac634a1a --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_esm_module_example.js @@ -0,0 +1,59 @@ +// 使用ES模块导入方式 +import { createHash } from "node:crypto"; +import { Buffer } from "node:buffer"; + +// 使用Node.js内置模块处理数据 +function hashData(data, algorithm = 'sha256') { + console.log(`使用 ${algorithm} 算法处理数据`); + + // 将数据转换为Buffer + const buffer = typeof data === 'string' + ? Buffer.from(data) + : Buffer.from(JSON.stringify(data)); + + // 创建哈希 + const hash = createHash(algorithm); + hash.update(buffer); + + // 获取哈希结果 + const digest = hash.digest('hex'); + console.log(`哈希结果: ${digest}`); + + return digest; +} + +// 加密数据 +function encryptData(data, salt = 'default-salt') { + console.log(`加密数据,使用盐值: ${salt}`); + + // 组合数据和盐值 + const combined = `${data}:${salt}:${Date.now()}`; + + // 生成多种哈希 + const sha256Hash = hashData(combined, 'sha256'); + const md5Hash = hashData(combined, 'md5'); + + return { + original: data, + salt, + sha256: sha256Hash, + md5: md5Hash, + timestamp: new Date().toISOString() + }; +} + +export default async function handler(input) { + console.log("开始处理加密任务"); + + // 获取输入数据 + const data = input.data || "这是需要加密的默认数据"; + const salt = input.salt || "custom-salt-" + Math.floor(Math.random() * 1000); + + // 加密数据 + const encryptedResult = encryptData(data, salt); + + return { + message: '加密处理完成', + result: encryptedResult + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_jsr_example.js b/qiming-run_code_rmcp/fixtures/import_jsr_example.js new file mode 100644 index 00000000..10e8db25 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_jsr_example.js @@ -0,0 +1,79 @@ +import { assertEquals } from "jsr:@std/assert"; +import { delay } from "jsr:@std/async"; +import { bold, red, green } from "jsr:@std/fmt/colors"; + +// 使用JSR标准库中的工具函数 +async function runTests(input) { + console.log(bold("开始运行测试...")); + + // 使用delay函数模拟异步操作 + console.log("等待1秒..."); + await delay(1000); + + // 测试结果数组 + const results = []; + + // 测试1: 加法运算 + try { + const a = input.a || 5; + const b = input.b || 3; + const expected = input.expected || 8; + + console.log(`测试加法: ${a} + ${b} = ${expected}`); + assertEquals(a + b, expected); + + console.log(green("✓ 加法测试通过")); + results.push({ name: "加法测试", success: true }); + } catch (error) { + console.log(red(`✗ 加法测试失败: ${error.message}`)); + results.push({ name: "加法测试", success: false, error: error.message }); + } + + // 测试2: 字符串操作 + try { + const str1 = input.str1 || "hello"; + const str2 = input.str2 || "world"; + const expectedStr = input.expectedStr || "hello world"; + + console.log(`测试字符串连接: "${str1}" + " " + "${str2}" = "${expectedStr}"`); + assertEquals(`${str1} ${str2}`, expectedStr); + + console.log(green("✓ 字符串测试通过")); + results.push({ name: "字符串测试", success: true }); + } catch (error) { + console.log(red(`✗ 字符串测试失败: ${error.message}`)); + results.push({ name: "字符串测试", success: false, error: error.message }); + } + + // 等待再次展示结果 + console.log("处理结果中..."); + await delay(500); + + return results; +} + +export default async function handler(input) { + console.log(bold("JSR依赖示例")); + + // 运行测试 + const testResults = await runTests(input); + + // 统计结果 + const passed = testResults.filter(r => r.success).length; + const failed = testResults.filter(r => !r.success).length; + + // 使用彩色输出显示结果 + console.log(bold("\n测试结果摘要:")); + console.log(green(`通过: ${passed}`)); + console.log(failed > 0 ? red(`失败: ${failed}`) : green(`失败: ${failed}`)); + + return { + message: '测试完成', + results: testResults, + summary: { + total: testResults.length, + passed, + failed + } + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_local_module_example.js b/qiming-run_code_rmcp/fixtures/import_local_module_example.js new file mode 100644 index 00000000..a29b548e --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_local_module_example.js @@ -0,0 +1,74 @@ +// 首先创建一个本地模块,通过相对路径引入 +// 注意:这个示例假设有一个名为utils.js的本地模块 + +// 模拟本地utils.js模块的内容 +const utils = { + formatNumber(num) { + return num.toLocaleString('zh-CN'); + }, + + calculateTax(amount, rate = 0.1) { + return amount * rate; + }, + + generateId() { + return `id-${Date.now()}-${Math.floor(Math.random() * 1000)}`; + }, + + formatDate(date = new Date()) { + return date.toLocaleDateString('zh-CN'); + } +}; + +// 使用本地模块的函数 +function processOrder(order) { + console.log(`处理订单: ${order.id || utils.generateId()}`); + + // 计算订单总额 + const total = order.items.reduce((sum, item) => sum + (item.price * item.quantity), 0); + console.log(`订单总额: ${utils.formatNumber(total)}`); + + // 计算税费 + const taxRate = order.taxRate || 0.13; + const tax = utils.calculateTax(total, taxRate); + console.log(`税费(${taxRate * 100}%): ${utils.formatNumber(tax)}`); + + // 计算最终金额 + const finalAmount = total + tax; + console.log(`最终金额: ${utils.formatNumber(finalAmount)}`); + + // 生成订单日期 + const orderDate = order.date ? new Date(order.date) : new Date(); + console.log(`订单日期: ${utils.formatDate(orderDate)}`); + + return { + id: order.id || utils.generateId(), + items: order.items, + subtotal: total, + tax: tax, + total: finalAmount, + date: utils.formatDate(orderDate) + }; +} + +export default async function handler(input) { + console.log("开始处理订单"); + + // 默认订单或从输入获取 + const order = input.order || { + items: [ + { name: "产品A", price: 100, quantity: 2 }, + { name: "产品B", price: 50, quantity: 1 }, + { name: "产品C", price: 200, quantity: 3 } + ], + taxRate: 0.13 + }; + + // 处理订单 + const processedOrder = processOrder(order); + + return { + message: '订单处理完成', + order: processedOrder + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/import_lodash_example.js b/qiming-run_code_rmcp/fixtures/import_lodash_example.js new file mode 100644 index 00000000..5400af9b --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/import_lodash_example.js @@ -0,0 +1,43 @@ +import _ from 'npm:lodash'; + +// 使用lodash处理数据的函数 +function processData(input) { + console.log("输入数据:", input); + + // 使用lodash的chunk方法将数组分块 + if (Array.isArray(input.data)) { + const chunks = _.chunk(input.data, input.chunkSize || 2); + console.log("数据分块结果:", chunks); + + // 使用lodash的map方法处理每个块 + const processedChunks = _.map(chunks, (chunk) => { + return { + items: chunk, + sum: _.sum(chunk), + average: _.mean(chunk) + }; + }); + + return processedChunks; + } + + return { error: "输入数据不是数组" }; +} + +export default async function handler(input) { + console.log("开始处理数据"); + + // 从输入获取数据,或使用默认数据 + const data = input.data || [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const chunkSize = input.chunkSize || 3; + + // 处理数据 + const result = processData({ data, chunkSize }); + + return { + message: '数据处理完成', + originalData: data, + processedData: result, + timestamp: new Date().toISOString() + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js b/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js new file mode 100644 index 00000000..6c10280c --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js @@ -0,0 +1,13 @@ +//引入js组件,必须是http(s)形式引入,可以去网站: https://deno.land/x 搜索自己所需组件 +//下面是一个引入示例 +//import * as o from https://deno.land/x/cowsay/mod.ts +//网络请求,可以直接使用fetch ,具体自行查阅 fetch 文档 +//打印日志使用console,比如:console.log + +// 入口函数不可修改,否则无法执行,args 为配置的入参 +export default async function main(args) { + // 构建输出对象,出参中的key需与配置的出参保持一致 + return { + key: 'value', + }; +} diff --git a/qiming-run_code_rmcp/fixtures/rfunction_python.py b/qiming-run_code_rmcp/fixtures/rfunction_python.py new file mode 100644 index 00000000..9eb7f648 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/rfunction_python.py @@ -0,0 +1,26 @@ +import requests +from bs4 import BeautifulSoup + +# 发送请求到百度 +url = 'https://www.baidu.com' +response = requests.get(url) + +# 检查请求是否成功 +if response.status_code == 200: + # 解析网页内容 + soup = BeautifulSoup(response.text, 'html.parser') + + # 提取你想要的信息,例如标题 + title = soup.title.string +else: + print(f"请求失败,状态码:") + + +# 入口函数不可修改,否则无法执行,args 为配置的入参 +def main(args: dict) -> dict: + params = args.get("params") + # 构建输出对象,出参中的key需与配置的出参保持一致 + ret = { + "key": "value" + } + return ret diff --git a/qiming-run_code_rmcp/fixtures/rfunction_test1.js b/qiming-run_code_rmcp/fixtures/rfunction_test1.js new file mode 100644 index 00000000..08bb4dee --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/rfunction_test1.js @@ -0,0 +1,20 @@ +// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript) +function add(input) { + console.log("test Add", input.a, "+", input.b); + + // 简单的字符串输出,不使用外部模块 + console.log("Hello from JavaScript!"); + + return input.a + input.b; +} + +function handler(input) { + console.log("输入参数:", JSON.stringify(input)); + + let a = add(input); + console.log("计算结果=" + a); + + return { + message: a, + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/rfunction_test2.py b/qiming-run_code_rmcp/fixtures/rfunction_test2.py new file mode 100644 index 00000000..2e2571c5 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/rfunction_test2.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import pandas as pd + +import logging +import json + +# 创建一个简单的数据结构,不使用pandas +data = { + 'Name': ['Alice', 'Bob', 'Charlie'], + 'Age': [25, 30, 35] +} +df = pd.DataFrame(data) +# 记录数据信息 +logging.info(f"Created data structure:\n{json.dumps(data, indent=2)}") + +def main(args: dict) -> dict: + logging.info(f"input args: {args}") + + params = args.get("params") + + # 处理params为None的情况 + if params is None: + logging.warning("params is None, using default values") + params = {"input": "default_value"} + elif not isinstance(params, dict): + logging.warning(f"params is not a dictionary: {type(params)}, using default values") + params = {"input": str(params)} + elif "input" not in params: + logging.warning("input key not found in params, using default value") + params["input"] = "default_value" + + # 构建输出对象 + ret = { + "key0": params['input'], # 使用input参数值 + "key1": ["hello", "world"], # 输出一个数组 + "key2": { # 输出一个Object + "key21": "hi" + }, + } + return ret \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_js.js b/qiming-run_code_rmcp/fixtures/test_js.js new file mode 100644 index 00000000..58e0bb01 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_js.js @@ -0,0 +1,25 @@ +// Sample JavaScript file with handler function + +// Log some debug information +console.log("Initializing JavaScript test..."); + +// Define a simple add function +function add(a, b) { + console.log(`Adding ${a} + ${b}`); + return a + b; +} + +// Some processing +const numbers = [1, 2, 3, 4, 5]; +console.log("Processing numbers:", numbers); + +// Handler function that will be called to get the result +function handler() { + console.log("Handler function called"); + + // Calculate sum + const sum = numbers.reduce((acc, num) => add(acc, num), 0); + console.log("Final calculation completed"); + + return `The sum of [${numbers.join(", ")}] is ${sum}`; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_js_large_params.js b/qiming-run_code_rmcp/fixtures/test_js_large_params.js new file mode 100644 index 00000000..16076c8b --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_js_large_params.js @@ -0,0 +1,75 @@ +// Test JavaScript script with large text parameters to verify "Argument list too long" fix + +// Generate a large text parameter (>2MB) to test the temporary file parameter passing solution +function generateLargeText(sizeInMB) { + const chunk = "This is a test chunk of text designed to generate large parameters. ".repeat(100); + const iterations = Math.ceil((sizeInMB * 1024 * 1024) / chunk.length); + + let result = ""; + for (let i = 0; i < iterations; i++) { + result += chunk + `Iteration ${i}\n`; + } + + return result; +} + +// Handler function that processes large text parameters +function handler(args) { + console.log("Handler function called with large parameters"); + + // Test that we received the large text parameter + if (!args || !args.largeText) { + console.log("ERROR: No largeText parameter found"); + return { success: false, error: "Missing largeText parameter" }; + } + + const largeText = args.largeText; + console.log("Received large text parameter, length:", largeText.length); + console.log("Large text parameter size (MB):", (largeText.length / 1024 / 1024).toFixed(2)); + + // Verify the text contains expected content + if (largeText.includes("This is a test chunk of text designed to generate large parameters")) { + console.log("SUCCESS: Large text parameter contains expected content"); + + // Count occurrences of "Iteration" to verify data integrity + const iterationCount = (largeText.match(/Iteration \d+/g) || []).length; + console.log("Found", iterationCount, "iterations in the text"); + + // Return some statistics about the processed data + return { + success: true, + message: "Successfully processed large text parameter", + textSize: largeText.length, + sizeMB: (largeText.length / 1024 / 1024).toFixed(2), + iterationCount: iterationCount, + sampleText: largeText.substring(0, 100) + "..." + }; + } else { + console.log("ERROR: Large text parameter does not contain expected content"); + return { success: false, error: "Invalid content in largeText parameter" }; + } +} + +// This part won't be executed when the code is run through the MCP runner +if (typeof require !== 'undefined' && require.main === module) { + console.log("Running large parameter test directly..."); + + // Generate test data (3MB to ensure we exceed the 2MB threshold) + const largeText = generateLargeText(3); + console.log("Generated test data, size:", (largeText.length / 1024 / 1024).toFixed(2), "MB"); + + // Create test arguments + const testArgs = { + largeText: largeText, + additionalParam: "test value", + numberParam: 42 + }; + + console.log("Calling handler with large parameters..."); + const result = handler(testArgs); + console.log("Result:", JSON.stringify(result, null, 2)); +} + +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = { handler, generateLargeText }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_js_params.js b/qiming-run_code_rmcp/fixtures/test_js_params.js new file mode 100644 index 00000000..65301562 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_js_params.js @@ -0,0 +1,39 @@ +// JavaScript测试文件,演示参数传递 + +// 打印一些调试信息 +console.log("JavaScript脚本开始执行..."); + +// 定义一个加法函数 +function add(a, b) { + console.log(`正在计算: ${a} + ${b}`); + return a + b; +} + +// 处理一些数据 +const numbers = [1, 2, 3, 4, 5]; +console.log(`处理数字列表: ${numbers}`); + +/** + * 处理函数,接收参数并返回结果 + * 注意:这个函数必须存在,并且会被框架调用来获取结果 + */ +function handler(input) { + console.log(`接收到的参数: ${JSON.stringify(input)}`); + + // 从参数中获取值,提供默认值 + const a = input.a || 0; + const b = input.b || 0; + const name = input.name || "用户"; + + // 计算结果 + const result = add(a, b); + console.log(`计算完成: ${a} + ${b} = ${result}`); + + // 返回结果 + return { + sum: result, + numbers: numbers, + greeting: `你好,${name}!`, + message: `成功计算 ${a} + ${b} 的结果` + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python.py b/qiming-run_code_rmcp/fixtures/test_python.py new file mode 100644 index 00000000..c5685101 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Sample Python file with handler function + +import time +time.time() +# Debug print to confirm script execution +print("DEBUGGING: Script is being executed!") + +# Log some debug information +print("Initializing Python test...") + +# Define a simple function +def multiply(a, b): + print(f"Multiplying {a} * {b}") + return a * b + +# Some processing +numbers = [1, 2, 3, 4, 5] +print(f"Processing numbers: {numbers}") + +# Handler function that will be called to get the result +def handler(args): + print("Handler function called") + print(f"Received args: {args}") + + # Calculate product (using the predefined numbers) + product = 1 + for num in numbers: + product = multiply(product, num) + + print("Final calculation completed") + + return f"The product of {numbers} is {product}" + +# This part won't be executed when the code is run through the MCP runner +if __name__ == "__main__": + print("Running directly, calling handler...") + # Create sample args for direct execution + sample_args = {"test": "direct_execution", "data": [1, 2, 3]} + result = handler(sample_args) + print(f"Result: {result}") \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python_large_params.py b/qiming-run_code_rmcp/fixtures/test_python_large_params.py new file mode 100644 index 00000000..6dcb0285 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python_large_params.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Test Python script with large text parameters to verify "Argument list too long" fix + +import sys +import json +import time + +# Generate a large text parameter (>2MB) to test the temporary file parameter passing solution +def generate_large_text(size_in_mb): + chunk = "This is a test chunk of text designed to generate large parameters for Python testing. " * 100 + iterations = (size_in_mb * 1024 * 1024) // len(chunk) + + result = "" + for i in range(iterations): + result += chunk + f"Iteration {i}\n" + + return result + +# Handler function that processes large text parameters +def handler(args): + print("Handler function called with large parameters") + + # Test that we received the large text parameter + if not args or 'largeText' not in args: + print("ERROR: No largeText parameter found") + return {"success": False, "error": "Missing largeText parameter"} + + large_text = args['largeText'] + print(f"Received large text parameter, length: {len(large_text)}") + print(f"Large text parameter size (MB): {len(large_text) / 1024 / 1024:.2f}") + + # Verify the text contains expected content + expected_content = "This is a test chunk of text designed to generate large parameters for Python testing" + if expected_content in large_text: + print("SUCCESS: Large text parameter contains expected content") + + # Count occurrences of "Iteration" to verify data integrity + iteration_count = large_text.count("Iteration") + print(f"Found {iteration_count} iterations in the text") + + return { + "success": True, + "message": "Successfully processed large text parameter", + "textSize": len(large_text), + "sizeMB": round(len(large_text) / 1024 / 1024, 2), + "iterationCount": iteration_count, + "sampleText": large_text[:100] + "..." + } + else: + print("ERROR: Large text parameter does not contain expected content") + return {"success": False, "error": "Invalid content in largeText parameter"} + +# This part won't be executed when the code is run through the MCP runner +if __name__ == "__main__": + print("Running large parameter test directly...") + + # Generate test data (3MB to ensure we exceed the 2MB threshold) + large_text = generate_large_text(3) + print(f"Generated test data, size: {len(large_text) / 1024 / 1024:.2f} MB") + + # Create test arguments + test_args = { + "largeText": large_text, + "additionalParam": "test value", + "numberParam": 42 + } + + print("Calling handler with large parameters...") + result = handler(test_args) + print("Result:", json.dumps(result, indent=2)) \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python_logging.py b/qiming-run_code_rmcp/fixtures/test_python_logging.py new file mode 100644 index 00000000..771f4f42 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python_logging.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# 测试logging模块的日志捕获功能 + +import logging +import json + +# 配置日志 +logging.basicConfig(level=logging.DEBUG) + +# 使用不同级别的日志 +def log_messages(): + logging.debug("这是一条DEBUG级别的日志") + logging.info("这是一条INFO级别的日志") + logging.warning("这是一条WARNING级别的日志") + logging.error("这是一条ERROR级别的日志") + logging.critical("这是一条CRITICAL级别的日志") + + # 使用格式化 + data = {"name": "测试", "value": 42} + logging.info(f"格式化的JSON数据: {json.dumps(data, ensure_ascii=False)}") + + return "日志测试完成" + +def handler(args): + # 记录输入参数 + logging.info(f"收到参数: {args}") + + # 生成一些日志 + result = log_messages() + + # 返回结果 + return { + "message": result, + "log_count": 6, # 总共记录了6条日志 + "args": args + } \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python_params.py b/qiming-run_code_rmcp/fixtures/test_python_params.py new file mode 100644 index 00000000..4307fb6a --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python_params.py @@ -0,0 +1,33 @@ +import json +import os + +# 打印一些调试信息 +print("Python脚本开始执行...") + +# 定义一个简单的加法函数 +def add(a, b): + print("正在计算: " + str(a) + " + " + str(b)) + return a + b + +# 处理一些数据 +numbers = [1, 2, 3, 4, 5] +print("处理数字列表: " + str(numbers)) + +# 入口函数,接收参数 +def main(args): + print("接收到的参数: " + str(args)) + + # 从参数中获取值 + a = args.get("a", 0) + b = args.get("b", 0) + + # 计算结果 + result = add(a, b) + print("计算完成: " + str(a) + " + " + str(b) + " = " + str(result)) + + # 返回结果 + return { + "sum": result, + "numbers": numbers, + "message": "成功计算 " + str(a) + " + " + str(b) + " 的结果" + } \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python_simple.py b/qiming-run_code_rmcp/fixtures/test_python_simple.py new file mode 100644 index 00000000..624a752f --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python_simple.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# 简单的Python测试脚本,用于测试参数传递机制 + +def main(args: dict) -> dict: + print(f"接收到的参数: {args}") + + # 尝试通过两种方式获取参数 + direct_params = args.get("input", "direct_default") + nested_params = None + + params = args.get("params") + if params: + if isinstance(params, dict): + nested_params = params.get("input", "nested_default") + else: + nested_params = str(params) + + # 返回结果,展示两种方式获取的参数 + return { + "direct_access": direct_params, + "nested_access": nested_params, + "args_structure": args + } \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_python_types.py b/qiming-run_code_rmcp/fixtures/test_python_types.py new file mode 100644 index 00000000..3615b830 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_python_types.py @@ -0,0 +1,46 @@ +import json +import os + +# 打印一些调试信息 +print("Python类型测试脚本开始执行...") + +# 入口函数,接收参数并返回不同类型的值 +def main(args): + print("接收到的参数:", args) + + # 获取要测试的类型 + test_type = args.get("type", "string") + + # 根据参数返回不同类型的值 + if test_type == "string": + print("返回字符串类型") + return "这是一个字符串" + + elif test_type == "number": + print("返回数字类型") + return 12345 + + elif test_type == "boolean": + print("返回布尔类型") + return True + + elif test_type == "null": + print("返回None类型") + return None + + elif test_type == "list": + print("返回列表类型") + return [1, 2, 3, "四", "五", True] + + elif test_type == "dict": + print("返回字典类型") + return { + "name": "测试用户", + "age": 30, + "is_active": True, + "tags": ["python", "rust", "json"] + } + + else: + print("未知类型,返回默认字符串") + return "未知类型" \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_ts.ts b/qiming-run_code_rmcp/fixtures/test_ts.ts new file mode 100644 index 00000000..848a7d7a --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_ts.ts @@ -0,0 +1,41 @@ +// Sample TypeScript file with handler function + +// Log some debug information +console.log("Initializing TypeScript test..."); + +// Define a simple add function with type annotations +function add(a: number, b: number): number { + console.log(`Adding ${a} + ${b}`); + return a + b; +} + +// Some processing with type annotations +const numbers: number[] = [1, 2, 3, 4, 5]; +console.log("Processing numbers:", numbers); + +// Interface for a person object +interface Person { + name: string; + age: number; +} + +// Create a person object +const person: Person = { + name: "TypeScript User", + age: 30 +}; +console.log("Person object:", person); + +/** + * Handler function that will be called to get the result + * 注意:这个函数必须存在,并且会被框架调用来获取结果 + */ +function handler(): string { + console.log("Handler function called"); + + // Calculate sum + const sum = numbers.reduce((acc, num) => add(acc, num), 0); + console.log("Final calculation completed"); + + return `Hello ${person.name}! The sum of [${numbers.join(", ")}] is ${sum}`; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/fixtures/test_ts_params.ts b/qiming-run_code_rmcp/fixtures/test_ts_params.ts new file mode 100644 index 00000000..11c2eb23 --- /dev/null +++ b/qiming-run_code_rmcp/fixtures/test_ts_params.ts @@ -0,0 +1,46 @@ +// TypeScript测试文件,演示参数传递 + +// 定义参数接口 +interface InputParams { + a: number; + b: number; + name?: string; +} + +// 打印一些调试信息 +console.log("TypeScript脚本开始执行..."); + +// 定义一个带类型的加法函数 +function add(a: number, b: number): number { + console.log(`正在计算: ${a} + ${b}`); + return a + b; +} + +// 处理一些数据 +const numbers: number[] = [1, 2, 3, 4, 5]; +console.log(`处理数字列表: ${numbers}`); + +/** + * 处理函数,接收参数并返回结果 + * 注意:这个函数必须存在,并且会被框架调用来获取结果 + */ +function handler(input: InputParams): object { + console.log(`接收到的参数: ${JSON.stringify(input)}`); + + // 从参数中获取值,提供默认值 + const a = input.a || 0; + const b = input.b || 0; + const name = input.name || "用户"; + + // 计算结果 + const result = add(a, b); + console.log(`计算完成: ${a} + ${b} = ${result}`); + + // 返回结果 + return { + sum: result, + numbers: numbers, + greeting: `你好,${name}!`, + message: `成功计算 ${a} + ${b} 的结果` + }; +} \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/app_error.rs b/qiming-run_code_rmcp/src/app_error.rs new file mode 100644 index 00000000..826bd1d0 --- /dev/null +++ b/qiming-run_code_rmcp/src/app_error.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AppError { + #[error("Failed to create temporary file: {0}")] + IoError(#[from] std::io::Error), + + #[error("Failed to parse JSON: {0}")] + JsonError(#[from] serde_json::Error), +} diff --git a/qiming-run_code_rmcp/src/cache/code_file_cache.rs b/qiming-run_code_rmcp/src/cache/code_file_cache.rs new file mode 100644 index 00000000..f3c5beb5 --- /dev/null +++ b/qiming-run_code_rmcp/src/cache/code_file_cache.rs @@ -0,0 +1,131 @@ +use anyhow::{Context, Result}; +use log::info; +use std::path::{Path, PathBuf}; +use tokio::fs::{self, File, create_dir_all}; +use tokio::io::AsyncWriteExt; + +use crate::model::LanguageScript; +///针对用的代码,进行检测和缓存 +pub struct CodeFileCache; + +impl CodeFileCache { + /// 检查代码文件缓存 + pub fn obtain_code_hash(code: &str) -> String { + // 使用BLAKE3计算哈希,更快速且安全 + let hash = blake3::hash(code.as_bytes()); + let hash_str = hash.to_hex().to_string(); + info!("计算代码hash值: {}", &hash_str); + hash_str + } + + /// 根据代码的hash检查是否存在缓存 + pub async fn check_code_file_cache_exisht(hash: &str, language: &LanguageScript) -> bool { + fs::try_exists(Self::get_cache_file_path(hash, language)) + .await + .unwrap_or(false) + } + + /// 获取代码文件缓存 + pub async fn get_code_file_cache( + hash: &str, + language: &LanguageScript, + ) -> Result<(File, PathBuf)> { + let file_path = Self::get_cache_file_path(hash, language); + let file = File::open(&file_path) + .await + .with_context(|| format!("无法打开缓存文件: {}", file_path.display()))?; + Ok((file, file_path)) + } + + /// 保存代码文件缓存 + pub async fn save_code_file_cache( + hash: &str, + code: &str, + language: &LanguageScript, + ) -> Result<(File, PathBuf)> { + // 确保缓存目录存在 + let cache_dir = Self::get_cache_dir(); + //检查目录是否存在 + if !fs::try_exists(&cache_dir).await.unwrap_or(false) { + create_dir_all(&cache_dir) + .await + .with_context(|| format!("无法创建缓存目录: {}", cache_dir.display()))?; + } + + // 构建文件路径并保存代码 + let file_path = Self::get_cache_file_path(hash, language); + let mut file = File::create(&file_path) + .await + .with_context(|| format!("无法创建缓存文件: {}", file_path.display()))?; + + file.write_all(code.as_bytes()) + .await + .with_context(|| "写入代码到缓存文件失败")?; + + // 设置文件权限 + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(&file_path).await?.permissions(); + perms.set_mode(0o755); // rwxr-xr-x + fs::set_permissions(&file_path, perms).await?; + } + + let file_path = Self::get_cache_file_path(hash, language); + Ok((file, file_path)) + } + + /// 清除指定语言的缓存文件 + pub async fn clear_cache_by_language(language: &LanguageScript) -> Result<()> { + let cache_dir = Self::get_cache_dir(); + if !fs::try_exists(&cache_dir).await.unwrap_or(false) { + return Ok(()); + } + + let suffix = language.get_file_suffix(); + let mut entries = fs::read_dir(cache_dir).await?; + + while let Some(entry) = entries.next_entry().await? { + let path = entry.path(); + if let Some(file_name) = path.file_name() { + if let Some(name_str) = file_name.to_str() { + if name_str.ends_with(suffix) { + fs::remove_file(&path) + .await + .with_context(|| format!("无法删除缓存文件: {}", path.display()))?; + } + } + } + } + + Ok(()) + } + + /// 清除所有缓存文件 + pub async fn clear_all_cache() -> Result<()> { + let cache_dir = Self::get_cache_dir(); + if fs::try_exists(&cache_dir).await.unwrap_or(false) { + fs::remove_dir_all(&cache_dir) + .await + .with_context(|| format!("无法删除缓存目录: {}", cache_dir.display()))?; + } + Ok(()) + } + + /// 获取缓存目录路径 + fn get_cache_dir() -> PathBuf { + // 在容器环境中使用固定路径 + Path::new("/tmp/code_cache").to_path_buf() + } + + /// 获取缓存文件路径 + fn get_cache_file_path(hash: &str, language: &LanguageScript) -> PathBuf { + let mut path = Self::get_cache_dir(); + + // 根据语言类型添加对应的文件扩展名 + let file_name = format!("{}{}", hash, language.get_file_suffix()); + + path.push(file_name); + path + } +} diff --git a/qiming-run_code_rmcp/src/cache/mod.rs b/qiming-run_code_rmcp/src/cache/mod.rs new file mode 100644 index 00000000..bf41884d --- /dev/null +++ b/qiming-run_code_rmcp/src/cache/mod.rs @@ -0,0 +1,3 @@ +mod code_file_cache; + +pub use code_file_cache::CodeFileCache; diff --git a/qiming-run_code_rmcp/src/deno_runner/common_runner.rs b/qiming-run_code_rmcp/src/deno_runner/common_runner.rs new file mode 100644 index 00000000..fd78228b --- /dev/null +++ b/qiming-run_code_rmcp/src/deno_runner/common_runner.rs @@ -0,0 +1,117 @@ +use crate::cache::CodeFileCache; +use crate::model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript}; +use anyhow::Result; +use log::{debug, error, info}; +use serde_json::Value; +use std::io::Write; +use std::path::PathBuf; +use tempfile::NamedTempFile; +use tokio::fs; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +/// 通用的 Deno 脚本执行逻辑,供 JS/TS Runner 复用 +pub async fn run_deno_script_with_params( + code: &str, + params: Option, + timeout_seconds: Option, + lang: LanguageScript, + prepare_code_fn: F, +) -> Result +where + F: Fn(&str, bool) -> String, +{ + debug!("开始执行{lang:?}脚本...,执行参数: {params:?}"); + + let hash = CodeFileCache::obtain_code_hash(code); + let cache_exist = CodeFileCache::check_code_file_cache_exisht(&hash, &lang).await; + + let run_code_script_file_tuple = if cache_exist { + let cache_code = CodeFileCache::get_code_file_cache(&hash, &lang).await; + debug!("从缓存中读取代码:hash值 {:?}", &hash); + cache_code? + } else { + let wrapped_code = prepare_code_fn(code, true); + CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &lang).await?; + let code_script_file_tuple = CodeFileCache::get_code_file_cache(&hash, &lang).await?; + debug!("创建脚本缓存:hash值 {:?}", &hash); + code_script_file_tuple + }; + + let temp_path = run_code_script_file_tuple.1; + + let mut execute_command = Command::new("deno"); + execute_command + .arg("run") + .arg("--allow-net") + .arg("--allow-env") + .arg("--allow-read") + .arg("--no-check") + .arg("--v8-flags=--max-heap-size=512") + .arg(&temp_path) + .kill_on_drop(true); + + // 处理参数:统一使用临时文件传递 + let temp_input_path = if let Some(params) = params { + let params_json = serde_json::to_string(¶ms)?; + + // 创建临时文件写入参数 + let temp_dir = tempfile::TempDir::new()?; + let temp_file_path = temp_dir.path().join("input_params.json"); + + // 写入参数到临时文件 + std::fs::write(&temp_file_path, params_json.as_bytes())?; + + // 保持TempDir存在(这样文件就不会被删除) + let temp_dir_path = temp_dir.path().to_path_buf(); + std::mem::forget(temp_dir); + + // 设置环境变量指向临时文件 + execute_command.env("INPUT_JSON_FILE", &temp_file_path); + debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path); + + Some(temp_file_path) + } else { + // 没有参数时设置空对象 + execute_command.env("INPUT_JSON", "{}"); + None + }; + + debug!("Deno命令[{:?}]: {:?}", lang, &execute_command); + + let executor = match timeout_seconds { + Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout), + None => CommandExecutor::default(execute_command.output()), + }; + info!("执行命令: {:?}", &execute_command); + + let executor_result = executor.await; + let output = match executor_result { + Ok(cmd_result) => match cmd_result { + Ok(output) => output, + Err(e) => { + error!("Deno命令执行失败 [{lang:?}]: {e:?}"); + return Err(e.into()); + } + }, + Err(e) => { + error!("Deno任务执行异常 [{lang:?}]: {e:?}"); + return Err(e.into()); + } + }; + debug!("标准输出:\n{}", String::from_utf8_lossy(&output.stdout)); + debug!("错误输出:\n{}", String::from_utf8_lossy(&output.stderr)); + + // 执行完成后删除临时文件和目录 + if let Some(temp_file_path) = temp_input_path { + // 删除文件 + let _ = fs::remove_file(&temp_file_path).await; + // 尝试删除父目录(如果为空) + if let Some(parent) = temp_file_path.parent() { + let _ = fs::remove_dir(parent).await; + } + debug!("已删除临时文件: {:?}", temp_file_path); + } + + CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await +} diff --git a/qiming-run_code_rmcp/src/deno_runner/js_runner.rs b/qiming-run_code_rmcp/src/deno_runner/js_runner.rs new file mode 100644 index 00000000..9630076b --- /dev/null +++ b/qiming-run_code_rmcp/src/deno_runner/js_runner.rs @@ -0,0 +1,61 @@ +// deno 运行js脚本 +use crate::deno_runner::common_runner::run_deno_script_with_params; +use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode}; +use anyhow::Result; + +#[derive(Default)] +pub struct JsRunner; + + +impl RunCode for JsRunner { + async fn run_with_params( + &self, + code: &str, + params: Option, + timeout_seconds: Option, + ) -> Result { + run_deno_script_with_params( + code, + params, + timeout_seconds, + LanguageScript::Js, + |c, show_logs| self.prepare_js_code(c, show_logs), + ) + .await + } +} + +impl JsRunner { + /// 准备JavaScript代码,添加日志捕获和handler函数执行逻辑 + fn prepare_js_code(&self, code: &str, show_logs: bool) -> String { + // 检查代码中是否包含ES模块特征 + let is_esm = { + // 检查正式的 import/export 语句 + let has_import_export = code.contains("import ") + || code.contains("export ") + || code.contains("import{") + || code.contains("export{"); + + // 检查动态 import + let has_dynamic_import = code.contains("import("); + + // 检查是否有 require - CommonJS 的标志 + let has_require = code.contains("require("); + + // 如果有 import/export 特征,或者动态 import,但没有 require,则判定为 ESM + (has_import_export || has_dynamic_import) && !has_require + }; + + // 根据代码特征选择合适的模板 + let template = if is_esm { + include_str!("../templates/js_template_es.js") + } else { + include_str!("../templates/js_template_normal.js") + }; + + // 替换模板中的占位符 + template + .replace("{{USER_CODE}}", code) + .replace("{{SHOW_LOGS}}", &show_logs.to_string()) + } +} diff --git a/qiming-run_code_rmcp/src/deno_runner/mod.rs b/qiming-run_code_rmcp/src/deno_runner/mod.rs new file mode 100644 index 00000000..b3652922 --- /dev/null +++ b/qiming-run_code_rmcp/src/deno_runner/mod.rs @@ -0,0 +1,6 @@ +mod common_runner; +mod js_runner; +mod ts_runner; + +pub use js_runner::JsRunner; +pub use ts_runner::TsRunner; diff --git a/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs b/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs new file mode 100644 index 00000000..d0252bf5 --- /dev/null +++ b/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs @@ -0,0 +1,37 @@ +// deno 运行ts脚本 +use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode}; +use anyhow::Result; +use crate::deno_runner::common_runner::run_deno_script_with_params; + +#[derive(Default)] +pub struct TsRunner; + +impl RunCode for TsRunner { + async fn run_with_params( + &self, + code: &str, + params: Option, + timeout_seconds: Option, + ) -> Result { + run_deno_script_with_params( + code, + params, + timeout_seconds, + LanguageScript::Ts, + |c, show_logs| self.prepare_ts_code(c, show_logs), + ).await + } +} + + +impl TsRunner { + + /// 准备TypeScript代码,添加日志捕获和handler函数执行逻辑 + fn prepare_ts_code(&self, code: &str, show_logs: bool) -> String { + let template = include_str!("../templates/ts_template.ts"); + + template + .replace("{{USER_CODE}}", code) + .replace("{{SHOW_LOGS}}", &show_logs.to_string()) + } +} diff --git a/qiming-run_code_rmcp/src/lib.rs b/qiming-run_code_rmcp/src/lib.rs new file mode 100644 index 00000000..eb579329 --- /dev/null +++ b/qiming-run_code_rmcp/src/lib.rs @@ -0,0 +1,19 @@ +mod app_error; +mod cache; +mod deno_runner; +#[cfg(feature = "mcp")] +mod mcp; +mod model; +mod python_runner; +#[cfg(test)] +mod tests; +mod warm_up; + +pub use cache::*; +pub use deno_runner::*; +#[cfg(feature = "mcp")] +pub use mcp::CodeRunRequest; +pub use model::RunCodeHttpResult; +pub use model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript}; +pub use python_runner::*; +pub use warm_up::warm_up_all_envs; diff --git a/qiming-run_code_rmcp/src/main.rs b/qiming-run_code_rmcp/src/main.rs new file mode 100644 index 00000000..a068dfd4 --- /dev/null +++ b/qiming-run_code_rmcp/src/main.rs @@ -0,0 +1,194 @@ +use anyhow::{Context, Result}; +use clap::{Args, Parser, Subcommand}; +use log::{error, info}; +use serde_json::Value; +use std::{fs, path::PathBuf}; + +mod app_error; +mod cache; +mod deno_runner; +#[cfg(feature = "mcp")] +mod mcp; +mod model; +mod python_runner; + +use crate::cache::CodeFileCache; +use crate::model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript}; + +#[derive(Parser)] +#[command(name = "run_code_rmcp")] +#[command(about = "Execute JavaScript and Python code using MCP SDK", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, + + /// Show execution logs + #[arg(short, long)] + show_logs: bool, + + /// Use MCP SDK integration + #[cfg(feature = "mcp")] + #[arg(short, long)] + use_mcp: bool, + + /// Clear cache before execution + #[arg(short = 'c', long)] + clear_cache: bool, +} + +#[derive(Subcommand)] +enum Commands { + /// Execute JavaScript code + Js(CodeArgs), + + /// Execute TypeScript code + Ts(CodeArgs), + + /// Execute Python code + Python(CodeArgs), + + /// Clear cache files + ClearCache { + /// Language to clear cache for (js, ts, python, or all) + #[arg(short, long)] + language: String, + }, +} + +#[derive(Args)] +struct CodeArgs { + /// Path to the code file + #[arg(short, long)] + file: Option, + + /// Code content as string + #[arg(short, long)] + code: Option, + + /// Parameters to pass to the script (JSON format) + #[arg(short, long)] + params: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + // 解析命令行参数 + let cli = Cli::parse(); + + // 初始化日志 + let mut builder = env_logger::Builder::from_default_env(); + if cli.show_logs { + // 如果--show-logs参数为true,则至少显示info级别的日志 + builder.filter_level(log::LevelFilter::Info); + } + builder.init(); + + if let Commands::ClearCache { language } = &cli.command { + match language.to_lowercase().as_str() { + "js" => { + CodeFileCache::clear_cache_by_language(&LanguageScript::Js).await?; + info!("已清除 JavaScript 缓存"); + } + "ts" => { + CodeFileCache::clear_cache_by_language(&LanguageScript::Ts).await?; + info!("已清除 TypeScript 缓存"); + } + "python" => { + CodeFileCache::clear_cache_by_language(&LanguageScript::Python).await?; + info!("已清除 Python 缓存"); + } + "all" => { + CodeFileCache::clear_all_cache().await?; + info!("已清除所有缓存"); + } + _ => { + info!("无效的语言类型,可选项: js, ts, python, all"); + return Ok(()); + } + } + return Ok(()); + } + + // 解析传递给脚本的参数 + let params = match &cli.command { + Commands::Js(args) => parse_params(&args.params)?, + Commands::Ts(args) => parse_params(&args.params)?, + Commands::Python(args) => parse_params(&args.params)?, + _ => None, + }; + + // 从参数获取代码 + let (code, language) = match &cli.command { + Commands::Js(args) => (get_code(args)?, LanguageScript::Js), + Commands::Ts(args) => (get_code(args)?, LanguageScript::Ts), + Commands::Python(args) => (get_code(args)?, LanguageScript::Python), + _ => unreachable!(), + }; + + // 如果指定了清除缓存选项,则清除对应语言的缓存 + if cli.clear_cache { + CodeFileCache::clear_cache_by_language(&language).await?; + info!("已清除 {language:?} 缓存"); + } + + // 执行代码 + #[cfg(feature = "mcp")] + let result = if cli.use_mcp { + // 使用MCP SDK集成 + CodeExecutor::execute_with_params_compat(&code, language, params).await? + } else { + // 直接执行 + CodeExecutor::execute_with_params_compat(&code, language, params).await? + }; + + #[cfg(not(feature = "mcp"))] + let result = CodeExecutor::execute_with_params_compat(&code, language, params).await?; + + // 打印结果 + print_result(result); + + Ok(()) +} + +fn get_code(args: &CodeArgs) -> Result { + if let Some(file) = &args.file { + fs::read_to_string(file).context("Failed to read code file") + } else if let Some(code) = &args.code { + Ok(code.clone()) + } else { + anyhow::bail!("Either file or code must be provided") + } +} + +fn parse_params(params_str: &Option) -> Result> { + match params_str { + Some(s) if !s.is_empty() => { + let parsed: Value = + serde_json::from_str(s).context("Failed to parse parameters as JSON")?; + Ok(Some(parsed)) + } + _ => Ok(None), + } +} + +fn print_result(result: CodeScriptExecutionResult) { + if !result.logs.is_empty() { + info!("--- Logs ---"); + for log in result.logs { + info!("{log}"); + } + info!("------------"); + } + + if let Some(result_val) = result.result { + // 使用 serde_json 序列化结果,确保所有类型都能正确显示 + match serde_json::to_string_pretty(&result_val) { + Ok(json_str) => info!("Result: {json_str}"), + Err(_) => info!("Result: {result_val}"), + } + } + + if let Some(error) = result.error { + error!("Error: {error}"); + } +} diff --git a/qiming-run_code_rmcp/src/mcp/mcp_server.rs b/qiming-run_code_rmcp/src/mcp/mcp_server.rs new file mode 100644 index 00000000..9f145b34 --- /dev/null +++ b/qiming-run_code_rmcp/src/mcp/mcp_server.rs @@ -0,0 +1,164 @@ +use anyhow::Result; +use rmcp::{ + ErrorData as McpError, ServerHandler, + handler::server::wrapper::Parameters, + model::{ + CallToolResult, Content, Implementation, ProtocolVersion, ServerCapabilities, ServerInfo, + }, + tool, tool_handler, tool_router, +}; +use serde::Deserialize; +use serde_json::json; + +use crate::model::{CodeExecutor, LanguageScript}; + +/// 代码执行请求参数 +#[derive(Debug, Deserialize, schemars::JsonSchema)] +pub struct CodeRunRequest { + #[schemars(description = "要执行的代码")] + pub code: String, + + #[schemars(description = "可选的执行参数")] + pub params: Option, +} + +/// 代码执行工具服务 +#[derive(Debug, Clone, Default)] +pub struct CodeRunnerService; + +#[tool_router] +impl CodeRunnerService { + #[tool(description = "执行JavaScript代码并返回结果")] + async fn run_javascript( + &self, + request: Parameters, + ) -> Result { + let request = request.0; + match CodeExecutor::execute_with_params_compat( + &request.code, + LanguageScript::Js, + request.params, + ) + .await + { + Ok(result) => { + if result.success { + let content = Content::json(json!({ + "result": result.result, + "logs": result.logs, + "success": true + }))?; + Ok(CallToolResult::success(vec![content])) + } else { + let content = Content::json(json!({ + "success": false, + "error": result.error, + "logs": result.logs + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + Err(err) => { + let content = Content::json(json!({ + "success": false, + "error": err.to_string(), + "logs": [] + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + } + + #[tool(description = "执行TypeScript代码并返回结果")] + async fn run_typescript( + &self, + request: Parameters, + ) -> Result { + let request = request.0; + match CodeExecutor::execute_with_params_compat( + &request.code, + LanguageScript::Ts, + request.params, + ) + .await + { + Ok(result) => { + if result.success { + let content = Content::json(json!({ + "result": result.result, + "logs": result.logs, + "success": true + }))?; + Ok(CallToolResult::success(vec![content])) + } else { + let content = Content::json(json!({ + "success": false, + "error": result.error, + "logs": result.logs + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + Err(err) => { + let content = Content::json(json!({ + "success": false, + "error": err.to_string(), + "logs": [] + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + } + + #[rmcp::tool(description = "执行Python代码并返回结果")] + async fn run_python( + &self, + request: Parameters, + ) -> Result { + let request = request.0; + match CodeExecutor::execute_with_params_compat( + &request.code, + LanguageScript::Python, + request.params, + ) + .await + { + Ok(result) => { + if result.success { + let content = Content::json(json!({ + "result": result.result, + "logs": result.logs, + "success": true + }))?; + Ok(CallToolResult::success(vec![content])) + } else { + let content = Content::json(json!({ + "success": false, + "error": result.error, + "logs": result.logs + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + Err(err) => { + let content = Content::json(json!({ + "success": false, + "error": err.to_string(), + "logs": [] + }))?; + Ok(CallToolResult::success(vec![content])) + } + } + } +} + +impl ServerHandler for CodeRunnerService { + fn get_info(&self) -> ServerInfo { + ServerInfo { + protocol_version: ProtocolVersion::V_2024_11_05, + capabilities: ServerCapabilities::builder().enable_tools().build(), + server_info: Implementation::from_build_env(), + instructions: Some("一个支持执行JavaScript、TypeScript和Python代码的服务".to_string()), + } + } +} diff --git a/qiming-run_code_rmcp/src/mcp/mod.rs b/qiming-run_code_rmcp/src/mcp/mod.rs new file mode 100644 index 00000000..eb8a7717 --- /dev/null +++ b/qiming-run_code_rmcp/src/mcp/mod.rs @@ -0,0 +1,3 @@ +mod mcp_server; + +pub use mcp_server::{CodeRunnerService,CodeRunRequest}; \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/model/code_run_model.rs b/qiming-run_code_rmcp/src/model/code_run_model.rs new file mode 100644 index 00000000..8c8161b4 --- /dev/null +++ b/qiming-run_code_rmcp/src/model/code_run_model.rs @@ -0,0 +1,268 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use anyhow::{Context as AnyHowContext, Result}; +use log::{info, warn}; +use pin_project::pin_project; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::future::Future; +use tokio::{ + io, + process::Command, + time::{Duration, Sleep, sleep}, +}; + +use crate::{deno_runner::JsRunner, deno_runner::TsRunner, python_runner::PythonRunner}; + +///语言脚本,选择对应的语言脚本运行期 +#[derive(Debug, Clone)] +pub enum LanguageScript { + Js, + Ts, + Python, +} + +impl LanguageScript { + /// 获取文件后缀 + pub fn get_file_suffix(&self) -> &str { + match self { + LanguageScript::Js => ".js", + LanguageScript::Ts => ".ts", + LanguageScript::Python => ".py", + } + } +} + +///执行结果,包含js/python 执行结果,和打印的log日志 +#[derive(Debug, Serialize, Deserialize)] +pub struct CodeScriptExecutionResult { + //js/python 执行结果 + pub result: Option, + //js/python 打印的log日志 + pub logs: Vec, + // 是否执行成功,ture:默认值,执行成功 + #[serde(skip_serializing)] + pub success: bool, + //如果执行错误的话,错误信息 + #[serde(skip_serializing)] + pub error: Option, +} + +///运行代码的抽象 +#[allow(async_fn_in_trait)] +pub trait RunCode { + ///运行代码并传递参数,可选设置超时时间 + async fn run_with_params( + &self, + code: &str, + params: Option, + timeout_seconds: Option, + ) -> Result; +} + +/// 代码执行器 +pub struct CodeExecutor; + +impl CodeExecutor { + /// 执行代码并传递参数,可选设置超时时间 + pub async fn execute_with_params( + code: &str, + language: LanguageScript, + params: Option, + timeout_seconds: Option, + ) -> Result { + info!( + "开始执行代码... 语言[{language:?}],执行参数: {params:?}" + ); + match language { + LanguageScript::Js => { + JsRunner + .run_with_params(code, params, timeout_seconds) + .await + } + LanguageScript::Ts => { + TsRunner + .run_with_params(code, params, timeout_seconds) + .await + } + LanguageScript::Python => { + PythonRunner + .run_with_params(code, params, timeout_seconds) + .await + } + } + } + + /// 兼容旧代码的方法,不指定超时时间 + pub async fn execute_with_params_compat( + code: &str, + language: LanguageScript, + params: Option, + ) -> Result { + Self::execute_with_params(code, language, params, None).await + } + + /// 解析执行输出 + pub async fn parse_execution_output( + stdout: &[u8], + stderr: &[u8], + ) -> Result { + let stdout_str = String::from_utf8_lossy(stdout).to_string(); + let stderr_str = String::from_utf8_lossy(stderr).to_string(); + + // 尝试从stdout中查找JSON输出 + let json_pattern = r#"\{"logs":\s*\[.*\],\s*"result":.*,\s*"error":.*\}"#; + let re = Regex::new(json_pattern)?; + + if let Some(captures) = re.find(&stdout_str) { + let json_str = captures.as_str(); + let parsed: serde_json::Value = + serde_json::from_str(json_str).context("Failed to parse JSON output")?; + + // 从JSON中提取logs、result和error + let logs = parsed["logs"] + .as_array() + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect() + }) + .unwrap_or_default(); + + // 处理结果,尝试解析JSON字符串 + let result = if parsed["result"].is_null() { + None + } else if let Some(result_str) = parsed["result"].as_str() { + // 如果是字符串,尝试解析为JSON对象 + match serde_json::from_str::(result_str) { + Ok(json_value) => Some(json_value), + Err(_) => Some(Value::String(result_str.to_string())), + } + } else { + // 其他类型(数字、布尔值等)直接使用 + Some(parsed["result"].clone()) + }; + + let error = parsed["error"].as_str().map(String::from); + + return Ok(CodeScriptExecutionResult { + logs, + result, + success: error.is_none(), + error, + }); + } + + // 如果没有找到结构化输出,返回原始输出 + Ok(CodeScriptExecutionResult { + logs: if !stdout_str.is_empty() { + vec![stdout_str] + } else { + vec![] + }, + result: None, + success: false, + error: Some(format!( + "Failed to extract structured output: {stderr_str}" + )), + }) + } +} + +/// 封装 tokio::command 的执行,并设置堆大小限制 +#[allow(dead_code)] +pub struct TokioHeapSize { + //堆大小限制 + pub heap_size: u64, +} + +impl Default for TokioHeapSize { + fn default() -> Self { + // 堆大小限制为 2GB + Self::new(2048 * 1024 * 1024) + } +} + +impl TokioHeapSize { + pub fn new(heap_size: u64) -> Self { + Self { heap_size } + } + + /// 启动带有堆内存限制的子进程,返回 tokio::process::Child + #[allow(dead_code)] + pub async fn with_heap_limit(&self, command: &mut Command) { + #[cfg(target_os = "linux")] + { + use libc::{RLIMIT_AS, rlimit, setrlimit}; + let heap_size = self.heap_size.clone(); + unsafe { + command.pre_exec(move || { + let rlim = rlimit { + rlim_cur: heap_size, + rlim_max: heap_size, + }; + if setrlimit(RLIMIT_AS, &rlim) != 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + }); + } + } + // macOS/Windows 下不做限制 + } +} + +///使用 pin-project 实现一个代码执行器,参数:timeout 超时时间; command 执行命令; 以及内置限制 command命令执行的堆大小限制 +#[pin_project] +pub struct CommandExecutor { + #[pin] + timeout: Sleep, + #[pin] + future: F, +} + +impl CommandExecutor { + pub fn default(future: F) -> Self { + let timeout = sleep(Duration::from_secs(180)); + + Self { timeout, future } + } + + pub fn with_timeout(future: F, timeout_seconds: u64) -> Self { + let timeout = sleep(Duration::from_secs(timeout_seconds)); + + Self { timeout, future } + } + + #[allow(dead_code)] + pub fn change_timeout(&mut self, timeout: Duration) { + self.timeout = sleep(timeout); + } +} + +impl Future for CommandExecutor { + type Output = io::Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + //根据 heap_size ,通过setrlimit设置执行 command 的堆大小限制 + match this.future.poll(cx) { + Poll::Ready(result) => Poll::Ready(Ok(result)), + Poll::Pending => match this.timeout.poll(cx) { + Poll::Ready(()) => { + warn!("执行命令超时"); + Poll::Ready(Err(io::Error::new( + io::ErrorKind::TimedOut, + "future timed out", + ))) + } + Poll::Pending => Poll::Pending, + }, + } + } +} diff --git a/qiming-run_code_rmcp/src/model/mod.rs b/qiming-run_code_rmcp/src/model/mod.rs new file mode 100644 index 00000000..0c38ca60 --- /dev/null +++ b/qiming-run_code_rmcp/src/model/mod.rs @@ -0,0 +1,8 @@ +mod code_run_model; +mod tool_params; + +pub use code_run_model::{ + CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode, +}; +#[allow(unused_imports)] +pub use tool_params::RunCodeHttpResult; diff --git a/qiming-run_code_rmcp/src/model/tool_params.rs b/qiming-run_code_rmcp/src/model/tool_params.rs new file mode 100644 index 00000000..f9c7cc8d --- /dev/null +++ b/qiming-run_code_rmcp/src/model/tool_params.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; + +///代码运行请求,mcp调用tool工具时传入的参数 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RunCodeMessageRequest { + //js运行参数 + pub(crate) json_param: HashMap, + //运行的代码 + pub code: String, + //前端生成的随机uid,用于查找websocket连接,发送执行过程中的log日志 + pub uid: String, + pub engine_type: String, +} + +//http返回的结构,data的结构一般是: CodeScriptExecutionResult,就是代码脚本的执行结果 +#[derive(Debug, Serialize, Deserialize)] +pub struct RunCodeHttpResult { + //js 结果,json_value + pub data: Value, + // 是否执行成功,ture:默认值,执行成功 + pub success: bool, + //如果执行错误的话,错误日志 + pub error: Option, +} + +/// 代码执行参数 +#[derive(Debug, Serialize, Deserialize)] +pub struct CodeExecutionParams { + /// 要执行的代码内容 + pub code: String, + /// 代码语言 + pub language: String, + /// 是否显示日志 + pub show_logs: bool, +} + +/// 代码执行结果 +#[derive(Debug, Serialize, Deserialize)] +pub struct ToolExecutionResult { + /// 执行日志 + pub logs: Vec, + /// 执行结果 + pub result: Option, + /// 执行是否成功 + pub success: bool, + /// 错误信息 + pub error: Option, +} diff --git a/qiming-run_code_rmcp/src/python_imports.pest b/qiming-run_code_rmcp/src/python_imports.pest new file mode 100644 index 00000000..3482828f --- /dev/null +++ b/qiming-run_code_rmcp/src/python_imports.pest @@ -0,0 +1,25 @@ +WHITESPACE = _{ " " | "\t" } +alpha = { 'a'..'z' | 'A'..'Z' } +digit = { '0'..'9' } + +module_name = @{ alpha ~ (alpha | digit | "_")* } + +module_name_alias = @{ alpha ~ (alpha | digit | "_")* } + +import_statement = ${ "import" ~ WHITESPACE+ ~ module_name} + +from_import_statement = ${ "from" ~ WHITESPACE+ ~ module_name ~ WHITESPACE+ ~ ("import" ~ WHITESPACE+ ~ module_name_alias)? } + + +quote = {( "'" | "\"")} + +importlib_statement = { module_name_alias? ~ "="? ~ "importlib.import_module" ~ "(" ~ WHITESPACE* ~ quote ~ module_name ~ quote ~ WHITESPACE* ~ ")" } + +empty_line = _{ WHITESPACE* ~ ("\r\n" | "\n") } + +line = _{ (!("\r\n" | "\n") ~ ANY)* ~ ("\r\n" | "\n") } + +record = { from_import_statement| import_statement | importlib_statement | empty_line | line } + +file = { SOI ~ record* ~ EOI } + diff --git a/qiming-run_code_rmcp/src/python_runner/dependencies/mod.rs b/qiming-run_code_rmcp/src/python_runner/dependencies/mod.rs new file mode 100644 index 00000000..ac3b5db1 --- /dev/null +++ b/qiming-run_code_rmcp/src/python_runner/dependencies/mod.rs @@ -0,0 +1,3 @@ +mod python_dependencies; + +pub use python_dependencies::*; diff --git a/qiming-run_code_rmcp/src/python_runner/dependencies/python_dependencies.rs b/qiming-run_code_rmcp/src/python_runner/dependencies/python_dependencies.rs new file mode 100644 index 00000000..e367a604 --- /dev/null +++ b/qiming-run_code_rmcp/src/python_runner/dependencies/python_dependencies.rs @@ -0,0 +1,406 @@ +use anyhow::Result; +use log::{debug, info}; +use pest::Parser; +use pest_derive::Parser; + +#[derive(Parser)] +#[grammar = "python_imports.pest"] // 这里是你的pest语法文件 +struct ImportParser; + +// Python标准库模块列表,使用静态变量定义 +static PYTHON_STDLIB_MODULES: &[&str] = &[ + "abc", + "argparse", + "array", + "ast", + "asyncio", + "atexit", + "base64", + "bdb", + "binascii", + "bisect", + "builtins", + "bz2", + "calendar", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "copy", + "copyreg", + "cProfile", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "doctest", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "graphlib", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "idlelib", + "imaplib", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msvcrt", + "multiprocessing", + "netrc", + "numbers", + "operator", + "optparse", + "os", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtplib", + "socket", + "socketserver", + "sqlite3", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "tomllib", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", + "zoneinfo", +]; + +pub fn parse_import(code: &str) -> Result> { + let modules = parse_python_imports(code)?; + // 在非测试环境中,过滤标准库模块 + let mut imports = Vec::new(); + for module in modules { + if !is_standard_library(&module) { + imports.push(module); + } + } + + Ok(imports) +} + +// 解析Python代码中的import语句 +fn parse_python_imports(python_code: &str) -> Result> { + let input = if python_code.ends_with('\n') { + python_code.to_string() + } else { + format!("{python_code}\n") + }; + debug!("Processing input: {input:?}"); + + let pairs = ImportParser::parse(Rule::file, &input) + .map_err(|e| anyhow::anyhow!("Failed to parse input: {}", e))?; + debug!("Initial parse successful"); + + let mut imported_modules = Vec::new(); + + for pair in pairs { + debug!("Top level rule: {:?}", pair.as_rule()); + for record in pair.into_inner() { + debug!( + "Record rule: {:?}, text: {:?}", + record.as_rule(), + record.as_str() + ); + // 遍历 record 的内部规则 + for inner in record.into_inner() { + debug!( + "Inner rule: {:?}, text: {:?}", + inner.as_rule(), + inner.as_str() + ); + match inner.as_rule() { + Rule::from_import_statement => { + for module in inner.into_inner() { + if let Rule::module_name = module.as_rule() { + imported_modules.push(module.as_str().to_string()); + } + } + } + Rule::import_statement => { + // 遍历 import_statement 的内部规则找到 module_name + for module in inner.into_inner() { + if let Rule::module_name = module.as_rule() { + imported_modules.push(module.as_str().to_string()); + } + } + } + Rule::importlib_statement => { + // 遍历 importlib_statement 的内部规则找到 module_name + for module in inner.into_inner() { + if let Rule::module_name = module.as_rule() { + imported_modules.push(module.as_str().to_string()); + } + } + } + _ => { + info!("Unknown rule: {:?}", inner.as_rule()); + } + } + } + } + } + + debug!("Final result: {imported_modules:?}"); + Ok(imported_modules) +} + +// 判断是否为Python标准库模块 +fn is_standard_library(module: &str) -> bool { + // 检查模块名是否在标准库列表中 + PYTHON_STDLIB_MODULES.contains(&module) +} + +#[cfg(test)] +mod tests { + use super::parse_import; + use anyhow::Result; + use log::{LevelFilter, info}; + use std::sync::Once; + + // 使用 Once 确保 env_logger 只初始化一次 + static INIT: Once = Once::new(); + + fn setup() { + INIT.call_once(|| { + env_logger::builder() + .filter_level(LevelFilter::Debug) + .init(); + }); + } + + #[test] + fn test_line() -> Result<()> { + setup(); + let python_code = " + import pandas as pd\n"; + info!("Testing with input: {python_code:?}"); + + let imported_modules = parse_import(python_code)?; + info!("Parsed modules: {imported_modules:?}"); + + assert_eq!(imported_modules, vec!["pandas"]); + Ok(()) + } + + #[test] + fn test_line2() -> Result<()> { + setup(); + let python_code = " + numpy_alias = importlib.import_module('numpy')"; + info!("Testing with input: {python_code:?}"); + + let imported_modules = parse_import(python_code)?; + info!("Parsed modules: {imported_modules:?}"); + + assert_eq!(imported_modules, vec!["numpy"]); + Ok(()) + } + + #[test] + fn test_parse_import_bs() -> Result<()> { + setup(); + let python_code = r#" +import requests +from bs4 import BeautifulSoup + +# 发送请求到百度 +url = 'https://www.baidu.com' +response = requests.get(url) + +# 检查请求是否成功 +if response.status_code == 200: + # 解析网页内容 + soup = BeautifulSoup(response.text, 'html.parser') + + # 提取你想要的信息,例如标题 + title = soup.title.string + print(f"网页标题: {title}") +else: + print(f"请求失败,状态码: {response.status_code}") + + "#; + let imported_modules = parse_import(python_code)?; + info!("Parsed modules: {imported_modules:?}"); + assert_eq!(imported_modules, vec!["requests", "bs4"]); + + Ok(()) + } + + #[test] + fn test_parse_import() -> Result<()> { + setup(); + let python_code = r#" +import pandas as pd +import logging + +# 配置日志记录 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +import importlib + +# 使用 importlib 动态导入 numpy +numpy = importlib.import_module('numpy') +# 创建一个简单的 numpy 数组 +arr = numpy.array([1, 2, 3, 4, 5]) +# 记录数组信息 +logging.info(f"Created numpy array: {arr}") + +# 进行一些简单的操作 +sum_arr = numpy.sum(arr) +logging.info(f"Sum of array: {sum_arr}") + +# 创建一个简单的 DataFrame +data = { + 'Name': ['Alice', 'Bob', 'Charlie'], + 'Age': [25, 30, 35] +} +df = pd.DataFrame(data) + +# 记录 DataFrame 信息 +logging.info(f"Created DataFrame:\n{df}") + +def main(args: dict) -> dict: + logging.info(f"input args: {args}") + + params = args.get("params") + # 构建输出对象 + ret = { + "key0": params['input'], # 拼接两次入参 input 的值 + "key1": ["hello", "world"], # 输出一个数组 + "key2": { # 输出一个Object + "key21": "hi" + }, + } + return ret +"#; + + let imported_modules = parse_import(python_code)?; + info!("Parsed modules: {imported_modules:?}"); + assert_eq!(imported_modules, vec!["pandas", "numpy"]); + Ok(()) + } +} diff --git a/qiming-run_code_rmcp/src/python_runner/mod.rs b/qiming-run_code_rmcp/src/python_runner/mod.rs new file mode 100644 index 00000000..7448d544 --- /dev/null +++ b/qiming-run_code_rmcp/src/python_runner/mod.rs @@ -0,0 +1,5 @@ +mod dependencies; +mod python_runner; + +pub use dependencies::parse_import; +pub use python_runner::PythonRunner; diff --git a/qiming-run_code_rmcp/src/python_runner/python_runner.rs b/qiming-run_code_rmcp/src/python_runner/python_runner.rs new file mode 100644 index 00000000..90807525 --- /dev/null +++ b/qiming-run_code_rmcp/src/python_runner/python_runner.rs @@ -0,0 +1,200 @@ +//通过 uv 命令,来运行 python脚本 +use crate::{ + cache::CodeFileCache, + model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode}, + python_runner::parse_import, +}; +use anyhow::{Context, Result}; +use log::{debug, error, info, warn}; +use std::io::Write; +use std::path::PathBuf; +use tempfile::NamedTempFile; +use tokio::fs; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +#[derive(Default)] +pub struct PythonRunner; + +//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple +const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple"; + +impl RunCode for PythonRunner { + async fn run_with_params( + &self, + code: &str, + params: Option, + timeout_seconds: Option, + ) -> Result { + debug!("开始执行Python脚本...,执行参数: {params:?}"); + // 根据 code ,获取对应的hash, 对用户脚本代码,使用胶水代码处理后,缓存到文件系统里,下次使用如果hash相同,直接使用 + let hash = CodeFileCache::obtain_code_hash(code); + let cache_exist = + CodeFileCache::check_code_file_cache_exisht(&hash, &LanguageScript::Python).await; + + let run_code_script_file_tuple = if cache_exist { + // 从缓存中读取代码 + let cache_code = + CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await; + debug!("从缓存中读取代码:hash值 {:?}", &hash); + cache_code? + } else { + // 分析用户python代码依赖 + let dependencies = parse_import(code)?; + // 准备Python代码,添加日志捕获和handler函数执行逻辑 + let wrapped_code = self.prepare_python_code(code, true); + // 缓存代码 + CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &LanguageScript::Python) + .await?; + + //保存后,获取文件 + let code_script_file_tuple = + CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await?; + let run_code_script_file_path = code_script_file_tuple.1.clone(); + // 按照 uv命令的规范,添加用户脚本所需的依赖 + // uv add --script example.py 'requests<3' 'rich' 这是添加依赖的参考命令,dependencies 是解析出来的依赖列表 + + // 执行添加依赖命令 + if !dependencies.is_empty() { + info!("正在添加依赖: {dependencies:?}"); + let mut cmd = Command::new("uv"); + cmd.arg("add") + .arg("--script") + .arg(&run_code_script_file_path); + //添加 python加速地址 + cmd.arg("--default-index").arg(PYTHON_ACCELERATION_ADDRESS); + + // 为每个依赖添加一个参数 + for dep in &dependencies { + cmd.arg(dep); + } + + // 打印 cmd 命令,可以直接复制执行的命令字符串 + let cmd_str = format!("{:?}", &cmd); + info!("uv命令字符串: {cmd_str}"); + + let cmd_output = match cmd.kill_on_drop(true).output().await { + Ok(output) => output, + Err(e) => { + error!("安装Python依赖失败: {e:?}"); + error!("失败的命令: {cmd:?}"); + return Err(e).context("Failed to add dependencies with uv"); + } + }; + + let stdout = String::from_utf8_lossy(&cmd_output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&cmd_output.stderr).to_string(); + info!("添加依赖结果 - stdout: {stdout}"); + info!("添加依赖结果 - stderr: {stderr}"); + + if !cmd_output.status.success() { + warn!("添加依赖失败,状态码: {}", cmd_output.status); + } + } + debug!("创建脚本缓存:hash值 {:?}", &hash); + code_script_file_tuple + }; + + let temp_path = run_code_script_file_tuple.1; + + // 使用uv run命令执行Python脚本,提供隔离环境 + let mut execute_command = Command::new("uv"); + //还需要指定国内镜像地址,参考示例: uv run -s -p 3.13 d5ebe48b7d9da8cb835af6ef77b212921f9a44881fb232837b4dcc6ebecf9401.py --default-index https://mirrors.aliyun.com/pypi/simple + execute_command + .arg("run") + .arg("-s") // 明确指定作为脚本运行 + // .arg("-p") + // .arg("3.13") // 指定Python解释器版本3.13 + .arg("--default-index") + .arg(PYTHON_ACCELERATION_ADDRESS) + .arg(&temp_path) + .kill_on_drop(true); + + // 处理参数:统一使用临时文件传递 + let temp_input_path = if let Some(params) = params { + let params_json = serde_json::to_string(¶ms)?; + + // 创建临时文件写入参数 + let temp_dir = tempfile::TempDir::new()?; + let temp_file_path = temp_dir.path().join("input_params.json"); + + // 写入参数到临时文件 + std::fs::write(&temp_file_path, params_json.as_bytes())?; + + // 保持TempDir存在(这样文件就不会被删除) + let temp_dir_path = temp_dir.path().to_path_buf(); + std::mem::forget(temp_dir); + + // 设置环境变量指向临时文件 + execute_command.env("INPUT_JSON_FILE", &temp_file_path); + debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path); + + Some(temp_file_path) + } else { + // 没有参数时设置空对象 + execute_command.env("INPUT_JSON", "{}"); + None + }; + + info!("执行命令: {:?}", &execute_command); + + // let tokio_child_command = TokioHeapSize::default(); + // // 设置堆大小限制 + // tokio_child_command + // .with_heap_limit(&mut execute_command) + // .await; + + //限制command 的执行超时时间 + let executor = match timeout_seconds { + Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout), + None => CommandExecutor::default(execute_command.output()), + }; + + let executor_result = executor.await; + let output = match executor_result { + Ok(cmd_result) => match cmd_result { + Ok(output) => output, + Err(e) => { + error!("Python命令执行失败: {e:?}"); + return Err(e.into()); + } + }, + Err(e) => { + error!("Python任务执行异常: {e:?}"); + return Err(e.into()); + } + }; + // 调试输出 + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + debug!("Python stdout: {stdout}"); + debug!("Python stderr: {stderr}"); + + // 执行完成后删除临时文件和目录 + if let Some(temp_file_path) = temp_input_path { + // 删除文件 + let _ = fs::remove_file(&temp_file_path).await; + // 尝试删除父目录(如果为空) + if let Some(parent) = temp_file_path.parent() { + let _ = fs::remove_dir(parent).await; + } + debug!("已删除临时文件: {:?}", temp_file_path); + } + + // 解析输出 + CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await + } +} + +impl PythonRunner { + /// 准备Python代码,添加日志捕获和handler函数执行逻辑 + fn prepare_python_code(&self, code: &str, show_logs: bool) -> String { + let show_logs_value = if show_logs { "True" } else { "False" }; + + let template = include_str!("../templates/python_template.py"); + + template + .replace("{{USER_CODE}}", code) + .replace("{{SHOW_LOGS}}", show_logs_value) + } +} diff --git a/qiming-run_code_rmcp/src/script_runner.rs b/qiming-run_code_rmcp/src/script_runner.rs new file mode 100644 index 00000000..756c0e00 --- /dev/null +++ b/qiming-run_code_rmcp/src/script_runner.rs @@ -0,0 +1,166 @@ +use anyhow::{Context, Result}; +use clap::Parser; +use log::{error, info}; +use rmcp::ServiceExt; +use tokio::io::{stdin, stdout}; + +mod app_error; +mod cache; +mod deno_runner; +mod mcp; +mod model; +mod python_runner; + +use mcp::CodeRunnerService; + +/// MCP脚本运行器 - 通过MCP协议执行JavaScript、TypeScript和Python代码 +#[derive(Parser)] +#[command(name = "script_runner")] +#[command(author = "MCP Script Runner")] +#[command(version = "0.1.0")] +#[command(about = "通过MCP协议执行JavaScript、TypeScript和Python代码", long_about = None)] +struct Cli { + /// 启用详细日志输出 + #[arg(short, long)] + verbose: bool, +} + +/// 启动MCP服务器 +async fn start_mcp_server(verbose: bool) -> Result<()> { + if verbose { + info!("初始化 MCP 服务..."); + } + + // 创建服务实例 + let service = CodeRunnerService::default(); + + // 使用标准输入输出作为传输方式 + let transport = (stdin(), stdout()); + + if verbose { + info!("MCP 服务已启动,等待连接..."); + } + + // 启动服务 + let server = service.serve(transport).await.context("启动MCP服务失败")?; + + // 等待服务结束 + let result = server.waiting().await; + + if verbose { + match &result { + Ok(reason) => error!("MCP 服务已停止: {:?}", reason), + Err(err) => error!("MCP 服务出错: {}", err), + } + } + + result.map(|_| ()).map_err(Into::into) +} + +#[tokio::main] +async fn main() -> Result<()> { + // 解析命令行参数 + let cli = Cli::parse(); + + // 启动MCP服务 + start_mcp_server(cli.verbose).await +} + +#[cfg(test)] +mod tests { + use super::*; + use rmcp::{model::CallToolRequestParam, ServiceExt}; + use tokio::sync::oneshot; + use tokio::time::timeout; + use std::time::Duration; + + #[tokio::test] + #[ignore = "MCP client test requires proper service implementation - TODO: fix later"] + async fn test_start_mcp_server() -> Result<()> { + // 创建管道,模拟标准输入输出 + let (client_stream, server_stream) = tokio::io::duplex(8192); + + // 分割成读写部分 + let (server_read, server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + + // 使用通道控制服务器生命周期 + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + + // 在单独的任务中启动服务器 + let server_task = tokio::spawn(async move { + // 创建服务实例 + let service = CodeRunnerService::default(); + + // 创建服务Future并pin + let service_fut = service.serve((server_read, server_write)); + tokio::pin!(service_fut); + + // 等待关闭信号或服务器完成 + tokio::select! { + res = &mut service_fut => { + match res { + Ok(server) => { + match server.waiting().await { + Ok(reason) => println!("服务正常结束: {:?}", reason), + Err(e) => println!("服务错误: {}", e), + } + } + Err(e) => println!("启动服务失败: {}", e), + } + } + _ = shutdown_rx => { + println!("收到关闭信号"); + } + } + }); + + // 给服务器一点时间启动 + tokio::time::sleep(Duration::from_millis(200)).await; + + // 使用RMCP客户端SDK连接到服务器 + let client = ().serve((client_read, client_write)).await?; + + // 获取服务器信息 + let server_info = client.peer_info(); + println!("服务器信息: {:?}", server_info); + + // 验证服务器信息 + assert!(!server_info.unwrap().instructions.is_none(), "服务器应该提供说明"); + + // 测试执行JavaScript代码 + let js_code = "function handler(input) { return {success: true, message: 'JavaScript测试成功'}; }"; + let result = client.call_tool(CallToolRequestParam { + name: "run_javascript".into(), + arguments: serde_json::json!({ + "code": js_code, + }).as_object().cloned(), + }).await?; + + // 验证JavaScript结果,这里只简单验证调用成功 + println!("JavaScript执行结果: {:?}", result); + + // 测试执行Python代码 + let py_code = "def handler(input):\n return {'success': True, 'message': 'Python测试成功'}"; + let result = client.call_tool(CallToolRequestParam { + name: "run_python".into(), + arguments: serde_json::json!({ + "code": py_code, + }).as_object().cloned(), + }).await?; + + // 验证Python结果,这里只简单验证调用成功 + println!("Python执行结果: {:?}", result); + + // 关闭客户端 + client.cancel().await?; + + // 发送服务器关闭信号 + let _ = shutdown_tx.send(()); + + // 等待服务器任务结束 + let _ = timeout(Duration::from_secs(5), server_task).await; + + Ok(()) + } +} diff --git a/qiming-run_code_rmcp/src/templates/js_template_es.js b/qiming-run_code_rmcp/src/templates/js_template_es.js new file mode 100644 index 00000000..c4830d11 --- /dev/null +++ b/qiming-run_code_rmcp/src/templates/js_template_es.js @@ -0,0 +1,92 @@ +// @ts-nocheck +// ES模块格式,支持import/export语句 + +// 保存原始console.log +const originalConsoleLog = console.log; +let logs = []; + +// 替换console.log以捕获日志 +console.log = function() { + // 将参数转换为字符串并连接它们 + const message = Array.from(arguments).map(arg => + typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg) + ).join(' '); + + // 存储日志 + logs.push(message); + + // 如果显示日志,也输出到原始控制台 + if ({{SHOW_LOGS}}) { + originalConsoleLog.apply(console, arguments); + } +}; + +// 异步读取输入参数的函数 +async function readInputParams() { + let input = {}; + try { + const inputFile = Deno.env.get("INPUT_JSON_FILE"); + if (inputFile) { + const inputJson = await Deno.readTextFile(inputFile); + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } else { + // 兼容旧的环境变量方式 + const inputJson = Deno.env.get("INPUT_JSON"); + if (inputJson) { + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } + } + } catch (error) { + console.error("解析输入参数失败:", error); + } + return input; +} + +// 用户代码 +{{USER_CODE}} + +// 执行handler函数并获取结果 +let result = null; + +// 异步立即执行函数 +(async () => { + // 读取输入参数 + const input = await readInputParams(); + + try { + // 优先检查main函数 + if (typeof main === 'function') { + // 检查main是否是异步函数 + if (main.constructor.name === 'AsyncFunction') { + result = await main(input); + } else { + result = main(input); + } + } else if (typeof handler === 'function') { + // 如果没有main函数,检查handler + if (handler.constructor.name === 'AsyncFunction') { + result = await handler(input); + } else { + result = handler(input); + } + }else{ + throw new Error("没有找到main或handler函数"); + } + + // 打印最终输出为JSON + originalConsoleLog(JSON.stringify({ + logs: logs, + result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null, + error: null + })); + } catch (error) { + // 处理错误 + originalConsoleLog(JSON.stringify({ + logs: logs, + result: null, + error: error.toString() + })); + } +})(); \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/templates/js_template_normal.js b/qiming-run_code_rmcp/src/templates/js_template_normal.js new file mode 100644 index 00000000..0684e6b1 --- /dev/null +++ b/qiming-run_code_rmcp/src/templates/js_template_normal.js @@ -0,0 +1,93 @@ +// @ts-nocheck +// 普通脚本格式 + +// 保存原始console.log +const originalConsoleLog = console.log; +let logs = []; + +// 替换console.log以捕获日志 +console.log = function() { + // 将参数转换为字符串并连接它们 + const message = Array.from(arguments).map(arg => + typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg) + ).join(' '); + + // 存储日志 + logs.push(message); + + // 如果显示日志,也输出到原始控制台 + if ({{SHOW_LOGS}}) { + originalConsoleLog.apply(console, arguments); + } +}; + +// 异步读取输入参数的函数 +async function readInputParams() { + let input = {}; + try { + const inputFile = Deno.env.get("INPUT_JSON_FILE"); + if (inputFile) { + const inputJson = await Deno.readTextFile(inputFile); + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } else { + // 兼容旧的环境变量方式 + const inputJson = Deno.env.get("INPUT_JSON"); + if (inputJson) { + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } + } + } catch (error) { + console.error("解析输入参数失败:", error); + } + return input; +} + +// 异步立即执行函数 +(async () => { + // 读取输入参数 + const input = await readInputParams(); + + try { + // 用户代码开始 + {{USER_CODE}} + // 用户代码结束 + + // 执行函数并获取结果 + let result = null; + + // 优先检查main函数 + if (typeof main === 'function') { + // 检查main是否是异步函数 + if (main.constructor.name === 'AsyncFunction') { + result = await main(input); + } else { + result = main(input); + } + } else if (typeof handler === 'function') { + // 如果没有main函数,检查handler + if (handler.constructor.name === 'AsyncFunction') { + result = await handler(input); + } else { + result = handler(input); + } + } else { + throw new Error("没有找到main或handler函数"); + } + + // 打印最终输出为JSON + originalConsoleLog(JSON.stringify({ + logs: logs, + result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null, + error: null + })); + } catch (error) { + // 处理错误 + originalConsoleLog(JSON.stringify({ + logs: logs, + result: null, + error: error.toString() + })); + } +})(); \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/templates/python_template.py b/qiming-run_code_rmcp/src/templates/python_template.py new file mode 100644 index 00000000..cfb2fd88 --- /dev/null +++ b/qiming-run_code_rmcp/src/templates/python_template.py @@ -0,0 +1,134 @@ +import sys +import json +import os +import logging +from io import StringIO + +# 保存原始的stdout +original_stdout = sys.stdout +logs = [] + +# 创建一个StringIO对象来捕获输出 +class LogCapture: + def __init__(self, show_logs=False): + self.buffer = StringIO() + self.show_logs = show_logs + + def write(self, text): + if text.strip(): # 忽略空行 + logs.append(text.rstrip()) + if self.show_logs: + original_stdout.write(text) + + def flush(self): + self.buffer.flush() + if self.show_logs: + original_stdout.flush() + +# 替换sys.stdout为我们的捕获器 +sys.stdout = LogCapture(show_logs={{SHOW_LOGS}}) + +# 配置logging,将日志输出重定向到我们的捕获器 +class LoggingHandler(logging.Handler): + def emit(self, record): + msg = self.format(record) + print(f"[{record.levelname}] {msg}") + +# 配置根日志记录器 +root_logger = logging.getLogger() +root_logger.setLevel(logging.INFO) +# 移除所有现有的处理程序 +for handler in root_logger.handlers[:]: + root_logger.removeHandler(handler) +# 添加我们自定义的处理程序 +handler = LoggingHandler() +handler.setFormatter(logging.Formatter('%(message)s')) +root_logger.addHandler(handler) + +# 从环境变量获取输入参数 +args = {} +has_input = False +try: + # 优先从临时文件读取参数 + input_file = os.environ.get('INPUT_JSON_FILE') + if input_file: + with open(input_file, 'r', encoding='utf-8') as f: + input_json = f.read() + args = json.loads(input_json) + has_input = True + print(f"接收到的参数: {args}") + else: + # 兼容旧的环境变量方式 + input_json = os.environ.get('INPUT_JSON') + if input_json: + args = json.loads(input_json) + has_input = True + print(f"接收到的参数: {args}") + + # 确保参数同时可以通过args直接访问,也可以通过args.get("params")访问 + # 如果args中没有params键,但有其他键,则将整个args作为params的值 + if has_input and "params" not in args and len(args) > 0: + args["params"] = args.copy() + # 如果params存在但为None,则初始化为空字典 + elif has_input and args.get("params") is None: + args["params"] = {} +except Exception as e: + print(f"解析输入参数失败: {e}") + +# 用户代码开始 +{{USER_CODE}} + +try: + # 执行handler函数或main函数并获取结果 + result = None + if 'handler' in globals() and callable(globals()['handler']): + # 优先使用 handler 函数 + try: + if has_input: + result = handler(args) + else: + # handler 函数不接受参数,无参数调用 + result = handler() + # 确保结果不为 None + if result is None: + print("警告: handler 函数返回了 None") + except Exception as e: + print(f"执行 handler 函数时出错: {e}") + elif 'main' in globals() and callable(globals()['main']): + # 如果没有 handler 函数,尝试使用 main 函数 + try: + result = main(args) + except Exception as e: + print(f"执行 main 函数时出错: {e}") + + # 打印最终输出为JSON + sys.stdout = original_stdout + # 根据结果类型选择合适的处理方式 + result_json = None + if result is not None: + if isinstance(result, (dict, list)): + # 如果是字典或列表,直接使用 json.dumps 序列化 + result_json = json.dumps(result) + elif isinstance(result, (int, float, bool)) or result is None: + # 如果是基本类型,直接传递给外层 JSON + result_json = result + else: + # 其他类型(如字符串)转换为字符串 + result_json = str(result) + + print(json.dumps({ + 'logs': logs, + 'result': result_json, + 'error': None + })) +except Exception as e: + # 处理错误 + import traceback + error_msg = f"{str(e)}\n{traceback.format_exc()}" + # 处理错误 + sys.stdout = original_stdout + print(json.dumps({ + 'logs': logs, + 'result': None, + 'error': error_msg + })) \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/templates/ts_template.ts b/qiming-run_code_rmcp/src/templates/ts_template.ts new file mode 100644 index 00000000..5a6d6c94 --- /dev/null +++ b/qiming-run_code_rmcp/src/templates/ts_template.ts @@ -0,0 +1,96 @@ +// TypeScript类型声明 +// @ts-nocheck +type LogFunction = (...args: any[]) => void; +type Handler = (input: any) => any; + +// Save original console.log +const originalConsoleLog: LogFunction = console.log; +let logs: string[] = []; + +// Replace console.log to capture logs +console.log = function(...args: any[]): void { + // Convert arguments to string and join them + const message = args.map(arg => + typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg) + ).join(' '); + + // Store log + logs.push(message); + + // Also log to original console if showing logs + if ({{SHOW_LOGS}}) { + originalConsoleLog.apply(console, args); + } +}; + +// 异步读取输入参数的函数 +async function readInputParams(): Promise { + let input: any = {}; + try { + const inputFile = Deno.env.get("INPUT_JSON_FILE"); + if (inputFile) { + const inputJson = await Deno.readTextFile(inputFile); + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } else { + // 兼容旧的环境变量方式 + const inputJson = Deno.env.get("INPUT_JSON"); + if (inputJson) { + input = JSON.parse(inputJson); + console.log("接收到的参数:", JSON.stringify(input)); + } + } + } catch (error) { + console.error("解析输入参数失败:", error); + } + return input; +} + +async function executeHandler() { + try { + // Add the original code + {{USER_CODE}} + + // 读取输入参数 + const input = await readInputParams(); + + // Execute handler function and get result + let result: any = null; + + // 优先检查main函数 + if (typeof main === 'function') { + // 检查main是否是异步函数 + if (main.constructor.name === 'AsyncFunction') { + result = await (main as (input: any) => Promise)(input); + } else { + result = (main as Handler)(input); + } + } else if (typeof handler === 'function') { + // 如果没有main函数,检查handler + if (handler.constructor.name === 'AsyncFunction') { + result = await (handler as (input: any) => Promise)(input); + } else { + result = (handler as Handler)(input); + } + } else { + throw new Error("没有找到main或handler函数"); + } + + // Print final output as JSON + originalConsoleLog(JSON.stringify({ + logs: logs, + result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null, + error: null + })); + } catch (error) { + // Handle errors + originalConsoleLog(JSON.stringify({ + logs: logs, + result: null, + error: String(error) + })); + } +} + +// 执行并等待结果 +executeHandler(); \ No newline at end of file diff --git a/qiming-run_code_rmcp/src/tests/js_tests.rs b/qiming-run_code_rmcp/src/tests/js_tests.rs new file mode 100644 index 00000000..e62a49f8 --- /dev/null +++ b/qiming-run_code_rmcp/src/tests/js_tests.rs @@ -0,0 +1,740 @@ +#[cfg(test)] +mod js_tests { + use anyhow::Result; + use log::info; + use serde_json::json; + + use crate::model::{CodeExecutor, LanguageScript}; + use crate::tests::test_utils::setup; + + #[tokio::test] + async fn test_js_basic_execution() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_js.js")?; + info!("读取测试脚本: test_js.js"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, None).await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + Ok(()) + } + + #[tokio::test] + async fn test_js_with_params() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_js_params.js")?; + info!("读取测试脚本: test_js_params.js"); + + // 准备参数 + let params = json!({ + "a": 10, + "b": 20, + "name": "测试用户" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("sum"), "结果应包含 sum 字段"); + assert!(json_str.contains("greeting"), "结果应包含 greeting 字段"); + assert!(json_str.contains("message"), "结果应包含 message 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_object_result() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/rfunction_test1.js")?; + info!("读取测试脚本: rfunction_test1.js"); + + // 准备参数 + let params = json!({ + "a": 1, + "b": 2 + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("3"), "message 字段应为 3"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_with_timeout() -> Result<()> { + // 初始化日志 + setup(); + + // 创建一个会超时的JavaScript脚本 + let code = r#" +// 创建一个长时间运行的脚本 +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function handler(input) { + console.log("开始执行耗时操作"); + + // 这个操作会运行10秒钟 + for (let i = 0; i < 10; i++) { + console.log(`已经执行了 ${i+1} 秒`); + await sleep(1000); // 等待1秒 + } + + console.log("操作完成"); + return { result: "完成" }; +} +"#; + info!("创建了一个会运行10秒的测试脚本"); + + // 准备参数 + let params = json!({ + "test": "超时测试" + }); + info!("准备测试参数: {params:?}"); + + // 设置2秒超时并执行脚本 + info!("开始执行JavaScript脚本,设置2秒超时..."); + let start_time = std::time::Instant::now(); + let result = + CodeExecutor::execute_with_params(code, LanguageScript::Js, Some(params), Some(2)) + .await; + let elapsed = start_time.elapsed(); + + // 检查是否在2-3秒内超时(给一点缓冲时间) + info!("脚本执行耗时: {elapsed:?}"); + assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒"); + assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时"); + + // 检查是否返回超时错误 + match result { + Ok(exec_result) => { + if let Some(error) = exec_result.error { + info!("正确捕获到超时错误: {error}"); + assert!( + error.contains("timed out") || + error.contains("TimedOut") || + error.contains("executor await error"), + "错误信息应该包含超时相关信息" + ); + } else { + panic!("应该捕获到超时错误,但脚本执行成功了"); + } + } + Err(e) => { + // 使用特殊格式化获取完整错误链 + let full_error = format!("{e:#}"); + info!("捕获到错误: {full_error}"); + assert!( + full_error.contains("timed out"), + "完整错误链中应该包含'timed out'超时信息" + ); + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_cow_say_hello() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/cow_say_hello.js")?; + info!("读取测试脚本: cow_say_hello.js"); + + // 准备参数 + let params = json!({ + "a": 5, + "b": 7 + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params(&code, LanguageScript::Js, Some(params), None) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + + // 检查计算结果是否正确 (5 + 7 = 12) + if let Some(obj) = result_val.as_object() { + if let Some(message) = obj.get("message") { + assert_eq!(message, 12, "message 字段的值应为 12"); + } + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_import_deno_std() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_deno_std_example.js")?; + info!("读取测试脚本: import_deno_std_example.js"); + + // 准备参数 + let params = json!({ + "path": "test.txt" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("pathInfo"), "结果应包含 pathInfo 字段"); + assert!( + json_str.contains("fileContent"), + "结果应包含 fileContent 字段" + ); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_import_jsr() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_jsr_example.js")?; + info!("读取测试脚本: import_jsr_example.js"); + + // 准备参数 + let params = json!({ + "a": 10, + "b": 5, + "expected": 15 + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("results"), "结果应包含 results 字段"); + assert!(json_str.contains("summary"), "结果应包含 summary 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_local_module() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_local_module_example.js")?; + info!("读取测试脚本: import_local_module_example.js"); + + // 准备参数 + let params = json!({ + "order": { + "items": [ + { "name": "测试产品", "price": 200, "quantity": 2 } + ], + "taxRate": 0.05 + } + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("order"), "结果应包含 order 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_import_axios() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_axios_example.js")?; + info!("读取测试脚本: import_axios_example.js"); + + // 准备参数 + let params = json!({ + "url": "https://jsonplaceholder.typicode.com/todos/1" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("data"), "结果应包含 data 字段"); + assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_import_esm() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_esm_module_example.js")?; + info!("读取测试脚本: import_esm_module_example.js"); + + // 准备参数 + let params = json!({ + "data": "测试数据", + "salt": "test-salt-123" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!(json_str.contains("result"), "结果应包含 result 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_import_lodash() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/import_lodash_example.js")?; + info!("读取测试脚本: import_lodash_example.js"); + + // 准备参数 + let params = json!({ + "data": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "chunkSize": 3 + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("message"), "结果应包含 message 字段"); + assert!( + json_str.contains("originalData"), + "结果应包含 originalData 字段" + ); + assert!( + json_str.contains("processedData"), + "结果应包含 processedData 字段" + ); + assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_main_function() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/rfunction_js_test3.js")?; + info!("读取测试脚本: rfunction_js_test3.js"); + + // 准备参数 + let params = json!({ + "testKey": "测试值" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行JavaScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值 + if let Some(result_val) = result.result { + // 检查结果是否包含预期的字段 + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("key"), "结果应包含 key 字段"); + assert!(json_str.contains("value"), "key 字段的值应为 value"); + + // 检查JSON结构 + if let Some(obj) = result_val.as_object() { + if let Some(key_value) = obj.get("key") { + assert_eq!(key_value, "value", "key 字段的值应为 value"); + } + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_js_large_parameters() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_js_large_params.js")?; + info!("读取测试脚本: test_js_large_params.js"); + + // 生成大于2MB的文本参数 + fn generate_large_text(size_in_mb: usize) -> String { + let chunk = "This is a test chunk of text designed to generate large parameters for testing the Argument list too long fix. ".repeat(100); + let iterations = (size_in_mb * 1024 * 1024) / chunk.len(); + + let mut result = String::new(); + for i in 0..iterations { + result.push_str(&chunk); + result.push_str(&format!("Iteration {}\n", i)); + } + + result + } + + let large_text = generate_large_text(3); // 3MB + info!("生成了3MB大小的文本参数,实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0); + + // 准备参数 + let params = json!({ + "largeText": large_text + }); + info!("准备测试参数,JSON大小: {:.2} MB", serde_json::to_string(¶ms).unwrap().len() as f64 / 1024.0 / 1024.0); + + // 执行脚本 + info!("开始执行JavaScript脚本,测试大参数处理..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值包含成功处理大参数的信息 + if let Some(result_val) = result.result { + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("success"), "结果应包含 success 字段"); + assert!(json_str.contains("true"), "success 应为 true"); + assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息"); + assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段"); + + // 验证返回值是对象格式 + if let Some(obj) = result_val.as_object() { + if let Some(success) = obj.get("success") { + assert_eq!(success, true, "success 字段的值应为 true"); + } + if let Some(text_size) = obj.get("textSize") { + assert!(text_size.is_number(), "textSize 应为数字"); + let size = text_size.as_u64().unwrap(); + assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB"); + } + } + } + + info!("大参数测试通过!临时文件解决方案有效。"); + Ok(()) + } +} diff --git a/qiming-run_code_rmcp/src/tests/mod.rs b/qiming-run_code_rmcp/src/tests/mod.rs new file mode 100644 index 00000000..fc7253c1 --- /dev/null +++ b/qiming-run_code_rmcp/src/tests/mod.rs @@ -0,0 +1,90 @@ +#[cfg(test)] +pub mod test_utils { + use log::LevelFilter; + use log::info; + use std::fs; + use std::path::Path; + use std::sync::Once; + + // 使用 Once 确保 env_logger 只初始化一次 + static INIT: Once = Once::new(); + // 控制是否清理缓存的环境变量名 + const CLEAN_CACHE_ENV: &str = "CLEAN_TEST_CACHE"; + + pub fn setup() { + INIT.call_once(|| { + // 尝试初始化日志,如果失败则忽略 + let _ = std::panic::catch_unwind(|| { + env_logger::builder().filter_level(LevelFilter::Info).init(); + }); + }); + + // 只有当环境变量设置为"1"时才清理缓存 + if let Ok(clean_cache) = std::env::var(CLEAN_CACHE_ENV) { + if clean_cache == "1" { + clean_cache_dir(); + } + } + } + + // 清理缓存目录函数 + fn clean_cache_dir() { + let cache_dir = Path::new("/tmp/code_cache"); + + // 如果缓存目录存在,清理其中的文件 + if cache_dir.exists() { + info!("正在清理缓存目录: {cache_dir:?}"); + + match fs::read_dir(cache_dir) { + Ok(entries) => { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_file() { + if let Err(e) = fs::remove_file(&path) { + info!("删除文件失败 {path:?}: {e}"); + } else { + info!("已删除缓存文件: {path:?}"); + } + } + } + } + info!("缓存目录清理完成"); + } + Err(e) => { + info!("读取缓存目录失败: {e}"); + } + } + } else { + // 如果缓存目录不存在,创建它 + info!("缓存目录不存在,创建目录: {cache_dir:?}"); + if let Err(e) = fs::create_dir_all(cache_dir) { + info!("创建缓存目录失败: {e}"); + } else { + info!("缓存目录创建成功"); + } + } + + // 确保缓存目录存在且可写 + if !cache_dir.exists() { + if let Err(e) = fs::create_dir_all(cache_dir) { + info!("创建缓存目录失败: {e}"); + } + } + + // 设置目录权限为777(所有用户可读写执行) + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Err(e) = fs::set_permissions(cache_dir, fs::Permissions::from_mode(0o775)) { + info!("设置缓存目录权限失败: {e}"); + } else { + info!("设置缓存目录权限成功"); + } + } + } +} + +pub mod js_tests; +pub mod python_tests; +pub mod ts_tests; diff --git a/qiming-run_code_rmcp/src/tests/python_tests.rs b/qiming-run_code_rmcp/src/tests/python_tests.rs new file mode 100644 index 00000000..ab339aa3 --- /dev/null +++ b/qiming-run_code_rmcp/src/tests/python_tests.rs @@ -0,0 +1,591 @@ +#[cfg(test)] +mod python_tests { + use anyhow::Result; + use log::info; + use serde_json::json; + + use crate::model::{CodeExecutor, LanguageScript}; + use crate::tests::test_utils::setup; + + #[tokio::test] + async fn test_python_basic_execution() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python.py")?; + info!("读取测试脚本: test_python.py"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, None).await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + // 检查日志中是否包含预期的输出 + let logs_str = result.logs.join(" "); + assert!( + logs_str.contains("Handler function called"), + "日志应包含 'Handler function called'" + ); + assert!( + logs_str.contains("Final calculation completed"), + "日志应包含 'Final calculation completed'" + ); + assert!( + logs_str.contains("The product of [1, 2, 3, 4, 5] is 120"), + "日志应包含计算结果" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_python_with_params() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python_params.py")?; + info!("读取测试脚本: test_python_params.py"); + + // 准备参数 + let params = json!({ + "a": 10, + "b": 20 + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证返回值包含预期的字段 + if let Some(result_val) = result.result { + // 结果应该是 JSON 对象(Python 字典被正确解析) + assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段"); + assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段"); + assert!(result_val.get("message").is_some(), "结果应包含 message 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_python_different_return_types() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python_types.py")?; + info!("读取测试脚本: test_python_types.py"); + + // 测试字符串类型 + let params = json!({"type": "string"}); + info!("测试字符串类型, 参数: {params:?}"); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + if let Some(result_val) = &result.result { + assert!(result_val.is_string(), "结果应为字符串类型"); + } + + // 测试数字类型 + let params = json!({"type": "number"}); + info!("测试数字类型, 参数: {params:?}"); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + if let Some(result_val) = &result.result { + assert!(result_val.is_number(), "结果应为数字类型"); + assert_eq!(result_val.as_i64().unwrap(), 12345, "结果应为 12345"); + } + + // 测试布尔类型 + let params = json!({"type": "boolean"}); + info!("测试布尔类型, 参数: {params:?}"); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + if let Some(result_val) = &result.result { + assert!(result_val.is_boolean(), "结果应为布尔类型"); + assert!(result_val.as_bool().unwrap(), "结果应为 true"); + } + + // 测试列表类型 + let params = json!({"type": "list"}); + info!("测试列表类型, 参数: {params:?}"); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + if let Some(result_val) = &result.result { + // 结果应该是数组类型(Python列表被正确解析) + assert!(result_val.is_array(), "结果应为数组类型"); + assert_eq!(result_val.as_array().unwrap().len(), 6, "数组长度应为 6"); + } + + // 测试字典类型 + let params = json!({"type": "dict"}); + info!("测试字典类型, 参数: {params:?}"); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + if let Some(result_val) = &result.result { + // 结果应该是对象类型(Python字典被正确解析) + assert!(result_val.is_object(), "结果应为对象类型"); + assert!(result_val.get("name").is_some(), "结果应包含 name 字段"); + assert!(result_val.get("age").is_some(), "结果应包含 age 字段"); + assert!(result_val.get("tags").is_some(), "结果应包含 tags 字段"); + } + + // 测试 None 类型 + let params = json!({"type": "null"}); + info!("测试 None 类型, 参数: {params:?}"); + let _result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + + Ok(()) + } + + // 此测试使用标准库替代pandas,不再需要忽略 + #[tokio::test] + async fn test_python_with_pandas() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/rfunction_test2.py")?; + info!("读取测试脚本: rfunction_test2.py"); + + // 检查依赖识别 + let dependencies = crate::python_runner::parse_import(&code)?; + info!("识别到的依赖: {dependencies:?}"); + + // 验证依赖识别结果 - 应该包含pandas依赖 + assert!(!dependencies.is_empty(), "依赖列表不应为空"); + assert!( + dependencies.contains(&"pandas".to_string()), + "依赖列表应包含pandas" + ); + + // 准备参数 + let params = json!({ + "params": { + "input": "测试数据" + } + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + // 如果有执行错误,我们只验证依赖识别是否正确,不再检查执行结果 + // 这是因为pandas可能未安装或环境问题导致执行失败 + return Ok(()); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 如果脚本执行成功,才验证结果 + // 验证日志和结果 + if result.logs.is_empty() { + info!("警告: 日志为空,但脚本执行成功"); + } else { + info!("捕获到的日志数量: {}", result.logs.len()); + + // 逐条打印日志 + for (i, log) in result.logs.iter().enumerate() { + info!("日志[{i}]: {log}"); + } + + // 检查日志中是否包含logging.info的输出 + let logs_str = result.logs.join(" "); + info!("合并后的日志字符串: {logs_str}"); + assert!( + logs_str.contains("Created data structure"), + "日志应包含'Created data structure'" + ); + assert!(logs_str.contains("input args"), "日志应包含'input args'"); + } + + // 如果有结果,验证返回值包含预期的字段 + if let Some(result_val) = result.result { + // 结果应该是JSON对象(Python字典被正确解析) + assert!(result_val.get("key0").is_some(), "结果应包含 key0 字段"); + assert_eq!( + result_val["key0"].as_str().unwrap(), + "测试数据", + "key0 应等于输入参数" + ); + assert!(result_val.get("key1").is_some(), "结果应包含 key1 字段"); + assert!(result_val["key1"].is_array(), "key1 应为数组"); + assert!(result_val.get("key2").is_some(), "结果应包含 key2 字段"); + assert!(result_val["key2"].is_object(), "key2 应为对象"); + assert!(result_val["key2"].get("key21").is_some(), "key2.key21 应存在"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_python_params_access() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python_simple.py")?; + info!("读取测试脚本: test_python_simple.py"); + + // 准备参数 - 直接提供input参数 + let params = json!({ + "input": "直接提供的参数" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = CodeExecutor::execute_with_params_compat( + &code, + LanguageScript::Python, + Some(params.clone()), + ) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + panic!("执行出错: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + + if let Some(result_str) = result_val.as_str() { + let json_val = serde_json::from_str::(result_str)?; + + // 验证两种访问方式都能获取到参数 + assert_eq!( + json_val["direct_access"], "直接提供的参数", + "直接访问应该能获取到参数" + ); + assert_eq!( + json_val["nested_access"], "直接提供的参数", + "嵌套访问也应该能获取到参数" + ); + + // 验证args结构中同时包含直接参数和params嵌套参数 + assert!( + json_val["args_structure"].get("input").is_some(), + "args结构应包含直接参数" + ); + assert!( + json_val["args_structure"].get("params").is_some(), + "args结构应包含params参数" + ); + } + } + + // 准备参数 - 通过params嵌套提供 + let nested_params = json!({ + "params": { + "input": "嵌套提供的参数" + } + }); + info!("准备嵌套测试参数: {nested_params:?}"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = CodeExecutor::execute_with_params_compat( + &code, + LanguageScript::Python, + Some(nested_params), + ) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + panic!("执行出错: {error}"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + + if let Some(result_str) = result_val.as_str() { + let json_val = serde_json::from_str::(result_str)?; + + // 验证嵌套参数可以被访问 + assert_eq!( + json_val["nested_access"], "嵌套提供的参数", + "应该能通过params.input访问到参数" + ); + + // 验证args结构 + assert!( + json_val["args_structure"].get("params").is_some(), + "args结构应包含params参数" + ); + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_python_logging() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python_logging.py")?; + info!("读取测试脚本: test_python_logging.py"); + + // 准备参数 + let params = json!({ + "test": "日志测试" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行Python脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + panic!("执行出错: {error}"); + } else { + info!("脚本执行成功"); + } + + // 验证日志捕获 + assert!(!result.logs.is_empty(), "日志不应为空"); + info!("捕获到的日志数量: {}", result.logs.len()); + + // 逐条打印日志 + for (i, log) in result.logs.iter().enumerate() { + info!("日志[{i}]: {log}"); + } + + // 检查是否捕获了所有级别的日志 + let logs_str = result.logs.join(" "); + // DEBUG级别的日志可能不会被捕获,因为Python胶水代码中可能设置了最低级别为INFO + // assert!(logs_str.contains("DEBUG级别"), "应捕获DEBUG级别的日志"); + assert!(logs_str.contains("INFO级别"), "应捕获INFO级别的日志"); + assert!(logs_str.contains("WARNING级别"), "应捕获WARNING级别的日志"); + assert!(logs_str.contains("ERROR级别"), "应捕获ERROR级别的日志"); + assert!( + logs_str.contains("CRITICAL级别"), + "应捕获CRITICAL级别的日志" + ); + assert!( + logs_str.contains("格式化的JSON数据"), + "应捕获格式化的JSON数据" + ); + + // 验证返回值 + if let Some(result_val) = result.result { + // 结果应该是JSON对象(Python字典被正确解析) + assert_eq!( + result_val["message"], "日志测试完成", + "返回的message字段不正确" + ); + assert_eq!(result_val["log_count"], 6, "返回的log_count字段不正确"); + } else { + panic!("应有返回结果"); + } + + Ok(()) + } + + // 这个测试使用execute_with_params并指定超时时间 + #[tokio::test] + async fn test_python_with_timeout() -> Result<()> { + // 初始化日志 + setup(); + + // 创建一个会超时的Python脚本 + let code = r#" +import time +import logging + +def main(args: dict) -> dict: + logging.info("开始执行耗时操作") + + # 这个操作会运行10秒钟 + for i in range(10): + logging.info(f"已经执行了 {i+1} 秒") + time.sleep(1) + + logging.info("操作完成") + return {"result": "完成"} +"#; + info!("创建了一个会运行10秒的测试脚本"); + + // 准备参数 + let params = json!({ + "test": "超时测试" + }); + info!("准备测试参数: {params:?}"); + + // 设置3秒超时并执行脚本 + info!("开始执行Python脚本,设置3秒超时..."); + let start_time = std::time::Instant::now(); + let result = + CodeExecutor::execute_with_params(code, LanguageScript::Python, Some(params), Some(3)) + .await; + let elapsed = start_time.elapsed(); + + // 检查是否在3-4秒内超时(给一点缓冲时间) + info!("脚本执行耗时: {elapsed:?}"); + assert!(elapsed.as_secs() >= 3, "脚本应该至少运行3秒"); + assert!(elapsed.as_secs() < 5, "脚本应该在5秒内超时"); + + // 检查是否返回超时错误 + match result { + Ok(exec_result) => { + if let Some(error) = exec_result.error { + info!("正确捕获到超时错误: {error}"); + assert!( + error.contains("timed out") || + error.contains("TimedOut") || + error.contains("executor await error"), + "错误信息应该包含超时相关信息" + ); + } else { + panic!("应该捕获到超时错误,但脚本执行成功了"); + } + } + Err(e) => { + // 使用特殊格式化获取完整错误链 + let full_error = format!("{e:#}"); + info!("捕获到错误: {full_error}"); + assert!( + full_error.contains("timed out"), + "完整错误链中应该包含'timed out'超时信息" + ); + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_python_large_parameters() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_python_large_params.py")?; + info!("读取测试脚本: test_python_large_params.py"); + + // 生成大于2MB的文本参数 + fn generate_large_text(size_in_mb: usize) -> String { + let chunk = "This is a test chunk of text designed to generate large parameters for Python testing. ".repeat(100); + let iterations = (size_in_mb * 1024 * 1024) / chunk.len(); + + let mut result = String::new(); + for i in 0..iterations { + result.push_str(&chunk); + result.push_str(&format!("Iteration {}\n", i)); + } + + result + } + + let large_text = generate_large_text(3); // 3MB + info!("生成了3MB大小的文本参数,实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0); + + // 准备参数 + let params = json!({ + "largeText": large_text + }); + info!("准备测试参数,JSON大小: {:.2} MB", serde_json::to_string(¶ms).unwrap().len() as f64 / 1024.0 / 1024.0); + + // 执行脚本 + info!("开始执行Python脚本,测试大参数处理..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值包含成功处理大参数的信息 + if let Some(result_val) = result.result { + let json_str = serde_json::to_string(&result_val)?; + assert!(json_str.contains("success"), "结果应包含 success 字段"); + assert!(json_str.contains("true"), "success 应为 true"); + assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息"); + assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段"); + + // 验证返回值是对象格式 + if let Some(obj) = result_val.as_object() { + if let Some(success) = obj.get("success") { + assert_eq!(success, true, "success 字段的值应为 true"); + } + if let Some(text_size) = obj.get("textSize") { + assert!(text_size.is_number(), "textSize 应为数字"); + let size = text_size.as_u64().unwrap(); + assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB"); + } + } + } + + info!("Python大参数测试通过!临时文件解决方案有效。"); + Ok(()) + } +} diff --git a/qiming-run_code_rmcp/src/tests/ts_tests.rs b/qiming-run_code_rmcp/src/tests/ts_tests.rs new file mode 100644 index 00000000..5da027fa --- /dev/null +++ b/qiming-run_code_rmcp/src/tests/ts_tests.rs @@ -0,0 +1,202 @@ +#[cfg(test)] +mod ts_tests { + use anyhow::Result; + use log::info; + use serde_json::json; + + use crate::model::{CodeExecutor, LanguageScript}; + use crate::tests::test_utils::setup; + + #[tokio::test] + async fn test_ts_basic_execution() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_ts.ts")?; + info!("读取测试脚本: test_ts.ts"); + + // 执行脚本 + info!("开始执行TypeScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + Ok(()) + } + + #[tokio::test] + async fn test_ts_with_params() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_ts_params.ts")?; + info!("读取测试脚本: test_ts_params.ts"); + + // 准备参数 + let params = json!({ + "a": 10, + "b": 20, + "name": "测试用户" + }); + info!("准备测试参数: {params:?}"); + + // 执行脚本 + info!("开始执行TypeScript脚本..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, Some(params)) + .await?; + info!("脚本执行完成, 日志: {:?}", result.logs); + + if let Some(error) = &result.error { + info!("执行错误: {error}"); + } else { + info!("脚本执行成功"); + } + + if let Some(result_val) = &result.result { + info!("执行结果: {result_val:?}"); + } else { + info!("无执行结果"); + } + + // 验证结果 + assert!(!result.logs.is_empty(), "日志不应为空"); + assert!(result.error.is_none(), "不应有错误"); + assert!(result.result.is_some(), "应有返回结果"); + + // 验证返回值包含预期的字段 + if let Some(result_val) = result.result { + // 结果应该是JSON对象(TypeScript对象被正确解析) + assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段"); + assert!( + result_val.get("greeting").is_some(), + "结果应包含 greeting 字段" + ); + assert!(result_val.get("message").is_some(), "结果应包含 message 字段"); + assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段"); + } + + Ok(()) + } + + #[tokio::test] + async fn test_ts_type_checking() -> Result<()> { + // 初始化日志 + setup(); + + // 从 fixtures 目录读取测试脚本 + let code = std::fs::read_to_string("fixtures/test_ts.ts")?; + info!("读取测试脚本: test_ts.ts"); + + // 执行脚本 + info!("开始执行TypeScript类型检查..."); + let result = + CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?; + info!("类型检查完成"); + + if let Some(error) = &result.error { + info!("类型检查错误: {error}"); + } else { + info!("类型检查通过"); + } + + // 验证结果 + assert!(result.error.is_none(), "TypeScript 类型检查应通过"); + + Ok(()) + } + + #[tokio::test] + async fn test_ts_with_timeout() -> Result<()> { + // 初始化日志 + setup(); + + // 创建一个会超时的TypeScript脚本 + let code = r#" +// 创建一个长时间运行的脚本 +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function handler(input: any): Promise<{result: string}> { + console.log("开始执行耗时操作"); + + // 这个操作会运行10秒钟 + for (let i = 0; i < 10; i++) { + console.log(`已经执行了 ${i+1} 秒`); + await sleep(1000); // 等待1秒 + } + + console.log("操作完成"); + return { result: "完成" }; +} +"#; + info!("创建了一个会运行10秒的测试脚本"); + + // 准备参数 + let params = json!({ + "test": "超时测试" + }); + info!("准备测试参数: {params:?}"); + + // 设置2秒超时并执行脚本 + info!("开始执行TypeScript脚本,设置2秒超时..."); + let start_time = std::time::Instant::now(); + let result = + CodeExecutor::execute_with_params(code, LanguageScript::Ts, Some(params), Some(2)) + .await; + let elapsed = start_time.elapsed(); + + // 检查是否在2-3秒内超时(给一点缓冲时间) + info!("脚本执行耗时: {elapsed:?}"); + assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒"); + assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时"); + + // 检查是否返回超时错误 + match result { + Ok(exec_result) => { + if let Some(error) = exec_result.error { + info!("正确捕获到超时错误: {error}"); + assert!( + error.contains("timed out") || + error.contains("TimedOut") || + error.contains("executor await error"), + "错误信息应该包含超时相关信息" + ); + } else { + panic!("应该捕获到超时错误,但脚本执行成功了"); + } + } + Err(e) => { + // 使用特殊格式化获取完整错误链 + let full_error = format!("{e:#}"); + info!("捕获到错误: {full_error}"); + assert!( + full_error.contains("timed out"), + "完整错误链中应该包含'timed out'超时信息" + ); + } + } + + Ok(()) + } +} diff --git a/qiming-run_code_rmcp/src/warm_up.rs b/qiming-run_code_rmcp/src/warm_up.rs new file mode 100644 index 00000000..ae39d80b --- /dev/null +++ b/qiming-run_code_rmcp/src/warm_up.rs @@ -0,0 +1,366 @@ +use crate::model::CommandExecutor; +use anyhow::Result; +use log::{info, warn}; +use std::path::Path; +use tokio::process::Command; + +//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple +const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple"; + +//使用 uv安装 python 3.13,比如: uv python install 3.13 +async fn install_python_3_13() -> Result<()> { + let mut cmd = Command::new("uv"); + cmd.arg("python") + .arg("install") + .arg("3.13") + .kill_on_drop(true); + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("安装Python 3.13失败"); + return Err(anyhow::anyhow!("安装Python 3.13失败")); + } + info!("安装Python 3.13成功"); + Ok(()) + } + Ok(Err(e)) => { + warn!("命令执行失败: {e}"); + Err(anyhow::anyhow!("命令执行失败: {}", e)) + } + Err(e) => { + warn!("执行超时或系统错误: {e}"); + Err(anyhow::anyhow!("执行超时或系统错误: {}", e)) + } + } +} + +// 检查 uv 虚拟环境是否存在 +async fn check_and_create_uv_venv() -> Result<()> { + info!("检查 uv 虚拟环境..."); + + // 检查 .venv 目录是否存在 + if Path::new(".venv").exists() { + info!("uv 虚拟环境已存在"); + return Ok(()); + } + + info!("未检测到 uv 虚拟环境,开始创建..."); + let mut cmd = Command::new("uv"); + cmd.arg("venv").kill_on_drop(true); + + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("创建 uv 虚拟环境失败"); + return Err(anyhow::anyhow!("创建 uv 虚拟环境失败")); + } + info!("创建 uv 虚拟环境成功"); + Ok(()) + } + Ok(Err(e)) => { + warn!("命令执行失败: {e}"); + Err(anyhow::anyhow!("命令执行失败: {}", e)) + } + Err(e) => { + warn!("执行超时或系统错误: {e}"); + Err(anyhow::anyhow!("执行超时或系统错误: {}", e)) + } + } +} + +/// 预热Python环境,安装常用依赖 +async fn warm_up_python_env(custom_deps: Option>) -> Result<()> { + info!("开始预热Python环境..."); + + // 默认Python依赖列表 + let default_deps = [ + "requests", + "pandas", + "numpy", + "matplotlib", + "scikit-learn", + "pytest", + "pydantic", + "fastapi", + "uvicorn", + "sqlalchemy", + "opencv-python", + "python-docx", + ]; + + // 使用自定义依赖或默认依赖 + let deps_to_install = if let Some(deps) = custom_deps { + info!("使用自定义Python依赖列表"); + deps + } else { + info!("使用默认Python依赖列表"); + default_deps.iter().map(|&s| s.to_string()).collect() + }; + + let total_deps = deps_to_install.len(); + info!("总共需要预热 {total_deps} 个Python依赖"); + + // 使用uv安装依赖 + for (index, dep) in deps_to_install.iter().enumerate() { + let progress = ((index + 1) as f32 / total_deps as f32 * 100.0) as u32; + info!("预热进度: {progress}% - 正在安装Python依赖: {dep}"); + + let mut cmd = Command::new("uv"); + cmd.arg("pip") + .arg("install") + .arg("--default-index") + .arg(PYTHON_ACCELERATION_ADDRESS) + .arg(dep) + .kill_on_drop(true); + + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("安装依赖 {dep} 失败"); + } + } + Ok(Err(e)) => { + warn!("命令执行失败: {e} - 依赖: {dep}"); + continue; + } + Err(e) => { + warn!("执行超时或系统错误: {e} - 依赖: {dep}"); + continue; + } + } + } + + info!("Python环境预热完成 (100%)"); + Ok(()) +} + +/// 预热JavaScript/TypeScript环境,缓存常用模块 +async fn warm_up_js_env( + custom_npm_packages: Option>, + custom_jsr_packages: Option>, + custom_node_modules: Option>, +) -> Result<()> { + info!("开始预热JavaScript/TypeScript环境..."); + + // 默认npm包列表 + let default_npm_packages = [ + "lodash", + "axios", + "moment", + "uuid", + "express", + "react", + "react-dom", + "typescript", + "jest", + "webpack", + ]; + + // 默认JSR包列表 + let default_jsr_packages = [ + "@std/testing", + "@std/http", + "@std/path", + "@std/fs", + "@std/encoding/json", + ]; + + // 默认Node.js内置模块列表 + let default_node_modules = [ + "crypto", "buffer", "fs", "path", "http", "https", "url", "util", "stream", "events", + ]; + + // 使用自定义npm包或默认包 + let npm_packages = if let Some(packages) = custom_npm_packages { + info!("使用自定义npm包列表"); + packages + } else { + info!("使用默认npm包列表"); + default_npm_packages + .iter() + .map(|&s| s.to_string()) + .collect() + }; + + // 使用自定义JSR包或默认包 + let jsr_packages = if let Some(packages) = custom_jsr_packages { + info!("使用自定义JSR包列表"); + packages + } else { + info!("使用默认JSR包列表"); + default_jsr_packages + .iter() + .map(|&s| s.to_string()) + .collect() + }; + + // 使用自定义Node.js模块或默认模块 + let node_modules = if let Some(modules) = custom_node_modules { + info!("使用自定义Node.js模块列表"); + modules + } else { + info!("使用默认Node.js模块列表"); + default_node_modules + .iter() + .map(|&s| s.to_string()) + .collect() + }; + + // 计算总任务数 + let total_tasks = npm_packages.len() + jsr_packages.len() + node_modules.len(); + info!("总共需要预热 {total_tasks} 个JavaScript/TypeScript模块"); + + let mut completed_tasks = 0; + + // 预热npm包 + for pkg in npm_packages.iter() { + completed_tasks += 1; + let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32; + info!("预热进度: {progress}% - 正在缓存npm包: {pkg}"); + + let mut cmd = Command::new("deno"); + cmd.args(["cache", "--reload", &format!("npm:{pkg}")]) + .kill_on_drop(true); + + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("缓存npm包 {pkg} 失败"); + } + } + Ok(Err(e)) => { + warn!("命令执行失败: {e} - 包: {pkg}"); + continue; + } + Err(e) => { + warn!("执行超时或系统错误: {e} - 包: {pkg}"); + continue; + } + } + } + + // 预热JSR包 + for pkg in jsr_packages.iter() { + completed_tasks += 1; + let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32; + info!("预热进度: {progress}% - 正在缓存JSR包: {pkg}"); + + let mut cmd = Command::new("deno"); + cmd.args(["cache", "--reload", &format!("jsr:{pkg}")]) + .kill_on_drop(true); + + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("缓存JSR包 {pkg} 失败"); + } + } + Ok(Err(e)) => { + warn!("命令执行失败: {e} - 包: {pkg}"); + continue; + } + Err(e) => { + warn!("执行超时或系统错误: {e} - 包: {pkg}"); + continue; + } + } + } + + // 预热Node.js内置模块 + for module in node_modules.iter() { + completed_tasks += 1; + let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32; + info!("预热进度: {progress}% - 正在缓存Node.js模块: {module}"); + + let mut cmd = Command::new("deno"); + cmd.args(["cache", "--reload", &format!("node:{module}")]) + .kill_on_drop(true); + + match CommandExecutor::with_timeout(cmd.status(), 600).await { + Ok(Ok(status)) => { + if !status.success() { + warn!("缓存Node.js模块 {module} 失败"); + } + } + Ok(Err(e)) => { + warn!("命令执行失败: {e} - 模块: {module}"); + continue; + } + Err(e) => { + warn!("执行超时或系统错误: {e} - 包: {module}"); + continue; + } + } + } + + info!("JavaScript/TypeScript环境预热完成 (100%)"); + Ok(()) +} + +/// 预热所有脚本执行环境 +pub async fn warm_up_all_envs( + custom_python_deps: Option>, + custom_npm_packages: Option>, + custom_jsr_packages: Option>, + custom_node_modules: Option>, +) -> Result<()> { + info!("开始预热所有脚本执行环境..."); + + // 检查并创建 uv 虚拟环境 + if let Err(e) = check_and_create_uv_venv().await { + warn!("检查或创建 uv 虚拟环境失败: {e}"); + } + + // 安装Python 3.13 + if let Err(e) = install_python_3_13().await { + warn!("安装Python 3.13失败: {e}"); + } + + // 预热Python环境 + if let Err(e) = warm_up_python_env(custom_python_deps).await { + warn!("预热Python环境失败: {e}"); + } + + // 预热JavaScript/TypeScript环境 + if let Err(e) = warm_up_js_env( + custom_npm_packages, + custom_jsr_packages, + custom_node_modules, + ) + .await + { + warn!("预热JavaScript/TypeScript环境失败: {e}"); + } + + info!("所有脚本执行环境预热完成"); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_warm_up_python_env() { + // 测试默认依赖 + warm_up_python_env(None).await.unwrap(); + + // 测试自定义依赖 + let custom_deps = vec!["requests".to_string(), "pandas".to_string()]; + warm_up_python_env(Some(custom_deps)).await.unwrap(); + } + + #[tokio::test] + async fn test_warm_up_js_env() { + // 测试默认依赖 + warm_up_js_env(None, None, None).await.unwrap(); + + // 测试自定义依赖 + let custom_npm = vec!["lodash".to_string(), "axios".to_string()]; + let custom_jsr = vec!["@std/testing".to_string()]; + let custom_node = vec!["crypto".to_string(), "buffer".to_string()]; + warm_up_js_env(Some(custom_npm), Some(custom_jsr), Some(custom_node)) + .await + .unwrap(); + } +} diff --git a/qiming-vite-plugin-design-mode/.gitignore b/qiming-vite-plugin-design-mode/.gitignore new file mode 100644 index 00000000..907b545a --- /dev/null +++ b/qiming-vite-plugin-design-mode/.gitignore @@ -0,0 +1,137 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea +*.iml +*.iws + +# VS Code +.vscode/* +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json + +# macOS +.DS_Store + +# Local History +.history +.npm-cache \ No newline at end of file diff --git a/qiming-vite-plugin-design-mode/.npmignore b/qiming-vite-plugin-design-mode/.npmignore new file mode 100644 index 00000000..7b518509 --- /dev/null +++ b/qiming-vite-plugin-design-mode/.npmignore @@ -0,0 +1,38 @@ +# 排除所有 source map 文件(对用户不必要,减少包体积) +dist/**/*.map +dist/**/*.js.map +dist/**/*.d.ts.map +*.map + +# 排除 TypeScript 源文件(只保留编译后的文件) +**/*.ts +**/*.tsx +!dist/**/*.d.ts + +# 排除测试文件 +test/ +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +**/*.spec.tsx + +# 排除开发文件 +src/ +examples/ +scripts/ +*.config.ts +*.config.js +tsconfig*.json +vitest.config.ts + +# 排除其他不必要的文件 +.git/ +.gitignore +.npmignore +*.log +.DS_Store +node_modules/ +coverage/ +.vscode/ +.idea/ + diff --git a/qiming-vite-plugin-design-mode/CHANGELOG.md b/qiming-vite-plugin-design-mode/CHANGELOG.md new file mode 100644 index 00000000..569499ea --- /dev/null +++ b/qiming-vite-plugin-design-mode/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog + +All notable changes to `@xagi/vite-plugin-design-mode` are documented in this file. + +## [1.1.0-beta.5] - 2026-04-22 + +### Fixed +- Replaced published package workspace protocol dependencies with explicit semver versions to avoid `ERR_PNPM_WORKSPACE_PKG_NOT_FOUND` in external installs. +- Aligned internal package dependency graph for published artifacts: + - `@xagi/vite-plugin-design-mode -> @xagi/design-mode-client-react/@xagi/design-mode-client-vue/@xagi/design-mode-shared` + - `@xagi/design-mode-client-react -> @xagi/design-mode-shared` + - `@xagi/design-mode-client-vue -> @xagi/design-mode-shared` + +## [1.1.0-beta.4] - 2026-04-22 + +### Fixed +- Plugin runtime entry resolution now prefers installed client packages (`@xagi/design-mode-client-react` / `@xagi/design-mode-client-vue`) to avoid publish-time client loading failures. +- Hardened file path validation in update middleware and code updater to prevent path-prefix traversal bypass. +- Updated and stabilized test suites after monorepo migration; full test run passes. + +### Changed +- Published `1.1.0-beta.4` to `next` tag for prerelease consumption. + +## [1.1.0-beta.3] - 2026-04-22 + +### Changed +- Migrated repository to pnpm workspace multi-package layout under `packages/*`. +- Removed legacy root `src` implementation and moved active sources to package-local directories. +- Updated examples/tests to consume package-based paths. + +### Added +- Kept CLI support in the plugin package and included CLI entry in published artifacts. + +## [1.1.0-beta.2] - 2026-04-22 + +### Added +- Dynamic framework support for both `react-vite` and `vue3-vite` projects. +- Vue 3 peer dependency declaration (`vue: ^3.0.0`, optional peer). +- Release tag strategy documentation (`latest` stable only, `beta/next` for prerelease). + +### Changed +- `framework: 'auto'` behavior is documented as React/Vue 3 auto-detection. +- CLI install flow now accepts Vue 3 projects (not only React projects). + +### Fixed +- Vue 2 guard: plugin now fails fast when detecting `vue@2` or `@vitejs/plugin-vue2`. +- npm release workflow now defaults to beta publishing via `npm run release`. + +## [1.1.0-beta.1] - 2026-04-22 + +### Added +- Initial beta channel release for the Vue SFC support line. + +## [1.0.37] - 2026-04-22 + +### Changed +- Translated source comments and CLI/user-facing strings to English in core plugin scope. + +## [1.0.36] - 2026-04-10 + +### Added +- Batch update endpoints and optional history/backup controls. +- Validation endpoint and enhanced source metadata return payload. + +### Changed +- Default source attribute prefix updated to `data-xagi`. + diff --git a/qiming-vite-plugin-design-mode/FEATURES.md b/qiming-vite-plugin-design-mode/FEATURES.md new file mode 100644 index 00000000..36569f6e --- /dev/null +++ b/qiming-vite-plugin-design-mode/FEATURES.md @@ -0,0 +1,84 @@ +# Vite Design Mode 插件 - 功能文档 + +本文档列出了 `@xagi/vite-plugin-design-mode` 当前支持的功能,作为维护、测试和开发的参考。 + +## 1. 核心功能 (Core Functionality) + +### 1.0 框架支持与识别 (Framework Support & Detection) +- **动态双栈支持**: 支持 `react-vite` 与 `vue3-vite` 两种项目形态。 +- **自动识别**: `framework: 'auto'` 会根据项目依赖自动选择 React 或 Vue 3 处理链路。 +- **显式模式**: 可通过 `framework: 'react' | 'vue'` 强制指定处理模式。 +- **Vue 2 保护**: 检测到 `vue@2` 或 `@vitejs/plugin-vue2` 时,插件会快速失败并提示升级到 Vue 3,避免部分可用的隐性问题。 + +### 1.1 元素选择 (Element Selection) +- **点击选择**: 点击 iframe 中的元素即可选中。 +- **高亮显示**: 选中的元素会显示蓝色实线边框 (`outline: 2px solid #007acc`)。 +- **悬停效果**: 鼠标悬停在元素上时显示虚线边框 (`outline: 1px dashed #007acc`)。 +- **面包屑导航**: 显示选中元素的 DOM 层级路径。 + +### 1.2 组件识别 (Component Identification) +- **基本信息**: 显示标签名 (`tagName`)、类名 (`className`) 和文本内容。 +- **源码映射**: 识别源文件路径 (`fileName`)、行号 (`lineNumber`) 和列号 (`columnNumber`)。 +- **组件名称**: 识别 React 组件名称(例如 `Button`, `CardTitle`)。 +- **组件定义**: 显示组件的导入路径(例如 `@/components/ui/button`),以区分组件的使用位置和定义位置。 +- **父级组件**: 识别 React 树中的父级组件。 + +## 2. 编辑能力 (Editing Capabilities) + +### 2.1 静态内容编辑 (Static Content Editing) +- **双击编辑**: 双击静态文本元素即可进入 `contentEditable` 模式。 +- **仅限纯文本**: 只有包含**纯静态文本**(无变量、表达式或子元素)的元素才能编辑。这是通过 `data-xagi-static-content` 属性强制执行的。 +- **实时预览**: 修改内容会立即反映在浏览器中。 +- **源码同步**: 失去焦点(保存)时,更改会发送到服务器并写入源文件。 + +### 2.2 样式 (Class) 编辑 (Style Editing) +- **Class 修改**: 支持添加、删除或修改 Tailwind CSS 类名。 +- **实时更新**: 类名更改会立即应用到 DOM。 +- **源码同步**: 更改会持久化保存到源代码中。 + +### 2.3 列表项同步 (List Item Synchronization) +- **智能分组**: 编辑列表项(例如在 `.map()` 循环中)时,插件会使用生成的 `element-id` 识别所有相关项。 +- **同步更新**: + - **内容**: 编辑一个列表项的静态文本会更新预览中的*所有*实例(以保持一致性)。 + - **样式**: 修改一个列表项的 Class 会将新样式应用到*所有*实例。 + +## 3. 技术实现 (Technical Implementation) + +### 3.1 优化的数据属性 (Optimized Data Attributes) +为了减少 DOM 体积,插件采用了紧凑的属性策略: +- **`data-xagi-info`**: 单个 JSON 属性,包含所有元数据(`fileName`, `line`, `column`, `componentName`, `importPath`, `elementId`)。 +- **`data-xagi-element-id`**: 简短且唯一的元素标识符(格式:`file:line:col_tag`)。 +- **`data-xagi-static-content`**: 布尔标志 (`"true"`),表示元素仅包含纯静态文本。 +- **`data-xagi-children-source`**: 追踪静态文本子元素的来源位置(用于透传组件)。 + +### 3.2 Babel 插件 (`sourceMapper.ts`) +- **AST 遍历**: 分析代码以查找 JSX 元素。 +- **导入追踪**: 追踪 `ImportDeclaration` 以解析组件定义。 +- **静态分析**: 确定内容是否为静态 (`isStaticContent`)。 +- **属性注入**: 在构建/服务过程中注入 `data-xagi-*` 属性。 + +### 3.2.1 Vue SFC 转换链路 (`vueSfcTransformer.ts`) +- **模板解析**: 使用 `@vue/compiler-sfc` 与 `@vue/compiler-dom` 解析 `.vue` 模板 AST。 +- **安全注入**: 在模板节点中注入源码映射属性,供运行时选择与编辑能力使用。 +- **安全回写**: 对 `class` 与静态文本进行 AST 级更新,复杂动态表达式场景保护性拒绝。 + +### 3.3 客户端逻辑 (Client-Side Logic) +- **`SelectionManager`**: 处理点击/悬停事件,并解析 `data-xagi-info` 以提取 `ElementInfo`。 +- **`EditManager`**: 管理 `contentEditable` 状态,处理保存,并执行列表项同步。 +- **`DesignModeContext`**: 管理全局状态以及与父窗口(如果在 iframe 中)的通信。 +- **`DesignModeBridge`**: 处理 iframe 与父级编辑器/IDE 之间的消息传递。 +- **运行时入口解析**: 插件优先从已安装包解析 React/Vue 客户端入口,确保发布后环境可用。 + +## 4. 通信协议 (Communication Protocol) + +### 4.1 Iframe <-> Parent 消息 +- **`ELEMENT_SELECTED`**: 选中元素时发送。Payload 包含 `ElementInfo`。 +- **`UPDATE_STYLE`**: 从父级发送以更新 Class。 +- **`UPDATE_CONTENT`**: 从父级发送以更新文本内容。 +- **`CONTENT_UPDATED`**: 内容在本地编辑后,从 iframe 发送到父级。 +- **`STYLE_UPDATED`**: 样式更新后,从 iframe 发送到父级。 + +## 5. 服务器中间件 (Server Middleware) +- **`/__appdev_design_mode/update`**: 处理源代码更新的 API 端点。 +- **`codeUpdater.ts`**: 基于行/列信息和规则校验进行源码更新(含文件类型/大小与路径安全校验)。 +- **路径安全防护**: `serverMiddleware` 与 `codeUpdater` 均校验目标路径必须位于项目根目录内,防止路径穿越。 diff --git a/qiming-vite-plugin-design-mode/README.md b/qiming-vite-plugin-design-mode/README.md new file mode 100644 index 00000000..0f987128 --- /dev/null +++ b/qiming-vite-plugin-design-mode/README.md @@ -0,0 +1,627 @@ +# Vite Plugin Design Mode + +一个为 AppDev 启用设计模式功能的 Vite 插件,为 React 和 Vue 3 项目提供源码映射与可视化编辑能力(不支持 Vue 2)。 + +## 特性 + +- **源码映射**: 在编译时自动向DOM元素注入源码位置信息(使用紧凑的 `data-xagi-info` JSON 属性) +- **可视化编辑**: 提供直观的设计模式UI,支持实时样式编辑 +- **双击编辑**: 双击静态文本元素即可进入编辑模式,实时预览并自动保存到源码 +- **静态内容检测**: 智能识别纯静态文本(无变量、表达式或子元素),只有静态内容才能直接编辑 +- **列表项同步**: 编辑列表项时自动同步所有相关实例(内容或样式) +- **组件识别**: 识别组件名称、导入路径,区分组件使用位置和定义位置 +- **Vue SFC 支持**: 支持 `.vue` 文件模板注入和 AST 安全回写(class/text) +- **实时修改**: 支持实时编辑样式和内容,并自动持久化到源码文件 +- **Tailwind CSS集成**: 内置Tailwind CSS预设,提供快速样式编辑 +- **桥接通信**: 提供消息桥接机制,支持与外部工具和设计系统集成(支持 iframe 模式) +- **开发服务器集成**: 无缝集成Vite开发服务器,支持热重载 +- **零配置**: 开箱即用,无需复杂配置即可开始使用 + +## 框架支持矩阵 + +- React + Vite:支持(自动识别或 `framework: 'react'`) +- Vue 3 + Vite:支持(自动识别或 `framework: 'vue'`) +- Vue 2 + Vite:不支持(启动时会直接报错,避免出现部分可用的隐性问题) + +## Installation + +### 一键安装(推荐) + +使用 CLI 工具一键配置插件: + +```bash +npx @xagi/vite-plugin-design-mode install +# 或 +pnpm dlx @xagi/vite-plugin-design-mode install +``` + +这个命令会: +- 在 `package.json` 的 `devDependencies` 中添加插件依赖 +- 自动在 `vite.config.ts/js/mjs` 中添加插件配置 +- 使用默认配置,无需手动传参 + +CLI 适用前提: +- 项目需为 Vite 项目(`package.json` 中存在 `vite`) +- 项目需使用 React 或 Vue 3(`package.json` 中存在 `react`,或 `vue@3.x`) + +**注意:** CLI 只会在配置文件中添加配置,不会执行包管理器安装命令。配置完成后,请手动运行包管理器安装命令来安装依赖: + +```bash +pnpm install +# 或 +npm install +# 或 +yarn install +``` + +### 一键卸载 + +使用 CLI 工具一键卸载插件并清理配置: + +```bash +npx @xagi/vite-plugin-design-mode uninstall +# 或 +pnpm dlx @xagi/vite-plugin-design-mode uninstall +``` + +这个命令会: +- 从 `package.json` 中移除插件依赖 +- 从 `vite.config.ts/js/mjs` 中移除 import 和插件配置 +- 自动清理所有相关配置 + +**注意:** CLI 只会在配置文件中移除配置,不会执行包管理器卸载命令。配置清理完成后,请手动运行包管理器卸载命令来移除依赖: + +```bash +pnpm remove @xagi/vite-plugin-design-mode +# 或 +npm uninstall @xagi/vite-plugin-design-mode +# 或 +yarn remove @xagi/vite-plugin-design-mode +``` + +### 手动安装 + +如果需要手动安装: + +```bash +npm install @xagi/vite-plugin-design-mode --save-dev +# or +yarn add @xagi/vite-plugin-design-mode --dev +# or +pnpm add @xagi/vite-plugin-design-mode -D +``` + +### 预发布 / XAGI 集成(推荐) + +`1.1.x` 预发布会频繁发 **`1.1.0-beta.N`**,registry 上历史 beta 会自然变多。**模板、宿主应用、内部文档请不要写死某个 `beta.N`**,否则每发一版就要改依赖。 + +集成时请**统一跟 `next` dist-tag**(始终对应当前灰度线): + +```bash +pnpm add @xagi/vite-plugin-design-mode@next -D +# 若直接依赖 client 包(少见),同样使用 @next: +# pnpm add @xagi/design-mode-client-vue@next -D +``` + +`package.json` 里可写 **`"^1.1.0-0"`**(接受 `1.1.0` 线下任意预发版本)并定期 `pnpm update`;或在文档与 CI 中约定:**安装/升级一律执行 `pnpm add @xagi/vite-plugin-design-mode@next -D`**,由 lockfile 固定实际解析版本。 + +稳定正式版发布后仍从 **`latest`** 安装即可(与上面预发布通道互不干扰)。 + +## 版本发布与 Tag 策略 + +为避免预发布版本影响生产用户,仓库采用以下 npm dist-tag 规则: + +- `latest`:仅用于稳定正式版(如 `1.0.37`、`1.1.0`) +- `next`:**集成方应使用的预发布通道**;标签指向当前推荐的 `1.1.0-beta.*`(或其它预发 semver),版本号会递增,**请勿在对外文档中要求用户锁定某一枚 `beta.N`** +- `beta`:可选的额外 dist-tag(历史或兼容);新集成优先跟 `next` + +### 多包发布 SOP(推荐) + +发布采用统一版本策略,以下 4 个包必须同版本: + +- `@xagi/design-mode-shared` +- `@xagi/design-mode-client-react` +- `@xagi/design-mode-client-vue` +- `@xagi/vite-plugin-design-mode` + +```bash +# 1) 切换 npm 官方源(强烈建议每次发布前显式执行) +nrm use npm + +# 2) 如需升级版本,一次性同步全部包版本和内部依赖(版本号按需替换) +pnpm run release:version:sync -- 1.1.0-beta.12 + +# 3) 一键发布 next(预检 -> 构建 -> 按依赖顺序发布 -> 发布后校验) +pnpm run release:next + +# 4) 一键发布 beta(与 next 同流程,但写入 beta tag) +pnpm run release:beta + +# 5) 发布前演练(不真正 publish) +pnpm run release:next:dry-run +pnpm run release:beta:dry-run +``` + +其中 `release:next` / `release:beta` 都会自动执行以下步骤: + +1. `release:preflight`:校验 registry、版本一致性、禁止 `workspace:*` 泄露到发布包 +2. `release:build`:构建所有包 +3. `release:publish:*`:按依赖顺序发布(`shared -> react/vue并发 -> plugin`) +4. `release:verify:*`:校验对应 dist-tag(`next` 或 `beta`)及目标版本可见性;`verify:next` 对 registry 传播做了**有限次重试**(可用环境变量 `VERIFY_NEXT_MAX_TRIES`、`VERIFY_NEXT_SLEEP_SECS` 调整) + +### 常见问题与修复 + +- 报错 `ERR_PNPM_WORKSPACE_PKG_NOT_FOUND` + 原因:发布包中包含 `workspace:*` 依赖。 + 处理:运行 `release:preflight` 找出问题并改成明确版本号。 + +- `next` tag 未指向本次版本 + 处理: + +```bash +npm dist-tag add @xagi/vite-plugin-design-mode@1.1.0-beta.5 next +``` + +## Basic Usage + +使用一键安装后,插件已自动配置,`vite.config.ts` 中会包含: + +```typescript +// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from '@xagi/vite-plugin-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode() // 使用默认配置,无需传参 + ] +}); +``` + +**默认配置说明:** +- `enabled: true` - 启用插件 +- `enableInProduction: false` - 仅在开发环境生效,生产构建时自动禁用 +- `verbose: true` - 启用详细日志 +- `attributePrefix: 'data-xagi'` - 源码映射属性的前缀 +- `include: ['src/**/*.{ts,js,tsx,jsx,vue}']` - 处理 src 目录下的 TypeScript/JavaScript/TSX/JSX/Vue 文件 +- `exclude: ['node_modules', 'dist']` - 排除指定目录 +- `enableBackup: false` - 是否启用备份功能 +- `enableHistory: false` - 是否启用历史记录功能 +- `framework: 'auto'` - 自动识别 React / Vue 3(可显式设置 `react` 或 `vue`) + +## Advanced Usage + +```typescript +// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from '@xagi/vite-plugin-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode({ + enabled: true, + enableInProduction: false, + attributePrefix: 'data-appdev', + verbose: true, + exclude: ['node_modules', '.git'], + include: ['**/*.{js,jsx,ts,tsx}'] + }) + ] +}); +``` + +## Vue 3 使用说明 + +```typescript +// vite.config.ts +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import appdevDesignMode from '@xagi/vite-plugin-design-mode'; + +export default defineConfig({ + plugins: [ + vue(), + appdevDesignMode({ + framework: 'vue', + }), + ], +}); +``` + +- `framework: 'vue'` 模式下不会注入 React 运行时 UI 脚本,这是预期行为。 +- Vue 当前支持模板元素的 `class` 与静态文本回写,复杂动态表达式会被保护性拒绝。 +- 如检测到 Vue 2(或 `@vitejs/plugin-vue2`),插件会在启动时抛错并提示升级到 Vue 3。 + +## 工作原理 + +1. **编译时转换**: 插件在编译时按框架选择转换链路 + - React: 使用 Babel AST 转换 JSX/TSX/JS 文件 + - Vue: 使用 `@vue/compiler-sfc` + `@vue/compiler-dom` 解析并转换 `.vue` 模板 +2. **抽象语法树分析**: 基于 AST 识别可编辑元素与源码位置信息 + - React: 追踪 ImportDeclaration 以解析组件定义和导入路径,并识别 UI 组件(components/ui) + - React: 静态分析确定内容是否为纯静态文本(`isStaticContent`) + - Vue: 在模板 AST 中定位元素节点,进行注入并用于后续 AST 安全回写 +3. **源码映射数据注入**: 将源码位置信息作为紧凑的`data-*`属性(默认前缀 `data-xagi`)注入到DOM元素 + - 主要信息存储在 `data-xagi-info` JSON 属性中(减少 DOM 体积) + - 添加 `data-xagi-element-id` 用于唯一标识和列表项同步 + - 添加 `data-xagi-static-content` 标记可编辑的静态内容 +4. **虚拟模块加载**: 通过Vite虚拟模块机制动态加载客户端代码 +5. **运行时通信**: 设计模式UI通过桥接系统与外部工具通信,支持实时编辑 + - 支持 iframe 模式,通过 `postMessage` 与父窗口通信 + - 支持元素选择、样式更新、内容更新等消息类型 +6. **源码持久化**: 修改的样式和内容通过API端点实时写入源码文件 + - 使用智能文本替换算法,基于行/列信息安全修改源码 + - 支持样式(className)、内容(innerText)、属性更新 +7. **列表项同步**: 使用 `element-id` 识别相关列表项,编辑一个实例自动同步到所有实例 +8. **批量更新**: 支持批量更新多个元素,通过防抖机制优化性能 +9. **备份与历史**: 可选启用备份和历史记录功能,支持撤销操作 + +## API 端点 + +插件提供以下API端点: + +### 基础端点 +- `GET /__appdev_design_mode/get-source?elementId=xxx` - 获取指定元素的源码信息和文件内容(增强版,包含更多元数据) +- `POST /__appdev_design_mode/modify-source` - 修改源码文件中的样式或内容(兼容旧版本) +- `POST /__appdev_design_mode/update` - 统一更新接口,支持样式、内容、属性更新 +- `GET /__appdev_design_mode/health` - 健康检查,返回插件运行状态 + +### 批量操作端点 +- `POST /__appdev_design_mode/batch-update` - 批量更新多个元素的源码(需要 `enableHistory: true` 以保存会话) +- `GET /__appdev_design_mode/batch-update-status?batchId=xxx` - 查询批量更新会话状态(需要 `enableHistory: true`) + +### 历史记录端点(需要 `enableHistory: true`) +- `GET /__appdev_design_mode/get-history` - 获取更新历史记录 +- `POST /__appdev_design_mode/undo` - 撤销操作(需要 `enableBackup: true`) +- `POST /__appdev_design_mode/redo` - 重做操作(暂未实现) + +### 验证端点 +- `POST /__appdev_design_mode/validate-update` - 验证更新请求的有效性 + +## 生成属性 + +插件处理的元素将具有以下属性(默认前缀为 `data-xagi`,可通过 `attributePrefix` 配置项自定义): + +### 核心属性(实际注入) + +- **`{prefix}-info`**: 包含完整源码映射信息的 JSON 字符串,包含以下字段: + - `fileName`: 源码文件路径 + - `lineNumber`: 源码行号 + - `columnNumber`: 源码列号 + - `elementType`: 元素类型(标签名) + - `componentName`: 组件名称(如果适用) + - `functionName`: 函数名称(如果适用) + - `elementId`: 唯一元素标识符 + - `importPath`: 组件导入路径(用于区分组件使用位置和定义位置) + - `isUIComponent`: 是否是 UI 组件(components/ui 目录下的组件) + +- **`{prefix}-element-id`**: 唯一元素标识符,格式为 `文件路径:行号:列号_标签名#ID`(如果有 id 属性)或 `文件路径:行号:列号_标签名`(如果没有 id 属性)。用于列表项同步和元素识别。 + +- **`{prefix}-position`**: 位置信息,格式为 `行号:列号`(简化版位置信息) + +- **`{prefix}-static-content`**: 标记元素是否包含纯静态内容(值为 `'true'`),用于判断元素是否可以直接编辑。只有包含纯静态文本(无变量、表达式或子元素)的元素才会被标记。 + +- **`{prefix}-static-class`**: 标记 className 是否为纯静态字符串(值为 `'true'`),用于判断样式是否可以直接编辑。 + +- **`{prefix}-children-source`**: 子元素的源码位置信息,格式为 `文件路径:行号:列号`(用于透传组件追踪静态文本子元素的来源位置) + +### 优化说明 + +为了减少 DOM 体积,插件采用了紧凑的属性策略: +- **所有主要信息都包含在 `{prefix}-info` 中**,不再单独注入 `{prefix}-file`、`{prefix}-line`、`{prefix}-column`、`{prefix}-component`、`{prefix}-function`、`{prefix}-import` 等属性 +- 这些信息可以通过解析 `{prefix}-info` JSON 字符串获取 + +**注意**:这些属性使用可配置的前缀,默认值为 `data-xagi`。通过设置 `attributePrefix` 选项,可以避免与用户自定义的 `data-*` 属性冲突。例如,设置 `attributePrefix: 'data-appdev'` 后,属性名将变为 `data-appdev-info`、`data-appdev-element-id`、`data-appdev-static-content` 等。 + +## 浏览器集成 + +### 直接DOM访问 + +在浏览器开发者工具或外部应用中: + +```javascript +// 获取元素源码信息(注意:默认前缀已改为 data-xagi) +const element = document.querySelector('.my-element'); +// 从全局配置获取前缀,或使用默认值 'data-xagi' +const prefix = window.__APPDEV_DESIGN_MODE_CONFIG__?.attributePrefix || 'data-xagi'; +const sourceInfo = element.getAttribute(`${prefix}-info`); +const sourceData = JSON.parse(sourceInfo); + +console.log(sourceData); +// { +// fileName: 'src/App.tsx', +// lineNumber: 10, +// columnNumber: 5, +// elementType: 'div', +// componentName: 'App', +// functionName: 'App', +// elementId: 'src/App.tsx:10:5_div', +// importPath: undefined, // 如果是组件,会显示导入路径 +// isUIComponent: false // 是否是 UI 组件 +// } + +// 检查元素是否可以直接编辑(静态内容) +const isEditable = element.getAttribute(`${prefix}-static-content`) === 'true'; +if (isEditable) { + // 双击元素即可编辑 + element.addEventListener('dblclick', () => { + element.contentEditable = 'true'; + }); +} +``` + +### 桥接通信 + +使用内置桥接系统与设计模式UI交互: + +```javascript +// 监听元素选择变化 +window.addEventListener('message', (event) => { + const { type, payload } = event.data; + + if (type === 'SELECTION_CHANGED') { + console.log('选中的元素:', payload); + // payload 包含: tagName, id, className, innerText, source + } +}); + +// 发送样式更新命令 +window.parent.postMessage({ + type: 'UPDATE_STYLE', + payload: { + newClass: 'bg-blue-500 text-white p-4 rounded-lg' + } +}, '*'); + +// 发送内容更新命令 +window.parent.postMessage({ + type: 'UPDATE_CONTENT', + payload: { + newContent: '更新后的文本内容' + } +}, '*'); +``` + +## 配置选项 + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| `enabled` | boolean | `true` | 是否启用设计模式插件 | +| `enableInProduction` | boolean | `false` | 是否在生产环境启用,通常保持false | +| `attributePrefix` | string | `'data-xagi'` | 自定义源码映射属性的前缀 | +| `verbose` | boolean | `true` | 是否启用详细日志输出,便于调试 | +| `exclude` | string[] | `['node_modules', 'dist']` | 排除处理的文件模式和目录 | +| `include` | string[] | `['src/**/*.{ts,js,tsx,jsx}']` | 包含处理的文件模式,支持glob语法 | +| `enableBackup` | boolean | `false` | 是否启用备份功能,启用后会在修改源码前创建备份文件 | +| `enableHistory` | boolean | `false` | 是否启用历史记录功能,启用后会保存批量更新会话和历史记录 | + +### 配置示例 + +```typescript +// vite.config.ts +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode({ + enabled: true, + enableInProduction: false, + attributePrefix: 'data-appdev', + verbose: true, + exclude: ['node_modules', '.git', 'dist'], + include: ['src/**/*.{ts,js,tsx,jsx}', 'pages/**/*.{ts,js,tsx,jsx}'], + enableBackup: true, // 启用备份功能 + enableHistory: true // 启用历史记录功能 + }) + ] +}); +``` + +## 使用场景 + +### 1. 设计与开发协作 +- 设计师可以直接在浏览器中调整组件样式 +- 实时预览设计效果,所见即所得 +- 自动生成样式代码,无需手动编写 + +### 2. 组件库开发 +- 快速迭代组件样式和变体 +- 可视化调整组件参数和样式 +- 生成一致的设计语言 + +### 3. 原型开发 +- 快速构建和调整页面布局 +- 实时验证设计想法 +- 加速从原型到代码的转换 + +### 4. 团队协作 +- 统一设计系统和代码规范 +- 减少设计师和开发者的沟通成本 +- 提高开发效率和代码质量 + +## Tailwind CSS 预设 + +插件内置了常用的Tailwind CSS类预设: + +### 背景颜色预设 +- `bg-white` - 白色背景 +- `bg-slate-50` - 浅灰色背景 +- `bg-blue-50` - 浅蓝色背景 +- `bg-blue-100` - 中浅蓝色背景 +- `bg-blue-600` - 深蓝色背景 +- `bg-red-50` - 浅红色背景 +- `bg-green-50` - 浅绿色背景 + +### 文字颜色预设 +- `text-slate-900` - 深灰色文字 +- `text-slate-600` - 中灰色文字 +- `text-blue-600` - 蓝色文字 +- `text-white` - 白色文字 +- `text-red-600` - 红色文字 + +### 间距预设 +- `p-0` - 无内边距 +- `p-2` - 小内边距 +- `p-4` - 中等内边距 +- `p-6` - 大内边距 +- `p-8` - 特大内边距 +- `p-12` - 超大内边距 + +### 圆角预设 +- `rounded-none` - 无圆角 +- `rounded-sm` - 小圆角 +- `rounded-md` - 中等圆角 +- `rounded-lg` - 大圆角 +- `rounded-full` - 完全圆角 + +## 设计模式UI使用指南 + +### 启动设计模式 +1. 在页面右下角找到设计模式开关 +2. 点击开关启用设计模式 +3. 开始选择和编辑页面元素 + +### 编辑元素样式 +1. 点击页面中的任意元素 +2. 在右侧编辑面板中选择预设样式 +3. 实时预览样式效果 +4. 修改会自动保存到源码文件 +5. 对于列表项,修改一个实例会自动同步到所有相关实例 + +### 编辑元素内容 +1. **双击**包含静态文本的元素(只有标记了 `data-xagi-static-content="true"` 的元素才能编辑) +2. 进入 `contentEditable` 模式,直接编辑文本 +3. 失去焦点时自动保存到源码文件 +4. 对于列表项,编辑一个实例会自动同步到所有相关实例 + +### 重置修改 +- 点击“重置所有修改”按钮撤销所有更改 +- 页面将重新加载并恢复原始状态 +- 如果启用了备份功能(`enableBackup: true`),可以使用撤销功能恢复之前的版本 +- 如果启用了历史记录功能(`enableHistory: true`),可以查看和恢复历史更新会话 + +## 故障排除 + +### 常见问题 + +**Q: 设计模式UI没有显示** +A: 检查插件是否正确配置,确保在开发模式下运行 + +**Q: 点击元素没有反应** +A: 确认已经启用设计模式,检查浏览器控制台是否有错误信息 + +**Q: 样式修改没有保存** +A: 检查文件权限,确保有写入权限,或者检查开发服务器状态 + +**Q: 源码映射不准确** +A: 检查是否正确配置了include模式,确保源文件被正确处理 + +### 调试技巧 + +1. 启用verbose模式查看详细日志 +2. 检查浏览器开发者工具的网络面板,确认API请求状态 +3. 查看元素是否正确生成了源码映射属性 +4. 使用健康检查端点验证插件运行状态 + +```bash +# 检查插件状态 +curl http://localhost:3000/__appdev_design_mode/health +``` + +## 开发指南 + +### 本地开发 + +```bash +# 克隆项目 +git clone +cd vite-plugin-design-mode + +# 安装依赖 +npm install + +# 运行示例 +cd examples/advanced +npm install +npm run dev +``` + +### 自定义预设 + +如需自定义样式预设,可以修改客户端配置: + +```typescript +// 在 DesignModeUI.tsx 中自定义预设 +const CUSTOM_PRESETS = { + bgColors: [ + { label: 'Primary', value: 'bg-blue-600' }, + { label: 'Secondary', value: 'bg-gray-600' }, + // 添加更多自定义颜色 + ] +}; +``` + +## 贡献指南 + +欢迎提交Issue和Pull Request来改进这个插件! + +### 开发流程 +1. Fork项目 +2. 创建功能分支 +3. 提交更改 +4. 创建Pull Request + +### 代码规范 +- 使用TypeScript +- 遵循ESLint规则 +- 添加适当的测试 +- 更新相关文档 + +## 更新日志 + +完整变更记录见 [`CHANGELOG.md`](./CHANGELOG.md)。 + +### v1.1.0-beta.5 +- 修复 npm 发布产物中的 workspace 依赖协议问题,确保外部使用 pnpm/npm 安装可正常解析依赖 + +### v1.1.0-beta.4 +- 修复插件发布后客户端运行时入口解析,优先从安装包解析 React/Vue runtime +- 增加路径安全校验,避免通过路径前缀绕过项目根目录限制 +- 完成多包迁移后的测试回归修复,测试全量通过 + +### v1.1.0-beta.3 +- 完成项目向 `packages/*` 多包结构迁移 +- 保留并迁移 CLI 到 plugin 包内,发布产物包含 CLI +- 调整 examples/test 引用路径,移除根 `src` 目录实现 + +### v1.1.0-beta.2 +- 新增 React/Vue 3 动态识别支持(`framework: 'auto'`) +- CLI 支持在 Vue 3 + Vite 项目安装 +- 检测到 Vue 2(含 `@vitejs/plugin-vue2`)时快速失败并给出升级提示 +- 发布策略调整:`latest` 仅稳定版,预发布使用 `beta/next` + +### v1.0.36 +- 更新默认属性前缀为 `data-xagi` +- 新增批量更新功能(`/batch-update` 端点) +- 新增历史记录功能(`enableHistory` 配置项) +- 新增备份功能(`enableBackup` 配置项) +- 新增撤销/重做功能(需要启用备份和历史记录) +- 增强 `/get-source` 端点,返回更多元数据 +- 新增 `/validate-update` 端点用于验证更新请求 +- 支持 TypeScript/JavaScript/TSX/JSX 文件处理 +- 优化默认文件包含模式,支持更多文件类型 +- 改进CLI工具,支持一键安装和卸载 + +### v1.0.0 +- 初始版本发布 +- 实现基本的源码映射功能 +- 添加可视化设计模式UI +- 支持实时样式编辑和源码修改 +- 集成Tailwind CSS预设 + +## License + +MIT diff --git a/qiming-vite-plugin-design-mode/TESTING_CLI.md b/qiming-vite-plugin-design-mode/TESTING_CLI.md new file mode 100644 index 00000000..7949e08d --- /dev/null +++ b/qiming-vite-plugin-design-mode/TESTING_CLI.md @@ -0,0 +1,277 @@ +# CLI 功能本地测试指南 + +本文档说明如何在本地测试 `@xagi/vite-plugin-design-mode` 的 CLI 安装和卸载功能。 + +## 前置准备 + +1. 确保项目已安装依赖: +```bash +npm install +``` + +2. 构建项目(编译 TypeScript 到 dist 目录): +```bash +npm run build +``` + +这会编译所有源代码到 `dist/` 目录,包括 CLI 文件。 + +## 测试方法 + +### 方法 1: 使用 npm link(推荐) + +这是最接近真实使用场景的测试方法。 + +#### 步骤 1: 在项目根目录创建链接 + +```bash +# 在项目根目录执行 +npm link +``` + +这会在全局创建一个符号链接,指向当前项目。 + +#### 步骤 2: 在测试项目中链接插件 + +```bash +# 进入测试项目目录(例如 examples/demo) +cd examples/demo + +# 链接插件 +npm link @xagi/vite-plugin-design-mode +``` + +#### 步骤 3: 测试安装命令 + +```bash +# 在测试项目目录执行 +npx @xagi/vite-plugin-design-mode install +``` + +#### 步骤 4: 测试卸载命令 + +```bash +# 在测试项目目录执行 +npx @xagi/vite-plugin-design-mode uninstall +``` + +### 方法 2: 直接使用 node 运行编译后的文件 + +如果不想使用 npm link,可以直接运行编译后的 CLI 文件。 + +#### 步骤 1: 构建项目 + +```bash +npm run build +``` + +#### 步骤 2: 在测试项目中运行 CLI + +```bash +# 进入测试项目目录 +cd examples/demo + +# 直接运行编译后的 CLI 文件 +node ../../packages/plugin/dist/cli/index.js install +# 或 +node ../../packages/plugin/dist/cli/index.js uninstall +``` + +### 方法 3: 使用 tsx 直接运行 TypeScript 文件(开发调试) + +在开发过程中,可以使用 `tsx` 直接运行 TypeScript 文件,无需先编译。 + +#### 安装 tsx(如果还没有) + +```bash +npm install -g tsx +# 或 +npm install --save-dev tsx +``` + +#### 运行 CLI + +```bash +# 在项目根目录 +tsx packages/plugin/src/cli/index.ts install + +# 或指定工作目录为测试项目 +cd examples/demo +tsx ../../packages/plugin/src/cli/index.ts install +``` + +## 完整测试流程示例 + +以下是在 `examples/demo` 项目中完整测试安装和卸载的流程: + +### 1. 准备测试环境 + +```bash +# 在项目根目录构建 +npm run build + +# 进入测试项目 +cd examples/demo + +# 备份当前的 vite.config.ts(可选) +cp vite.config.ts vite.config.ts.backup + +# 备份 package.json(可选) +cp package.json package.json.backup +``` + +### 2. 测试安装功能 + +```bash +# 方法 A: 使用 npm link +npm link @xagi/vite-plugin-design-mode +npx @xagi/vite-plugin-design-mode install + +# 方法 B: 直接运行编译后的文件 +node ../../packages/plugin/dist/cli/index.js install + +# 方法 C: 使用 tsx(开发时) +tsx ../../packages/plugin/src/cli/index.ts install +``` + +**验证安装结果:** + +1. 检查 `package.json` 中是否添加了 `@xagi/vite-plugin-design-mode` 依赖 +2. 检查 `vite.config.ts` 中是否添加了: + - `import appdevDesignMode from '@xagi/vite-plugin-design-mode';` + - `appdevDesignMode()` 在 plugins 数组中 + +### 3. 测试卸载功能 + +```bash +# 使用相同的方法运行卸载命令 +npx @xagi/vite-plugin-design-mode uninstall +# 或 +node ../../packages/plugin/dist/cli/index.js uninstall +# 或 +tsx ../../packages/plugin/src/cli/index.ts uninstall +``` + +**验证卸载结果:** + +1. 检查 `package.json` 中是否移除了 `@xagi/vite-plugin-design-mode` 依赖 +2. 检查 `vite.config.ts` 中是否移除了: + - import 语句 + - `appdevDesignMode()` 调用 + +### 4. 恢复测试环境(可选) + +```bash +# 如果需要恢复到原始状态 +cp vite.config.ts.backup vite.config.ts +cp package.json.backup package.json +``` + +## 测试不同场景 + +### 场景 1: 全新安装(插件未安装) + +1. 确保测试项目的 `package.json` 中没有 `@xagi/vite-plugin-design-mode` +2. 确保 `vite.config.ts` 中没有相关配置 +3. 运行 `install` 命令 +4. 验证插件已安装且配置已添加 + +### 场景 2: 升级已安装的插件 + +1. 先在测试项目中安装旧版本(如果有) +2. 运行 `install` 命令 +3. 验证插件已升级到最新版本 + +### 场景 3: 卸载已安装的插件 + +1. 确保插件已安装 +2. 运行 `uninstall` 命令 +3. 验证插件和配置都已移除 + +### 场景 4: 测试不同的包管理器 + +在不同使用不同包管理器的项目中测试: + +```bash +# npm 项目 +cd examples/basic +node ../../packages/plugin/dist/cli/index.js install + +# pnpm 项目 +cd examples/demo # 如果有 pnpm-lock.yaml +node ../../packages/plugin/dist/cli/index.js install + +# yarn 项目(如果有 yarn.lock) +node ../../packages/plugin/dist/cli/index.js install +``` + +## 调试技巧 + +### 查看详细输出 + +CLI 工具会输出详细的执行信息,包括: +- 检测到的包管理器 +- 插件安装状态 +- 执行的命令 +- 配置文件修改情况 + +### 检查编译后的文件 + +确保 CLI 文件已正确编译: + +```bash +# 检查 dist/cli 目录 +ls -la dist/cli/ + +# 应该看到: +# - index.js +# - install.js +# - uninstall.js +``` + +### 常见问题排查 + +1. **命令未找到** + - 确保已运行 `npm run build` + - 检查 `packages/plugin/dist/cli/index.js` 是否存在 + - 如果使用 npm link,确保已正确链接 + +2. **权限问题** + - 确保有写入 `package.json` 和 `vite.config.ts` 的权限 + +3. **配置文件格式问题** + - CLI 会尝试处理不同的代码风格,但某些复杂格式可能需要手动调整 + +## 自动化测试脚本(可选) + +可以创建一个测试脚本来自动化测试流程: + +```bash +#!/bin/bash +# test-cli.sh + +echo "构建项目..." +npm run build + +echo "进入测试项目..." +cd examples/demo + +echo "测试安装..." +node ../../packages/plugin/dist/cli/index.js install + +echo "等待 2 秒..." +sleep 2 + +echo "测试卸载..." +node ../../packages/plugin/dist/cli/index.js uninstall + +echo "测试完成!" +``` + +保存为 `test-cli.sh`,然后运行: + +```bash +chmod +x test-cli.sh +./test-cli.sh +``` + diff --git a/qiming-vite-plugin-design-mode/examples/advanced/index.html b/qiming-vite-plugin-design-mode/examples/advanced/index.html new file mode 100644 index 00000000..6bced506 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/index.html @@ -0,0 +1,14 @@ + + + + + + + Advanced AppDev Design Mode Example + + + +
+ + + diff --git a/qiming-vite-plugin-design-mode/examples/advanced/package-lock.json b/qiming-vite-plugin-design-mode/examples/advanced/package-lock.json new file mode 100644 index 00000000..58f938fe --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/package-lock.json @@ -0,0 +1,3148 @@ +{ + "name": "vite-plugin-appdev-design-mode-advanced-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite-plugin-appdev-design-mode-advanced-example", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "clsx": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", + "tailwind-merge": "^3.4.0", + "zustand": "^4.3.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.17", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.22", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", + "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", + "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", + "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", + "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", + "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", + "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", + "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", + "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", + "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", + "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", + "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", + "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", + "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.6.0", + "@emnapi/runtime": "^1.6.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", + "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", + "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz", + "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", + "postcss": "^8.4.41", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", + "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz", + "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1", + "react-router": "6.30.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", + "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/package.json b/qiming-vite-plugin-design-mode/examples/advanced/package.json new file mode 100644 index 00000000..9a9abc3b --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/package.json @@ -0,0 +1,34 @@ +{ + "name": "vite-plugin-appdev-design-mode-advanced-example", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "clsx": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", + "tailwind-merge": "^3.4.0", + "zustand": "^4.3.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.17", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.22", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/postcss.config.js b/qiming-vite-plugin-design-mode/examples/advanced/postcss.config.js new file mode 100644 index 00000000..1c878468 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx new file mode 100644 index 00000000..42bfa2fe --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/App.tsx @@ -0,0 +1,119 @@ +import React, { Suspense } from 'react'; +import { Routes, Route, Link, useLocation } from 'react-router-dom'; +import { useAppStore } from './store/appStore'; + +// 页面组件 +const Dashboard = React.lazy(() => import('./pages/Dashboard')); +const ComponentShowcase = React.lazy(() => import('./pages/ComponentShowcase')); +const InteractiveDemo = React.lazy(() => import('./pages/InteractiveDemo')); +const ConfigurationGuide = React.lazy(() => import('./pages/ConfigurationGuide')); + +// 通用组件 +const LoadingSpinner: React.FC = () => ( +
+
+ +
+); + +const Navigation: React.FC = () => { + const location = useLocation(); + const { theme, toggleTheme } = useAppStore(); + + const navItems = [ + { path: '/', label: 'Dashboard', icon: '🏠' }, + { path: '/showcase', label: 'Component Showcase', icon: '🎨' }, + { path: '/interactive', label: 'Interactive Demo', icon: '🎯' }, + { path: '/config', label: 'Configuration', icon: '⚙️' } + ]; + + return ( + + ); +}; + +const App: React.FC = () => { + const { isLoading, error } = useAppStore(); + + if (error) { + return ( +
+

Application Error

+

{error}

+ +
+ ); + } + + return ( +
+ + +
+ }> + + } /> + } /> + } /> + } /> + + +
+ +
+

Built with Vite + React + TypeScript + AppDev Design Mode Plugin

+
+
+ ); +}; + +export default App; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx new file mode 100644 index 00000000..64a1e04d --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { cn } from '../utils/cn'; + +export interface DemoElementProps { + element: { + id: string; + tag: string; + content: string; + className: string; + }; + isDesignMode: boolean; + onSelect: (element: HTMLElement) => void; +} + +export const DemoElement: React.FC = ({ element, isDesignMode, onSelect }) => { + const Tag = element.tag as any; + + return ( + { + if (isDesignMode) { + e.preventDefault(); + onSelect(e.currentTarget as HTMLElement); + } + }} + data-element={element.id} + > + {element.content} + + ); +}; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/index.css b/qiming-vite-plugin-design-mode/examples/advanced/src/index.css new file mode 100644 index 00000000..ecb18830 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/index.css @@ -0,0 +1,957 @@ +@import "tailwindcss"; + +/* Advanced AppDev Design Mode Example Styles */ + +/* CSS Variables for theming */ +:root { + /* Light theme colors */ + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-tertiary: #f1f5f9; + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-muted: #94a3b8; + --border-color: #e2e8f0; + --border-hover: #cbd5e1; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --accent-primary: #3b82f6; + --accent-secondary: #8b5cf6; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 3rem; + + /* Typography */ + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'Fira Code', 'Monaco', 'Cascadia Code', monospace; + + /* Border radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +/* Dark theme */ +.dark { + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-tertiary: #334155; + --text-primary: #f8fafc; + --text-secondary: #cbd5e1; + --text-muted: #94a3b8; + --border-color: #334155; + --border-hover: #475569; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4); +} + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + line-height: 1.6; + color: var(--text-primary); + background: var(--bg-primary); + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* App layout */ +.app { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Navigation */ +.nav { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md) var(--spacing-xl); + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + position: sticky; + top: 0; + z-index: 100; + backdrop-filter: blur(10px); +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.nav-brand h1 { + font-size: 1.25rem; + font-weight: 700; + color: var(--text-primary); +} + +.nav-brand .version { + font-size: 0.75rem; + color: var(--text-muted); + background: var(--bg-tertiary); + padding: 0.25rem 0.5rem; + border-radius: var(--radius-sm); +} + +.nav-links { + display: flex; + gap: var(--spacing-lg); +} + +.nav-link { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + color: var(--text-secondary); + text-decoration: none; + border-radius: var(--radius-md); + transition: all 0.2s ease; +} + +.nav-link:hover { + color: var(--text-primary); + background: var(--bg-tertiary); +} + +.nav-link.active { + color: var(--accent-primary); + background: rgba(59, 130, 246, 0.1); +} + +.nav-link .icon { + font-size: 1.1rem; +} + +.theme-toggle { + background: none; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: var(--spacing-sm); + cursor: pointer; + font-size: 1.2rem; + transition: all 0.2s ease; +} + +.theme-toggle:hover { + background: var(--bg-tertiary); + border-color: var(--border-hover); +} + +/* Main content */ +.main-content { + flex: 1; + padding: var(--spacing-xl); + max-width: 1200px; + margin: 0 auto; + width: 100%; +} + +/* Dashboard specific styles */ +.dashboard { + display: flex; + flex-direction: column; + gap: var(--spacing-2xl); +} + +/* Welcome section */ +.welcome-section { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-xl); + background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); + border-radius: var(--radius-xl); + color: white; +} + +.welcome-content h1 { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: var(--spacing-sm); +} + +.welcome-content p { + font-size: 1.2rem; + opacity: 0.9; +} + +.status-indicators { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.status-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); + background: rgba(255, 255, 255, 0.1); + padding: var(--spacing-sm) var(--spacing-md); + border-radius: var(--radius-md); + backdrop-filter: blur(10px); +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--success); + animation: pulse 2s infinite; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +/* Sections */ +.section-title { + font-size: 1.875rem; + font-weight: 700; + color: var(--text-primary); + margin-bottom: var(--spacing-lg); +} + +/* Metrics section */ +.metrics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--spacing-lg); +} + +.metric-card { + background: var(--bg-secondary); + padding: var(--spacing-xl); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); + transition: all 0.2s ease; +} + +.metric-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.metric-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-md); +} + +.metric-header h3 { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.metric-icon { + font-size: 1.5rem; +} + +.metric-value { + font-size: 2.25rem; + font-weight: 700; + color: var(--text-primary); + margin-bottom: var(--spacing-sm); +} + +.metric-change { + font-size: 0.875rem; + font-weight: 600; + display: flex; + align-items: center; + gap: var(--spacing-xs); +} + +.metric-change.up { + color: var(--success); +} + +.metric-change.down { + color: var(--danger); +} + +.metric-change.neutral { + color: var(--text-muted); +} + +/* Feature demonstrations */ +.features-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--spacing-xl); +} + +.feature-demo { + background: var(--bg-secondary); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); + overflow: hidden; +} + +.feature-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-lg); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.feature-header h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-primary); +} + +.feature-tabs { + display: flex; + background: var(--bg-primary); + border-radius: var(--radius-md); + padding: 0.25rem; +} + +.tab { + padding: var(--spacing-sm) var(--spacing-md); + border: none; + background: none; + color: var(--text-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.2s ease; +} + +.tab.active { + background: var(--accent-primary); + color: white; +} + +.feature-description { + padding: var(--spacing-lg); + color: var(--text-secondary); + border-bottom: 1px solid var(--border-color); +} + +.feature-content { + min-height: 200px; +} + +.demo-container, +.code-container { + padding: var(--spacing-lg); +} + +.code-container { + background: var(--bg-tertiary); + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1.6; +} + +/* Component registry */ +.components-section { + margin-top: var(--spacing-2xl); +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-lg); +} + +.search-container { + position: relative; +} + +.search-input { + padding: var(--spacing-sm) var(--spacing-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-secondary); + color: var(--text-primary); + width: 300px; + transition: border-color 0.2s ease; +} + +.search-input:focus { + outline: none; + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.components-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--spacing-lg); +} + +.component-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + transition: all 0.2s ease; +} + +.component-card:hover { + box-shadow: var(--shadow-md); + border-color: var(--border-hover); +} + +.component-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-md); +} + +.component-header h4 { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-primary); +} + +.component-type { + font-size: 0.75rem; + color: var(--text-muted); + background: var(--bg-tertiary); + padding: 0.25rem 0.5rem; + border-radius: var(--radius-sm); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.props-preview { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs); +} + +.prop-tag { + font-size: 0.75rem; + color: var(--text-secondary); + background: var(--bg-tertiary); + padding: 0.25rem 0.5rem; + border-radius: var(--radius-sm); +} + +/* Demo specific styles */ +.source-map-demo { + text-align: center; + padding: var(--spacing-lg); +} + +.demo-element { + display: inline-block; + padding: var(--spacing-md) var(--spacing-lg); + background: var(--accent-primary); + color: white; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; +} + +.demo-element:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.component-tree { + padding: var(--spacing-lg); + font-family: var(--font-mono); + font-size: 0.875rem; +} + +.tree-node { + margin: var(--spacing-sm) 0; +} + +.tree-node.root>.node-label { + font-weight: 600; + color: var(--accent-primary); +} + +.children { + margin-left: var(--spacing-lg); + border-left: 2px solid var(--border-color); + padding-left: var(--spacing-md); +} + +.node-label { + color: var(--text-primary); +} + +.live-edit-demo { + padding: var(--spacing-lg); +} + +.editable-element { + background: var(--bg-tertiary); + padding: var(--spacing-lg); + border-radius: var(--radius-md); + text-align: center; + margin-bottom: var(--spacing-lg); + cursor: pointer; + transition: all 0.2s ease; +} + +.editable-element:hover { + background: var(--accent-primary); + color: white; +} + +.style-controls { + display: flex; + gap: var(--spacing-sm); + justify-content: center; + flex-wrap: wrap; +} + +.style-btn { + padding: var(--spacing-sm) var(--spacing-md); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: 500; + transition: all 0.2s ease; +} + +.style-btn.primary { + background: var(--accent-primary); + color: white; +} + +.style-btn.secondary { + background: var(--accent-secondary); + color: white; +} + +.style-btn.danger { + background: var(--danger); + color: white; +} + +.style-btn:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} + +/* Footer */ +.app-footer { + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); + padding: var(--spacing-xl); + text-align: center; + color: var(--text-secondary); + margin-top: auto; +} + +/* App error state */ +.app-error { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 50vh; + padding: var(--spacing-xl); + text-align: center; +} + +.app-error h1 { + color: var(--danger); + margin-bottom: var(--spacing-md); +} + +.app-error button { + margin-top: var(--spacing-lg); + padding: var(--spacing-md) var(--spacing-lg); + background: var(--accent-primary); + color: white; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: 600; + transition: background 0.2s ease; +} + +.app-error button:hover { + background: var(--accent-secondary); +} + +/* Responsive design */ +@media (max-width: 768px) { + .nav { + flex-direction: column; + gap: var(--spacing-md); + } + + .nav-links { + flex-wrap: wrap; + justify-content: center; + } + + .main-content { + padding: var(--spacing-md); + } + + .welcome-section { + flex-direction: column; + text-align: center; + } + + .welcome-content h1 { + font-size: 2rem; + } + + .section-header { + flex-direction: column; + gap: var(--spacing-md); + align-items: stretch; + } + + .search-input { + width: 100%; + } + + .metrics-grid { + grid-template-columns: 1fr; + } + + .components-grid { + grid-template-columns: 1fr; + } +} + +/* Design mode specific styles */ +[data-design-mode-enabled="true"] [data-source-info] { + position: relative; +} + +[data-design-mode-enabled="true"] [data-source-info]:hover { + outline: 2px dashed var(--accent-primary) !important; + outline-offset: 2px !important; + cursor: pointer !important; +} + +/* Print styles */ +@media print { + + .nav, + .app-footer { + display: none; + } + + .main-content { + padding: 0; + } +} + +/* Interactive Demo Styles */ +.interactive-demo { + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +.demo-header { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); + background: var(--bg-secondary); + padding: var(--spacing-xl); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); +} + +.demo-controls { + display: flex; + justify-content: space-between; + align-items: center; +} + +.design-mode-toggle { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-md) var(--spacing-lg); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.design-mode-toggle.active { + background: var(--accent-primary); + color: white; + border-color: var(--accent-primary); +} + +.element-editor { + background: var(--bg-primary); + padding: var(--spacing-lg); + border-radius: var(--radius-md); + border: 1px solid var(--border-color); + animation: slideDown 0.3s ease; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.editor-controls { + display: flex; + gap: var(--spacing-xl); + margin-top: var(--spacing-md); + flex-wrap: wrap; +} + +.control-group { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.demo-workspace { + display: grid; + grid-template-columns: 1fr 300px; + gap: var(--spacing-xl); + min-height: 500px; +} + +.preview-area { + background: var(--bg-secondary); + border: 2px dashed var(--border-color); + border-radius: var(--radius-lg); + padding: var(--spacing-xl); + position: relative; + transition: all 0.3s ease; +} + +.preview-area.design-mode-active { + border-color: var(--accent-primary); + background: rgba(59, 130, 246, 0.05); +} + +.preview-content { + background: var(--bg-primary); + padding: var(--spacing-2xl); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + min-height: 100%; +} + +.modifications-panel { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.panel-header { + padding: var(--spacing-md) var(--spacing-lg); + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; +} + +.reset-btn { + font-size: 0.875rem; + color: var(--danger); + background: none; + border: none; + cursor: pointer; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-sm); +} + +.reset-btn:hover:not(:disabled) { + background: rgba(239, 68, 68, 0.1); +} + +.reset-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.modifications-list { + flex: 1; + overflow-y: auto; + padding: var(--spacing-md); +} + +.modification-item { + background: var(--bg-primary); + padding: var(--spacing-md); + border-radius: var(--radius-md); + border: 1px solid var(--border-color); + margin-bottom: var(--spacing-sm); + font-size: 0.875rem; +} + +.mod-header { + display: flex; + justify-content: space-between; + margin-bottom: var(--spacing-xs); + font-weight: 600; +} + +.property { + color: var(--text-secondary); + font-family: var(--font-mono); +} + +.mod-values { + display: flex; + justify-content: space-between; + align-items: center; + color: var(--text-secondary); +} + +.value-change { + display: flex; + align-items: center; + gap: var(--spacing-xs); + font-family: var(--font-mono); +} + +.arrow { + color: var(--text-muted); +} + +.new-value { + color: var(--accent-primary); +} + +.mod-time { + font-size: 0.75rem; + color: var(--text-muted); +} + +.demo-footer { + background: var(--bg-secondary); + padding: var(--spacing-lg); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); +} + +.instructions ol { + margin-left: var(--spacing-xl); + margin-top: var(--spacing-md); + color: var(--text-secondary); +} + +.instructions li { + margin-bottom: var(--spacing-xs); +} + +/* Demo Elements */ +.demo-title { + font-size: 2.5rem; + color: var(--text-primary); + margin-bottom: var(--spacing-md); +} + +.demo-description { + font-size: 1.125rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-xl); + max-width: 600px; +} + +.demo-button { + padding: var(--spacing-md) var(--spacing-xl); + border-radius: var(--radius-md); + border: none; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.demo-button.primary { + background: var(--accent-primary); + color: white; +} + +.demo-button:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.demo-card { + background: white; + padding: var(--spacing-xl); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + margin-top: var(--spacing-xl); + border: 1px solid var(--border-color); +} + +/* Selected state for design mode */ +.selected { + outline: 2px solid var(--accent-primary) !important; + outline-offset: 2px; + position: relative; +} + +.selected::after { + content: 'Selected'; + position: absolute; + top: -24px; + left: 0; + background: var(--accent-primary); + color: white; + font-size: 0.75rem; + padding: 2px 6px; + border-radius: 4px; + pointer-events: none; +} \ No newline at end of file diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx new file mode 100644 index 00000000..8ec9ad88 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; +import './index.css'; + +// 开发模式下的额外日志 +if (import.meta.env.DEV) { + console.log('🚀 Advanced AppDev Design Mode Example'); + console.log('🔧 Plugin Configuration:', { + enabled: true, + prefix: 'design-mode', + verbose: true, + typescript: true, + react: true, + routing: true, + stateManagement: true + }); +} + +// 错误边界组件 +class ErrorBoundary extends React.Component< + { children: React.ReactNode }, + { hasError: boolean } +> { + constructor(props: { children: React.ReactNode }) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): { hasError: boolean } { + return { hasError: true }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Error caught by boundary:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong.

+

Please refresh the page to try again.

+
+ ); + } + + return this.props.children; + } +} + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + +); diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx new file mode 100644 index 00000000..d7755cca --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx @@ -0,0 +1,197 @@ +import React, { useState } from 'react'; + +const ComponentShowcase: React.FC = () => { + const [activeCategory, setActiveCategory] = useState('buttons'); + const [selectedComponent, setSelectedComponent] = useState(null); + + const categories = [ + { id: 'buttons', label: 'Buttons', icon: '🔘' }, + { id: 'forms', label: 'Forms', icon: '📝' }, + { id: 'cards', label: 'Cards', icon: '🃏' }, + { id: 'navigation', label: 'Navigation', icon: '🧭' } + ]; + + const components = { + buttons: [ + { + id: 'primary-btn', + name: 'Primary Button', + description: 'Main action button with primary styling', + component: ( + + ), + code: `` + }, + { + id: 'secondary-btn', + name: 'Secondary Button', + description: 'Secondary action button', + component: ( + + ), + code: `` + }, + { + id: 'danger-btn', + name: 'Danger Button', + description: 'Destructive action button', + component: ( + + ), + code: `` + } + ], + forms: [ + { + id: 'input-field', + name: 'Input Field', + description: 'Standard text input with label', + component: ( +
+ + +
+ ), + code: `
+ + +
` + }, + { + id: 'checkbox-group', + name: 'Checkbox Group', + description: 'Multiple choice checkboxes', + component: ( +
+ +
+ + + +
+
+ ), + code: `
+ +
+ + + +
+
` + } + ], + cards: [ + { + id: 'info-card', + name: 'Info Card', + description: 'Card with title, content and actions', + component: ( +
+
+

Card Title

+
+
+

This is a sample card component with some descriptive content.

+
+
+ +
+
+ ), + code: `
+
+

Card Title

+
+
+

This is a sample card component with some descriptive content.

+
+
+ +
+
` + } + ], + navigation: [ + { + id: 'breadcrumb', + name: 'Breadcrumb', + description: 'Navigation breadcrumb trail', + component: ( + + ), + code: `` + } + ] + }; + + return ( +
+
+

Component Showcase

+

Explore various UI components and their source mappings

+
+ +
+ + +
+
+ {components[activeCategory as keyof typeof components]?.map(component => ( +
setSelectedComponent( + selectedComponent === component.id ? null : component.id + )} + > +
+ {component.component} +
+
+

{component.name}

+

{component.description}

+
+ {selectedComponent === component.id && ( +
+
{component.code}
+
+ )} +
+ ))} +
+
+
+
+ ); +}; + +export default ComponentShowcase; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx new file mode 100644 index 00000000..2b8fed0d --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx @@ -0,0 +1,292 @@ +import React, { useState } from 'react'; + +const ConfigurationGuide: React.FC = () => { + const [activeSection, setActiveSection] = useState('basic'); + + const sections = [ + { id: 'basic', label: 'Basic Setup', icon: '⚙️' }, + { id: 'options', label: 'Configuration Options', icon: '🔧' }, + { id: 'advanced', label: 'Advanced Usage', icon: '🚀' }, + { id: 'troubleshooting', label: 'Troubleshooting', icon: '🛠️' } + ]; + + const configExamples = { + basic: { + title: 'Basic Setup', + description: 'Get started with the plugin in minutes', + code: `// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from 'vite-plugin-appdev-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode() // Basic configuration + ] +});`, + explanation: 'This is the minimal configuration needed to get started. The plugin will use all default options.' + }, + options: { + title: 'Configuration Options', + description: 'Customize the plugin behavior with various options', + code: `// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from 'vite-plugin-appdev-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode({ + // Enable/disable the plugin + enabled: true, + + // Only enable in development + enableInProduction: false, + + // Custom attribute prefix + attributePrefix: 'design-mode', + + // Enable verbose logging + verbose: true, + + // File patterns to exclude + exclude: [ + 'node_modules', + '.git', + 'dist', + '**/*.test.{ts,tsx,js,jsx}' + ], + + // File patterns to include + include: [ + 'src/**/*.{ts,tsx,js,jsx}', + 'components/**/*.{ts,tsx,js,jsx}' + ] + }) + ] +});`, + explanation: 'This configuration shows all available options and their default values.' + }, + advanced: { + title: 'Advanced Usage', + description: 'Advanced patterns and integration techniques', + code: `// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from 'vite-plugin-appdev-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + // Multiple configurations for different environments + appdevDesignMode({ + enabled: true, + attributePrefix: 'dev-mode', + verbose: process.env.NODE_ENV === 'development', + + // Conditional inclusion based on environment + include: process.env.NODE_ENV === 'development' + ? ['src/**/*.{ts,tsx,js,jsx}'] + : [], // Disable in production + + // Custom exclude patterns + exclude: [ + 'node_modules', + '.git', + 'dist', + 'build', + '**/*.test.{ts,tsx,js,jsx}', + '**/__tests__/**', + 'stories/**', + '**/*.stories.{ts,tsx}' + ] + }) + ], + + // Server configuration for API endpoints + server: { + port: 5173, + host: true, + middlewareMode: false + } +}); + +// Environment-specific configuration +const isDevelopment = process.env.NODE_ENV === 'development'; + +export default defineConfig({ + plugins: isDevelopment ? [ + react(), + appdevDesignMode({ + verbose: true, + attributePrefix: 'dev' + }) + ] : [react()] +});`, + explanation: 'This example shows how to configure the plugin differently based on environment and use cases.' + }, + troubleshooting: { + title: 'Troubleshooting', + description: 'Common issues and their solutions', + code: `# Common Issues and Solutions + +## Plugin not injecting source information + +### Problem: Elements don't have data attributes +### Solution: Check file inclusion patterns +\`\`\`js +appdevDesignMode({ + include: ['src/**/*.{ts,tsx,js,jsx}'] // Make sure this matches your project structure +}) +\`\`\` + +## Performance issues + +### Problem: Plugin slowing down development server +### Solution: Exclude unnecessary files +\`\`\`js +appdevDesignMode({ + exclude: [ + 'node_modules', + '**/*.test.{ts,tsx,js,jsx}', + '**/__tests__/**', + 'stories/**' + ] +}) +\`\`\` + +## TypeScript errors + +### Problem: Cannot find module '@babel/core' +### Solution: Install peer dependencies +\`\`\`bash +npm install --save-dev @babel/core @babel/traverse @babel/types +\`\`\` + +## Custom configuration not working + +### Problem: Plugin using default configuration +### Solution: Check import path and plugin options +\`\`\`js +// Correct import +import appdevDesignMode from 'vite-plugin-appdev-design-mode'; + +// Make sure options are passed correctly +appdevDesignMode({ + enabled: true, // This should not be undefined + attributePrefix: 'custom' +}) +\`\`\` + +## API endpoints not accessible + +### Problem: Cannot access plugin API endpoints +### Solution: Check server configuration and CORS +\`\`\`js +export default defineConfig({ + server: { + cors: true, // Enable CORS for API access + headers: { + 'Access-Control-Allow-Origin': '*' + } + } +}) +\`\`\``, + explanation: 'This section covers common problems developers encounter and their solutions.' + } + }; + + return ( +
+
+

Configuration Guide

+

Learn how to configure and use the AppDev Design Mode plugin effectively

+
+ +
+ + +
+
+
+

+ {configExamples[activeSection as keyof typeof configExamples].title} +

+

+ {configExamples[activeSection as keyof typeof configExamples].description} +

+
+ +
+
+
+

Configuration

+ +
+
+                  {configExamples[activeSection as keyof typeof configExamples].code}
+                
+
+ +
+

Explanation

+

{configExamples[activeSection as keyof typeof configExamples].explanation}

+
+
+
+
+
+ + +
+ ); +}; + +export default ConfigurationGuide; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx new file mode 100644 index 00000000..14070055 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx @@ -0,0 +1,242 @@ +import React, { useState } from 'react'; +import { useAppStore } from '../store/appStore'; + +interface MetricCardProps { + title: string; + value: string | number; + change?: string; + trend?: 'up' | 'down' | 'neutral'; + icon: string; +} + +const MetricCard: React.FC = ({ title, value, change, trend, icon }) => ( +
+
+

{title}

+ {icon} +
+
{value}
+ {change && ( +
+ {trend === 'up' && '↗️'} + {trend === 'down' && '↘️'} + {trend === 'neutral' && '➡️'} + {change} +
+ )} +
+); + +interface FeatureDemoProps { + title: string; + description: string; + code: string; + demo: React.ReactNode; +} + +const FeatureDemo: React.FC = ({ title, description, code, demo }) => { + const [showCode, setShowCode] = useState(false); + const [activeTab, setActiveTab] = useState<'demo' | 'code'>('demo'); + + return ( +
+
+

{title}

+
+ + +
+
+ +

{description}

+ +
+ {activeTab === 'demo' ? ( +
+ {demo} +
+ ) : ( +
+            {code}
+          
+ )} +
+
+ ); +}; + +const Dashboard: React.FC = () => { + const { theme, user, components } = useAppStore(); + const [searchTerm, setSearchTerm] = useState(''); + + const metrics = [ + { title: 'Total Components', value: components.length, change: '+2', trend: 'up' as const, icon: '🧩' }, + { title: 'Active Elements', value: 24, change: '+5', trend: 'up' as const, icon: '⚡' }, + { title: 'Design Mode Sessions', value: 156, change: '+12', trend: 'up' as const, icon: '🎨' }, + { title: 'Code Quality Score', value: '94%', change: '+2%', trend: 'up' as const, icon: '📊' } + ]; + + const componentList = components.filter(comp => + comp.name.toLowerCase().includes(searchTerm.toLowerCase()) || + comp.type.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( +
+ {/* Welcome Section */} +
+
+

+ Welcome back, {user.name}! +

+

+ Here's what's happening with your design mode plugin today. +

+
+
+
+ + Plugin Active +
+
+ {theme === 'light' ? '☀️' : '🌙'} + {theme.charAt(0).toUpperCase() + theme.slice(1)} Mode +
+
+
+ + {/* Metrics Grid */} +
+

System Metrics

+
+ {metrics.map((metric, index) => ( + + ))} +
+
+ + {/* Feature Demonstrations */} +
+

Plugin Features

+
+ + Content here +
`} + demo={ +
+
+ Hover over me to see source mapping info +
+
+ } + /> + + +
+
App
+
+
+
Header
+
+
+
MainContent
+
+
+
+
+ } + /> + + { + await fetch('/__modify_element_source', { + method: 'POST', + body: JSON.stringify({ elementId, newStyles }) + }); +};`} + demo={ +
+
+ Click to edit styles +
+
+ + + +
+
+ } + /> +
+ + + {/* Component Registry */} +
+
+

Component Registry

+
+ setSearchTerm(e.target.value)} + className="search-input" + data-element="input" + /> +
+
+ +
+ {componentList.map((component) => ( +
+
+

{component.name}

+ {component.type} +
+
+ ID: {component.id} +
+ {Object.entries(component.props).slice(0, 2).map(([key, value]) => ( + + {key}: {String(value)} + + ))} +
+
+
+ ))} +
+
+ + ); +}; + +export default Dashboard; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx new file mode 100644 index 00000000..bfbe257e --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { DemoElement } from '../components/DemoElement'; + +const InteractiveDemo: React.FC = () => { + // No local state or context needed - Design Mode is handled globally by the plugin + + const demoElements = [ + { + id: 'hero-title', + tag: 'h1', + content: 'Interactive Design Mode Demo', + className: 'text-4xl font-bold text-slate-900 mb-4' + }, + { + id: 'hero-description', + tag: 'p', + content: 'Click on elements in design mode to modify their properties in real-time', + className: 'text-lg text-slate-600 mb-8 max-w-2xl' + }, + { + id: 'demo-button', + tag: 'button', + content: 'Click Me', + className: 'px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors shadow-sm' + }, + { + id: 'demo-card', + tag: 'div', + content: 'Sample Card Content', + className: 'mt-8 p-6 bg-white rounded-xl shadow-md border border-slate-200' + } + ]; + + return ( +
+
+
+

Interactive Design Mode

+

+ Design Mode is now injected globally. Use the floating toggle in the bottom right to enable it. +

+
+
+ +
+
+
+ {demoElements.map(element => ( + {}} // Handled globally + /> + ))} +
+
+
+ +
+
+

Instructions:

+
    +
  1. Toggle Design Mode using the floating button
  2. +
  3. Click on any element to select and edit it
  4. +
  5. Modifications are applied globally
  6. +
+
+
+
+ ); +}; + +export default InteractiveDemo; diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts b/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts new file mode 100644 index 00000000..e9690af7 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts @@ -0,0 +1,173 @@ +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; + +interface AppState { + // UI状态 + theme: 'light' | 'dark'; + isLoading: boolean; + error: string | null; + + // 应用状态 + currentPage: string; + user: { + name: string; + email: string; + preferences: { + showTooltips: boolean; + animations: boolean; + compactMode: boolean; + }; + }; + + // 组件状态 + components: Array<{ + id: string; + name: string; + type: string; + props: Record; + children?: string[]; + parent?: string; + }>; + + // 设计模式状态 + designMode: { + enabled: boolean; + selectedElement: string | null; + showGrid: boolean; + showInfo: boolean; + }; + + // Actions + setTheme: (theme: 'light' | 'dark') => void; + toggleTheme: () => void; + setLoading: (loading: boolean) => void; + setError: (error: string | null) => void; + setCurrentPage: (page: string) => void; + updateUserPreferences: (preferences: Partial) => void; + + // 组件操作 + addComponent: (component: Omit) => void; + updateComponent: (id: string, updates: Partial) => void; + removeComponent: (id: string) => void; + + // 设计模式操作 + setDesignModeEnabled: (enabled: boolean) => void; + setSelectedElement: (element: string | null) => void; + toggleGrid: () => void; + toggleInfo: () => void; + + // 重置状态 + reset: () => void; +} + +const initialState = { + theme: 'light' as const, + isLoading: false, + error: null, + currentPage: 'dashboard', + user: { + name: 'Demo User', + email: 'demo@example.com', + preferences: { + showTooltips: true, + animations: true, + compactMode: false + } + }, + components: [ + { + id: 'header-1', + name: 'AppHeader', + type: 'header', + props: { title: 'Advanced Demo', showNav: true } + }, + { + id: 'card-1', + name: 'InfoCard', + type: 'card', + props: { title: 'Welcome', content: 'This is an advanced example' } + } + ], + designMode: { + enabled: false, + selectedElement: null, + showGrid: false, + showInfo: true + } +}; + +export const useAppStore = create()( + devtools( + persist( + (set, get) => ({ + ...initialState, + + // UI Actions + setTheme: (theme) => set({ theme }, false, 'setTheme'), + toggleTheme: () => set((state) => ({ + theme: state.theme === 'light' ? 'dark' : 'light' + }), false, 'toggleTheme'), + + setLoading: (loading) => set({ isLoading: loading }, false, 'setLoading'), + setError: (error) => set({ error }, false, 'setError'), + setCurrentPage: (page) => set({ currentPage: page }, false, 'setCurrentPage'), + + updateUserPreferences: (preferences) => set((state) => ({ + user: { + ...state.user, + preferences: { ...state.user.preferences, ...preferences } + } + }), false, 'updateUserPreferences'), + + // Component Actions + addComponent: (component) => set((state) => ({ + components: [ + ...state.components, + { ...component, id: `${component.name}-${Date.now()}` } + ] + }), false, 'addComponent'), + + updateComponent: (id, updates) => set((state) => ({ + components: state.components.map(comp => + comp.id === id ? { ...comp, ...updates } : comp + ) + }), false, 'updateComponent'), + + removeComponent: (id) => set((state) => ({ + components: state.components.filter(comp => comp.id !== id) + }), false, 'removeComponent'), + + // Design Mode Actions + setDesignModeEnabled: (enabled) => set((state) => ({ + designMode: { ...state.designMode, enabled } + }), false, 'setDesignModeEnabled'), + + setSelectedElement: (element) => set((state) => ({ + designMode: { ...state.designMode, selectedElement: element } + }), false, 'setSelectedElement'), + + toggleGrid: () => set((state) => ({ + designMode: { ...state.designMode, showGrid: !state.designMode.showGrid } + }), false, 'toggleGrid'), + + toggleInfo: () => set((state) => ({ + designMode: { ...state.designMode, showInfo: !state.designMode.showInfo } + }), false, 'toggleInfo'), + + // Reset + reset: () => set(initialState, false, 'reset') + }), + { + name: 'advanced-design-mode-storage', + partialize: (state) => ({ + theme: state.theme, + user: state.user, + designMode: state.designMode + }) + } + ), + { + name: 'app-store' + } + ) +); diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts b/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts new file mode 100644 index 00000000..365058ce --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js b/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js new file mode 100644 index 00000000..d37737fc --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json new file mode 100644 index 00000000..ca631e91 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@pages/*": ["src/pages/*"], + "@utils/*": ["src/utils/*"], + "@types/*": ["src/types/*"], + "@hooks/*": ["src/hooks/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json new file mode 100644 index 00000000..42872c59 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts b/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts new file mode 100644 index 00000000..e4c08817 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts @@ -0,0 +1,77 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from '../../packages/plugin/src/index'; + +export default defineConfig({ + plugins: [ + react(), + // 高级配置示例 + appdevDesignMode({ + enabled: true, + enableInProduction: false, // 只在开发环境启用 + attributePrefix: 'design-mode', // 自定义属性前缀 + verbose: true, // 详细日志输出 + exclude: [ + 'node_modules', + '.git', + 'dist', + 'build', + '**/*.test.{ts,tsx,js,jsx}', + '**/*.spec.{ts,tsx,js,jsx}', + '**/__tests__/**', + '**/tests/**', + ], + include: [ + 'src/**/*.{ts,tsx,js,jsx}', + 'components/**/*.{ts,tsx,js,jsx}', + 'pages/**/*.{ts,tsx,js,jsx}', + '!**/node_modules/**', + ], + }), + ], + + // 高级Vite配置 + server: { + port: 5174, // 使用不同端口避免冲突 + host: true, + open: true, + cors: true, + fs: { + allow: ['..', '../../src'], // Allow serving the plugin source + }, + }, + + build: { + outDir: 'dist', + sourcemap: true, + minify: 'esbuild', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + router: ['react-router-dom'], + state: ['zustand'], + }, + }, + }, + }, + + resolve: { + alias: { + '@': '/src', + '@components': '/src/components', + '@pages': '/src/pages', + '@utils': '/src/utils', + }, + }, + + css: { + modules: { + localsConvention: 'camelCaseOnly', + }, + }, + + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom', 'zustand'], + }, +}); diff --git a/qiming-vite-plugin-design-mode/examples/basic/index.html b/qiming-vite-plugin-design-mode/examples/basic/index.html new file mode 100644 index 00000000..1f1c1cbd --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/basic/index.html @@ -0,0 +1,78 @@ + + + + + + + Basic AppDev Design Mode Example + + + +
+

Basic Design Mode Example

+

+ This is a simple HTML example showing how the design mode plugin works. +

+ +
+

Interactive Elements

+

+ Click the buttons below to test the design mode functionality. +

+ + + +
+ +
+

Content Section

+

+ This section contains various HTML elements for testing source mapping. +

+
    +
  • Source mapping works with vanilla HTML
  • +
  • Plugin adds data attributes automatically
  • +
  • No React or framework required
  • +
+
+
+ + + + diff --git a/qiming-vite-plugin-design-mode/examples/basic/main.js b/qiming-vite-plugin-design-mode/examples/basic/main.js new file mode 100644 index 00000000..f2c56db4 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/basic/main.js @@ -0,0 +1,171 @@ +// Basic JavaScript example for AppDev Design Mode plugin +console.log('AppDev Design Mode Plugin - Basic Example Loaded'); + +// Sample data for the application +const sampleData = { + features: [ + 'No framework required', + 'Works with vanilla HTML', + 'Automatic source mapping', + 'Lightweight and fast', + ], + actions: { + primary: 'Primary action triggered', + secondary: 'Secondary action triggered', + danger: 'Danger action triggered', + }, +}; + +// Interactive functionality +function initializeApp() { + console.log('Initializing basic app...'); + + // Add event listeners to buttons + const buttons = document.querySelectorAll('[data-appdev-action]'); + buttons.forEach(button => { + button.addEventListener('click', handleButtonClick); + }); + + // Add click handlers to list items + const listItems = document.querySelectorAll('[data-appdev-item]'); + listItems.forEach((item, index) => { + item.addEventListener('click', () => handleListItemClick(item, index)); + }); + + // Simulate dynamic content addition + addDynamicContent(); +} + +function handleButtonClick(event) { + const action = event.target.getAttribute('data-appdev-action'); + const message = sampleData.actions[action] || 'Unknown action'; + + console.log(`Button clicked: ${action}`); + + // Visual feedback + event.target.style.background = '#48bb78'; + setTimeout(() => { + event.target.style.background = '#4299e1'; + }, 200); + + showNotification(message); +} + +function handleListItemClick(item, index) { + console.log(`List item clicked: ${item.textContent}`); + + // Visual feedback + item.style.background = '#e6fffa'; + setTimeout(() => { + item.style.background = ''; + }, 300); + + showNotification(`Clicked: ${item.textContent}`); +} + +function addDynamicContent() { + const contentSection = document.querySelector( + '[data-appdev-component="content-section"]' + ); + if (!contentSection) return; + + const dynamicDiv = document.createElement('div'); + dynamicDiv.setAttribute('data-appdev-component', 'dynamic-content'); + dynamicDiv.setAttribute('data-dynamic', 'true'); + dynamicDiv.innerHTML = ` +

Dynamically Added Content

+

+ This content was added dynamically after page load. +

+ + `; + + contentSection.appendChild(dynamicDiv); + + // Add event listener to new button + const newButton = dynamicDiv.querySelector('[data-appdev-action="dynamic"]'); + newButton.addEventListener('click', event => { + console.log('Dynamic button clicked'); + showNotification('Dynamic action triggered!'); + event.target.style.background = '#ed8936'; + setTimeout(() => { + event.target.style.background = '#4299e1'; + }, 200); + }); +} + +function showNotification(message) { + // Remove existing notification + const existing = document.querySelector('.notification'); + if (existing) { + existing.remove(); + } + + // Create new notification + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.textContent = message; + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #48bb78; + color: white; + padding: 1rem 1.5rem; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + animation: slideIn 0.3s ease-out; + `; + + document.body.appendChild(notification); + + // Auto remove after 3 seconds + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-in'; + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, 300); + }, 3000); +} + +// Add CSS animations +const style = document.createElement('style'); +style.textContent = ` + @keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } + } +`; +document.head.appendChild(style); + +// Initialize when DOM is loaded +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeApp); +} else { + initializeApp(); +} + +// Log plugin status +console.log('AppDev Design Mode Plugin: Basic example ready for testing'); diff --git a/qiming-vite-plugin-design-mode/examples/basic/package.json b/qiming-vite-plugin-design-mode/examples/basic/package.json new file mode 100644 index 00000000..483e44ca --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/basic/package.json @@ -0,0 +1,13 @@ +{ + "name": "vite-plugin-appdev-design-mode-basic-example", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.0.0" + } +} diff --git a/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts b/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts new file mode 100644 index 00000000..6a43c00b --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from '../../packages/plugin/src/index'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode({ + verbose: true, + attributePrefix: 'data-appdev' + }) + ] +}); diff --git a/qiming-vite-plugin-design-mode/examples/demo/.gitignore b/qiming-vite-plugin-design-mode/examples/demo/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/qiming-vite-plugin-design-mode/examples/demo/README.md b/qiming-vite-plugin-design-mode/examples/demo/README.md new file mode 100644 index 00000000..10304903 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/README.md @@ -0,0 +1,61 @@ +# Design Mode Demo + +这是一个完整的示例项目,展示了 `@xagi/vite-plugin-design-mode` 的所有功能。 + +## 功能演示 + +### 1. 样式编辑 +- 点击任意元素查看编辑面板 +- 实时修改 Tailwind CSS 类 +- 自动保存到源文件 + +### 2. 内容编辑 +- 双击文本元素进入编辑模式 +- 修改后自动更新源代码 +- 支持纯文本内容 + +### 3. Iframe 支持 +- 在 iframe 中自动隐藏 UI +- 通过 postMessage 通信 +- 支持外部设计面板控制 + +## 快速开始 + +```bash +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev +``` + +访问 `http://localhost:5175` 查看演示。 + +## 使用说明 + +1. **启用设计模式**:点击右下角的 "Design Mode" 开关 +2. **选择元素**:点击页面上的任意元素 +3. **修改样式**:使用右侧面板修改背景、文字颜色、内边距等 +4. **编辑内容**:双击文本元素直接编辑 +5. **查看源码**:所有修改会自动保存到对应的 `.tsx` 文件 + +## 页面说明 + +- **Home**: 主页,展示核心功能卡片和可编辑示例 +- **Features**: 详细功能展示,包含各种可编辑元素 +- **Iframe Demo**: 演示 iframe 集成和通信机制 + +## 技术栈 + +- React 18 +- TypeScript +- Vite 6 +- Tailwind CSS 4 +- Radix UI +- @xagi/vite-plugin-design-mode + +## 注意事项 + +- 仅支持静态字符串 className 的修改 +- 内容编辑仅支持纯文本节点 +- 所有修改会直接写入源文件,建议使用版本控制 diff --git a/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md b/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md new file mode 100644 index 00000000..510aed6a --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md @@ -0,0 +1,580 @@ +# 可自定义属性面板配置指南 + +## 概述 + +本文档详细介绍了如何使用和配置可自定义的属性面板系统,该系统支持用户自定义配置,提供完整的双向同步设计模式架构。 + +## 功能特性 + +### 🎨 样式自定义 +- **颜色方案**: 自定义颜色库,支持导入导出 +- **样式预设**: 保存和管理常用样式组合 +- **主题切换**: 浅色/深色/跟随系统主题 +- **响应式布局**: 自适应不同屏幕尺寸 + +### 📝 内容自定义 +- **富文本编辑**: 支持格式化文本编辑 +- **历史记录**: 自动保存编辑历史,支持回溯 +- **模板系统**: 自定义内容模板和占位符 +- **快捷操作**: 支持快捷键和批量操作 + +### ⚙️ 高级配置 +- **面板布局**: 水平和垂直布局切换 +- **快捷键自定义**: 用户可自定义键盘快捷键 +- **自动保存**: 配置自动保存间隔和策略 +- **数据管理**: 支持配置的导入导出和重置 + +## 安装和配置 + +### 1. 基础安装 + +```bash +# 安装依赖 +npm install antd @ant-design/icons + +# 或使用 yarn +yarn add antd @ant-design/icons +``` + +### 2. 配置文件 + +创建 `vite.config.ts`: + +```typescript +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import appdevDesignMode from '@xagi/vite-plugin-design-mode'; + +export default defineConfig({ + plugins: [ + react(), + appdevDesignMode({ + enabled: true, + iframeMode: { + enabled: true, + hideUI: true, + enableSelection: true, + enableDirectEdit: true, + }, + batchUpdate: { + enabled: true, + debounceMs: 300, + }, + bridge: { + timeout: 10000, + retryAttempts: 3, + heartbeatInterval: 30000, + debug: true, + } + }) + ] +}); +``` + +### 3. 基础组件使用 + +```tsx +import React, { useState } from 'react'; +import { PropertyPanel } from '@xagi/design-mode'; +import type { PropertyPanelConfig } from '@xagi/design-mode'; + +function MyApp() { + const [selectedElement, setSelectedElement] = useState(null); + const [currentStyle, setCurrentStyle] = useState(''); + const [currentContent, setCurrentContent] = useState(''); + + const handleStyleUpdate = (data) => { + console.log('Style updated:', data); + }; + + const handleContentUpdate = (data) => { + console.log('Content updated:', data); + }; + + return ( +
+ { + console.log('Config changed:', config); + }} + /> +
+ ); +} +``` + +## 高级配置 + +### 自定义配置接口 + +```typescript +export interface PropertyPanelConfig { + // 主题配置 + theme: 'light' | 'dark' | 'auto'; + + // 自动保存设置 + autoSave: boolean; + autoSaveInterval: number; + + // 显示选项 + showElementInfo: boolean; + showTooltips: boolean; + + // 交互设置 + enableKeyboardShortcuts: boolean; + defaultPanel: 'style' | 'content' | 'settings'; + panelLayout: 'horizontal' | 'vertical'; + + // 自定义颜色库 + colorPalette: string[]; + + // 自定义预设 + customPresets: CustomPreset[]; + + // 快捷键配置 + shortcuts: ShortcutConfig; +} +``` + +### 快捷键配置 + +```typescript +const shortcuts = { + save: 'Ctrl+S', // 保存当前设置 + undo: 'Ctrl+Z', // 撤销操作 + redo: 'Ctrl+Y', // 重做操作 + togglePanel: 'F2', // 切换面板 + clearAll: 'Ctrl+Delete', // 清除所有 + custom1: 'Ctrl+1', // 自定义快捷键1 + custom2: 'Ctrl+2', // 自定义快捷键2 + custom3: 'Ctrl+3' // 自定义快捷键3 +}; +``` + +### 自定义预设管理 + +```typescript +interface CustomPreset { + id: string; + name: string; // 预设名称 + type: 'style' | 'content'; // 预设类型 + value: string; // 预设值 + description: string; // 预设描述 + tags: string[]; // 标签数组 + favorite: boolean; // 是否收藏 + createdAt: number; // 创建时间 +} + +// 创建自定义预设 +const myPreset: CustomPreset = { + id: 'preset_001', + name: '主要按钮样式', + type: 'style', + value: 'bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600', + description: '常用的主要按钮样式', + tags: ['按钮', '主要', '蓝色'], + favorite: true, + createdAt: Date.now() +}; +``` + +## API 接口 + +### 组件属性 + +| 属性名 | 类型 | 描述 | 默认值 | +|--------|------|------|--------| +| `selectedElement` | `ElementInfo \| null` | 当前选中的元素信息 | `null` | +| `currentStyle` | `string` | 当前样式类名 | `''` | +| `currentContent` | `string` | 当前内容 | `''` | +| `onStyleUpdate` | `(data) => void` | 样式更新回调 | 必填 | +| `onContentUpdate` | `(data) => void` | 内容更新回调 | 必填 | +| `onStyleChange` | `(style) => void` | 样式变化回调 | 必填 | +| `onContentChange` | `(content) => void` | 内容变化回调 | 必填 | +| `config` | `Partial` | 面板配置 | `undefined` | +| `onConfigChange` | `(config) => void` | 配置变化回调 | `undefined` | + +### 回调函数参数 + +#### onStyleUpdate +```typescript +{ + sourceInfo: { + fileName: string; + lineNumber: number; + columnNumber: number; + }; + newClass: string; +} +``` + +#### onContentUpdate +```typescript +{ + sourceInfo: { + fileName: string; + lineNumber: number; + columnNumber: number; + }; + newContent: string; +} +``` + +## 事件处理 + +### 键盘事件 + +组件支持以下键盘事件: + +- **Ctrl+S**: 保存当前设置 +- **Ctrl+Z**: 撤销操作 +- **Ctrl+Y**: 重做操作 +- **F2**: 切换面板 +- **Ctrl+Delete**: 清除所有 + +### 鼠标事件 + +- **单击**: 选择元素 +- **双击**: 进入编辑模式 +- **右键**: 显示上下文菜单 +- **悬停**: 显示工具提示 + +## 样式定制 + +### 主题定制 + +```typescript +import { ConfigProvider } from 'antd'; + +// 自定义主题 +const customTheme = { + algorithm: theme.darkAlgorithm, + token: { + colorPrimary: '#your-primary-color', + borderRadius: 8, + // 更多主题配置... + } +}; + + + + +``` + +### 自定义样式类 + +```css +/* 自定义面板样式 */ +.custom-property-panel { + --panel-bg: #f8f9fa; + --panel-border: #e9ecef; + --panel-text: #212529; +} + +.custom-property-panel .ant-card { + background: var(--panel-bg); + border-color: var(--panel-border); +} + +.custom-property-panel .ant-tabs-tab { + color: var(--panel-text); +} +``` + +## 数据存储 + +### LocalStorage 键值 + +- `appdev_property_panel_config`: 面板配置 +- `appdev_property_panel_presets`: 自定义预设 + +### 数据格式 + +#### 配置数据格式 +```json +{ + "theme": "light", + "autoSave": true, + "autoSaveInterval": 3000, + "showElementInfo": true, + "showTooltips": true, + "enableKeyboardShortcuts": true, + "defaultPanel": "style", + "panelLayout": "horizontal", + "colorPalette": ["#000000", "#ffffff", "#3b82f6"], + "customPresets": [], + "shortcuts": { + "save": "Ctrl+S", + "undo": "Ctrl+Z", + "redo": "Ctrl+Y", + "togglePanel": "F2", + "clearAll": "Ctrl+Delete" + } +} +``` + +#### 预设数据格式 +```json +[ + { + "id": "preset_001", + "name": "主要按钮样式", + "type": "style", + "value": "bg-blue-500 text-white px-4 py-2 rounded-lg", + "description": "常用的主要按钮样式", + "tags": ["按钮", "主要", "蓝色"], + "favorite": true, + "createdAt": 1640995200000 + } +] +``` + +## 错误处理 + +### 常见错误 + +#### 1. 导入配置失败 +```typescript +try { + const config = JSON.parse(configString); + // 验证配置格式 + if (!config.theme || !config.shortcuts) { + throw new Error('配置格式不完整'); + } +} catch (error) { + console.error('导入配置失败:', error); + message.error('配置格式错误,请检查文件格式'); +} +``` + +#### 2. 预设保存失败 +```typescript +const savePreset = (preset) => { + try { + // 验证预设格式 + if (!preset.name || !preset.value) { + throw new Error('预设名称和值不能为空'); + } + + // 保存预设 + presetManager.savePreset(preset); + message.success('预设保存成功'); + } catch (error) { + console.error('保存预设失败:', error); + message.error('保存失败: ' + error.message); + } +}; +``` + +### 错误边界 + +```typescript +import { ErrorBoundary } from 'react-error-boundary'; + +function ErrorFallback({ error }) { + return ( +
+

配置面板出现错误

+
{error.message}
+ +
+ ); +} + +// 使用错误边界包装组件 + + + +``` + +## 性能优化 + +### 1. 组件优化 + +```typescript +// 使用 React.memo 优化重渲染 +const PropertyPanel = React.memo(({ + selectedElement, + currentStyle, + currentContent, + onStyleUpdate, + onContentUpdate, + onStyleChange, + onContentChange, + config, + onConfigChange +}) => { + // 组件实现... +}); + +// 使用 useMemo 缓存计算结果 +const processedPresets = useMemo(() => { + return presets.filter(preset => preset.favorite); +}, [presets]); + +// 使用 useCallback 缓存回调函数 +const handleStyleUpdate = useCallback((data) => { + onStyleUpdate(data); +}, [onStyleUpdate]); +``` + +### 2. 内存管理 + +```typescript +// 清理副作用 +useEffect(() => { + const handleKeydown = (event) => { + // 处理键盘事件 + }; + + document.addEventListener('keydown', handleKeydown); + + return () => { + document.removeEventListener('keydown', handleKeydown); + }; +}, []); + +// 清理定时器 +useEffect(() => { + const timer = setInterval(() => { + // 自动保存逻辑 + }, config.autoSaveInterval); + + return () => { + clearInterval(timer); + }; +}, [config.autoSaveInterval]); +``` + +## 最佳实践 + +### 1. 配置管理 +- 合理设置自动保存间隔,避免过于频繁的保存操作 +- 为不同用户角色设置不同的默认配置 +- 定期清理无用的自定义预设 + +### 2. 性能优化 +- 大量预设数据时分页加载 +- 使用虚拟滚动优化长列表显示 +- 合理使用防抖和节流 + +### 3. 用户体验 +- 提供清晰的错误提示和恢复机制 +- 支持快捷键组合,满足熟练用户需求 +- 保持界面的响应性和直观性 + +### 4. 数据安全 +- 定期备份用户配置 +- 验证导入数据的格式和安全性 +- 提供配置恢复功能 + +## 故障排除 + +### 常见问题 + +#### Q: 面板不显示或加载失败 +**A**: 检查以下项目: +1. 确认所有依赖包已正确安装 +2. 检查网络连接是否正常 +3. 查看浏览器控制台是否有错误信息 +4. 验证配置格式是否正确 + +#### Q: 自定义预设无法保存 +**A**: 可能原因: +1. LocalStorage 存储已满,尝试清理无用数据 +2. 预设数据格式不正确,检查数据结构 +3. 浏览器隐私设置阻止了本地存储 + +#### Q: 快捷键不生效 +**A**: 检查项目: +1. 确认键盘事件监听器正确注册 +2. 检查是否有其他组件占用了相同的快捷键 +3. 验证快捷键格式是否符合系统要求 + +#### Q: 配置导入导出失败 +**A**: 可能的问题: +1. JSON 格式错误,使用在线工具验证 +2. 文件权限问题,检查文件读写权限 +3. 数据大小超限,压缩或分批导入 + +### 调试技巧 + +#### 1. 启用调试模式 +```typescript +const config = { + debug: true, + // 其他配置... +}; +``` + +#### 2. 监控配置变化 +```typescript +useEffect(() => { + console.log('Config changed:', config); +}, [config]); +``` + +#### 3. 检查存储状态 +```typescript +const checkStorage = () => { + const config = localStorage.getItem('appdev_property_panel_config'); + const presets = localStorage.getItem('appdev_property_panel_presets'); + console.log('Stored config:', config); + console.log('Stored presets:', presets); +}; +``` + +## 更新日志 + +### v2.0.0 (当前版本) +- ✨ 新增完整的可自定义属性面板 +- ✨ 支持主题切换和个性化配置 +- ✨ 新增预设管理系统 +- ✨ 支持配置导入导出 +- ✨ 增强键盘快捷键支持 +- ✨ 改进错误处理和恢复机制 + +### v1.0.0 +- 🎉 初始版本发布 +- ✨ 基础的样式和内容编辑功能 +- ✨ 简单的面板配置选项 + +## 技术支持 + +如果您在使用过程中遇到问题,请通过以下方式获取帮助: + +1. **查看文档**: 详细阅读本文档和使用指南 +2. **检查示例**: 参考 examples/demo 目录中的示例代码 +3. **调试模式**: 启用 debug 模式查看详细日志 +4. **社区支持**: 在 GitHub 上提交 Issue +5. **联系支持**: 发送邮件到 support@example.com + +## 贡献指南 + +我们欢迎社区贡献!请查看 CONTRIBUTING.md 了解如何参与项目开发。 + +### 贡献方式 +- 🐛 报告 Bug +- 💡 提出新功能建议 +- 📝 完善文档 +- 💻 提交代码改进 +- 🧪 编写测试用例 + +感谢您对项目的关注和支持!🎉 diff --git a/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js b/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js new file mode 100644 index 00000000..5e6b472f --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/qiming-vite-plugin-design-mode/examples/demo/index.html b/qiming-vite-plugin-design-mode/examples/demo/index.html new file mode 100644 index 00000000..3bcfba0d --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/index.html @@ -0,0 +1,32 @@ + + + + + + + + 设计模式演示 + + + + + + +
+ + + + \ No newline at end of file diff --git a/qiming-vite-plugin-design-mode/examples/demo/package-lock.json b/qiming-vite-plugin-design-mode/examples/demo/package-lock.json new file mode 100644 index 00000000..01729e1e --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/package-lock.json @@ -0,0 +1,2097 @@ +{ + "name": "design-mode-demo", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "design-mode-demo", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-switch": "^1.1.1", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.5" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "tailwindcss": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^7.2.6" + } + }, + "../..": { + "name": "@xagi/vite-plugin-design-mode", + "version": "1.0.25", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/standalone": "^7.23.0", + "@babel/traverse": "^7.24.0", + "clsx": "^2.1.1", + "tailwind-merge": "^3.4.0" + }, + "bin": { + "vite-plugin-design-mode": "dist/cli/index.js" + }, + "devDependencies": { + "@types/babel__core": "^7.20.0", + "@types/babel__standalone": "^7.1.9", + "@types/babel__traverse": "^7.20.0", + "@types/node": "^20.0.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.0.0", + "vite": "^5.4.21", + "vitest": "^1.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@babel/types": "^7.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "vite": ">=5.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": false + }, + "react-dom": { + "optional": false + } + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.17", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.17", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", + "postcss": "^8.4.41", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.263", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.17", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.6.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + } + } +} diff --git a/qiming-vite-plugin-design-mode/examples/demo/package.json b/qiming-vite-plugin-design-mode/examples/demo/package.json new file mode 100644 index 00000000..cc1a2178 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/package.json @@ -0,0 +1,28 @@ +{ + "name": "design-mode-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-switch": "^1.1.1", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.5" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "tailwindcss": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^7.2.6" + } +} diff --git a/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js b/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js new file mode 100644 index 00000000..a34a3d56 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg b/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/App.css b/qiming-vite-plugin-design-mode/examples/demo/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx new file mode 100644 index 00000000..01c888d4 --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import HomePage from './pages/HomePage'; +import FeaturesPage from './pages/FeaturesPage'; +import IframeDemoPage from './pages/IframeDemoPage'; + +function App() { + const [currentPage, setCurrentPage] = useState<'home' | 'features' | 'iframe'>('home'); + + return ( +
+ {/* 导航栏 */} + + + {/* Page Content */} +
+ {currentPage === 'home' && } + {currentPage === 'features' && } + {currentPage === 'iframe' && } +
+
+ ); +} + +export default App; diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg b/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx new file mode 100644 index 00000000..a71ea57f --- /dev/null +++ b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx @@ -0,0 +1,612 @@ +import React from '../../react'; +import { useState, useEffect } from 'react'; +import { Card, Button, Input, Select, Space, Row, Col, Divider, Tooltip, Badge } from 'antd'; +import { + FontSizeOutlined, + BoldOutlined, + ItalicOutlined, + UnderlineOutlined, + AlignLeftOutlined, + AlignCenterOutlined, + AlignRightOutlined, + EditOutlined, + HistoryOutlined, + ClearOutlined, + SaveOutlined, + UndoOutlined, + EyeOutlined +} from '@ant-design/icons'; +import type { ElementInfo, SourceInfo } from '../../../../packages/client-shared/src/messages'; + +const { TextArea } = Input; +const { Option } = Select; + +export interface ContentUpdateData { + sourceInfo: SourceInfo; + newContent: string; +} + +export interface ContentPanelProps { + selectedElement: ElementInfo | null; + onUpdateContent: (data: ContentUpdateData) => void; + currentContent: string; + onContentChange: (newContent: string) => void; +} + +/** + * 内容编辑工具配置 + */ +const contentTools = { + // 文本格式化 + formatting: [ + { label: '加粗', value: '**', icon: , description: '添加加粗格式' }, + { label: '斜体', value: '*', icon: , description: '添加斜体格式' }, + { label: '下划线', value: '__', icon: , description: '添加下划线' } + ], + + // 对齐方式 + alignment: [ + { label: '左对齐', value: 'text-left', icon: }, + { label: '居中对齐', value: 'text-center', icon: }, + { label: '右对齐', value: 'text-right', icon: } + ], + + // 文本样式预设 + textStyles: [ + { label: '标题 1', value: 'text-4xl font-bold', preview: '大标题' }, + { label: '标题 2', value: 'text-3xl font-semibold', preview: '中标题' }, + { label: '标题 3', value: 'text-2xl font-medium', preview: '小标题' }, + { label: '正文', value: 'text-base', preview: '普通文本' }, + { label: '小字', value: 'text-sm text-gray-600', preview: '小字体' }, + { label: '说明文字', value: 'text-xs text-gray-500', preview: '说明文字' } + ], + + // 常用的占位符文本 + placeholders: [ + '请输入内容...', + '点击编辑文本', + '请输入标题', + '请输入描述', + '请输入按钮文本', + '请输入链接文本' + ] +}; + +/** + * 内容历史记录 + */ +interface ContentHistory { + id: string; + content: string; + timestamp: number; + description: string; +} + +/** + * 内容编辑面板组件 + */ +export const ContentPanel: React.FC = ({ + selectedElement, + onUpdateContent, + currentContent, + onContentChange +}) => { + const [activeTab, setActiveTab] = useState('edit'); + const [editingContent, setEditingContent] = useState(currentContent); + const [originalContent, setOriginalContent] = useState(currentContent); + const [contentHistory, setContentHistory] = useState([]); + const [isPreviewMode, setIsPreviewMode] = useState(false); + const [autoSave, setAutoSave] = useState(true); + const [wordCount, setWordCount] = useState(0); + const [characterCount, setCharacterCount] = useState(0); + + useEffect(() => { + setEditingContent(currentContent); + setOriginalContent(currentContent); + updateCounts(currentContent); + }, [currentContent]); + + /** + * 更新字符和单词计数 + */ + const updateCounts = (content: string) => { + setCharacterCount(content.length); + const words = content.trim().split(/\s+/).filter(word => word.length > 0); + setWordCount(words.length); + }; + + /** + * 处理内容变化 + */ + const handleContentChange = (newContent: string) => { + setEditingContent(newContent); + onContentChange(newContent); + updateCounts(newContent); + + // 自动保存历史记录 + if (autoSave && newContent !== originalContent) { + addToHistory(newContent, '自动保存'); + } + }; + + /** + * 添加到历史记录 + */ + const addToHistory = (content: string, description: string) => { + const historyItem: ContentHistory = { + id: `history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + content, + timestamp: Date.now(), + description + }; + + setContentHistory(prev => [historyItem, ...prev.slice(0, 9)]); // 保留最新10条记录 + }; + + /** + * 恢复到历史记录 + */ + const restoreFromHistory = (historyItem: ContentHistory) => { + setEditingContent(historyItem.content); + onContentChange(historyItem.content); + updateCounts(historyItem.content); + addToHistory(historyItem.content, '恢复历史记录'); + }; + + /** + * 清除内容 + */ + const clearContent = () => { + setEditingContent(''); + onContentChange(''); + updateCounts(''); + addToHistory('', '清除内容'); + }; + + /** + * 恢复原始内容 + */ + const restoreOriginal = () => { + setEditingContent(originalContent); + onContentChange(originalContent); + updateCounts(originalContent); + }; + + /** + * 应用内容更新 + */ + const applyChanges = () => { + if (!selectedElement) return; + + onUpdateContent({ + sourceInfo: selectedElement.sourceInfo, + newContent: editingContent + }); + + setOriginalContent(editingContent); + addToHistory(editingContent, '应用更改'); + }; + + /** + * 插入格式化文本 + */ + const insertFormatting = (format: string) => { + const textarea = document.querySelector('textarea[data-content-editor]') as HTMLTextAreaElement; + if (!textarea) return; + + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const selectedText = editingContent.substring(start, end); + + let newContent = editingContent; + let newSelectedText = selectedText; + + // 根据格式化类型插入 + switch (format) { + case '**': + newSelectedText = `**${selectedText}**`; + break; + case '*': + newSelectedText = `*${selectedText}*`; + break; + case '__': + newSelectedText = `__${selectedText}__`; + break; + } + + newContent = + editingContent.substring(0, start) + + newSelectedText + + editingContent.substring(end); + + handleContentChange(newContent); + + // 重新设置光标位置 + setTimeout(() => { + textarea.focus(); + textarea.setSelectionRange( + start + (format === '**' || format === '__' ? 2 : 1), + start + (format === '**' || format === '__' ? 2 : 1) + selectedText.length + ); + }, 0); + }; + + /** + * 插入占位符 + */ + const insertPlaceholder = (placeholder: string) => { + const newContent = editingContent + placeholder; + handleContentChange(newContent); + }; + + /** + * 应用文本样式 + */ + const applyTextStyle = (style: string) => { + // 这里可以应用内联样式或类名 + console.log('Applying text style:', style); + }; + + /** + * 获取文本统计信息 + */ + const getTextStats = () => { + const lines = editingContent.split('\n'); + const paragraphs = editingContent.split('\n\n').filter(p => p.trim().length > 0); + + return { + lines: lines.length, + paragraphs: paragraphs.length, + words: wordCount, + characters: characterCount, + charactersNoSpaces: editingContent.replace(/\s/g, '').length + }; + }; + + if (!selectedElement) { + return ( + +
+
+ +

请先选择一个元素

+
+
+
+ ); + } + + // Check if element is editable (has static text) + if (selectedElement.isStaticText === false) { + return ( + +
+
+ +

该元素不可编辑

+

只有纯静态文本可以编辑

+

(不包含变量或表达式)

+
+
+
+ ); + } + + const stats = getTextStats(); + + return ( + + + 内容编辑 + + + } + className="h-full" + extra={ + + + + + + + + } + > +
+ {/* 元素信息 */} +
+

当前元素

+
+
标签: <{selectedElement.tagName}>
+
当前位置: {selectedElement.sourceInfo.fileName.split('/').pop()}:{selectedElement.sourceInfo.lineNumber}
+
原内容: {originalContent.substring(0, 50)}{originalContent.length > 50 ? '...' : ''}
+
+
+ + {/* 标签页导航 */} +
+
+ {[ + { key: 'edit', label: '编辑内容' }, + { key: 'format', label: '格式化' }, + { key: 'style', label: '文本样式' }, + { key: 'history', label: '历史记录' }, + { key: 'stats', label: '统计信息' } + ].map(tab => ( + + ))} +
+
+ + {/* 编辑内容 */} + {activeTab === 'edit' && ( +
+ {isPreviewMode ? ( + // 预览模式 +
+
') }} /> +
+ ) : ( + // 编辑模式 +
+