mcpywrap 0.3.6__tar.gz → 0.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/CHANGELOG.md +7 -0
  2. {mcpywrap-0.3.6/mcpywrap.egg-info → mcpywrap-0.3.7}/PKG-INFO +10 -6
  3. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/README.md +9 -5
  4. mcpywrap-0.3.7/docs/local-dependencies.md +41 -0
  5. mcpywrap-0.3.7/mcpywrap/__init__.py +4 -0
  6. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/dependency_manager.py +34 -4
  7. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/project_builder.py +3 -0
  8. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/watcher.py +17 -7
  9. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/command_context.py +15 -1
  10. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/add_cmd.py +9 -9
  11. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/build_cmd.py +4 -2
  12. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/dependency_prompt.py +1 -1
  13. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/edit_cmd.py +3 -3
  14. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/init_cmd.py +3 -4
  15. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/modsdk_cmd.py +1 -1
  16. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/package_cmd.py +1 -1
  17. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/run_cmd.py +8 -2
  18. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/sync_cmd.py +1 -1
  19. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/dependencies.py +63 -11
  20. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
  21. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
  22. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
  23. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_payload/manifest.json +3 -3
  24. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/network.py +2 -0
  25. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/ui/project_ui.py +17 -9
  26. {mcpywrap-0.3.6 → mcpywrap-0.3.7/mcpywrap.egg-info}/PKG-INFO +10 -6
  27. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap.egg-info/SOURCES.txt +2 -0
  28. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/pyproject.toml +1 -1
  29. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/SKILL.md +4 -1
  30. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/references/remote-testing.md +1 -1
  31. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/references/troubleshooting.md +2 -0
  32. mcpywrap-0.3.7/tests/manual_dependency_boundaries.py +118 -0
  33. mcpywrap-0.3.7/tests/test_dependency_boundaries.py +228 -0
  34. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_local_dependencies.py +5 -3
  35. mcpywrap-0.3.6/docs/local-dependencies.md +0 -30
  36. mcpywrap-0.3.6/mcpywrap/__init__.py +0 -4
  37. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/LICENSE +0 -0
  38. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/MANIFEST.in +0 -0
  39. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/docs/development.md +0 -0
  40. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/docs/engine-discovery.md +0 -0
  41. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/docs/mcs-auth.md +0 -0
  42. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/docs/releasing.md +0 -0
  43. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/__main__.py +0 -0
  44. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/AddonsPack.py +0 -0
  45. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/MapPack.py +0 -0
  46. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/__init__.py +0 -0
  47. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/assembly.py +0 -0
  48. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/builders/file_merge.py +0 -0
  49. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/cli.py +0 -0
  50. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/__init__.py +0 -0
  51. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/connect_cmd.py +0 -0
  52. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/default_cmd.py +0 -0
  53. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/desktop_cmd.py +0 -0
  54. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/dev_cmd.py +0 -0
  55. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/doctor_cmd.py +0 -0
  56. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/mod_cmd.py +0 -0
  57. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/publish_cmd.py +0 -0
  58. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/remove_cmd.py +0 -0
  59. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/serve_cmd.py +0 -0
  60. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/session_cmd.py +0 -0
  61. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/commands/ui_cmd.py +0 -0
  62. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/config.py +0 -0
  63. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/custom_packaging.py +0 -0
  64. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
  65. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/__init__.py +0 -0
  66. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/auth_interaction.py +0 -0
  67. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_assets.py +0 -0
  68. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
  69. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/diagnostics.py +0 -0
  70. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/discovery.py +0 -0
  71. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/editor.py +0 -0
  72. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/file_logs.py +0 -0
  73. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/game.py +0 -0
  74. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/log_protocol.py +0 -0
  75. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/mcs.py +0 -0
  76. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/mcs_auth.py +0 -0
  77. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/private_logs.py +0 -0
  78. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/processes.py +0 -0
  79. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
  80. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/session_worker.py +0 -0
  81. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/sessions.py +0 -0
  82. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/studio_server.py +0 -0
  83. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
  84. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
  85. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
  86. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/symlinks.py +0 -0
  87. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/mcstudio/window.py +0 -0
  88. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/__init__.py +0 -0
  89. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/addons.py +0 -0
  90. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/level_dat.py +0 -0
  91. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/map.py +0 -0
  92. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/netease_modsdk.py +0 -0
  93. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
  94. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/minecraft/template/mod_template.py +0 -0
  95. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/project_init.py +0 -0
  96. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/remote/__init__.py +0 -0
  97. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/remote/client.py +0 -0
  98. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/remote/http_server.py +0 -0
  99. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/remote/service.py +0 -0
  100. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/ui/__init__.py +0 -0
  101. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/utils/__init__.py +0 -0
  102. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/utils/pip_error_parser.py +0 -0
  103. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/utils/print_guide.py +0 -0
  104. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/utils/project_setup.py +0 -0
  105. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap/utils/utils.py +0 -0
  106. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap.egg-info/dependency_links.txt +0 -0
  107. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap.egg-info/entry_points.txt +0 -0
  108. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap.egg-info/requires.txt +0 -0
  109. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/mcpywrap.egg-info/top_level.txt +0 -0
  110. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/native/mcs_auth/Bridge.cs +0 -0
  111. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/native/mcs_auth/Injector.cs +0 -0
  112. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/native/mcs_auth/Loader.c +0 -0
  113. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/native/mcs_auth/publisher.cer +0 -0
  114. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/native/mcs_auth/publisher.json +0 -0
  115. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/scripts/build_mcs_auth_bridge.py +0 -0
  116. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/scripts/check_mcs_auth_bridge.py +0 -0
  117. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/setup.cfg +0 -0
  118. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/agents/openai.yaml +0 -0
  119. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/scripts/bootstrap.ps1 +0 -0
  120. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/scripts/bootstrap.py +0 -0
  121. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/scripts/game_window.py +0 -0
  122. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/skills/mcpywrap/scripts/smoke.py +0 -0
  123. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/manual_integration.py +0 -0
  124. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_auth_interaction.py +0 -0
  125. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_automation.py +0 -0
  126. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_engine_discovery.py +0 -0
  127. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_game_window_skill.py +0 -0
  128. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_local_auth_session.py +0 -0
  129. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_mcs_auth.py +0 -0
  130. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_mouse.py +0 -0
  131. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_network.py +0 -0
  132. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_network_sessions.py +0 -0
  133. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_package.py +0 -0
  134. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_remote.py +0 -0
  135. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_remote_scripts.py +0 -0
  136. {mcpywrap-0.3.6 → mcpywrap-0.3.7}/tests/test_skill_workflows.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.3.7
4
+
5
+ - 明确工具 Python 与游戏内置 Python 的边界:普通包保留为开发依赖,CLI、JSON 与 GUI 提示“仅开发环境”,不会自动进入游戏产物。
6
+ - 主项目、递归 Addon 依赖与地图包中的原生二进制会在构建、打包、运行或编辑前被拒绝;增量更新同样校验,失败保留已有产物。
7
+ - 包安装后校验候选声明,无效 Addon 不写配置;明确提示已发生的工具环境安装,不自动卸载。
8
+ - README 与 Skill 补充依赖分类、游戏兼容性及实际产物验收要求;开发项目和独立解包产物均已通过本机客户端/服务端共享源码加载测试。
9
+
3
10
  ## 0.3.6
4
11
 
5
12
  - 新增局域网 serve 服务与 --remote/--local 路由;macOS 本机开发、Windows 网络游戏测试共用 CLI 和 Skill。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpywrap
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: A wrapper for Minecraft China Edition Addons management
5
5
  Author-email: boybook <boybook@easecation.net>
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ Dynamic: license-file
32
32
  [![PyPI Version](https://img.shields.io/pypi/v/mcpywrap)](https://pypi.org/project/mcpywrap/)
33
33
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
34
34
 
35
- mcpywrap 使用 `pyproject.toml` 管理项目,支持安装 Python 包依赖,也支持直接引用本地 Addon 目录。它将依赖管理、资源构建、游戏运行和 MC Studio 编辑器串成一套开发流程,方便多个项目共享代码与资源。
35
+ mcpywrap 使用 `pyproject.toml` 管理开发侧依赖,复用本地 Addon 的代码与资源,并串联构建、游戏测试和 MC Studio 编辑器。Python 包管理用于开发环境,不是游戏内的包安装器。
36
36
 
37
37
  ## 安装
38
38
 
@@ -65,7 +65,7 @@ mcpy edit
65
65
 
66
66
  ## 复用代码和资源
67
67
 
68
- 对于已发布的 Python 包,使用包名添加依赖:
68
+ 将 Python 包安装到 mcpy 所在的工具环境并记录声明:
69
69
 
70
70
  ```powershell
71
71
  mcpy add "package-name>=1.0"
@@ -81,7 +81,9 @@ mcpy add --path "../shared-addon"
81
81
 
82
82
  移除依赖使用 `mcpy remove <包名>` 或 `mcpy remove --path <目录>`。移除本地引用不会删除源目录。不带参数运行 `mcpy add` / `mcpy remove` 可进入选择向导。
83
83
 
84
- 本地路径适合同机开发;与他人共享项目时,需要同步这些目录,或将可复用组件发布为 Python 包。目录结构、配置及构建规则见[本地依赖参考](https://github.com/EaseCation/mcpywrap/blob/main/docs/local-dependencies.md)。
84
+ 本地路径适合同机开发;共享项目时需同步这些目录。工具目前通过包的本地安装来源识别 Addon,普通 PyPI 包或 wheel 安装成功不代表能参与组装;未识别到 Addon 的包会标记为“仅开发环境”。目录结构、配置及构建规则见[本地依赖参考](https://github.com/EaseCation/mcpywrap/blob/main/docs/local-dependencies.md)。
85
+
86
+ **游戏运行边界:** 测试和正式游戏都使用内置 Python,不读取 `dependencies`,也不继承工具环境的 `site-packages`。`add`、`sync --install` 和 ModSDK 安装均不向游戏安装模块。游戏所需代码与资源必须随 Addon 携带,或由用户加载相应 Mod;纯 Python 库也不会自动复制,仍需适配游戏解释器和 ModSDK。原生扩展及依赖外部安装步骤、系统环境的库不能直接用于游戏;包内 `.pyd/.dll/.so/.so.*(版本号)/.dylib` 会阻止构建和启动。
85
87
 
86
88
  ## 构建与日常开发
87
89
 
@@ -100,12 +102,14 @@ mcpy add --path "../shared-addon"
100
102
 
101
103
  ### 打包分发
102
104
 
103
- 在项目根目录执行 `mcpy package`,会先构建项目及 Python 包/本地 Addon 依赖,再生成 `dist/<项目名>-<版本>.zip`。名称和版本读取 `pyproject.toml` 的 `[project]`;不需要配置 `target_dir`。
105
+ 在项目根目录执行 `mcpy package`,会组装项目及已识别的 Addon 依赖,再生成 `dist/<项目名>-<版本>.zip`;仅开发环境的包不会进入 ZIP。名称和版本读取 `pyproject.toml` 的 `[project]`;不需要配置 `target_dir`。
104
106
 
105
107
  Addon ZIP 内为 `<项目名>_bp/`、`<项目名>_rp/`(仅包含实际构建出的包);地图 ZIP 根目录直接包含存档数据、行为包、资源包及世界包配置。地图默认保留独立包,使用 `mcpy package --merge`(或 `-m`)按构建规则合并依赖资源。构建产物中的空目录会保留。
106
108
 
107
109
  重复打包成功后会替换同名 ZIP;失败时保留已有 ZIP,临时文件自动清理。
108
110
 
111
+ `publish` 向 PyPI 发布 Python 分发包,与游戏 ZIP 分发不同。本地测试可配置多个独立 Addon,游戏不会解析 Python 依赖图;构建成功不保证兼容,发布前应检查产物并在游戏中验证加载日志。
112
+
109
113
  ## 游戏启动与排查
110
114
 
111
115
  通常不需要手动指定游戏路径。mcpywrap 会优先查找 MC Studio 登记的安装,必要时搜索固定磁盘中的标准下载目录,并跳过不完整的引擎版本。
@@ -173,7 +177,7 @@ mcpy --project D:\tests\server stop --session <id> --json
173
177
  也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
174
178
  无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
175
179
  两端使用跨平台 `scripts/bootstrap.py` 安装或复用各自的 CLI,Windows 也可用 `bootstrap.ps1`。脚本分别报告本机与远端能力;Skill、CLI 与 Windows 服务需要分别检查,Git/可编辑安装可能缺少登录组件。
176
- 局域网流程请使用 mcpywrap 0.3.6 或更高版本,可从 `v0.3.6` 标签安装配套 Skill。实际能力仍以脚本检测为准;更新 Skill 不会自动更新两端 CLI。
180
+ 推荐使用 mcpywrap 0.3.7 或更高版本,并从 `v0.3.7` 标签安装配套 Skill,以获得依赖边界提示与校验。实际能力仍以脚本检测为准;更新 Skill 不会自动更新两端 CLI。
177
181
 
178
182
  安装后可直接描述任务:
179
183
 
@@ -5,7 +5,7 @@
5
5
  [![PyPI Version](https://img.shields.io/pypi/v/mcpywrap)](https://pypi.org/project/mcpywrap/)
6
6
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
- mcpywrap 使用 `pyproject.toml` 管理项目,支持安装 Python 包依赖,也支持直接引用本地 Addon 目录。它将依赖管理、资源构建、游戏运行和 MC Studio 编辑器串成一套开发流程,方便多个项目共享代码与资源。
8
+ mcpywrap 使用 `pyproject.toml` 管理开发侧依赖,复用本地 Addon 的代码与资源,并串联构建、游戏测试和 MC Studio 编辑器。Python 包管理用于开发环境,不是游戏内的包安装器。
9
9
 
10
10
  ## 安装
11
11
 
@@ -38,7 +38,7 @@ mcpy edit
38
38
 
39
39
  ## 复用代码和资源
40
40
 
41
- 对于已发布的 Python 包,使用包名添加依赖:
41
+ 将 Python 包安装到 mcpy 所在的工具环境并记录声明:
42
42
 
43
43
  ```powershell
44
44
  mcpy add "package-name>=1.0"
@@ -54,7 +54,9 @@ mcpy add --path "../shared-addon"
54
54
 
55
55
  移除依赖使用 `mcpy remove <包名>` 或 `mcpy remove --path <目录>`。移除本地引用不会删除源目录。不带参数运行 `mcpy add` / `mcpy remove` 可进入选择向导。
56
56
 
57
- 本地路径适合同机开发;与他人共享项目时,需要同步这些目录,或将可复用组件发布为 Python 包。目录结构、配置及构建规则见[本地依赖参考](https://github.com/EaseCation/mcpywrap/blob/main/docs/local-dependencies.md)。
57
+ 本地路径适合同机开发;共享项目时需同步这些目录。工具目前通过包的本地安装来源识别 Addon,普通 PyPI 包或 wheel 安装成功不代表能参与组装;未识别到 Addon 的包会标记为“仅开发环境”。目录结构、配置及构建规则见[本地依赖参考](https://github.com/EaseCation/mcpywrap/blob/main/docs/local-dependencies.md)。
58
+
59
+ **游戏运行边界:** 测试和正式游戏都使用内置 Python,不读取 `dependencies`,也不继承工具环境的 `site-packages`。`add`、`sync --install` 和 ModSDK 安装均不向游戏安装模块。游戏所需代码与资源必须随 Addon 携带,或由用户加载相应 Mod;纯 Python 库也不会自动复制,仍需适配游戏解释器和 ModSDK。原生扩展及依赖外部安装步骤、系统环境的库不能直接用于游戏;包内 `.pyd/.dll/.so/.so.*(版本号)/.dylib` 会阻止构建和启动。
58
60
 
59
61
  ## 构建与日常开发
60
62
 
@@ -73,12 +75,14 @@ mcpy add --path "../shared-addon"
73
75
 
74
76
  ### 打包分发
75
77
 
76
- 在项目根目录执行 `mcpy package`,会先构建项目及 Python 包/本地 Addon 依赖,再生成 `dist/<项目名>-<版本>.zip`。名称和版本读取 `pyproject.toml` 的 `[project]`;不需要配置 `target_dir`。
78
+ 在项目根目录执行 `mcpy package`,会组装项目及已识别的 Addon 依赖,再生成 `dist/<项目名>-<版本>.zip`;仅开发环境的包不会进入 ZIP。名称和版本读取 `pyproject.toml` 的 `[project]`;不需要配置 `target_dir`。
77
79
 
78
80
  Addon ZIP 内为 `<项目名>_bp/`、`<项目名>_rp/`(仅包含实际构建出的包);地图 ZIP 根目录直接包含存档数据、行为包、资源包及世界包配置。地图默认保留独立包,使用 `mcpy package --merge`(或 `-m`)按构建规则合并依赖资源。构建产物中的空目录会保留。
79
81
 
80
82
  重复打包成功后会替换同名 ZIP;失败时保留已有 ZIP,临时文件自动清理。
81
83
 
84
+ `publish` 向 PyPI 发布 Python 分发包,与游戏 ZIP 分发不同。本地测试可配置多个独立 Addon,游戏不会解析 Python 依赖图;构建成功不保证兼容,发布前应检查产物并在游戏中验证加载日志。
85
+
82
86
  ## 游戏启动与排查
83
87
 
84
88
  通常不需要手动指定游戏路径。mcpywrap 会优先查找 MC Studio 登记的安装,必要时搜索固定磁盘中的标准下载目录,并跳过不完整的引擎版本。
@@ -146,7 +150,7 @@ mcpy --project D:\tests\server stop --session <id> --json
146
150
  也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
147
151
  无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
148
152
  两端使用跨平台 `scripts/bootstrap.py` 安装或复用各自的 CLI,Windows 也可用 `bootstrap.ps1`。脚本分别报告本机与远端能力;Skill、CLI 与 Windows 服务需要分别检查,Git/可编辑安装可能缺少登录组件。
149
- 局域网流程请使用 mcpywrap 0.3.6 或更高版本,可从 `v0.3.6` 标签安装配套 Skill。实际能力仍以脚本检测为准;更新 Skill 不会自动更新两端 CLI。
153
+ 推荐使用 mcpywrap 0.3.7 或更高版本,并从 `v0.3.7` 标签安装配套 Skill,以获得依赖边界提示与校验。实际能力仍以脚本检测为准;更新 Skill 不会自动更新两端 CLI。
150
154
 
151
155
  安装后可直接描述任务:
152
156
 
@@ -0,0 +1,41 @@
1
+ # 复用本地 Addon
2
+
3
+ 已有的 Addon 可以直接作为依赖,不需要先初始化或安装。
4
+
5
+ ```powershell
6
+ mcpy add --path "../shared-addon"
7
+ mcpy remove --path "../shared-addon"
8
+ ```
9
+
10
+ 请选择 Addon 根目录,例如:
11
+
12
+ ```text
13
+ shared-addon/
14
+ behavior_pack/manifest.json
15
+ resource_pack/manifest.json
16
+ ```
17
+
18
+ 可以只有行为包或资源包;目录名也支持工具识别的 `BehaviorPack*`、`ResourcePack*` 等名称。
19
+ 不支持直接引用单个包目录、地图、普通源码目录,或同一 Addon 中多个同类型包。
20
+
21
+ 引用保存在 `tool.mcpywrap.local_dependencies`,相对路径以声明它的项目为基准。
22
+ 移除引用不会删除源目录。已有 mcpywrap 项目的子依赖会一并解析;循环依赖或失效路径会报错。
23
+
24
+ `mcpy add "package-name>=1.0"` 在 mcpy 工具环境安装包。只有能从本地安装来源解析出有效 Addon 的内容参与组装;
25
+ 普通 Python 包或未识别到 Addon 内容的 wheel 标记为“仅开发环境”,不会复制 `site-packages`。
26
+ 本地路径不会自动转换为可分发的包依赖;共享项目时需同步引用目录。
27
+ 本地引用的包依赖未安装时,按警告显式安装;版本约束与环境标记检查的是工具 Python,不代表游戏兼容性。
28
+
29
+ 测试和正式游戏都使用游戏内置 Python,不读取 `dependencies`,不自动安装或加载其他 Mod。
30
+ 需要的纯 Python 代码及资源必须包含在 Addon 中,并适配游戏解释器、标准库及 ModSDK。
31
+ 游戏包内的 `.pyd`、`.dll`、`.so`、版本化 `.so.*`、`.dylib` 会在构建、打包、运行和编辑前报错,
32
+ 不再静默过滤;`dev` 检测到这类文件时保留原输出,移除问题文件后可恢复更新。
33
+ 开发工具可以使用二进制包,只要这些文件不放在游戏包中。依赖外部安装步骤或系统环境的库也不能直接用于游戏;
34
+ 当前校验不会分析所有 Python import,仍需检查 ZIP 内容和实际游戏日志。
35
+
36
+ 验收解包产物时,避免同时加载相同 manifest UUID 的源码包与产物,防止游戏选中旧内容。
37
+
38
+ 构建时主项目优先于依赖;同级依赖按声明顺序处理,后者覆盖重复内容。
39
+ 这不代表游戏运行时的资源包优先级。修改依赖声明后重启 `mcpy dev`。
40
+
41
+ 遇到结构错误先核对目录和清单;查看参数使用 `mcpy add --help`。
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ """mcpywrap - 我的世界中国版 Addon 开发依赖、构建与游戏测试工具。"""
3
+
4
+ __version__ = '0.3.7'
@@ -9,7 +9,8 @@ from urllib.request import url2pathname
9
9
 
10
10
  from packaging.requirements import Requirement, InvalidRequirement
11
11
  from packaging.utils import canonicalize_name
12
- from ..dependencies import DependencyError, addon_directories, canonical_path, declarations, read_project, resolve_path
12
+ from ..dependencies import (DependencyError, DependencyStatus, addon_directories, canonical_path,
13
+ declarations, read_project, resolve_path, validate_game_files)
13
14
  from .AddonsPack import AddonsPack
14
15
 
15
16
 
@@ -42,6 +43,8 @@ class DependencyManager:
42
43
  self.dependency_map = {}
43
44
  self.root_node = None
44
45
  self.warnings = []
46
+ self.errors = []
47
+ self.statuses = {}
45
48
  self._nodes, self._active, self._ordered = {}, [], []
46
49
 
47
50
  def find_dependency_path(self, package_name):
@@ -60,6 +63,10 @@ class DependencyManager:
60
63
  config.setdefault('project', {})['dependencies'] = dependencies
61
64
  root_id = re.sub(r'[^\w.-]', '_', project_name)[:60] + '_' + hashlib.sha256(canonical_path(project_path).encode('utf-8')).hexdigest()[:12]
62
65
  root = DependencyNode(project_name, AddonsPack(root_id, project_path, is_origin=True))
66
+ folders = [root.addon_pack.behavior_pack_dir, root.addon_pack.resource_pack_dir]
67
+ if config.get('tool', {}).get('mcpywrap', {}).get('project_type') == 'map':
68
+ folders = [Path(project_path) / name for name in ('behavior_packs', 'resource_packs')]
69
+ validate_game_files(project_path, folders)
63
70
  self.root_node = root
64
71
  key = canonical_path(project_path)
65
72
  self._nodes[key] = root
@@ -67,8 +74,8 @@ class DependencyManager:
67
74
  self._process(root, config)
68
75
  self._active.pop()
69
76
  self.dependency_map = {canonical_path(n.addon_pack.path): n.addon_pack for n in self._ordered}
70
- if self.warnings and not allow_missing:
71
- raise DependencyError('\n'.join(self.warnings))
77
+ if self.errors and not allow_missing:
78
+ raise DependencyError('\n'.join(self.errors))
72
79
  return root
73
80
 
74
81
  def _process(self, parent, config):
@@ -78,12 +85,14 @@ class DependencyManager:
78
85
  if entry.kind == 'local':
79
86
  path = str(resolve_path(source, entry.value))
80
87
  self._visit(parent, path, Path(path).name)
88
+ self._status(source, entry, 'addon')
81
89
  continue
82
90
  try:
83
91
  requirement = Requirement(entry.value)
84
92
  except InvalidRequirement as exc:
85
93
  raise DependencyError(f'无效的 Python 包声明: {entry.value}') from exc
86
94
  if requirement.marker and not requirement.marker.evaluate():
95
+ self._status(source, entry, 'inactive', '当前工具 Python 环境不满足环境标记,未启用;不代表游戏兼容性。')
87
96
  continue
88
97
  try:
89
98
  dist = metadata.distribution(requirement.name)
@@ -95,6 +104,7 @@ class DependencyManager:
95
104
  continue
96
105
  path = self.find_dependency_path(entry.value)
97
106
  if not path:
107
+ self._development_only(source, entry)
98
108
  continue
99
109
  direct = dist.read_text('direct_url.json')
100
110
  if not Path(path).is_dir() and direct and 'dir_info' in json.loads(direct):
@@ -107,11 +117,30 @@ class DependencyManager:
107
117
  for p in Path(path).iterdir())
108
118
  if has_mcpy or looks_like_addon:
109
119
  self._visit(parent, path, canonicalize_name(requirement.name))
120
+ self._status(source, entry, 'addon')
121
+ else:
122
+ self._development_only(source, entry)
110
123
  except (DependencyError, OSError, ValueError) as exc:
111
124
  raise DependencyError(f'来源 {source}\n依赖 {entry.value!r}: {exc}') from exc
112
125
 
113
126
  def _missing(self, source, value, reason):
114
- self.warnings.append(f'{source}: {value} {reason};请显式执行 mcpy add "{value}"')
127
+ from ..dependencies import DependencyDeclaration
128
+ message = f'{source}: {value} {reason};请显式执行 mcpy add "{value}"'
129
+ self.errors.append(message)
130
+ self.warnings.append(message)
131
+ self._status(source, DependencyDeclaration('package', value), 'unavailable', message)
132
+
133
+ def _status(self, source, entry, state, message=''):
134
+ self.statuses[(canonical_path(source), entry.kind, entry.value)] = DependencyStatus(state, message)
135
+
136
+ def status_for(self, source, entry):
137
+ return self.statuses[(canonical_path(source), entry.kind, entry.value)]
138
+
139
+ def _development_only(self, source, entry):
140
+ message = (f'来源 {source},依赖 {entry.value!r}: 未识别到可组装内容;'
141
+ '仅安装于工具环境,不会被打包或供游戏导入。')
142
+ self.warnings.append(message)
143
+ self._status(source, entry, 'development_only', message)
115
144
 
116
145
  def _visit(self, parent, path, name):
117
146
  key = canonical_path(path)
@@ -124,6 +153,7 @@ class DependencyManager:
124
153
  if config.get('tool', {}).get('mcpywrap', {}).get('project_type', 'addon') != 'addon':
125
154
  raise DependencyError(f'仅支持 Addon 目录依赖: {path}')
126
155
  folders = addon_directories(path)
156
+ validate_game_files(path, folders.values())
127
157
  suffix = hashlib.sha256(key.encode('utf-8')).hexdigest()[:12]
128
158
  safe_name = re.sub(r'[^\w.-]', '_', name)[:60] or 'addon'
129
159
  addon = AddonsPack(f'{safe_name}_{suffix}', path)
@@ -2,6 +2,7 @@
2
2
  import os
3
3
  import shutil
4
4
  from pathlib import Path
5
+ from ..command_context import report_dependency_warnings
5
6
  from ..dependencies import DependencyError, DependencyService, addon_directories, read_project
6
7
  from .AddonsPack import AddonsPack
7
8
  from .MapPack import MapPack
@@ -30,6 +31,7 @@ class AddonProjectBuilder:
30
31
  def initialize(self):
31
32
  addon_directories(self.source_dir)
32
33
  self.dependency_manager = DependencyService(self.source_dir).resolve()
34
+ report_dependency_warnings(self.dependency_manager)
33
35
  self.dependency_tree = self.dependency_manager.root_node
34
36
  self.origin_addon = self.dependency_tree.addon_pack
35
37
  self.packs = list(self.dependency_manager.get_all_dependencies().values()) + [self.origin_addon]
@@ -60,6 +62,7 @@ class MapProjectBuilder:
60
62
  def build(self):
61
63
  try:
62
64
  manager = DependencyService(self.source_dir).resolve()
65
+ report_dependency_warnings(manager)
63
66
  packs = list(manager.get_all_dependencies().values())
64
67
  root = manager.root_node.addon_pack
65
68
  validate_target(self.target_dir, self.source_dir, packs + [root])
@@ -4,7 +4,7 @@ import threading
4
4
  from pathlib import Path
5
5
  from watchdog.observers import Observer
6
6
  from watchdog.events import FileSystemEventHandler
7
- from ..dependencies import DependencyService
7
+ from ..dependencies import DependencyService, is_native_file, validate_game_files
8
8
  from .AddonsPack import AddonsPack
9
9
  from .assembly import is_within, pack_files, rebuild_file, validate_target
10
10
 
@@ -17,7 +17,8 @@ class FileChangeHandler(FileSystemEventHandler):
17
17
  self.is_dependency, self.dependency_name = is_dependency, dependency_name
18
18
 
19
19
  def _should_ignore_path(self, path):
20
- return is_within(path, self.target_dir) or self.addon_pack.should_exclude(path) and Path(path).name not in ('manifest.json', 'pack_manifest.json')
20
+ return is_within(path, self.target_dir) or (self.addon_pack.should_exclude(path)
21
+ and Path(path).name not in ('manifest.json', 'pack_manifest.json') and not is_native_file(path))
21
22
 
22
23
  def _process_event(self, event, event_type):
23
24
  if self._should_ignore_path(event.src_path):
@@ -117,18 +118,27 @@ class ProjectWatcher:
117
118
 
118
119
  def rebuild(self, kind, relative):
119
120
  with self.lock:
120
- destination = Path(getattr(self.target_addon_pack, kind + '_pack_dir')) / relative
121
- rebuild_file(self.packs, kind, relative, destination)
122
- self.known_files.add((kind, relative))
123
- return str(destination)
121
+ self.validate_sources()
122
+ return self._rebuild(kind, relative)
123
+
124
+ def _rebuild(self, kind, relative):
125
+ destination = Path(getattr(self.target_addon_pack, kind + '_pack_dir')) / relative
126
+ rebuild_file(self.packs, kind, relative, destination)
127
+ self.known_files.add((kind, relative))
128
+ return str(destination)
124
129
 
125
130
  def rebuild_all(self):
126
131
  with self.lock:
132
+ self.validate_sources()
127
133
  current = {(kind, rel) for p in self.packs for kind in ('behavior', 'resource') for rel in pack_files(p, kind)}
128
134
  for kind, relative in sorted(self.known_files | current):
129
- self.rebuild(kind, relative)
135
+ self._rebuild(kind, relative)
130
136
  self.known_files = current
131
137
 
138
+ def validate_sources(self):
139
+ for pack in self.packs:
140
+ validate_game_files(pack.path, [pack.behavior_pack_dir, pack.resource_pack_dir])
141
+
132
142
  def start(self):
133
143
  self.multi_watcher.start_all()
134
144
 
@@ -6,7 +6,7 @@ import os
6
6
  import sys
7
7
  import tempfile
8
8
  import traceback
9
- from dataclasses import dataclass
9
+ from dataclasses import dataclass, field
10
10
  from pathlib import Path
11
11
 
12
12
  import click
@@ -19,6 +19,7 @@ class CommandContext:
19
19
  json_output: bool = False
20
20
  remote: str = None
21
21
  local: bool = False
22
+ warnings: list = field(default_factory=list)
22
23
 
23
24
 
24
25
  _context = contextvars.ContextVar('mcpy_context', default=None)
@@ -44,6 +45,17 @@ def human_interaction():
44
45
  return current is not None and not non_interactive() and not json_output()
45
46
 
46
47
 
48
+ def report_dependency_warnings(manager):
49
+ """CLI 展示与 JSON 共用诊断;解析服务本身不进行输出。"""
50
+ current = _context.get()
51
+ for message in manager.warnings:
52
+ if current is not None:
53
+ if message in current.warnings:
54
+ continue
55
+ current.warnings.append(message)
56
+ click.secho(message, fg='yellow', err=True)
57
+
58
+
47
59
  @contextlib.contextmanager
48
60
  def project_scope(path):
49
61
  previous = _context.get()
@@ -117,6 +129,8 @@ class OperationGroup(click.Group):
117
129
  data = {'ok': code == 0, 'error': None, 'hint': None}
118
130
  if isinstance(result, dict):
119
131
  data.update(result)
132
+ if state.warnings:
133
+ data['warnings'] = list(dict.fromkeys([*data.get('warnings', []), *state.warnings]))
120
134
  # ASCII JSON survives both GBK consoles and UTF-8 subprocess pipes losslessly.
121
135
  click.echo(json.dumps(data, ensure_ascii=True), file=output)
122
136
  elif code:
@@ -1,8 +1,6 @@
1
1
  """添加 Python 包或本地 Addon 目录依赖。"""
2
- import os
3
- from pathlib import Path
4
2
  import click
5
- from ..command_context import OperationCommand, project_dir as current_project, non_interactive, require_project
3
+ from ..command_context import OperationCommand, project_dir as current_project, report_dependency_warnings
6
4
  from ..dependencies import DependencyDeclaration, DependencyError, DependencyService
7
5
  from .dependency_prompt import prompt_dependency, require_interactive
8
6
 
@@ -11,7 +9,7 @@ from .dependency_prompt import prompt_dependency, require_interactive
11
9
  @click.argument('package', required=False)
12
10
  @click.option('--path', 'local_path', help='直接引用本地 Addon 根目录,不安装目录')
13
11
  def add_cmd(package, local_path):
14
- """添加依赖;不带参数时进入类型选择向导。"""
12
+ """添加依赖;包安装到 mcpy 工具环境,不安装到游戏。无参数进入向导。"""
15
13
  if package is not None and local_path is not None:
16
14
  raise click.UsageError('包名与 --path 不能同时使用')
17
15
  if not (current_project() / 'pyproject.toml').exists():
@@ -24,13 +22,15 @@ def add_cmd(package, local_path):
24
22
  entry = DependencyDeclaration('local' if local_path is not None else 'package', local_path if local_path is not None else package)
25
23
  service = DependencyService(str(current_project()))
26
24
  if entry.kind == 'local':
27
- changed, warnings = service.add_local(entry.value)
28
- for warning in warnings:
29
- click.secho(warning, fg='yellow')
25
+ changed, _ = service.add_local(entry.value)
30
26
  else:
31
- click.echo(f'正在安装 {entry.value}...')
27
+ click.echo(f'正在向 mcpy 工具环境安装 {entry.value}(非游戏环境)...')
32
28
  changed = service.add_package(entry.value)
29
+ manager = service.last_resolution
30
+ report_dependency_warnings(manager)
33
31
  click.secho('依赖已添加' if changed else '依赖已存在', fg='green')
34
- return {'changed': changed, 'dependency': entry.value, 'source': entry.kind, 'warnings': warnings if entry.kind == 'local' else []}
32
+ return {'changed': changed, 'dependency': entry.value, 'source': entry.kind,
33
+ 'classification': 'addon' if entry.kind == 'local' else manager.status_for(current_project(), entry).state,
34
+ 'warnings': manager.warnings}
35
35
  except (DependencyError, OSError) as exc:
36
36
  raise click.ClickException(str(exc)) from exc
@@ -33,12 +33,12 @@ def build_cmd(merge):
33
33
  target_dir = os.path.normpath(os.path.join(str(current_project()), target_dir))
34
34
 
35
35
  # 实际构建
36
- if not build(str(current_project()), target_dir, force_merge=merge):
36
+ if not build(str(current_project()), target_dir, force_merge=merge, raise_errors=True):
37
37
  raise click.ClickException('构建失败')
38
38
  return {'output': target_dir}
39
39
 
40
40
 
41
- def build(source_dir, target_dir, force_merge: bool = False):
41
+ def build(source_dir, target_dir, force_merge: bool = False, raise_errors=False):
42
42
  """
43
43
  执行项目构建
44
44
 
@@ -72,6 +72,8 @@ def build(source_dir, target_dir, force_merge: bool = False):
72
72
  click.secho('✅ 构建成功!项目已生成到目标目录。', fg="green")
73
73
  return True
74
74
  else:
75
+ if raise_errors:
76
+ raise click.ClickException(f'构建失败: {error}')
75
77
  click.secho(f'❌ 构建失败: ', fg="red", nl=False)
76
78
  click.secho(f'{error}', fg="bright_red")
77
79
  return False
@@ -12,7 +12,7 @@ def require_interactive():
12
12
 
13
13
 
14
14
  def prompt_dependency():
15
- kind = click.prompt('依赖类型:1 Python 包,2 本地 Addon 目录', type=click.Choice(['1', '2']), default='1')
15
+ kind = click.prompt('依赖类型:1 Python 包(安装于工具环境),2 本地 Addon 目录', type=click.Choice(['1', '2']), default='1')
16
16
  value = click.prompt('包名/版本约束' if kind == '1' else 'Addon 目录').strip()
17
17
  if kind == '1':
18
18
  try:
@@ -42,6 +42,9 @@ def open_edit(project_dir=None, engine_overrides=None, raise_errors=False):
42
42
  config = read_project(base_dir)
43
43
  project_name = config.get('project', {}).get('name', 'project')
44
44
  project_type = config.get('tool', {}).get('mcpywrap', {}).get('project_type', 'addon')
45
+ all_packs = _setup_dependencies(project_name, base_dir, raise_errors=raise_errors)
46
+ if all_packs is None:
47
+ return False
45
48
  try:
46
49
  engine = discover_engines(base_dir, engine_overrides).require_engine()
47
50
  require_resources(engine, 'editor')
@@ -53,9 +56,6 @@ def open_edit(project_dir=None, engine_overrides=None, raise_errors=False):
53
56
  raise
54
57
  click.echo(str(exc))
55
58
  return False
56
- all_packs = _setup_dependencies(project_name, base_dir)
57
- if all_packs is None:
58
- return False
59
59
  studio_config_path = os.path.join(base_dir, 'studio.json')
60
60
  addon_packs_dirs = []
61
61
  for pack in all_packs:
@@ -1,6 +1,6 @@
1
1
  """初始化项目:向导与显式参数使用同一服务。"""
2
2
  import click
3
- from ..command_context import OperationCommand, project_dir, non_interactive
3
+ from ..command_context import OperationCommand, project_dir, non_interactive, report_dependency_warnings
4
4
  from ..project_init import initialize_project, detect_project
5
5
  from ..dependencies import DependencyService
6
6
  from .dependency_prompt import prompt_dependency
@@ -33,10 +33,9 @@ def init():
33
33
  while click.confirm('是否添加依赖?', default=False):
34
34
  entry = prompt_dependency()
35
35
  if entry.kind == 'local':
36
- _, warnings = service.add_local(entry.value)
37
- for warning in warnings:
38
- click.echo(warning, err=True)
36
+ service.add_local(entry.value)
39
37
  else:
40
38
  service.add_package(entry.value)
39
+ report_dependency_warnings(service.last_resolution)
41
40
  click.echo('项目已初始化;使用 mod 生成脚本、modsdk 安装 SDK、sync --install 安装项目。')
42
41
  return result
@@ -9,7 +9,7 @@ from ..minecraft.netease_modsdk import get_available_versions, download_and_inst
9
9
  @click.option('--version', help='安装指定版本')
10
10
  @click.option('--latest', is_flag=True, help='显式选择最新版本')
11
11
  def modsdk_cmd(list_versions, version, latest):
12
- """管理网易 ModSDK;非交互必须指定操作。"""
12
+ """在工具环境管理网易 ModSDK,不安装到游戏;非交互必须指定操作。"""
13
13
  if sum((list_versions, bool(version), latest)) > 1:
14
14
  raise click.UsageError('--list、--version、--latest 不能同时使用')
15
15
  if not any((list_versions, version, latest)) and non_interactive():
@@ -15,7 +15,7 @@ from ..builders.project_builder import AddonProjectBuilder, MapProjectBuilder
15
15
  @click.command(cls=OperationCommand)
16
16
  @click.option('--merge', '-m', is_flag=True, help='强制合并所有资源文件(仅地图项目)')
17
17
  def package_cmd(merge):
18
- """构建并打包可直接用于《我的世界》中国版市场发布的压缩包。"""
18
+ """组装 Addon/地图并生成分发 ZIP;游戏兼容性需实际测试。"""
19
19
  if not config_exists():
20
20
  raise click.ClickException('未找到配置文件。请先运行 `mcpy init` 初始化项目。')
21
21
 
@@ -97,7 +97,7 @@ def _generate_new_instance_config(base_dir, project_name):
97
97
 
98
98
  return level_id, config_path
99
99
 
100
- def _setup_dependencies(project_name, base_dir, raise_errors=False):
100
+ def _setup_dependencies(project_name, base_dir, raise_errors=False, report=None):
101
101
  """设置项目依赖"""
102
102
  from ..dependencies import DependencyService, DependencyError, addon_directories, read_project
103
103
  try:
@@ -106,6 +106,12 @@ def _setup_dependencies(project_name, base_dir, raise_errors=False):
106
106
  if project_type == 'addon':
107
107
  addon_directories(base_dir)
108
108
  manager = DependencyService(base_dir).resolve()
109
+ from ..command_context import report_dependency_warnings
110
+ if report is None:
111
+ report_dependency_warnings(manager)
112
+ else:
113
+ for message in manager.warnings:
114
+ report(message)
109
115
  packs = list(manager.get_all_dependencies().values())
110
116
  if project_type == 'addon':
111
117
  packs.append(manager.root_node.addon_pack)
@@ -407,7 +413,7 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
407
413
  return
408
414
 
409
415
  # 设置依赖
410
- all_packs = _setup_dependencies(project_name, base_dir)
416
+ all_packs = _setup_dependencies(project_name, base_dir, raise_errors=True)
411
417
  if all_packs is None:
412
418
  raise click.ClickException('依赖校验失败')
413
419
  auth_context = None
@@ -9,7 +9,7 @@ from ..dependencies import read_project, write_project
9
9
  @click.command(cls=OperationCommand)
10
10
  @click.option('--install', is_flag=True, help='同时在当前工具环境中可编辑安装项目')
11
11
  def sync_cmd(install):
12
- """同步包配置;--install 显式安装项目。"""
12
+ """同步包配置;--install 安装到 mcpy 工具环境,不安装到游戏。"""
13
13
  require_project()
14
14
  return sync_project(project_dir(), install)
15
15