py-wlcli 0.8.0__py3-none-any.whl

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 (320) hide show
  1. py_wlcli-0.8.0.dist-info/METADATA +139 -0
  2. py_wlcli-0.8.0.dist-info/RECORD +320 -0
  3. py_wlcli-0.8.0.dist-info/WHEEL +4 -0
  4. py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
  5. py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
  6. wl_cli/__init__.py +27 -0
  7. wl_cli/__main__.py +130 -0
  8. wl_cli/capabilities.py +79 -0
  9. wl_cli/cc/__init__.py +5 -0
  10. wl_cli/cc/checks.py +1019 -0
  11. wl_cli/cc/cli.py +370 -0
  12. wl_cli/cc/mutate.py +1350 -0
  13. wl_cli/cli_click.py +156 -0
  14. wl_cli/commands/__init__.py +84 -0
  15. wl_cli/commands/base.py +80 -0
  16. wl_cli/commands/buildcommands/__init__.py +1 -0
  17. wl_cli/commands/buildcommands/build.py +71 -0
  18. wl_cli/commands/buildcommands/build_utils.py +47 -0
  19. wl_cli/commands/buildcommands/builders/__init__.py +18 -0
  20. wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
  21. wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
  22. wl_cli/commands/buildcommands/builders/environment.py +81 -0
  23. wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
  24. wl_cli/commands/buildcommands/builders/stubs.py +58 -0
  25. wl_cli/commands/buildcommands/dist.py +326 -0
  26. wl_cli/commands/buildcommands/test.py +89 -0
  27. wl_cli/commands/clean/__init__.py +1 -0
  28. wl_cli/commands/clean/build.py +151 -0
  29. wl_cli/commands/clean/clean.py +294 -0
  30. wl_cli/commands/clean/clean_utils.py +77 -0
  31. wl_cli/commands/clean/core.py +236 -0
  32. wl_cli/commands/clean/dead_code/__init__.py +0 -0
  33. wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
  34. wl_cli/commands/clean/dead_code/detector.py +161 -0
  35. wl_cli/commands/clean/dead_code/fixer.py +176 -0
  36. wl_cli/commands/clean/dead_code/reporter.py +295 -0
  37. wl_cli/commands/clean/dead_code/utils.py +0 -0
  38. wl_cli/commands/clean/lfs.py +215 -0
  39. wl_cli/commands/clean/python.py +81 -0
  40. wl_cli/commands/clean/rust.py +91 -0
  41. wl_cli/commands/clean/utils/__init__.py +34 -0
  42. wl_cli/commands/clean/venv.py +91 -0
  43. wl_cli/commands/cleanup.py +73 -0
  44. wl_cli/commands/click_base.py +79 -0
  45. wl_cli/commands/config/__init__.py +1 -0
  46. wl_cli/commands/config/config.py +291 -0
  47. wl_cli/commands/debug/__init__.py +6 -0
  48. wl_cli/commands/debug/debug.py +309 -0
  49. wl_cli/commands/debug/debug_cache.py +86 -0
  50. wl_cli/commands/debug/debug_subagent.py +156 -0
  51. wl_cli/commands/debug/output_formatter.py +165 -0
  52. wl_cli/commands/debug/source_hasher.py +92 -0
  53. wl_cli/commands/format/__init__.py +13 -0
  54. wl_cli/commands/format/format_cache_adapter.py +125 -0
  55. wl_cli/commands/format/format_command.py +197 -0
  56. wl_cli/commands/format/format_coordinator.py +191 -0
  57. wl_cli/commands/format/format_executor.py +470 -0
  58. wl_cli/commands/format/format_outcome.py +44 -0
  59. wl_cli/commands/format/format_path_handler.py +1016 -0
  60. wl_cli/commands/format/format_progress.py +62 -0
  61. wl_cli/commands/format/format_reporter.py +31 -0
  62. wl_cli/commands/format/python_formatter.py +506 -0
  63. wl_cli/commands/format/rust_formatter.py +106 -0
  64. wl_cli/commands/gate/__init__.py +1 -0
  65. wl_cli/commands/gate/gate_command.py +862 -0
  66. wl_cli/commands/gate/preflight.py +445 -0
  67. wl_cli/commands/git/__init__.py +1 -0
  68. wl_cli/commands/git/config.py +169 -0
  69. wl_cli/commands/git/git_facts.py +60 -0
  70. wl_cli/commands/git/git_flow.py +86 -0
  71. wl_cli/commands/git/hook.py +112 -0
  72. wl_cli/commands/git/policy.py +216 -0
  73. wl_cli/commands/git/push_update.py +82 -0
  74. wl_cli/commands/initenv/__init__.py +1 -0
  75. wl_cli/commands/initenv/common/__init__.py +23 -0
  76. wl_cli/commands/initenv/common/check_git_bash.py +78 -0
  77. wl_cli/commands/initenv/common/error_handler.py +68 -0
  78. wl_cli/commands/initenv/common/exceptions.py +52 -0
  79. wl_cli/commands/initenv/components/__init__.py +4 -0
  80. wl_cli/commands/initenv/components/cargo_sync.py +240 -0
  81. wl_cli/commands/initenv/components/config_manager.py +106 -0
  82. wl_cli/commands/initenv/components/directory_utils.py +71 -0
  83. wl_cli/commands/initenv/components/git/__init__.py +9 -0
  84. wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
  85. wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
  86. wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
  87. wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
  88. wl_cli/commands/initenv/components/git/git_utility.py +254 -0
  89. wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
  90. wl_cli/commands/initenv/components/git_initializer.py +212 -0
  91. wl_cli/commands/initenv/components/i18n_manager.py +104 -0
  92. wl_cli/commands/initenv/components/log_manager.py +241 -0
  93. wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
  94. wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
  95. wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
  96. wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
  97. wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
  98. wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
  99. wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
  100. wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
  101. wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
  102. wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
  103. wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
  104. wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
  105. wl_cli/commands/initenv/components/rust_initializer.py +145 -0
  106. wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
  107. wl_cli/commands/initenv/components/symlink_manager.py +52 -0
  108. wl_cli/commands/initenv/components/venv_manager.py +118 -0
  109. wl_cli/commands/initenv/initenv.py +169 -0
  110. wl_cli/commands/initenv/services/__init__.py +11 -0
  111. wl_cli/commands/initenv/services/environment_initializer.py +288 -0
  112. wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
  113. wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
  114. wl_cli/commands/lfs/README.md +221 -0
  115. wl_cli/commands/lfs/__init__.py +5 -0
  116. wl_cli/commands/lfs/cli.py +106 -0
  117. wl_cli/commands/lfs/config.py +164 -0
  118. wl_cli/commands/lfs/hook.py +61 -0
  119. wl_cli/commands/lfs/initializer.py +531 -0
  120. wl_cli/commands/lfs/manager.py +95 -0
  121. wl_cli/commands/lfs/policy.py +165 -0
  122. wl_cli/commands/lfs/watcher.py +294 -0
  123. wl_cli/commands/lint/__init__.py +1 -0
  124. wl_cli/commands/lint/lint_command.py +297 -0
  125. wl_cli/commands/lint/lint_executor.py +409 -0
  126. wl_cli/commands/lint/lint_reporter.py +579 -0
  127. wl_cli/commands/lint/lint_run.py +258 -0
  128. wl_cli/commands/lint/mypy_config.py +90 -0
  129. wl_cli/commands/registry.py +103 -0
  130. wl_cli/commands/self/__init__.py +7 -0
  131. wl_cli/commands/self/common/__init__.py +8 -0
  132. wl_cli/commands/self/common/capabilities.py +30 -0
  133. wl_cli/commands/self/common/config.py +27 -0
  134. wl_cli/commands/self/common/version_cache.py +85 -0
  135. wl_cli/commands/self/handlers/__init__.py +19 -0
  136. wl_cli/commands/self/handlers/base_handler.py +28 -0
  137. wl_cli/commands/self/handlers/install_handler.py +85 -0
  138. wl_cli/commands/self/handlers/status_handler.py +66 -0
  139. wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
  140. wl_cli/commands/self/handlers/update_handler.py +102 -0
  141. wl_cli/commands/self/handlers/version_handler.py +16 -0
  142. wl_cli/commands/self/self_command.py +237 -0
  143. wl_cli/commands/self/services/__init__.py +15 -0
  144. wl_cli/commands/self/services/installation_manager.py +187 -0
  145. wl_cli/commands/self/services/local_installer.py +165 -0
  146. wl_cli/commands/self/services/pypi_updater.py +278 -0
  147. wl_cli/commands/self/services/windows_update.py +174 -0
  148. wl_cli/commands/testcommands/__init__.py +8 -0
  149. wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
  150. wl_cli/commands/testcommands/batch_processor.py +278 -0
  151. wl_cli/commands/testcommands/cache_manager.py +1156 -0
  152. wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
  153. wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
  154. wl_cli/commands/testcommands/progress_manager.py +79 -0
  155. wl_cli/commands/testcommands/python_env_detector.py +207 -0
  156. wl_cli/commands/testcommands/report_generator.py +101 -0
  157. wl_cli/commands/testcommands/single_test_executor.py +250 -0
  158. wl_cli/commands/testcommands/speed_checker.py +118 -0
  159. wl_cli/commands/testcommands/test_command_builder.py +165 -0
  160. wl_cli/commands/testcommands/test_commands.py +786 -0
  161. wl_cli/commands/testcommands/test_reporter.py +181 -0
  162. wl_cli/commands/testcommands/test_result_handler.py +96 -0
  163. wl_cli/commands/testcommands/test_runner.py +1343 -0
  164. wl_cli/commands/testcommands/test_utils.py +197 -0
  165. wl_cli/commands/testcommands/utils/__init__.py +17 -0
  166. wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
  167. wl_cli/commands/testcommands/utils/env_info.py +36 -0
  168. wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
  169. wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
  170. wl_cli/commands/testcommands/utils/project_root.py +61 -0
  171. wl_cli/commands/update/__init__.py +9 -0
  172. wl_cli/commands/update/services/__init__.py +21 -0
  173. wl_cli/commands/update/services/backup_manager.py +207 -0
  174. wl_cli/commands/update/services/component_manager.py +83 -0
  175. wl_cli/commands/update/services/conflict_resolver.py +189 -0
  176. wl_cli/commands/update/services/vendors_updater.py +85 -0
  177. wl_cli/commands/update/services/version_tracker.py +127 -0
  178. wl_cli/commands/update/update_command.py +270 -0
  179. wl_cli/commands/update/utils/__init__.py +3 -0
  180. wl_cli/commands/version/__init__.py +9 -0
  181. wl_cli/commands/version/version_command.py +155 -0
  182. wl_cli/commands/workflow.py +477 -0
  183. wl_cli/env/__init__.py +5 -0
  184. wl_cli/env/cli.py +167 -0
  185. wl_cli/env_status.py +157 -0
  186. wl_cli/envelope.py +91 -0
  187. wl_cli/exceptions.py +85 -0
  188. wl_cli/lib/__init__.py +3 -0
  189. wl_cli/lib/rust_utils.py +35 -0
  190. wl_cli/mcp/__init__.py +6 -0
  191. wl_cli/mcp/capabilities.py +19 -0
  192. wl_cli/mcp/cli.py +22 -0
  193. wl_cli/mcp/gitlab.py +553 -0
  194. wl_cli/mcp/k8s.py +344 -0
  195. wl_cli/mcp/protocol.py +159 -0
  196. wl_cli/platform.py +139 -0
  197. wl_cli/quality/__init__.py +18 -0
  198. wl_cli/utils/__init__.py +7 -0
  199. wl_cli/utils/cache.py +491 -0
  200. wl_cli/utils/cache_ledger.py +355 -0
  201. wl_cli/utils/cache_store.py +546 -0
  202. wl_cli/utils/config/__init__.py +27 -0
  203. wl_cli/utils/config/config_loader.py +163 -0
  204. wl_cli/utils/config/core.py +862 -0
  205. wl_cli/utils/config/defaults.py +142 -0
  206. wl_cli/utils/config/env_exporter.py +86 -0
  207. wl_cli/utils/config/env_parser.py +108 -0
  208. wl_cli/utils/config/validation.py +280 -0
  209. wl_cli/utils/error_handler.py +165 -0
  210. wl_cli/utils/file_copier.py +56 -0
  211. wl_cli/utils/file_operations.py +506 -0
  212. wl_cli/utils/language_utils.py +122 -0
  213. wl_cli/utils/lazy_import.py +240 -0
  214. wl_cli/utils/log_levels.py +35 -0
  215. wl_cli/utils/log_rotators.py +74 -0
  216. wl_cli/utils/logging.py +458 -0
  217. wl_cli/utils/output_context.py +142 -0
  218. wl_cli/utils/performance/__init__.py +1 -0
  219. wl_cli/utils/performance/decorators.py +111 -0
  220. wl_cli/utils/performance/exporter.py +38 -0
  221. wl_cli/utils/performance/models.py +16 -0
  222. wl_cli/utils/performance/monitor.py +161 -0
  223. wl_cli/utils/performance_monitor.py +32 -0
  224. wl_cli/utils/platform_adapter.py +131 -0
  225. wl_cli/utils/pre_commit.py +266 -0
  226. wl_cli/utils/pre_commit_config.py +72 -0
  227. wl_cli/utils/root.py +521 -0
  228. wl_cli/utils/root_fallback.py +256 -0
  229. wl_cli/utils/run_evidence.py +236 -0
  230. wl_cli/utils/structured_logger.py +18 -0
  231. wl_cli/utils/structured_logging/__init__.py +19 -0
  232. wl_cli/utils/structured_logging/config.py +337 -0
  233. wl_cli/utils/structured_logging/core.py +177 -0
  234. wl_cli/utils/structured_logging/env.py +56 -0
  235. wl_cli/utils/structured_logging/filtering.py +75 -0
  236. wl_cli/utils/structured_logging/filters.py +55 -0
  237. wl_cli/utils/structured_logging/formatters.py +251 -0
  238. wl_cli/utils/structured_logging/handlers.py +308 -0
  239. wl_cli/utils/structured_logging/logger.py +354 -0
  240. wl_cli/utils/subprocess.py +71 -0
  241. wl_cli/utils/subprocess_utils.py +260 -0
  242. wl_cli/utils/test_utils/__init__.py +5 -0
  243. wl_cli/utils/test_utils/test_filter.py +36 -0
  244. wl_cli/utils/uv_run.py +79 -0
  245. wl_cli/utils/uv_tool.py +117 -0
  246. wl_cli/utils/uv_tree.py +71 -0
  247. wl_cli/utils/uv_workspace.py +174 -0
  248. wl_cli/utils/version/__init__.py +15 -0
  249. wl_cli/utils/version/pypi_version_checker.py +95 -0
  250. wl_cli/utils/version/version_comparator.py +133 -0
  251. wl_cli/utils/version/version_detectors.py +312 -0
  252. wl_cli/utils/version/version_service.py +131 -0
  253. wl_cli/utils/version/version_updater.py +198 -0
  254. wl_cli/utils/wl_dir_updater.py +1440 -0
  255. wl_cli/utils/workspace_detector/__init__.py +28 -0
  256. wl_cli/utils/workspace_detector/core.py +161 -0
  257. wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
  258. wl_cli/utils/workspace_detector/exceptions.py +5 -0
  259. wl_cli/utils/workspace_detector/types.py +17 -0
  260. wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
  261. wl_cli/vendors/config/.codespellrc +2 -0
  262. wl_cli/vendors/config/dependencies.toml +10 -0
  263. wl_cli/vendors/config/mypy.ini +62 -0
  264. wl_cli/vendors/config/pyproject_defaults.toml +28 -0
  265. wl_cli/vendors/config/template_config.toml +24 -0
  266. wl_cli/vendors/config/tool_configs.toml +76 -0
  267. wl_cli/vendors/git/.gitignore +122 -0
  268. wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
  269. wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
  270. wl_cli/vendors/hooks/commit-msg.sample +24 -0
  271. wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
  272. wl_cli/vendors/hooks/post-update.sample +8 -0
  273. wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
  274. wl_cli/vendors/hooks/pre-commit +45 -0
  275. wl_cli/vendors/hooks/pre-commit.legacy +31 -0
  276. wl_cli/vendors/hooks/pre-commit.sample +49 -0
  277. wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
  278. wl_cli/vendors/hooks/pre-push.sample +60 -0
  279. wl_cli/vendors/hooks/pre-rebase.sample +169 -0
  280. wl_cli/vendors/hooks/pre-receive.sample +24 -0
  281. wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
  282. wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
  283. wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
  284. wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
  285. wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
  286. wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
  287. wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
  288. wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
  289. wl_cli/vendors/hooks/unix/post-update.sample +8 -0
  290. wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
  291. wl_cli/vendors/hooks/unix/pre-commit +190 -0
  292. wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
  293. wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
  294. wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
  295. wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
  296. wl_cli/vendors/hooks/unix/pre-push +417 -0
  297. wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
  298. wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
  299. wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
  300. wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
  301. wl_cli/vendors/hooks/update.sample +128 -0
  302. wl_cli/vendors/hooks/win/work.bat +63 -0
  303. wl_cli/vendors/project/.gitignore +90 -0
  304. wl_cli/vendors/project/.python-version +1 -0
  305. wl_cli/vendors/project/LICENSE +21 -0
  306. wl_cli/vendors/project/README.md +0 -0
  307. wl_cli/vendors/project/pyproject.toml +27 -0
  308. wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
  309. wl_cli/vendors/project/pyproject_workspace.toml +14 -0
  310. wl_cli/vendors/readme/README.md +120 -0
  311. wl_cli/vendors/rust/Cargo.toml +14 -0
  312. wl_cli/vendors/rust/lib/__init__.py +3 -0
  313. wl_cli/vendors/rust/lib/rust_utils.py +31 -0
  314. wl_cli/vendors/rust/src/lib.rs +13 -0
  315. wl_cli/vendors/rust/src/main.rs +3 -0
  316. wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
  317. wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
  318. wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
  319. wl_cli/vendors/sop_doc/python-sop.md +342 -0
  320. wl_cli/vendors/sop_doc/rust-readme.md +219 -0
@@ -0,0 +1,81 @@
1
+ """Environment detection and Python executable resolution utilities."""
2
+
3
+ import os
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ from ....exceptions import CommandError
8
+ from ....utils.logging import log_error, log_info
9
+ from ....utils.platform_adapter import PlatformAdapter
10
+ from ....utils.workspace_detector import WorkspaceDetector
11
+
12
+
13
+ class EnvironmentDetector:
14
+ """Detect project environment and resolve Python executable paths."""
15
+
16
+ @staticmethod
17
+ def is_rust_enabled() -> bool:
18
+ """
19
+ Check if Rust is enabled for this project.
20
+
21
+ Returns:
22
+ bool: True if Rust is enabled, False otherwise.
23
+ """
24
+ rust_dir = os.path.join(os.getcwd(), "rust")
25
+ cargo_toml = os.path.join(rust_dir, "Cargo.toml")
26
+ return os.path.exists(cargo_toml)
27
+
28
+ @staticmethod
29
+ def _create_venv() -> None:
30
+ """Create virtual environment using uv."""
31
+ try:
32
+ # Use uv to create virtual environment
33
+ cmd = ["uv", "venv"]
34
+ subprocess.run(
35
+ cmd,
36
+ check=True,
37
+ capture_output=False,
38
+ text=True,
39
+ )
40
+ log_info("✓ Virtual environment created successfully")
41
+ except subprocess.CalledProcessError as e:
42
+ log_error(f"Failed to create virtual environment: {e}")
43
+ raise CommandError(f"Failed to create virtual environment: {e}")
44
+ except Exception as e:
45
+ log_error(f"Unexpected error creating virtual environment: {e}")
46
+ raise CommandError(f"Failed to create virtual environment: {e}")
47
+
48
+ def resolve_python_executable(self) -> tuple[str | None, bool]:
49
+ """
50
+ Resolve the appropriate Python executable for the current environment.
51
+
52
+ Returns:
53
+ tuple: (python_executable_path, is_workspace) where:
54
+ - python_executable_path: Path to Python executable or None
55
+ - is_workspace: True if running in a workspace environment
56
+ """
57
+ detector = WorkspaceDetector()
58
+ is_workspace = detector.detect(Path.cwd())
59
+
60
+ if not is_workspace:
61
+ log_info("Not in uv workspace environment")
62
+ return "python", False
63
+
64
+ log_info("✓ uv workspace environment detected")
65
+ venv_root = detector.get_venv_path(Path.cwd())
66
+
67
+ if venv_root is None:
68
+ try:
69
+ venv_str = detector.get_active_venv_path_str(Path.cwd())
70
+ venv_root = Path(venv_str)
71
+ except Exception:
72
+ venv_root = None
73
+
74
+ if venv_root is None:
75
+ log_info("No venv found, creating local .venv for build...")
76
+ self._create_venv()
77
+ venv_root = Path(".venv")
78
+
79
+ if PlatformAdapter.is_windows():
80
+ return str((venv_root / "Scripts" / "python.exe").resolve()), True
81
+ return str((venv_root / "bin" / "python").resolve()), True
@@ -0,0 +1,39 @@
1
+ """Rust build utilities using maturin."""
2
+
3
+ import subprocess
4
+
5
+ from ....utils.logging import log_info, log_info_pair
6
+ from ....utils.subprocess import run_command
7
+
8
+
9
+ class RustBuilder:
10
+ """Handle Rust build processes using maturin."""
11
+
12
+ @staticmethod
13
+ def run_maturin_build(python_executable: str | None) -> None:
14
+ """Run maturin build process."""
15
+ command = ["maturin", "build", "--release", "--out", "dist"]
16
+ if python_executable:
17
+ command.extend(["-i", python_executable])
18
+ log_info(f"Trying to build with: {' '.join(command)}")
19
+ subprocess.run(command, check=True, capture_output=False, text=True)
20
+
21
+ @staticmethod
22
+ def run_maturin_develop() -> None:
23
+ """Run maturin develop for incremental builds."""
24
+ log_info_pair(
25
+ "Using maturin develop to build and install editable package...",
26
+ "使用 maturin develop 构建和安装可编辑包...",
27
+ )
28
+ # 使用maturin的原生增量编译功能
29
+ run_command(["maturin", "develop", "--skip-install"], capture_output=False)
30
+
31
+ @staticmethod
32
+ def build_full(python_executable: str | None) -> None:
33
+ """Perform a full build with maturin."""
34
+ RustBuilder.run_maturin_build(python_executable)
35
+
36
+ @staticmethod
37
+ def build_incremental() -> None:
38
+ """Perform an incremental build with maturin develop."""
39
+ RustBuilder.run_maturin_develop()
@@ -0,0 +1,58 @@
1
+ """Type stubs generation and management utilities."""
2
+
3
+ import os
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ from ....commands.format.python_formatter import generate_type_stubs
8
+ from ....utils.logging import log_error, log_info
9
+ from ....utils.uv_tool import is_running_in_uv_tool
10
+
11
+
12
+ class StubsManager:
13
+ """Manage type stubs generation and copying."""
14
+
15
+ @staticmethod
16
+ def generate_and_copy_stubs() -> None:
17
+ """Generate type stubs and copy them to the appropriate locations."""
18
+ try:
19
+ root = Path.cwd()
20
+ src_path = root / "src"
21
+ typings_path = root / "typings"
22
+
23
+ if not src_path.exists():
24
+ if not is_running_in_uv_tool():
25
+ log_info(f"Target {src_path} does not exist, skipping...")
26
+ return
27
+
28
+ log_info(f"Generating type stubs for {src_path} -> {typings_path}")
29
+ generate_type_stubs(
30
+ str(src_path), str(typings_path), os.environ.copy(), quiet=False
31
+ )
32
+ log_info("✓ Type stubs generated")
33
+
34
+ package_root = src_path / "wl_cli"
35
+ stub_root = typings_path / "wl_cli"
36
+
37
+ if stub_root.exists():
38
+ for pyi in stub_root.rglob("*.pyi"):
39
+ rel = pyi.relative_to(stub_root)
40
+ dest = package_root / rel
41
+ dest.parent.mkdir(parents=True, exist_ok=True)
42
+ shutil.copy2(pyi, dest)
43
+ log_info("✓ Type stubs copied into package source")
44
+ else:
45
+ for pyi in typings_path.rglob("*.pyi"):
46
+ try:
47
+ parts = list(pyi.parts)
48
+ if "wl_cli" in parts:
49
+ idx = parts.index("wl_cli")
50
+ rel = Path(*parts[idx + 1 :])
51
+ dest = package_root / rel
52
+ dest.parent.mkdir(parents=True, exist_ok=True)
53
+ shutil.copy2(pyi, dest)
54
+ except Exception:
55
+ continue
56
+
57
+ except Exception as e:
58
+ log_error(f"Failed to generate type stubs: {e}")
@@ -0,0 +1,326 @@
1
+ """Build dist command for WL Commands."""
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, cast
8
+
9
+ import tomllib
10
+
11
+ if TYPE_CHECKING:
12
+ from wl_cli.utils.workspace_detector.types import WorkspaceDetectionRules
13
+
14
+ import click
15
+
16
+ from ...exceptions import CommandError
17
+ from ...utils.logging import log_error, log_info
18
+ from ...utils.platform_adapter import PlatformAdapter
19
+ from ...utils.uv_workspace import is_uv_workspace
20
+ from ...utils.workspace_detector import WorkspaceDetectionError, WorkspaceDetector
21
+ from .. import Command
22
+ from ..format.python_formatter import generate_type_stubs
23
+
24
+
25
+ class BuildDistCommand(Command):
26
+ """Command to build distribution packages."""
27
+
28
+ @property
29
+ def name(self) -> str:
30
+ """Return the command name."""
31
+ return "dist"
32
+
33
+ @property
34
+ def help(self) -> str:
35
+ """Return the command help text."""
36
+ return "Build distribution packages"
37
+
38
+ def execute(self, *_: Any, **kwargs: Any) -> None:
39
+ """Execute the build dist command."""
40
+ # Determine the platform and run the appropriate build command
41
+ created_venv = False # Track if we created a new venv
42
+ try:
43
+ log_info("Building distribution packages...")
44
+ detector = WorkspaceDetector(self._build_rules(kwargs))
45
+ is_workspace = detector.detect(Path.cwd())
46
+ self._validate_workspace(detector, is_workspace, kwargs)
47
+ python_executable, created_venv = self._resolve_python_executable(
48
+ detector, is_workspace
49
+ )
50
+ self._generate_type_stubs()
51
+ self._run_build(python_executable)
52
+ self._cleanup_stubs()
53
+ self._cleanup_venv_if_needed(is_workspace, created_venv)
54
+ log_info("✓ Distribution packages built successfully")
55
+ except subprocess.CalledProcessError as e:
56
+ log_error(f"Failed to build distribution packages: {e}")
57
+ raise CommandError(f"Build dist failed with return code {e.returncode}")
58
+ except FileNotFoundError:
59
+ log_error(
60
+ "Build backend not found. Pure-Python projects need uv; Rust "
61
+ "projects need maturin. Ensure it is installed and in PATH."
62
+ )
63
+ raise CommandError("Build backend not found")
64
+ except WorkspaceDetectionError as e:
65
+ log_error(f"Workspace detection error: {e}")
66
+ raise CommandError(f"Build dist failed: {e}")
67
+ except Exception as e:
68
+ log_error(f"Unexpected error during build dist: {e}")
69
+ raise CommandError(f"Build dist failed: {e}")
70
+
71
+ def _detect_uv_workspace(self) -> bool:
72
+ """
73
+ Detect if we are in a uv workspace.
74
+ This is kept for backward compatibility with tests.
75
+
76
+ Returns:
77
+ bool: True if in a uv workspace, False otherwise.
78
+ """
79
+ return is_uv_workspace()
80
+
81
+ def _create_venv(self) -> None:
82
+ """Create virtual environment using uv."""
83
+ try:
84
+ # Use uv to create virtual environment
85
+ cmd = ["uv", "venv"]
86
+ subprocess.run(
87
+ cmd,
88
+ check=True,
89
+ capture_output=False,
90
+ text=True,
91
+ )
92
+ log_info("✓ Virtual environment created successfully")
93
+ except subprocess.CalledProcessError as e:
94
+ log_error(f"Failed to create virtual environment: {e}")
95
+ raise CommandError(f"Failed to create virtual environment: {e}")
96
+ except Exception as e:
97
+ log_error(f"Unexpected error creating virtual environment: {e}")
98
+ raise CommandError(f"Failed to create virtual environment: {e}")
99
+
100
+ def _build_rules(self, kwargs: dict[str, object]) -> "WorkspaceDetectionRules":
101
+ return cast(
102
+ "WorkspaceDetectionRules",
103
+ {
104
+ "uv_tree_min_roots": kwargs.get("ws_min_roots", 2),
105
+ "use_uv_tree": not kwargs.get("ws_disable_tree", False),
106
+ "use_uv_init": not kwargs.get("ws_disable_init", False),
107
+ "use_pyproject": not kwargs.get("ws_disable_pyproject", False),
108
+ "use_uv_lock": not kwargs.get("ws_disable_uv_lock", False),
109
+ },
110
+ )
111
+
112
+ def _validate_workspace(
113
+ self, detector: WorkspaceDetector, is_workspace: bool, kwargs: dict[str, object]
114
+ ) -> None:
115
+ validation = detector.validate(Path.cwd())
116
+ if not is_workspace and validation.details.get("pyproject.exists") == "True":
117
+ msg = "Detected pyproject but workspace detection failed; check tool.uv.workspace"
118
+ if kwargs.get("ws_strict", False):
119
+ log_error(msg)
120
+ raise CommandError(msg)
121
+ else:
122
+ log_info(f"Warning: {msg}")
123
+
124
+ def _resolve_python_executable(
125
+ self, detector: WorkspaceDetector, is_workspace: bool
126
+ ) -> tuple[str | None, bool]:
127
+ """Resolve Python executable and track if we created a new venv."""
128
+ if not is_workspace:
129
+ log_info("Not in uv workspace environment")
130
+ return None, False
131
+ log_info("✓ uv workspace environment detected")
132
+ venv_root = detector.get_venv_path(Path.cwd())
133
+ created_venv = False
134
+ if venv_root is None:
135
+ try:
136
+ venv_str = detector.getActiveVenvPath(Path.cwd())
137
+ venv_root = Path(venv_str)
138
+ except Exception:
139
+ venv_root = None
140
+ if venv_root is None:
141
+ log_info("No venv found, creating local .venv for build...")
142
+ self._create_venv()
143
+ venv_root = Path(".venv")
144
+ created_venv = True
145
+ if PlatformAdapter.is_windows():
146
+ return str((venv_root / "Scripts" / "python.exe").resolve()), created_venv
147
+ return str((venv_root / "bin" / "python").resolve()), created_venv
148
+
149
+ def _uses_maturin(self) -> bool:
150
+ """Return True when the project is a maturin/Rust distribution.
151
+
152
+ A pure-Python project (the normal case after #226) has neither a
153
+ maturin backend nor a ``rust/Cargo.toml``; it must build with
154
+ ``uv build`` and needs no Rust toolchain.
155
+ """
156
+ root = Path.cwd()
157
+ data: dict[str, Any] = {}
158
+ pyproject = root / "pyproject.toml"
159
+ if pyproject.exists():
160
+ try:
161
+ data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
162
+ except (OSError, tomllib.TOMLDecodeError):
163
+ data = {}
164
+ build_system = data.get("build-system") or {}
165
+ backend = str(build_system.get("build-backend") or "").lower()
166
+ return "maturin" in backend or (root / "rust" / "Cargo.toml").exists()
167
+
168
+ def _run_build(self, python_executable: str | None) -> None:
169
+ """Build the distribution with the project's own backend.
170
+
171
+ Rust/maturin projects keep using maturin; everything else (including
172
+ ``wl`` itself) uses ``uv build``.
173
+ """
174
+ if self._uses_maturin():
175
+ command = ["maturin", "build", "--release", "--out", "dist"]
176
+ if python_executable:
177
+ command.extend(["-i", python_executable])
178
+ else:
179
+ command = ["uv", "build", "--out-dir", "dist"]
180
+ if python_executable:
181
+ command.extend(["--python", python_executable])
182
+ log_info(f"Trying to build with: {' '.join(command)}")
183
+ subprocess.run(command, check=True, capture_output=False, text=True)
184
+
185
+ def _generate_type_stubs(self) -> None:
186
+ root = Path.cwd()
187
+ src_path = root / "src"
188
+ typings_path = root / "typings"
189
+ env = os.environ.copy()
190
+ if not src_path.exists():
191
+ log_error(f"Source directory not found for stubs: {src_path}")
192
+ return
193
+ log_info(f"Generating type stubs for {src_path} -> {typings_path}")
194
+ try:
195
+ generate_type_stubs(str(src_path), str(typings_path), env, quiet=False)
196
+ log_info("✓ Type stubs generated")
197
+
198
+ # Copy generated .pyi into package src so they are included in wheel
199
+ package_root = src_path / "wl_cli"
200
+ stub_root = typings_path / "wl_cli"
201
+ if stub_root.exists():
202
+ for pyi in stub_root.rglob("*.pyi"):
203
+ rel = pyi.relative_to(stub_root)
204
+ dest = package_root / rel
205
+ dest.parent.mkdir(parents=True, exist_ok=True)
206
+ shutil.copy2(pyi, dest)
207
+ log_info("✓ Type stubs copied into package source")
208
+ else:
209
+ # Fallback: copy all .pyi under typings preserving structure relative to src
210
+ for pyi in typings_path.rglob("*.pyi"):
211
+ try:
212
+ # attempt to find 'wl_cli' segment
213
+ parts = list(pyi.parts)
214
+ if "wl_cli" in parts:
215
+ idx = parts.index("wl_cli")
216
+ rel = Path(*parts[idx + 1 :])
217
+ dest = package_root / rel
218
+ dest.parent.mkdir(parents=True, exist_ok=True)
219
+ shutil.copy2(pyi, dest)
220
+ except Exception:
221
+ continue
222
+ except Exception as e:
223
+ log_error(f"Failed to generate type stubs: {e}")
224
+
225
+ def _cleanup_stubs(self) -> None:
226
+ try:
227
+ root = Path.cwd()
228
+ typings_path = root / "typings"
229
+ if typings_path.exists():
230
+ shutil.rmtree(typings_path, ignore_errors=True)
231
+ log_info("✓ typings directory removed")
232
+ src_path = root / "src"
233
+ if src_path.exists():
234
+ removed = 0
235
+ for p in src_path.rglob("*.pyi"):
236
+ try:
237
+ p.unlink()
238
+ removed += 1
239
+ except Exception:
240
+ pass
241
+ if removed:
242
+ log_info(f"✓ Removed {removed} .pyi files from src")
243
+ except Exception as e:
244
+ log_error(f"Failed to cleanup stubs: {e}")
245
+
246
+ def _cleanup_venv_if_needed(self, is_workspace: bool, created_venv: bool) -> None:
247
+ """Clean up .venv directory if in uv workspace environment.
248
+
249
+ Args:
250
+ is_workspace: Whether we are in a uv workspace environment
251
+ created_venv: Whether we created a new venv during this build
252
+ """
253
+ if not is_workspace:
254
+ # In non-workspace environments, preserve the user's existing venv
255
+ return
256
+
257
+ venv_path = Path.cwd() / ".venv"
258
+ if venv_path.exists():
259
+ # Only remove .venv if we created it during this build
260
+ # Preserve user's existing venv if it was already there
261
+ if created_venv:
262
+ try:
263
+ log_info(
264
+ "Removing temporary .venv directory created during build..."
265
+ )
266
+ shutil.rmtree(venv_path, ignore_errors=True)
267
+ log_info("✓ Temporary .venv directory removed")
268
+ except Exception as e:
269
+ log_error(f"Failed to remove .venv directory: {e}")
270
+ else:
271
+ log_info(
272
+ "Preserving existing .venv directory (not created during this build)"
273
+ )
274
+ else:
275
+ log_info("No .venv directory to remove")
276
+
277
+
278
+ @click.command(name="builddist", help="Build distribution packages")
279
+ @click.option(
280
+ "--ws-min-roots",
281
+ type=int,
282
+ default=2,
283
+ help="Minimum root packages to consider workspace (uv tree)",
284
+ )
285
+ @click.option(
286
+ "--ws-disable-tree", is_flag=True, default=False, help="Disable uv tree detection"
287
+ )
288
+ @click.option(
289
+ "--ws-disable-init", is_flag=True, default=False, help="Disable uv init detection"
290
+ )
291
+ @click.option(
292
+ "--ws-disable-pyproject",
293
+ is_flag=True,
294
+ default=False,
295
+ help="Disable pyproject workspace detection",
296
+ )
297
+ @click.option(
298
+ "--ws-disable-uv-lock",
299
+ is_flag=True,
300
+ default=False,
301
+ help="Disable uv.lock detection",
302
+ )
303
+ @click.option(
304
+ "--ws-strict",
305
+ is_flag=True,
306
+ default=False,
307
+ help="Fail build on abnormal workspace structure",
308
+ )
309
+ def builddist_command(
310
+ ws_min_roots,
311
+ ws_disable_tree,
312
+ ws_disable_init,
313
+ ws_disable_pyproject,
314
+ ws_disable_uv_lock,
315
+ ws_strict,
316
+ ):
317
+ """Build distribution packages."""
318
+ cmd = BuildDistCommand()
319
+ cmd.execute(
320
+ ws_min_roots=ws_min_roots,
321
+ ws_disable_tree=ws_disable_tree,
322
+ ws_disable_init=ws_disable_init,
323
+ ws_disable_pyproject=ws_disable_pyproject,
324
+ ws_disable_uv_lock=ws_disable_uv_lock,
325
+ ws_strict=ws_strict,
326
+ )
@@ -0,0 +1,89 @@
1
+ """
2
+ Test command for WL Commands.
3
+ """
4
+
5
+ import subprocess
6
+ import sys
7
+ from typing import Any
8
+
9
+ import click
10
+
11
+ from ...exceptions import CommandError
12
+ from ...utils.logging import log_error, log_info
13
+ from ...utils.output_context import get_output_config
14
+ from ...utils.subprocess import run_command
15
+ from .. import Command
16
+
17
+
18
+ class BuildTestCommand(Command):
19
+ """Command to build test environment and run unit tests."""
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ """Return the command name."""
24
+ return "test"
25
+
26
+ @property
27
+ def help(self) -> str:
28
+ """Return the command help text."""
29
+ return "Build test environment and run unit tests"
30
+
31
+ def execute(self, *_: Any, **kwargs: Any) -> None:
32
+ """Execute the build test command."""
33
+ # Get output configuration
34
+ quiet = kwargs.get("quiet", False)
35
+ verbose = kwargs.get("verbose", False)
36
+ config = get_output_config(override_quiet=quiet, override_verbose=verbose)
37
+ concise = config.concise_mode
38
+
39
+ # Run tests using Python's unittest module directly
40
+ try:
41
+ if not concise:
42
+ log_info("Building test environment and running tests...")
43
+
44
+ # Use -v for verbose output when not concise
45
+ if config.verbose:
46
+ cmd = [
47
+ sys.executable,
48
+ "-m",
49
+ "unittest",
50
+ "discover",
51
+ "-s",
52
+ "tests",
53
+ "-v",
54
+ ]
55
+ else:
56
+ cmd = [sys.executable, "-m", "unittest", "discover", "-s", "tests"]
57
+
58
+ # Run tests using python -m unittest discover
59
+ run_command(
60
+ cmd,
61
+ capture_output=False,
62
+ )
63
+
64
+ if not concise:
65
+ log_info("✓ Tests completed successfully")
66
+
67
+ except subprocess.CalledProcessError as e:
68
+ log_error(f"Failed to run tests: {e}")
69
+
70
+ raise CommandError(f"Test failed with return code {e.returncode}")
71
+ except FileNotFoundError:
72
+ log_error("Python interpreter not found.")
73
+
74
+ raise CommandError("Python interpreter not found")
75
+ except Exception as e:
76
+ log_error(f"Unexpected error during test execution: {e}")
77
+
78
+ raise CommandError(f"Test failed: {e}")
79
+
80
+
81
+ @click.command(name="buildtest", help="Build test environment and run unit tests")
82
+ @click.option(
83
+ "-q", "--quiet", is_flag=True, help="Suppress verbose output (concise mode)"
84
+ )
85
+ @click.option("-v", "--verbose", is_flag=True, help="Force verbose output")
86
+ def buildtest_command(quiet: bool, verbose: bool) -> None:
87
+ """Build test environment and run unit tests."""
88
+ cmd = BuildTestCommand()
89
+ cmd.execute(quiet=quiet, verbose=verbose)
@@ -0,0 +1 @@
1
+ """Clean command module."""