comfygit-deploy 0.3.4__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.
@@ -0,0 +1,356 @@
1
+ """Dev CLI command handlers.
2
+
3
+ Commands for setting up development mode with local package paths.
4
+ """
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import subprocess
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+
13
+ DEV_CONFIG_PATH = Path.home() / ".config" / "comfygit" / "deploy" / "dev.json"
14
+
15
+
16
+ @dataclass
17
+ class DevNode:
18
+ """A development node configuration."""
19
+
20
+ name: str
21
+ path: str
22
+
23
+
24
+ def load_dev_config() -> dict:
25
+ """Load dev config from disk."""
26
+ if not DEV_CONFIG_PATH.exists():
27
+ return {}
28
+ try:
29
+ return json.loads(DEV_CONFIG_PATH.read_text())
30
+ except (json.JSONDecodeError, OSError):
31
+ return {}
32
+
33
+
34
+ def get_dev_nodes() -> list[DevNode]:
35
+ """Get list of configured dev nodes."""
36
+ config = load_dev_config()
37
+ return [DevNode(name=n["name"], path=n["path"]) for n in config.get("dev_nodes", [])]
38
+
39
+
40
+ def save_dev_config(config: dict) -> None:
41
+ """Save dev config to disk."""
42
+ DEV_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
43
+ DEV_CONFIG_PATH.write_text(json.dumps(config, indent=2))
44
+
45
+
46
+ def get_workspace_path() -> Path | None:
47
+ """Get workspace path from env or default."""
48
+ env_home = os.environ.get("COMFYGIT_HOME")
49
+ if env_home:
50
+ return Path(env_home)
51
+ default = Path.home() / "comfygit"
52
+ if default.exists():
53
+ return default
54
+ return None
55
+
56
+
57
+ def handle_setup(args: argparse.Namespace) -> int:
58
+ """Handle 'dev setup' command."""
59
+ config = load_dev_config()
60
+
61
+ # Show current config
62
+ if args.show:
63
+ if not config:
64
+ print("No dev config set.")
65
+ else:
66
+ print("Dev config:")
67
+ if config.get("core_path"):
68
+ print(f" Core: {config['core_path']}")
69
+ if config.get("manager_path"):
70
+ print(f" Manager: {config['manager_path']}")
71
+ dev_nodes = config.get("dev_nodes", [])
72
+ if dev_nodes:
73
+ print(f" Dev nodes ({len(dev_nodes)}):")
74
+ for node in dev_nodes:
75
+ print(f" - {node['name']}: {node['path']}")
76
+ return 0
77
+
78
+ # Clear config
79
+ if args.clear:
80
+ # Also restore manager symlink to original
81
+ workspace = get_workspace_path()
82
+ if workspace and config.get("manager_path"):
83
+ manager_link = workspace / ".metadata" / "system_nodes" / "comfygit-manager"
84
+ if manager_link.is_symlink():
85
+ manager_link.unlink()
86
+ print(f"Removed dev manager symlink: {manager_link}")
87
+ print("Run 'cg init' or manually clone the manager to restore.")
88
+
89
+ if DEV_CONFIG_PATH.exists():
90
+ DEV_CONFIG_PATH.unlink()
91
+ print("Dev config cleared.")
92
+ return 0
93
+
94
+ # Validate and set paths
95
+ if args.core:
96
+ core_path = Path(args.core).resolve()
97
+ if not (core_path / "pyproject.toml").exists():
98
+ print(f"Error: Not a valid package path: {core_path}")
99
+ print(" Expected pyproject.toml in the directory.")
100
+ return 1
101
+ config["core_path"] = str(core_path)
102
+ print(f"Core path: {core_path}")
103
+
104
+ if args.manager:
105
+ manager_path = Path(args.manager).resolve()
106
+ if not (manager_path / "__init__.py").exists() and not (manager_path / "server").exists():
107
+ print(f"Error: Not a valid manager path: {manager_path}")
108
+ return 1
109
+ config["manager_path"] = str(manager_path)
110
+ print(f"Manager path: {manager_path}")
111
+
112
+ # Symlink manager to system_nodes
113
+ workspace = get_workspace_path()
114
+ if workspace:
115
+ system_nodes = workspace / ".metadata" / "system_nodes"
116
+ system_nodes.mkdir(parents=True, exist_ok=True)
117
+ manager_link = system_nodes / "comfygit-manager"
118
+
119
+ # Remove existing (whether symlink or directory)
120
+ if manager_link.is_symlink():
121
+ manager_link.unlink()
122
+ elif manager_link.is_dir():
123
+ import shutil
124
+ shutil.rmtree(manager_link)
125
+
126
+ manager_link.symlink_to(manager_path)
127
+ print(f"Symlinked: {manager_link} -> {manager_path}")
128
+
129
+ if not args.core and not args.manager:
130
+ print("Usage: cg-deploy dev setup --core PATH --manager PATH")
131
+ print(" cg-deploy dev setup --show")
132
+ print(" cg-deploy dev setup --clear")
133
+ return 0
134
+
135
+ save_dev_config(config)
136
+ print()
137
+ print("Dev mode configured!")
138
+ print()
139
+ print("Start worker with dev paths:")
140
+ if config.get("core_path"):
141
+ print(f" cg-deploy worker up --dev-core {config['core_path']}")
142
+ print()
143
+ print("Or set environment variable:")
144
+ if config.get("core_path"):
145
+ print(f" export COMFYGIT_DEV_CORE_PATH={config['core_path']}")
146
+
147
+ return 0
148
+
149
+
150
+ def handle_patch(args: argparse.Namespace) -> int:
151
+ """Handle 'dev patch' command - patch existing environments with dev config."""
152
+ config = load_dev_config()
153
+ core_path = config.get("core_path")
154
+ dev_nodes = config.get("dev_nodes", [])
155
+
156
+ if not core_path and not dev_nodes:
157
+ print("No dev config found.")
158
+ print("Run: cg-deploy dev setup --core PATH")
159
+ print(" cg-deploy dev add-node NAME PATH")
160
+ return 1
161
+
162
+ workspace = get_workspace_path()
163
+ if not workspace:
164
+ print("No workspace found. Set COMFYGIT_HOME or run 'cg init'.")
165
+ return 1
166
+
167
+ envs_dir = workspace / "environments"
168
+ if not envs_dir.exists():
169
+ print("No environments found.")
170
+ return 0
171
+
172
+ # Find environments to patch
173
+ if args.env:
174
+ envs = [envs_dir / args.env]
175
+ if not envs[0].exists():
176
+ print(f"Environment not found: {args.env}")
177
+ return 1
178
+ else:
179
+ envs = [e for e in envs_dir.iterdir() if e.is_dir() and (e / ".venv").exists()]
180
+
181
+ if not envs:
182
+ print("No environments with .venv found.")
183
+ return 0
184
+
185
+ print(f"Patching {len(envs)} environment(s):")
186
+ if core_path:
187
+ print(f" - dev core: {core_path}")
188
+ for node in dev_nodes:
189
+ print(f" - dev node: {node['name']} -> {node['path']}")
190
+ print()
191
+
192
+ for env_path in envs:
193
+ env_name = env_path.name
194
+ venv_python = env_path / ".venv" / "bin" / "python"
195
+
196
+ if not venv_python.exists():
197
+ print(f" {env_name}: skipped (no .venv)")
198
+ continue
199
+
200
+ success = True
201
+
202
+ # Patch core if configured
203
+ if core_path:
204
+ cmd = ["uv", "pip", "install", "-e", core_path, "--python", str(venv_python)]
205
+ result = subprocess.run(cmd, capture_output=True, text=True)
206
+ if result.returncode != 0:
207
+ print(f" {env_name}: core failed - {result.stderr.strip()[:60]}")
208
+ success = False
209
+
210
+ # Apply dev nodes
211
+ for node in dev_nodes:
212
+ node_result = _apply_dev_node_to_env(env_path, node["name"], node["path"], workspace)
213
+ if not node_result:
214
+ print(f" {env_name}: node {node['name']} failed")
215
+ success = False
216
+
217
+ if success:
218
+ print(f" {env_name}: patched")
219
+
220
+ print()
221
+ print("Done. Restart any running ComfyUI instances to apply changes.")
222
+
223
+ return 0
224
+
225
+
226
+ def _apply_dev_node_to_env(env_path: Path, node_name: str, node_path: str, workspace: Path) -> bool:
227
+ """Apply a dev node to an environment (symlink + track).
228
+
229
+ Args:
230
+ env_path: Path to the environment
231
+ node_name: Name of the node
232
+ node_path: Path to the dev node source
233
+ workspace: Workspace path
234
+
235
+ Returns:
236
+ True if successful
237
+ """
238
+ import shutil
239
+
240
+ custom_nodes = env_path / "ComfyUI" / "custom_nodes"
241
+ if not custom_nodes.exists():
242
+ return False
243
+
244
+ target = custom_nodes / node_name
245
+ source = Path(node_path)
246
+
247
+ # Create/update symlink
248
+ if target.is_symlink():
249
+ if target.resolve() == source.resolve():
250
+ # Already correct
251
+ pass
252
+ else:
253
+ target.unlink()
254
+ target.symlink_to(source)
255
+ elif target.exists():
256
+ # Regular directory exists - replace with symlink
257
+ shutil.rmtree(target)
258
+ target.symlink_to(source)
259
+ else:
260
+ target.symlink_to(source)
261
+
262
+ # Track with cg node add --dev
263
+ env = os.environ.copy()
264
+ env["COMFYGIT_HOME"] = str(workspace)
265
+
266
+ cmd = ["cg", "-e", env_path.name, "node", "add", node_name, "--dev"]
267
+ result = subprocess.run(cmd, env=env, capture_output=True, text=True)
268
+
269
+ # Success if tracked or already tracked
270
+ return result.returncode == 0 or "already tracked" in result.stderr.lower()
271
+
272
+
273
+ def handle_add_node(args: argparse.Namespace) -> int:
274
+ """Handle 'dev add-node' command."""
275
+ config = load_dev_config()
276
+
277
+ node_name = args.name
278
+ node_path = Path(args.path).resolve()
279
+
280
+ # Validate path
281
+ if not node_path.is_dir():
282
+ print(f"Error: Path does not exist: {node_path}")
283
+ return 1
284
+
285
+ # Check for __init__.py or common node indicators
286
+ has_init = (node_path / "__init__.py").exists()
287
+ has_nodes = (node_path / "nodes.py").exists() or (node_path / "nodes").exists()
288
+ if not has_init and not has_nodes:
289
+ print(f"Warning: {node_path} doesn't look like a ComfyUI node (no __init__.py or nodes.py)")
290
+
291
+ # Add to dev_nodes list
292
+ dev_nodes = config.get("dev_nodes", [])
293
+
294
+ # Check if already exists
295
+ existing = next((n for n in dev_nodes if n["name"] == node_name), None)
296
+ if existing:
297
+ existing["path"] = str(node_path)
298
+ print(f"Updated dev node: {node_name} -> {node_path}")
299
+ else:
300
+ dev_nodes.append({"name": node_name, "path": str(node_path)})
301
+ print(f"Added dev node: {node_name} -> {node_path}")
302
+
303
+ config["dev_nodes"] = dev_nodes
304
+ save_dev_config(config)
305
+
306
+ print()
307
+ print("To apply to existing environments:")
308
+ print(" cg-deploy dev patch")
309
+ print()
310
+ print("New instances created with --dev will include this node.")
311
+
312
+ return 0
313
+
314
+
315
+ def handle_remove_node(args: argparse.Namespace) -> int:
316
+ """Handle 'dev remove-node' command."""
317
+ config = load_dev_config()
318
+ node_name = args.name
319
+
320
+ dev_nodes = config.get("dev_nodes", [])
321
+ original_len = len(dev_nodes)
322
+
323
+ dev_nodes = [n for n in dev_nodes if n["name"] != node_name]
324
+
325
+ if len(dev_nodes) == original_len:
326
+ print(f"Dev node not found: {node_name}")
327
+ return 1
328
+
329
+ config["dev_nodes"] = dev_nodes
330
+ save_dev_config(config)
331
+
332
+ print(f"Removed dev node: {node_name}")
333
+ print("Note: Existing symlinks in environments are not removed.")
334
+
335
+ return 0
336
+
337
+
338
+ def handle_list_nodes(args: argparse.Namespace) -> int:
339
+ """Handle 'dev list-nodes' command."""
340
+ config = load_dev_config()
341
+ dev_nodes = config.get("dev_nodes", [])
342
+
343
+ if not dev_nodes:
344
+ print("No dev nodes configured.")
345
+ print()
346
+ print("Add a dev node:")
347
+ print(" cg-deploy dev add-node NAME PATH")
348
+ return 0
349
+
350
+ print(f"Dev nodes ({len(dev_nodes)}):")
351
+ for node in dev_nodes:
352
+ path = Path(node["path"])
353
+ exists = "✓" if path.exists() else "✗"
354
+ print(f" {exists} {node['name']}: {node['path']}")
355
+
356
+ return 0