hatch-xclam 0.7.1.dev3__py3-none-any.whl → 0.8.0.dev1__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.
- hatch/__init__.py +1 -1
- hatch/cli/__init__.py +71 -0
- hatch/cli/__main__.py +1035 -0
- hatch/cli/cli_env.py +865 -0
- hatch/cli/cli_mcp.py +1965 -0
- hatch/cli/cli_package.py +566 -0
- hatch/cli/cli_system.py +136 -0
- hatch/cli/cli_utils.py +1289 -0
- hatch/cli_hatch.py +160 -2838
- hatch/mcp_host_config/__init__.py +10 -10
- hatch/mcp_host_config/adapters/__init__.py +34 -0
- hatch/mcp_host_config/adapters/base.py +170 -0
- hatch/mcp_host_config/adapters/claude.py +105 -0
- hatch/mcp_host_config/adapters/codex.py +104 -0
- hatch/mcp_host_config/adapters/cursor.py +83 -0
- hatch/mcp_host_config/adapters/gemini.py +75 -0
- hatch/mcp_host_config/adapters/kiro.py +78 -0
- hatch/mcp_host_config/adapters/lmstudio.py +79 -0
- hatch/mcp_host_config/adapters/registry.py +149 -0
- hatch/mcp_host_config/adapters/vscode.py +83 -0
- hatch/mcp_host_config/backup.py +5 -3
- hatch/mcp_host_config/fields.py +126 -0
- hatch/mcp_host_config/models.py +161 -456
- hatch/mcp_host_config/reporting.py +57 -16
- hatch/mcp_host_config/strategies.py +155 -87
- hatch/template_generator.py +1 -1
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
- hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
- tests/cli_test_utils.py +280 -0
- tests/integration/cli/__init__.py +14 -0
- tests/integration/cli/test_cli_reporter_integration.py +2439 -0
- tests/integration/mcp/__init__.py +0 -0
- tests/integration/mcp/test_adapter_serialization.py +173 -0
- tests/regression/cli/__init__.py +16 -0
- tests/regression/cli/test_color_logic.py +268 -0
- tests/regression/cli/test_consequence_type.py +298 -0
- tests/regression/cli/test_error_formatting.py +328 -0
- tests/regression/cli/test_result_reporter.py +586 -0
- tests/regression/cli/test_table_formatter.py +211 -0
- tests/regression/mcp/__init__.py +0 -0
- tests/regression/mcp/test_field_filtering.py +162 -0
- tests/test_cli_version.py +7 -5
- tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
- tests/unit/__init__.py +0 -0
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_adapter_protocol.py +138 -0
- tests/unit/mcp/test_adapter_registry.py +158 -0
- tests/unit/mcp/test_config_model.py +146 -0
- hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
- tests/integration/test_mcp_kiro_integration.py +0 -153
- tests/regression/test_mcp_codex_backup_integration.py +0 -162
- tests/regression/test_mcp_codex_host_strategy.py +0 -163
- tests/regression/test_mcp_codex_model_validation.py +0 -117
- tests/regression/test_mcp_kiro_backup_integration.py +0 -241
- tests/regression/test_mcp_kiro_cli_integration.py +0 -141
- tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
- tests/regression/test_mcp_kiro_host_strategy.py +0 -214
- tests/regression/test_mcp_kiro_model_validation.py +0 -116
- tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
- tests/test_mcp_atomic_operations.py +0 -276
- tests/test_mcp_backup_integration.py +0 -308
- tests/test_mcp_cli_all_host_specific_args.py +0 -496
- tests/test_mcp_cli_backup_management.py +0 -295
- tests/test_mcp_cli_direct_management.py +0 -456
- tests/test_mcp_cli_discovery_listing.py +0 -582
- tests/test_mcp_cli_host_config_integration.py +0 -823
- tests/test_mcp_cli_package_management.py +0 -360
- tests/test_mcp_cli_partial_updates.py +0 -859
- tests/test_mcp_environment_integration.py +0 -520
- tests/test_mcp_host_config_backup.py +0 -257
- tests/test_mcp_host_configuration_manager.py +0 -331
- tests/test_mcp_host_registry_decorator.py +0 -348
- tests/test_mcp_pydantic_architecture_v4.py +0 -603
- tests/test_mcp_server_config_models.py +0 -242
- tests/test_mcp_server_config_type_field.py +0 -221
- tests/test_mcp_sync_functionality.py +0 -316
- tests/test_mcp_user_feedback_reporting.py +0 -359
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
hatch/cli/cli_env.py
ADDED
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
"""Environment CLI handlers for Hatch.
|
|
2
|
+
|
|
3
|
+
This module contains handlers for environment management commands. Environments
|
|
4
|
+
provide isolated contexts for managing packages and their MCP server configurations.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
Basic Environment Management:
|
|
8
|
+
- hatch env create <name>: Create a new environment
|
|
9
|
+
- hatch env remove <name>: Remove an environment
|
|
10
|
+
- hatch env list: List all environments
|
|
11
|
+
- hatch env use <name>: Set current environment
|
|
12
|
+
- hatch env current: Show current environment
|
|
13
|
+
|
|
14
|
+
Python Environment Management:
|
|
15
|
+
- hatch env python init: Initialize Python virtual environment
|
|
16
|
+
- hatch env python info: Show Python environment info
|
|
17
|
+
- hatch env python remove: Remove Python virtual environment
|
|
18
|
+
- hatch env python shell: Launch interactive Python shell
|
|
19
|
+
- hatch env python add-hatch-mcp: Add hatch_mcp_server wrapper script
|
|
20
|
+
|
|
21
|
+
Handler Signature:
|
|
22
|
+
All handlers follow: (args: Namespace) -> int
|
|
23
|
+
- args.env_manager: HatchEnvironmentManager instance
|
|
24
|
+
- Returns: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
$ hatch env create my-project
|
|
28
|
+
$ hatch env use my-project
|
|
29
|
+
$ hatch env python init
|
|
30
|
+
$ hatch env python shell
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from argparse import Namespace
|
|
34
|
+
from typing import TYPE_CHECKING
|
|
35
|
+
|
|
36
|
+
from hatch.cli.cli_utils import (
|
|
37
|
+
EXIT_SUCCESS,
|
|
38
|
+
EXIT_ERROR,
|
|
39
|
+
request_confirmation,
|
|
40
|
+
ResultReporter,
|
|
41
|
+
ConsequenceType,
|
|
42
|
+
TableFormatter,
|
|
43
|
+
ColumnDef,
|
|
44
|
+
ValidationError,
|
|
45
|
+
format_validation_error,
|
|
46
|
+
format_info,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from hatch.environment_manager import HatchEnvironmentManager
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def handle_env_create(args: Namespace) -> int:
|
|
54
|
+
"""Handle 'hatch env create' command.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
args: Namespace with:
|
|
58
|
+
- env_manager: HatchEnvironmentManager instance
|
|
59
|
+
- name: Environment name
|
|
60
|
+
- description: Environment description
|
|
61
|
+
- python_version: Optional Python version
|
|
62
|
+
- no_python: Skip Python environment creation
|
|
63
|
+
- no_hatch_mcp_server: Skip hatch_mcp_server installation
|
|
64
|
+
- hatch_mcp_server_tag: Git tag for hatch_mcp_server
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Exit code (0 for success, 1 for error)
|
|
68
|
+
"""
|
|
69
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
70
|
+
name = args.name
|
|
71
|
+
description = getattr(args, "description", "")
|
|
72
|
+
python_version = getattr(args, "python_version", None)
|
|
73
|
+
create_python_env = not getattr(args, "no_python", False)
|
|
74
|
+
no_hatch_mcp_server = getattr(args, "no_hatch_mcp_server", False)
|
|
75
|
+
hatch_mcp_server_tag = getattr(args, "hatch_mcp_server_tag", None)
|
|
76
|
+
dry_run = getattr(args, "dry_run", False)
|
|
77
|
+
|
|
78
|
+
# Create reporter for unified output
|
|
79
|
+
reporter = ResultReporter("hatch env create", dry_run=dry_run)
|
|
80
|
+
reporter.add(ConsequenceType.CREATE, f"Environment '{name}'")
|
|
81
|
+
|
|
82
|
+
if create_python_env:
|
|
83
|
+
version_str = f" ({python_version})" if python_version else ""
|
|
84
|
+
reporter.add(ConsequenceType.CREATE, f"Python environment{version_str}")
|
|
85
|
+
|
|
86
|
+
if dry_run:
|
|
87
|
+
reporter.report_result()
|
|
88
|
+
return EXIT_SUCCESS
|
|
89
|
+
|
|
90
|
+
if env_manager.create_environment(
|
|
91
|
+
name,
|
|
92
|
+
description,
|
|
93
|
+
python_version=python_version,
|
|
94
|
+
create_python_env=create_python_env,
|
|
95
|
+
no_hatch_mcp_server=no_hatch_mcp_server,
|
|
96
|
+
hatch_mcp_server_tag=hatch_mcp_server_tag,
|
|
97
|
+
):
|
|
98
|
+
# Update reporter with actual Python environment details
|
|
99
|
+
if create_python_env and env_manager.is_python_environment_available():
|
|
100
|
+
python_exec = env_manager.python_env_manager.get_python_executable(name)
|
|
101
|
+
if python_exec:
|
|
102
|
+
python_version_info = env_manager.python_env_manager.get_python_version(name)
|
|
103
|
+
# Add details as child consequences would be ideal, but for now just report success
|
|
104
|
+
|
|
105
|
+
reporter.report_result()
|
|
106
|
+
return EXIT_SUCCESS
|
|
107
|
+
else:
|
|
108
|
+
reporter.report_error(f"Failed to create environment '{name}'")
|
|
109
|
+
return EXIT_ERROR
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def handle_env_remove(args: Namespace) -> int:
|
|
113
|
+
"""Handle 'hatch env remove' command.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
args: Namespace with:
|
|
117
|
+
- env_manager: HatchEnvironmentManager instance
|
|
118
|
+
- name: Environment name to remove
|
|
119
|
+
- dry_run: Preview changes without execution
|
|
120
|
+
- auto_approve: Skip confirmation prompt
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Exit code (0 for success, 1 for error)
|
|
124
|
+
|
|
125
|
+
Reference: R03 §3.1 (03-mutation_output_specification_v0.md)
|
|
126
|
+
"""
|
|
127
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
128
|
+
name = args.name
|
|
129
|
+
dry_run = getattr(args, "dry_run", False)
|
|
130
|
+
auto_approve = getattr(args, "auto_approve", False)
|
|
131
|
+
|
|
132
|
+
# Create reporter for unified output
|
|
133
|
+
reporter = ResultReporter("hatch env remove", dry_run=dry_run)
|
|
134
|
+
reporter.add(ConsequenceType.REMOVE, f"Environment '{name}'")
|
|
135
|
+
|
|
136
|
+
if dry_run:
|
|
137
|
+
reporter.report_result()
|
|
138
|
+
return EXIT_SUCCESS
|
|
139
|
+
|
|
140
|
+
# Show prompt and request confirmation unless auto-approved
|
|
141
|
+
if not auto_approve:
|
|
142
|
+
prompt = reporter.report_prompt()
|
|
143
|
+
if prompt:
|
|
144
|
+
print(prompt)
|
|
145
|
+
|
|
146
|
+
if not request_confirmation("Proceed?"):
|
|
147
|
+
format_info("Operation cancelled")
|
|
148
|
+
return EXIT_SUCCESS
|
|
149
|
+
|
|
150
|
+
if env_manager.remove_environment(name):
|
|
151
|
+
reporter.report_result()
|
|
152
|
+
return EXIT_SUCCESS
|
|
153
|
+
else:
|
|
154
|
+
reporter.report_error(f"Failed to remove environment '{name}'")
|
|
155
|
+
return EXIT_ERROR
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def handle_env_list(args: Namespace) -> int:
|
|
159
|
+
"""Handle 'hatch env list' command.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
args: Namespace with:
|
|
163
|
+
- env_manager: HatchEnvironmentManager instance
|
|
164
|
+
- pattern: Optional regex pattern to filter environments
|
|
165
|
+
- json: Optional flag for JSON output
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Exit code (0 for success)
|
|
169
|
+
|
|
170
|
+
Reference: R02 §2.1 (02-list_output_format_specification_v2.md)
|
|
171
|
+
"""
|
|
172
|
+
import json as json_module
|
|
173
|
+
import re
|
|
174
|
+
|
|
175
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
176
|
+
json_output: bool = getattr(args, 'json', False)
|
|
177
|
+
pattern: str = getattr(args, 'pattern', None)
|
|
178
|
+
environments = env_manager.list_environments()
|
|
179
|
+
|
|
180
|
+
# Apply pattern filter if specified
|
|
181
|
+
if pattern:
|
|
182
|
+
try:
|
|
183
|
+
regex = re.compile(pattern)
|
|
184
|
+
environments = [env for env in environments if regex.search(env.get("name", ""))]
|
|
185
|
+
except re.error as e:
|
|
186
|
+
format_validation_error(ValidationError(
|
|
187
|
+
f"Invalid regex pattern: {e}",
|
|
188
|
+
field="--pattern",
|
|
189
|
+
suggestion="Use a valid Python regex pattern"
|
|
190
|
+
))
|
|
191
|
+
return EXIT_ERROR
|
|
192
|
+
|
|
193
|
+
if json_output:
|
|
194
|
+
# JSON output per R02 §8.1
|
|
195
|
+
env_data = []
|
|
196
|
+
for env in environments:
|
|
197
|
+
env_name = env.get("name")
|
|
198
|
+
python_version = None
|
|
199
|
+
if env.get("python_environment", False):
|
|
200
|
+
python_info = env_manager.get_python_environment_info(env_name)
|
|
201
|
+
if python_info:
|
|
202
|
+
python_version = python_info.get("python_version")
|
|
203
|
+
|
|
204
|
+
packages_list = env_manager.list_packages(env_name)
|
|
205
|
+
pkg_names = [pkg["name"] for pkg in packages_list] if packages_list else []
|
|
206
|
+
|
|
207
|
+
env_data.append({
|
|
208
|
+
"name": env_name,
|
|
209
|
+
"is_current": env.get("is_current", False),
|
|
210
|
+
"python_version": python_version,
|
|
211
|
+
"packages": pkg_names
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
print(json_module.dumps({"environments": env_data}, indent=2))
|
|
215
|
+
return EXIT_SUCCESS
|
|
216
|
+
|
|
217
|
+
# Table output
|
|
218
|
+
print("Environments:")
|
|
219
|
+
|
|
220
|
+
# Define table columns per R10 §5.1 (simplified output - count only)
|
|
221
|
+
columns = [
|
|
222
|
+
ColumnDef(name="Name", width=15),
|
|
223
|
+
ColumnDef(name="Python", width=10),
|
|
224
|
+
ColumnDef(name="Packages", width=10, align="right"),
|
|
225
|
+
]
|
|
226
|
+
formatter = TableFormatter(columns)
|
|
227
|
+
|
|
228
|
+
for env in environments:
|
|
229
|
+
# Name with current marker
|
|
230
|
+
current_marker = "* " if env.get("is_current") else " "
|
|
231
|
+
name = f"{current_marker}{env.get('name')}"
|
|
232
|
+
|
|
233
|
+
# Python version
|
|
234
|
+
python_version = "-"
|
|
235
|
+
if env.get("python_environment", False):
|
|
236
|
+
python_info = env_manager.get_python_environment_info(env.get("name"))
|
|
237
|
+
if python_info:
|
|
238
|
+
python_version = python_info.get("python_version", "Unknown")
|
|
239
|
+
|
|
240
|
+
# Packages - show count only per R10 §5.1
|
|
241
|
+
packages_list = env_manager.list_packages(env.get("name"))
|
|
242
|
+
packages_count = str(len(packages_list)) if packages_list else "0"
|
|
243
|
+
|
|
244
|
+
formatter.add_row([name, python_version, packages_count])
|
|
245
|
+
|
|
246
|
+
print(formatter.render())
|
|
247
|
+
return EXIT_SUCCESS
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def handle_env_use(args: Namespace) -> int:
|
|
251
|
+
"""Handle 'hatch env use' command.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
args: Namespace with:
|
|
255
|
+
- env_manager: HatchEnvironmentManager instance
|
|
256
|
+
- name: Environment name to set as current
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Exit code (0 for success, 1 for error)
|
|
260
|
+
"""
|
|
261
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
262
|
+
name = args.name
|
|
263
|
+
dry_run = getattr(args, "dry_run", False)
|
|
264
|
+
|
|
265
|
+
# Create reporter for unified output
|
|
266
|
+
reporter = ResultReporter("hatch env use", dry_run=dry_run)
|
|
267
|
+
reporter.add(ConsequenceType.SET, f"Current environment → '{name}'")
|
|
268
|
+
|
|
269
|
+
if dry_run:
|
|
270
|
+
reporter.report_result()
|
|
271
|
+
return EXIT_SUCCESS
|
|
272
|
+
|
|
273
|
+
if env_manager.set_current_environment(name):
|
|
274
|
+
reporter.report_result()
|
|
275
|
+
return EXIT_SUCCESS
|
|
276
|
+
else:
|
|
277
|
+
reporter.report_error(f"Failed to set environment '{name}'")
|
|
278
|
+
return EXIT_ERROR
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def handle_env_current(args: Namespace) -> int:
|
|
282
|
+
"""Handle 'hatch env current' command.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
args: Namespace with:
|
|
286
|
+
- env_manager: HatchEnvironmentManager instance
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Exit code (0 for success)
|
|
290
|
+
"""
|
|
291
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
292
|
+
current_env = env_manager.get_current_environment()
|
|
293
|
+
print(f"Current environment: {current_env}")
|
|
294
|
+
return EXIT_SUCCESS
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def handle_env_python_init(args: Namespace) -> int:
|
|
299
|
+
"""Handle 'hatch env python init' command.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
args: Namespace with:
|
|
303
|
+
- env_manager: HatchEnvironmentManager instance
|
|
304
|
+
- hatch_env: Optional environment name (default: current)
|
|
305
|
+
- python_version: Optional Python version
|
|
306
|
+
- force: Force recreation if exists
|
|
307
|
+
- no_hatch_mcp_server: Skip hatch_mcp_server installation
|
|
308
|
+
- hatch_mcp_server_tag: Git tag for hatch_mcp_server
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Exit code (0 for success, 1 for error)
|
|
312
|
+
"""
|
|
313
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
314
|
+
hatch_env = getattr(args, "hatch_env", None)
|
|
315
|
+
python_version = getattr(args, "python_version", None)
|
|
316
|
+
force = getattr(args, "force", False)
|
|
317
|
+
no_hatch_mcp_server = getattr(args, "no_hatch_mcp_server", False)
|
|
318
|
+
hatch_mcp_server_tag = getattr(args, "hatch_mcp_server_tag", None)
|
|
319
|
+
dry_run = getattr(args, "dry_run", False)
|
|
320
|
+
|
|
321
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
322
|
+
|
|
323
|
+
# Create reporter for unified output
|
|
324
|
+
reporter = ResultReporter("hatch env python init", dry_run=dry_run)
|
|
325
|
+
version_str = f" ({python_version})" if python_version else ""
|
|
326
|
+
reporter.add(ConsequenceType.INITIALIZE, f"Python environment for '{env_name}'{version_str}")
|
|
327
|
+
|
|
328
|
+
if dry_run:
|
|
329
|
+
reporter.report_result()
|
|
330
|
+
return EXIT_SUCCESS
|
|
331
|
+
|
|
332
|
+
if env_manager.create_python_environment_only(
|
|
333
|
+
hatch_env,
|
|
334
|
+
python_version,
|
|
335
|
+
force,
|
|
336
|
+
no_hatch_mcp_server=no_hatch_mcp_server,
|
|
337
|
+
hatch_mcp_server_tag=hatch_mcp_server_tag,
|
|
338
|
+
):
|
|
339
|
+
reporter.report_result()
|
|
340
|
+
return EXIT_SUCCESS
|
|
341
|
+
else:
|
|
342
|
+
reporter.report_error(f"Failed to initialize Python environment for '{env_name}'")
|
|
343
|
+
return EXIT_ERROR
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def handle_env_python_info(args: Namespace) -> int:
|
|
347
|
+
"""Handle 'hatch env python info' command.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
args: Namespace with:
|
|
351
|
+
- env_manager: HatchEnvironmentManager instance
|
|
352
|
+
- hatch_env: Optional environment name (default: current)
|
|
353
|
+
- detailed: Show detailed diagnostics
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Exit code (0 for success, 1 for error)
|
|
357
|
+
"""
|
|
358
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
359
|
+
hatch_env = getattr(args, "hatch_env", None)
|
|
360
|
+
detailed = getattr(args, "detailed", False)
|
|
361
|
+
|
|
362
|
+
python_info = env_manager.get_python_environment_info(hatch_env)
|
|
363
|
+
|
|
364
|
+
if python_info:
|
|
365
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
366
|
+
print(f"Python environment info for '{env_name}':")
|
|
367
|
+
print(f" Status: {'Active' if python_info.get('enabled', False) else 'Inactive'}")
|
|
368
|
+
print(f" Python executable: {python_info['python_executable']}")
|
|
369
|
+
print(f" Python version: {python_info.get('python_version', 'Unknown')}")
|
|
370
|
+
print(f" Conda environment: {python_info.get('conda_env_name', 'N/A')}")
|
|
371
|
+
print(f" Environment path: {python_info['environment_path']}")
|
|
372
|
+
print(f" Created: {python_info.get('created_at', 'Unknown')}")
|
|
373
|
+
print(f" Package count: {python_info.get('package_count', 0)}")
|
|
374
|
+
print(f" Packages:")
|
|
375
|
+
for pkg in python_info.get("packages", []):
|
|
376
|
+
print(f" - {pkg['name']} ({pkg['version']})")
|
|
377
|
+
|
|
378
|
+
if detailed:
|
|
379
|
+
print(f"\nDiagnostics:")
|
|
380
|
+
diagnostics = env_manager.get_python_environment_diagnostics(hatch_env)
|
|
381
|
+
if diagnostics:
|
|
382
|
+
for key, value in diagnostics.items():
|
|
383
|
+
print(f" {key}: {value}")
|
|
384
|
+
else:
|
|
385
|
+
print(" No diagnostics available")
|
|
386
|
+
|
|
387
|
+
return EXIT_SUCCESS
|
|
388
|
+
else:
|
|
389
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
390
|
+
print(f"No Python environment found for: {env_name}")
|
|
391
|
+
|
|
392
|
+
# Show diagnostics for missing environment
|
|
393
|
+
if detailed:
|
|
394
|
+
print("\nDiagnostics:")
|
|
395
|
+
general_diagnostics = env_manager.get_python_manager_diagnostics()
|
|
396
|
+
for key, value in general_diagnostics.items():
|
|
397
|
+
print(f" {key}: {value}")
|
|
398
|
+
|
|
399
|
+
return EXIT_ERROR
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def handle_env_python_remove(args: Namespace) -> int:
|
|
403
|
+
"""Handle 'hatch env python remove' command.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
args: Namespace with:
|
|
407
|
+
- env_manager: HatchEnvironmentManager instance
|
|
408
|
+
- hatch_env: Optional environment name (default: current)
|
|
409
|
+
- force: Skip confirmation prompt
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Exit code (0 for success, 1 for error)
|
|
413
|
+
"""
|
|
414
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
415
|
+
hatch_env = getattr(args, "hatch_env", None)
|
|
416
|
+
force = getattr(args, "force", False)
|
|
417
|
+
dry_run = getattr(args, "dry_run", False)
|
|
418
|
+
|
|
419
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
420
|
+
|
|
421
|
+
# Create reporter for unified output
|
|
422
|
+
reporter = ResultReporter("hatch env python remove", dry_run=dry_run)
|
|
423
|
+
reporter.add(ConsequenceType.REMOVE, f"Python environment for '{env_name}'")
|
|
424
|
+
|
|
425
|
+
if dry_run:
|
|
426
|
+
reporter.report_result()
|
|
427
|
+
return EXIT_SUCCESS
|
|
428
|
+
|
|
429
|
+
if not force:
|
|
430
|
+
# Ask for confirmation using TTY-aware function
|
|
431
|
+
if not request_confirmation(f"Remove Python environment for '{env_name}'?"):
|
|
432
|
+
format_info("Operation cancelled")
|
|
433
|
+
return EXIT_SUCCESS
|
|
434
|
+
|
|
435
|
+
if env_manager.remove_python_environment_only(hatch_env):
|
|
436
|
+
reporter.report_result()
|
|
437
|
+
return EXIT_SUCCESS
|
|
438
|
+
else:
|
|
439
|
+
reporter.report_error(f"Failed to remove Python environment from '{env_name}'")
|
|
440
|
+
return EXIT_ERROR
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def handle_env_python_shell(args: Namespace) -> int:
|
|
444
|
+
"""Handle 'hatch env python shell' command.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
args: Namespace with:
|
|
448
|
+
- env_manager: HatchEnvironmentManager instance
|
|
449
|
+
- hatch_env: Optional environment name (default: current)
|
|
450
|
+
- cmd: Optional command to run in shell
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Exit code (0 for success, 1 for error)
|
|
454
|
+
"""
|
|
455
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
456
|
+
hatch_env = getattr(args, "hatch_env", None)
|
|
457
|
+
cmd = getattr(args, "cmd", None)
|
|
458
|
+
|
|
459
|
+
if env_manager.launch_python_shell(hatch_env, cmd):
|
|
460
|
+
return EXIT_SUCCESS
|
|
461
|
+
else:
|
|
462
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
463
|
+
reporter = ResultReporter("hatch env python shell")
|
|
464
|
+
reporter.report_error(f"Failed to launch Python shell for '{env_name}'")
|
|
465
|
+
return EXIT_ERROR
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def handle_env_python_add_hatch_mcp(args: Namespace) -> int:
|
|
469
|
+
"""Handle 'hatch env python add-hatch-mcp' command.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
args: Namespace with:
|
|
473
|
+
- env_manager: HatchEnvironmentManager instance
|
|
474
|
+
- hatch_env: Optional environment name (default: current)
|
|
475
|
+
- tag: Git tag/branch for wrapper installation
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Exit code (0 for success, 1 for error)
|
|
479
|
+
"""
|
|
480
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
481
|
+
hatch_env = getattr(args, "hatch_env", None)
|
|
482
|
+
tag = getattr(args, "tag", None)
|
|
483
|
+
dry_run = getattr(args, "dry_run", False)
|
|
484
|
+
|
|
485
|
+
env_name = hatch_env or env_manager.get_current_environment()
|
|
486
|
+
|
|
487
|
+
# Create reporter for unified output
|
|
488
|
+
reporter = ResultReporter("hatch env python add-hatch-mcp", dry_run=dry_run)
|
|
489
|
+
reporter.add(ConsequenceType.INSTALL, f"hatch_mcp_server wrapper in '{env_name}'")
|
|
490
|
+
|
|
491
|
+
if dry_run:
|
|
492
|
+
reporter.report_result()
|
|
493
|
+
return EXIT_SUCCESS
|
|
494
|
+
|
|
495
|
+
if env_manager.install_mcp_server(env_name, tag):
|
|
496
|
+
reporter.report_result()
|
|
497
|
+
return EXIT_SUCCESS
|
|
498
|
+
else:
|
|
499
|
+
reporter.report_error(f"Failed to install hatch_mcp_server wrapper in environment '{env_name}'")
|
|
500
|
+
return EXIT_ERROR
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def handle_env_show(args: Namespace) -> int:
|
|
504
|
+
"""Handle 'hatch env show' command.
|
|
505
|
+
|
|
506
|
+
Displays detailed hierarchical view of a specific environment.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
args: Namespace with:
|
|
510
|
+
- env_manager: HatchEnvironmentManager instance
|
|
511
|
+
- name: Environment name to show
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Exit code (0 for success, 1 for error)
|
|
515
|
+
|
|
516
|
+
Reference: R02 §2.2 (02-list_output_format_specification_v2.md)
|
|
517
|
+
"""
|
|
518
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
519
|
+
name = args.name
|
|
520
|
+
|
|
521
|
+
# Validate environment exists
|
|
522
|
+
if not env_manager.environment_exists(name):
|
|
523
|
+
format_validation_error(ValidationError(
|
|
524
|
+
f"Environment '{name}' does not exist",
|
|
525
|
+
field="name",
|
|
526
|
+
suggestion="Use 'hatch env list' to see available environments"
|
|
527
|
+
))
|
|
528
|
+
return EXIT_ERROR
|
|
529
|
+
|
|
530
|
+
# Get environment data
|
|
531
|
+
env_data = env_manager.get_environment_data(name)
|
|
532
|
+
current_env = env_manager.get_current_environment()
|
|
533
|
+
is_current = name == current_env
|
|
534
|
+
|
|
535
|
+
# Header
|
|
536
|
+
status = " (active)" if is_current else ""
|
|
537
|
+
print(f"Environment: {name}{status}")
|
|
538
|
+
|
|
539
|
+
# Description
|
|
540
|
+
description = env_data.get("description", "")
|
|
541
|
+
if description:
|
|
542
|
+
print(f" Description: {description}")
|
|
543
|
+
|
|
544
|
+
# Created timestamp
|
|
545
|
+
created_at = env_data.get("created_at", "Unknown")
|
|
546
|
+
print(f" Created: {created_at}")
|
|
547
|
+
print()
|
|
548
|
+
|
|
549
|
+
# Python Environment section
|
|
550
|
+
python_info = env_manager.get_python_environment_info(name)
|
|
551
|
+
print(" Python Environment:")
|
|
552
|
+
if python_info:
|
|
553
|
+
print(f" Version: {python_info.get('python_version', 'Unknown')}")
|
|
554
|
+
print(f" Executable: {python_info.get('python_executable', 'N/A')}")
|
|
555
|
+
conda_env = python_info.get('conda_env_name', 'N/A')
|
|
556
|
+
if conda_env and conda_env != 'N/A':
|
|
557
|
+
print(f" Conda env: {conda_env}")
|
|
558
|
+
status = "Active" if python_info.get('enabled', False) else "Inactive"
|
|
559
|
+
print(f" Status: {status}")
|
|
560
|
+
else:
|
|
561
|
+
print(" (not initialized)")
|
|
562
|
+
print()
|
|
563
|
+
|
|
564
|
+
# Packages section
|
|
565
|
+
packages = env_manager.list_packages(name)
|
|
566
|
+
pkg_count = len(packages) if packages else 0
|
|
567
|
+
print(f" Packages ({pkg_count}):")
|
|
568
|
+
|
|
569
|
+
if packages:
|
|
570
|
+
for pkg in packages:
|
|
571
|
+
pkg_name = pkg.get("name", "unknown")
|
|
572
|
+
print(f" {pkg_name}")
|
|
573
|
+
|
|
574
|
+
# Version
|
|
575
|
+
version = pkg.get("version", "unknown")
|
|
576
|
+
print(f" Version: {version}")
|
|
577
|
+
|
|
578
|
+
# Source
|
|
579
|
+
source = pkg.get("source", {})
|
|
580
|
+
source_type = source.get("type", "unknown")
|
|
581
|
+
source_path = source.get("path", source.get("url", "N/A"))
|
|
582
|
+
print(f" Source: {source_type} ({source_path})")
|
|
583
|
+
|
|
584
|
+
# Deployed hosts
|
|
585
|
+
configured_hosts = pkg.get("configured_hosts", {})
|
|
586
|
+
if configured_hosts:
|
|
587
|
+
hosts_list = ", ".join(configured_hosts.keys())
|
|
588
|
+
print(f" Deployed to: {hosts_list}")
|
|
589
|
+
else:
|
|
590
|
+
print(f" Deployed to: (none)")
|
|
591
|
+
print()
|
|
592
|
+
else:
|
|
593
|
+
print(" (empty)")
|
|
594
|
+
|
|
595
|
+
return EXIT_SUCCESS
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def handle_env_list_hosts(args: Namespace) -> int:
|
|
599
|
+
"""Handle 'hatch env list hosts' command.
|
|
600
|
+
|
|
601
|
+
Lists environment/host/server deployments from environment data.
|
|
602
|
+
Shows only Hatch-managed packages and their host deployments.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
args: Namespace with:
|
|
606
|
+
- env_manager: HatchEnvironmentManager instance
|
|
607
|
+
- env: Optional regex pattern to filter by environment name
|
|
608
|
+
- server: Optional regex pattern to filter by server name
|
|
609
|
+
- json: Optional flag for JSON output
|
|
610
|
+
|
|
611
|
+
Returns:
|
|
612
|
+
Exit code (0 for success, 1 for error)
|
|
613
|
+
|
|
614
|
+
Reference: R10 §3.3 (10-namespace_consistency_specification_v2.md)
|
|
615
|
+
"""
|
|
616
|
+
import json as json_module
|
|
617
|
+
import re
|
|
618
|
+
|
|
619
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
620
|
+
env_pattern: str = getattr(args, 'env', None)
|
|
621
|
+
server_pattern: str = getattr(args, 'server', None)
|
|
622
|
+
json_output: bool = getattr(args, 'json', False)
|
|
623
|
+
|
|
624
|
+
# Compile regex patterns if provided
|
|
625
|
+
env_re = None
|
|
626
|
+
if env_pattern:
|
|
627
|
+
try:
|
|
628
|
+
env_re = re.compile(env_pattern)
|
|
629
|
+
except re.error as e:
|
|
630
|
+
format_validation_error(ValidationError(
|
|
631
|
+
f"Invalid env regex pattern: {e}",
|
|
632
|
+
field="--env",
|
|
633
|
+
suggestion="Use a valid Python regex pattern"
|
|
634
|
+
))
|
|
635
|
+
return EXIT_ERROR
|
|
636
|
+
|
|
637
|
+
server_re = None
|
|
638
|
+
if server_pattern:
|
|
639
|
+
try:
|
|
640
|
+
server_re = re.compile(server_pattern)
|
|
641
|
+
except re.error as e:
|
|
642
|
+
format_validation_error(ValidationError(
|
|
643
|
+
f"Invalid server regex pattern: {e}",
|
|
644
|
+
field="--server",
|
|
645
|
+
suggestion="Use a valid Python regex pattern"
|
|
646
|
+
))
|
|
647
|
+
return EXIT_ERROR
|
|
648
|
+
|
|
649
|
+
# Get all environments
|
|
650
|
+
environments = env_manager.list_environments()
|
|
651
|
+
|
|
652
|
+
# Collect rows: (environment, host, server, version)
|
|
653
|
+
rows = []
|
|
654
|
+
|
|
655
|
+
for env_info in environments:
|
|
656
|
+
env_name = env_info.get("name", env_info) if isinstance(env_info, dict) else env_info
|
|
657
|
+
|
|
658
|
+
# Apply environment filter
|
|
659
|
+
if env_re and not env_re.search(env_name):
|
|
660
|
+
continue
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
env_data = env_manager.get_environment_data(env_name)
|
|
664
|
+
packages = env_data.get("packages", []) if isinstance(env_data, dict) else []
|
|
665
|
+
|
|
666
|
+
for pkg in packages:
|
|
667
|
+
pkg_name = pkg.get("name") if isinstance(pkg, dict) else None
|
|
668
|
+
pkg_version = pkg.get("version", "-") if isinstance(pkg, dict) else "-"
|
|
669
|
+
configured_hosts = pkg.get("configured_hosts", {}) if isinstance(pkg, dict) else {}
|
|
670
|
+
|
|
671
|
+
if not pkg_name or not configured_hosts:
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
# Apply server filter
|
|
675
|
+
if server_re and not server_re.search(pkg_name):
|
|
676
|
+
continue
|
|
677
|
+
|
|
678
|
+
# Add a row for each host deployment
|
|
679
|
+
for host_name in configured_hosts.keys():
|
|
680
|
+
rows.append((env_name, host_name, pkg_name, pkg_version))
|
|
681
|
+
except Exception:
|
|
682
|
+
continue
|
|
683
|
+
|
|
684
|
+
# Sort rows by environment (alphabetically), then host, then server
|
|
685
|
+
rows.sort(key=lambda x: (x[0], x[1], x[2]))
|
|
686
|
+
|
|
687
|
+
# JSON output per R10 §8
|
|
688
|
+
if json_output:
|
|
689
|
+
rows_data = []
|
|
690
|
+
for env, host, server, version in rows:
|
|
691
|
+
rows_data.append({
|
|
692
|
+
"environment": env,
|
|
693
|
+
"host": host,
|
|
694
|
+
"server": server,
|
|
695
|
+
"version": version
|
|
696
|
+
})
|
|
697
|
+
print(json_module.dumps({"rows": rows_data}, indent=2))
|
|
698
|
+
return EXIT_SUCCESS
|
|
699
|
+
|
|
700
|
+
# Display results
|
|
701
|
+
if not rows:
|
|
702
|
+
if env_pattern or server_pattern:
|
|
703
|
+
print("No matching environment host deployments found")
|
|
704
|
+
else:
|
|
705
|
+
print("No environment host deployments found")
|
|
706
|
+
return EXIT_SUCCESS
|
|
707
|
+
|
|
708
|
+
print("Environment Host Deployments:")
|
|
709
|
+
|
|
710
|
+
# Define table columns per R10 §3.3: Environment → Host → Server → Version
|
|
711
|
+
columns = [
|
|
712
|
+
ColumnDef(name="Environment", width=15),
|
|
713
|
+
ColumnDef(name="Host", width=18),
|
|
714
|
+
ColumnDef(name="Server", width=18),
|
|
715
|
+
ColumnDef(name="Version", width=10),
|
|
716
|
+
]
|
|
717
|
+
formatter = TableFormatter(columns)
|
|
718
|
+
|
|
719
|
+
for env, host, server, version in rows:
|
|
720
|
+
formatter.add_row([env, host, server, version])
|
|
721
|
+
|
|
722
|
+
print(formatter.render())
|
|
723
|
+
return EXIT_SUCCESS
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def handle_env_list_servers(args: Namespace) -> int:
|
|
727
|
+
"""Handle 'hatch env list servers' command.
|
|
728
|
+
|
|
729
|
+
Lists environment/server/host deployments from environment data.
|
|
730
|
+
Shows only Hatch-managed packages. Undeployed packages show '-' in Host column.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
args: Namespace with:
|
|
734
|
+
- env_manager: HatchEnvironmentManager instance
|
|
735
|
+
- env: Optional regex pattern to filter by environment name
|
|
736
|
+
- host: Optional regex pattern to filter by host name (use '-' for undeployed)
|
|
737
|
+
- json: Optional flag for JSON output
|
|
738
|
+
|
|
739
|
+
Returns:
|
|
740
|
+
Exit code (0 for success, 1 for error)
|
|
741
|
+
|
|
742
|
+
Reference: R10 §3.4 (10-namespace_consistency_specification_v2.md)
|
|
743
|
+
"""
|
|
744
|
+
import json as json_module
|
|
745
|
+
import re
|
|
746
|
+
|
|
747
|
+
env_manager: "HatchEnvironmentManager" = args.env_manager
|
|
748
|
+
env_pattern: str = getattr(args, 'env', None)
|
|
749
|
+
host_pattern: str = getattr(args, 'host', None)
|
|
750
|
+
json_output: bool = getattr(args, 'json', False)
|
|
751
|
+
|
|
752
|
+
# Compile regex patterns if provided
|
|
753
|
+
env_re = None
|
|
754
|
+
if env_pattern:
|
|
755
|
+
try:
|
|
756
|
+
env_re = re.compile(env_pattern)
|
|
757
|
+
except re.error as e:
|
|
758
|
+
format_validation_error(ValidationError(
|
|
759
|
+
f"Invalid env regex pattern: {e}",
|
|
760
|
+
field="--env",
|
|
761
|
+
suggestion="Use a valid Python regex pattern"
|
|
762
|
+
))
|
|
763
|
+
return EXIT_ERROR
|
|
764
|
+
|
|
765
|
+
# Special handling for '-' (undeployed filter)
|
|
766
|
+
filter_undeployed = host_pattern == "-"
|
|
767
|
+
host_re = None
|
|
768
|
+
if host_pattern and not filter_undeployed:
|
|
769
|
+
try:
|
|
770
|
+
host_re = re.compile(host_pattern)
|
|
771
|
+
except re.error as e:
|
|
772
|
+
format_validation_error(ValidationError(
|
|
773
|
+
f"Invalid host regex pattern: {e}",
|
|
774
|
+
field="--host",
|
|
775
|
+
suggestion="Use a valid Python regex pattern"
|
|
776
|
+
))
|
|
777
|
+
return EXIT_ERROR
|
|
778
|
+
|
|
779
|
+
# Get all environments
|
|
780
|
+
environments = env_manager.list_environments()
|
|
781
|
+
|
|
782
|
+
# Collect rows: (environment, server, host, version)
|
|
783
|
+
rows = []
|
|
784
|
+
|
|
785
|
+
for env_info in environments:
|
|
786
|
+
env_name = env_info.get("name", env_info) if isinstance(env_info, dict) else env_info
|
|
787
|
+
|
|
788
|
+
# Apply environment filter
|
|
789
|
+
if env_re and not env_re.search(env_name):
|
|
790
|
+
continue
|
|
791
|
+
|
|
792
|
+
try:
|
|
793
|
+
env_data = env_manager.get_environment_data(env_name)
|
|
794
|
+
packages = env_data.get("packages", []) if isinstance(env_data, dict) else []
|
|
795
|
+
|
|
796
|
+
for pkg in packages:
|
|
797
|
+
pkg_name = pkg.get("name") if isinstance(pkg, dict) else None
|
|
798
|
+
pkg_version = pkg.get("version", "-") if isinstance(pkg, dict) else "-"
|
|
799
|
+
configured_hosts = pkg.get("configured_hosts", {}) if isinstance(pkg, dict) else {}
|
|
800
|
+
|
|
801
|
+
if not pkg_name:
|
|
802
|
+
continue
|
|
803
|
+
|
|
804
|
+
if configured_hosts:
|
|
805
|
+
# Package is deployed to one or more hosts
|
|
806
|
+
for host_name in configured_hosts.keys():
|
|
807
|
+
# Apply host filter
|
|
808
|
+
if filter_undeployed:
|
|
809
|
+
# Skip deployed packages when filtering for undeployed
|
|
810
|
+
continue
|
|
811
|
+
if host_re and not host_re.search(host_name):
|
|
812
|
+
continue
|
|
813
|
+
rows.append((env_name, pkg_name, host_name, pkg_version))
|
|
814
|
+
else:
|
|
815
|
+
# Package is not deployed (undeployed)
|
|
816
|
+
if host_re:
|
|
817
|
+
# Skip undeployed when filtering by specific host pattern
|
|
818
|
+
continue
|
|
819
|
+
if not filter_undeployed and host_pattern:
|
|
820
|
+
# Skip undeployed when filtering by host (unless specifically filtering for undeployed)
|
|
821
|
+
continue
|
|
822
|
+
rows.append((env_name, pkg_name, "-", pkg_version))
|
|
823
|
+
except Exception:
|
|
824
|
+
continue
|
|
825
|
+
|
|
826
|
+
# Sort rows by environment (alphabetically), then server, then host
|
|
827
|
+
rows.sort(key=lambda x: (x[0], x[1], x[2]))
|
|
828
|
+
|
|
829
|
+
# JSON output per R10 §8
|
|
830
|
+
if json_output:
|
|
831
|
+
rows_data = []
|
|
832
|
+
for env, server, host, version in rows:
|
|
833
|
+
rows_data.append({
|
|
834
|
+
"environment": env,
|
|
835
|
+
"server": server,
|
|
836
|
+
"host": host if host != "-" else None,
|
|
837
|
+
"version": version
|
|
838
|
+
})
|
|
839
|
+
print(json_module.dumps({"rows": rows_data}, indent=2))
|
|
840
|
+
return EXIT_SUCCESS
|
|
841
|
+
|
|
842
|
+
# Display results
|
|
843
|
+
if not rows:
|
|
844
|
+
if env_pattern or host_pattern:
|
|
845
|
+
print("No matching environment server deployments found")
|
|
846
|
+
else:
|
|
847
|
+
print("No environment server deployments found")
|
|
848
|
+
return EXIT_SUCCESS
|
|
849
|
+
|
|
850
|
+
print("Environment Servers:")
|
|
851
|
+
|
|
852
|
+
# Define table columns per R10 §3.4: Environment → Server → Host → Version
|
|
853
|
+
columns = [
|
|
854
|
+
ColumnDef(name="Environment", width=15),
|
|
855
|
+
ColumnDef(name="Server", width=18),
|
|
856
|
+
ColumnDef(name="Host", width=18),
|
|
857
|
+
ColumnDef(name="Version", width=10),
|
|
858
|
+
]
|
|
859
|
+
formatter = TableFormatter(columns)
|
|
860
|
+
|
|
861
|
+
for env, server, host, version in rows:
|
|
862
|
+
formatter.add_row([env, server, host, version])
|
|
863
|
+
|
|
864
|
+
print(formatter.render())
|
|
865
|
+
return EXIT_SUCCESS
|