comfy-env 0.1.13__tar.gz → 0.1.14__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 (35) hide show
  1. {comfy_env-0.1.13 → comfy_env-0.1.14}/PKG-INFO +1 -1
  2. {comfy_env-0.1.13 → comfy_env-0.1.14}/pyproject.toml +1 -1
  3. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/__init__.py +25 -0
  4. comfy_env-0.1.14/src/comfy_env/cache.py +331 -0
  5. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/isolation/wrap.py +76 -20
  6. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/core.py +43 -8
  7. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/prestartup.py +37 -9
  8. {comfy_env-0.1.13 → comfy_env-0.1.14}/.github/workflows/ci.yml +0 -0
  9. {comfy_env-0.1.13 → comfy_env-0.1.14}/.github/workflows/publish.yml +0 -0
  10. {comfy_env-0.1.13 → comfy_env-0.1.14}/.gitignore +0 -0
  11. {comfy_env-0.1.13 → comfy_env-0.1.14}/LICENSE +0 -0
  12. {comfy_env-0.1.13 → comfy_env-0.1.14}/README.md +0 -0
  13. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/cli.py +0 -0
  14. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/__init__.py +0 -0
  15. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/parser.py +0 -0
  16. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/types.py +0 -0
  17. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/errors.py +0 -0
  18. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/install.py +0 -0
  19. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/isolation/__init__.py +0 -0
  20. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/nodes.py +0 -0
  21. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/__init__.py +0 -0
  22. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/cuda_detection.py +0 -0
  23. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/__init__.py +0 -0
  24. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/base.py +0 -0
  25. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/darwin.py +0 -0
  26. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/linux.py +0 -0
  27. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/windows.py +0 -0
  28. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/resolver.py +0 -0
  29. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
  30. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/templates/comfy-env.toml +0 -0
  31. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/__init__.py +0 -0
  32. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/base.py +0 -0
  33. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/mp.py +0 -0
  34. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/subprocess.py +0 -0
  35. {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/tensor_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.1.13
3
+ Version: 0.1.14
4
4
  Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
5
5
  Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
6
6
  Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "comfy-env"
3
- version = "0.1.13"
3
+ version = "0.1.14"
4
4
  description = "Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -56,6 +56,15 @@ from .install import install, verify_installation
56
56
  # Prestartup helpers
57
57
  from .prestartup import setup_env
58
58
 
59
+ # Cache management
60
+ from .cache import (
61
+ get_cache_dir,
62
+ cleanup_orphaned_envs,
63
+ resolve_env_path,
64
+ CACHE_DIR,
65
+ MARKER_FILE,
66
+ )
67
+
59
68
  # Errors
60
69
  from .errors import (
61
70
  EnvManagerError,
@@ -105,4 +114,20 @@ __all__ = [
105
114
  "DependencyError",
106
115
  "CUDANotFoundError",
107
116
  "InstallError",
117
+ # Cache
118
+ "get_cache_dir",
119
+ "cleanup_orphaned_envs",
120
+ "resolve_env_path",
121
+ "CACHE_DIR",
122
+ "MARKER_FILE",
108
123
  ]
124
+
125
+ # Run orphan cleanup once on module load (silently)
126
+ def _run_startup_cleanup():
127
+ """Clean orphaned envs on startup. Runs silently, never fails startup."""
128
+ try:
129
+ cleanup_orphaned_envs(log=lambda x: None) # Silent
130
+ except Exception:
131
+ pass # Never fail startup due to cleanup
132
+
133
+ _run_startup_cleanup()
@@ -0,0 +1,331 @@
1
+ """
2
+ Central environment cache management for comfy-env.
3
+
4
+ Stores environments in ~/.comfy-env/envs/<nodename>_<subfolder>_<hash>/
5
+ to avoid Windows MAX_PATH (260 char) issues.
6
+
7
+ Marker files in node folders link to central envs and enable orphan cleanup.
8
+ """
9
+
10
+ import hashlib
11
+ import os
12
+ import shutil
13
+ import sys
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Optional, Tuple, Callable
17
+
18
+ # Import version
19
+ try:
20
+ from . import __version__
21
+ except ImportError:
22
+ __version__ = "0.0.0-dev"
23
+
24
+ # Lazy import tomli/tomllib
25
+ def _get_tomli():
26
+ if sys.version_info >= (3, 11):
27
+ import tomllib
28
+ return tomllib
29
+ else:
30
+ try:
31
+ import tomli
32
+ return tomli
33
+ except ImportError:
34
+ return None
35
+
36
+ def _get_tomli_w():
37
+ try:
38
+ import tomli_w
39
+ return tomli_w
40
+ except ImportError:
41
+ return None
42
+
43
+
44
+ # Constants
45
+ CACHE_DIR = Path.home() / ".comfy-env" / "envs"
46
+ MARKER_FILE = ".comfy-env-marker.toml"
47
+ METADATA_FILE = ".comfy-env-metadata.toml"
48
+
49
+
50
+ def get_cache_dir() -> Path:
51
+ """Get central cache directory, create if needed."""
52
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
53
+ return CACHE_DIR
54
+
55
+
56
+ def compute_config_hash(config_path: Path) -> str:
57
+ """Compute hash of comfy-env.toml content (first 8 chars of SHA256)."""
58
+ content = config_path.read_bytes()
59
+ return hashlib.sha256(content).hexdigest()[:8]
60
+
61
+
62
+ def sanitize_name(name: str) -> str:
63
+ """Sanitize a name for use in filesystem paths."""
64
+ # Lowercase and replace problematic chars
65
+ name = name.lower()
66
+ for prefix in ("comfyui-", "comfyui_"):
67
+ if name.startswith(prefix):
68
+ name = name[len(prefix):]
69
+ return name.replace("-", "_").replace(" ", "_")
70
+
71
+
72
+ def get_env_name(node_dir: Path, config_path: Path) -> str:
73
+ """
74
+ Generate env name: <nodename>_<subfolder>_<hash>
75
+
76
+ Args:
77
+ node_dir: The custom node directory (e.g., custom_nodes/ComfyUI-UniRig)
78
+ config_path: Path to comfy-env.toml
79
+
80
+ Examples:
81
+ - ComfyUI-UniRig/nodes/comfy-env.toml -> unirig_nodes_a1b2c3d4
82
+ - ComfyUI-Pack/comfy-env.toml -> pack__f5e6d7c8 (double underscore = no subfolder)
83
+ """
84
+ # Get node name
85
+ node_name = sanitize_name(node_dir.name)
86
+
87
+ # Get subfolder (relative path from node_dir to config parent)
88
+ config_parent = config_path.parent
89
+ if config_parent == node_dir:
90
+ subfolder = ""
91
+ else:
92
+ try:
93
+ rel_path = config_parent.relative_to(node_dir)
94
+ subfolder = rel_path.as_posix().replace("/", "_")
95
+ except ValueError:
96
+ # config_path not under node_dir - use parent folder name
97
+ subfolder = sanitize_name(config_parent.name)
98
+
99
+ # Compute hash
100
+ config_hash = compute_config_hash(config_path)
101
+
102
+ return f"{node_name}_{subfolder}_{config_hash}"
103
+
104
+
105
+ def get_central_env_path(node_dir: Path, config_path: Path) -> Path:
106
+ """Get path to central environment for this config."""
107
+ env_name = get_env_name(node_dir, config_path)
108
+ return get_cache_dir() / env_name
109
+
110
+
111
+ def write_marker(config_path: Path, env_path: Path) -> None:
112
+ """
113
+ Write marker file linking node to central env.
114
+
115
+ Args:
116
+ config_path: Path to comfy-env.toml
117
+ env_path: Path to central environment
118
+ """
119
+ tomli_w = _get_tomli_w()
120
+ if not tomli_w:
121
+ # Fallback to manual TOML writing
122
+ marker_path = config_path.parent / MARKER_FILE
123
+ content = f'''[env]
124
+ name = "{env_path.name}"
125
+ path = "{env_path}"
126
+ config_hash = "{compute_config_hash(config_path)}"
127
+ created = "{datetime.now().isoformat()}"
128
+ comfy_env_version = "{__version__}"
129
+ '''
130
+ marker_path.write_text(content)
131
+ return
132
+
133
+ marker_path = config_path.parent / MARKER_FILE
134
+ marker_data = {
135
+ "env": {
136
+ "name": env_path.name,
137
+ "path": str(env_path),
138
+ "config_hash": compute_config_hash(config_path),
139
+ "created": datetime.now().isoformat(),
140
+ "comfy_env_version": __version__,
141
+ }
142
+ }
143
+ marker_path.write_text(tomli_w.dumps(marker_data))
144
+
145
+
146
+ def write_env_metadata(env_path: Path, marker_path: Path) -> None:
147
+ """
148
+ Write metadata file in env for orphan detection.
149
+
150
+ Args:
151
+ env_path: Path to central environment
152
+ marker_path: Path to marker file in node folder
153
+ """
154
+ tomli_w = _get_tomli_w()
155
+ metadata_path = env_path / METADATA_FILE
156
+
157
+ if not tomli_w:
158
+ # Fallback to manual TOML writing
159
+ content = f'''marker_path = "{marker_path}"
160
+ created = "{datetime.now().isoformat()}"
161
+ '''
162
+ metadata_path.write_text(content)
163
+ return
164
+
165
+ metadata = {
166
+ "marker_path": str(marker_path),
167
+ "created": datetime.now().isoformat(),
168
+ }
169
+ metadata_path.write_text(tomli_w.dumps(metadata))
170
+
171
+
172
+ def read_marker(marker_path: Path) -> Optional[dict]:
173
+ """
174
+ Read marker file, return None if invalid/missing.
175
+
176
+ Args:
177
+ marker_path: Path to .comfy-env-marker.toml
178
+
179
+ Returns:
180
+ Parsed marker data or None
181
+ """
182
+ if not marker_path.exists():
183
+ return None
184
+
185
+ tomli = _get_tomli()
186
+ if not tomli:
187
+ return None
188
+
189
+ try:
190
+ with open(marker_path, "rb") as f:
191
+ return tomli.load(f)
192
+ except Exception:
193
+ return None
194
+
195
+
196
+ def read_env_metadata(env_path: Path) -> Optional[dict]:
197
+ """
198
+ Read metadata file from env, return None if invalid/missing.
199
+
200
+ Args:
201
+ env_path: Path to central environment
202
+
203
+ Returns:
204
+ Parsed metadata or None
205
+ """
206
+ metadata_path = env_path / METADATA_FILE
207
+ if not metadata_path.exists():
208
+ return None
209
+
210
+ tomli = _get_tomli()
211
+ if not tomli:
212
+ return None
213
+
214
+ try:
215
+ with open(metadata_path, "rb") as f:
216
+ return tomli.load(f)
217
+ except Exception:
218
+ return None
219
+
220
+
221
+ def resolve_env_path(node_dir: Path) -> Tuple[Optional[Path], Optional[Path], Optional[Path]]:
222
+ """
223
+ Resolve environment path with fallback chain.
224
+
225
+ Args:
226
+ node_dir: Directory containing comfy-env.toml or marker file
227
+
228
+ Returns:
229
+ (env_path, site_packages, lib_dir) or (None, None, None)
230
+
231
+ Fallback order:
232
+ 1. Marker file -> central cache
233
+ 2. _env_<name> (current location)
234
+ 3. .pixi/envs/default (old pixi)
235
+ 4. .venv (venv support)
236
+ """
237
+ # 1. Check marker file -> central cache
238
+ marker_path = node_dir / MARKER_FILE
239
+ marker = read_marker(marker_path)
240
+ if marker and "env" in marker:
241
+ env_path = Path(marker["env"]["path"])
242
+ if env_path.exists():
243
+ return _get_env_paths(env_path)
244
+
245
+ # 2. Check _env_<name>
246
+ node_name = sanitize_name(node_dir.name)
247
+ env_name = f"_env_{node_name}"
248
+ local_env = node_dir / env_name
249
+ if local_env.exists():
250
+ return _get_env_paths(local_env)
251
+
252
+ # 3. Check .pixi/envs/default
253
+ pixi_env = node_dir / ".pixi" / "envs" / "default"
254
+ if pixi_env.exists():
255
+ return _get_env_paths(pixi_env)
256
+
257
+ # 4. Check .venv
258
+ venv_dir = node_dir / ".venv"
259
+ if venv_dir.exists():
260
+ return _get_env_paths(venv_dir)
261
+
262
+ return None, None, None
263
+
264
+
265
+ def _get_env_paths(env_path: Path) -> Tuple[Path, Optional[Path], Optional[Path]]:
266
+ """
267
+ Get site-packages and lib paths from an environment.
268
+
269
+ Args:
270
+ env_path: Path to environment root
271
+
272
+ Returns:
273
+ (env_path, site_packages, lib_dir)
274
+ """
275
+ import glob
276
+
277
+ if sys.platform == "win32":
278
+ site_packages = env_path / "Lib" / "site-packages"
279
+ lib_dir = env_path / "Library" / "bin"
280
+ else:
281
+ # Linux/Mac: lib/python*/site-packages
282
+ matches = glob.glob(str(env_path / "lib" / "python*" / "site-packages"))
283
+ site_packages = Path(matches[0]) if matches else None
284
+ lib_dir = env_path / "lib"
285
+
286
+ return env_path, site_packages, lib_dir
287
+
288
+
289
+ def cleanup_orphaned_envs(log: Callable[[str], None] = print) -> int:
290
+ """
291
+ Scan central cache and remove orphaned environments.
292
+
293
+ An env is orphaned if its marker file no longer exists
294
+ (meaning the node was deleted).
295
+
296
+ Args:
297
+ log: Logging function
298
+
299
+ Returns:
300
+ Number of envs cleaned up
301
+ """
302
+ cache_dir = get_cache_dir()
303
+ if not cache_dir.exists():
304
+ return 0
305
+
306
+ cleaned = 0
307
+ for env_dir in cache_dir.iterdir():
308
+ if not env_dir.is_dir():
309
+ continue
310
+
311
+ # Skip if no metadata (might be manually created or old format)
312
+ metadata = read_env_metadata(env_dir)
313
+ if not metadata:
314
+ continue
315
+
316
+ # Check if marker file still exists
317
+ marker_path_str = metadata.get("marker_path", "")
318
+ if not marker_path_str:
319
+ continue
320
+
321
+ marker_path = Path(marker_path_str)
322
+ if not marker_path.exists():
323
+ # Marker gone = node was deleted = orphan
324
+ log(f"[comfy-env] Cleaning orphaned env: {env_dir.name}")
325
+ try:
326
+ shutil.rmtree(env_dir)
327
+ cleaned += 1
328
+ except Exception as e:
329
+ log(f"[comfy-env] Failed to cleanup {env_dir.name}: {e}")
330
+
331
+ return cleaned
@@ -145,15 +145,19 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
145
145
  """
146
146
  Find site-packages and lib directories for the isolated environment.
147
147
 
148
+ Fallback order:
149
+ 1. Marker file -> central cache
150
+ 2. _env_<name> (local)
151
+ 3. .pixi/envs/default (old pixi)
152
+ 4. .venv
153
+
148
154
  Returns:
149
155
  (site_packages, lib_dir) - lib_dir is for LD_LIBRARY_PATH
150
156
  """
151
157
  import glob
152
158
 
153
- # Check _env_<name> directory first (new pattern)
154
- env_name = get_env_name(node_dir.name)
155
- env_dir = node_dir / env_name
156
- if env_dir.exists():
159
+ def _get_paths_from_env(env_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
160
+ """Extract site-packages and lib_dir from an env directory."""
157
161
  if sys.platform == "win32":
158
162
  site_packages = env_dir / "Lib" / "site-packages"
159
163
  lib_dir = env_dir / "Library" / "bin"
@@ -163,23 +167,45 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
163
167
  site_packages = Path(matches[0]) if matches else None
164
168
  lib_dir = env_dir / "lib"
165
169
  if site_packages and site_packages.exists():
166
- return site_packages, lib_dir if lib_dir.exists() else None
170
+ return site_packages, lib_dir if lib_dir and lib_dir.exists() else None
171
+ return None, None
167
172
 
168
- # Fallback: Check old .pixi/envs/default (for backward compat)
173
+ # 1. Check marker file -> central cache
174
+ marker_path = node_dir / ".comfy-env-marker.toml"
175
+ if marker_path.exists():
176
+ try:
177
+ if sys.version_info >= (3, 11):
178
+ import tomllib
179
+ else:
180
+ import tomli as tomllib
181
+ with open(marker_path, "rb") as f:
182
+ marker = tomllib.load(f)
183
+ env_path = marker.get("env", {}).get("path")
184
+ if env_path:
185
+ env_dir = Path(env_path)
186
+ if env_dir.exists():
187
+ result = _get_paths_from_env(env_dir)
188
+ if result[0]:
189
+ return result
190
+ except Exception:
191
+ pass # Fall through to other options
192
+
193
+ # 2. Check _env_<name> directory (local)
194
+ env_name = get_env_name(node_dir.name)
195
+ env_dir = node_dir / env_name
196
+ if env_dir.exists():
197
+ result = _get_paths_from_env(env_dir)
198
+ if result[0]:
199
+ return result
200
+
201
+ # 3. Fallback: Check old .pixi/envs/default (for backward compat)
169
202
  pixi_env = node_dir / ".pixi" / "envs" / "default"
170
203
  if pixi_env.exists():
171
- if sys.platform == "win32":
172
- site_packages = pixi_env / "Lib" / "site-packages"
173
- lib_dir = pixi_env / "Library" / "bin"
174
- else:
175
- pattern = str(pixi_env / "lib" / "python*" / "site-packages")
176
- matches = glob.glob(pattern)
177
- site_packages = Path(matches[0]) if matches else None
178
- lib_dir = pixi_env / "lib"
179
- if site_packages and site_packages.exists():
180
- return site_packages, lib_dir if lib_dir.exists() else None
204
+ result = _get_paths_from_env(pixi_env)
205
+ if result[0]:
206
+ return result
181
207
 
182
- # Check .venv directory
208
+ # 4. Check .venv directory
183
209
  venv_dir = node_dir / ".venv"
184
210
  if venv_dir.exists():
185
211
  if sys.platform == "win32":
@@ -195,19 +221,49 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
195
221
 
196
222
 
197
223
  def _find_env_dir(node_dir: Path) -> Optional[Path]:
198
- """Find the environment directory (for cache key)."""
199
- # Check _env_<name> first
224
+ """
225
+ Find the environment directory (for cache key).
226
+
227
+ Fallback order:
228
+ 1. Marker file -> central cache
229
+ 2. _env_<name> (local)
230
+ 3. .pixi/envs/default (old pixi)
231
+ 4. .venv
232
+ """
233
+ # 1. Check marker file -> central cache
234
+ marker_path = node_dir / ".comfy-env-marker.toml"
235
+ if marker_path.exists():
236
+ try:
237
+ if sys.version_info >= (3, 11):
238
+ import tomllib
239
+ else:
240
+ import tomli as tomllib
241
+ with open(marker_path, "rb") as f:
242
+ marker = tomllib.load(f)
243
+ env_path = marker.get("env", {}).get("path")
244
+ if env_path:
245
+ env_dir = Path(env_path)
246
+ if env_dir.exists():
247
+ return env_dir
248
+ except Exception:
249
+ pass
250
+
251
+ # 2. Check _env_<name> first
200
252
  env_name = get_env_name(node_dir.name)
201
253
  env_dir = node_dir / env_name
202
254
  if env_dir.exists():
203
255
  return env_dir
204
- # Fallback to old paths
256
+
257
+ # 3. Fallback to old .pixi path
205
258
  pixi_env = node_dir / ".pixi" / "envs" / "default"
206
259
  if pixi_env.exists():
207
260
  return pixi_env
261
+
262
+ # 4. Check .venv
208
263
  venv_dir = node_dir / ".venv"
209
264
  if venv_dir.exists():
210
265
  return venv_dir
266
+
211
267
  return None
212
268
 
213
269
 
@@ -534,20 +534,55 @@ def pixi_install(
534
534
 
535
535
  log("CUDA packages installed")
536
536
 
537
- # Move environment from .pixi/envs/default to _env_<name>
537
+ # Move environment from .pixi/envs/default to central cache
538
+ from ..cache import (
539
+ get_central_env_path, write_marker, write_env_metadata,
540
+ MARKER_FILE, get_cache_dir
541
+ )
542
+
538
543
  old_env = node_dir / ".pixi" / "envs" / "default"
539
- env_name = get_env_name(node_dir.name)
540
- new_env = node_dir / env_name
544
+ config_path = node_dir / "comfy-env.toml"
545
+
546
+ # Determine the main node directory (for naming)
547
+ # If node_dir is custom_nodes/NodeName/subdir, main_node_dir is custom_nodes/NodeName
548
+ # If node_dir is custom_nodes/NodeName, main_node_dir is custom_nodes/NodeName
549
+ if node_dir.parent.name == "custom_nodes":
550
+ main_node_dir = node_dir
551
+ else:
552
+ # Walk up to find custom_nodes parent
553
+ main_node_dir = node_dir
554
+ for parent in node_dir.parents:
555
+ if parent.parent.name == "custom_nodes":
556
+ main_node_dir = parent
557
+ break
558
+
559
+ # Get central env path
560
+ central_env = get_central_env_path(main_node_dir, config_path)
541
561
 
542
562
  if old_env.exists():
543
- if new_env.exists():
544
- shutil.rmtree(new_env) # Clean old env
545
- shutil.move(str(old_env), str(new_env))
546
- # Clean up .pixi directory (keep pixi.toml and pixi.lock)
563
+ # Ensure cache directory exists
564
+ get_cache_dir()
565
+
566
+ # Remove old central env if exists
567
+ if central_env.exists():
568
+ shutil.rmtree(central_env)
569
+
570
+ # Move to central cache
571
+ shutil.move(str(old_env), str(central_env))
572
+
573
+ # Write marker file in node directory
574
+ write_marker(config_path, central_env)
575
+
576
+ # Write metadata in env for orphan detection
577
+ marker_path = config_path.parent / MARKER_FILE
578
+ write_env_metadata(central_env, marker_path)
579
+
580
+ # Clean up .pixi directory
547
581
  pixi_dir = node_dir / ".pixi"
548
582
  if pixi_dir.exists():
549
583
  shutil.rmtree(pixi_dir)
550
- log(f"Moved environment to {new_env}")
584
+
585
+ log(f"Environment created at: {central_env}")
551
586
 
552
587
  log("Installation complete!")
553
588
  return True
@@ -127,15 +127,43 @@ def setup_env(node_dir: Optional[str] = None) -> None:
127
127
  for key, value in env_vars.items():
128
128
  os.environ[key] = value
129
129
 
130
- # Check _env_<name> first, then fallback to old .pixi path
131
- env_name = get_env_name(os.path.basename(node_dir))
132
- pixi_env = os.path.join(node_dir, env_name)
133
-
134
- if not os.path.exists(pixi_env):
135
- # Fallback to old .pixi path
136
- pixi_env = os.path.join(node_dir, ".pixi", "envs", "default")
137
- if not os.path.exists(pixi_env):
138
- return # No environment found
130
+ # Resolve environment path with fallback chain:
131
+ # 1. Marker file -> central cache
132
+ # 2. _env_<name> (current local)
133
+ # 3. .pixi/envs/default (old pixi)
134
+ pixi_env = None
135
+
136
+ # 1. Check marker file -> central cache
137
+ marker_path = os.path.join(node_dir, ".comfy-env-marker.toml")
138
+ if os.path.exists(marker_path):
139
+ try:
140
+ if sys.version_info >= (3, 11):
141
+ import tomllib
142
+ else:
143
+ import tomli as tomllib
144
+ with open(marker_path, "rb") as f:
145
+ marker = tomllib.load(f)
146
+ env_path = marker.get("env", {}).get("path")
147
+ if env_path and os.path.exists(env_path):
148
+ pixi_env = env_path
149
+ except Exception:
150
+ pass # Fall through to other options
151
+
152
+ # 2. Check _env_<name> (local)
153
+ if not pixi_env:
154
+ env_name = get_env_name(os.path.basename(node_dir))
155
+ local_env = os.path.join(node_dir, env_name)
156
+ if os.path.exists(local_env):
157
+ pixi_env = local_env
158
+
159
+ # 3. Fallback to old .pixi path
160
+ if not pixi_env:
161
+ old_pixi = os.path.join(node_dir, ".pixi", "envs", "default")
162
+ if os.path.exists(old_pixi):
163
+ pixi_env = old_pixi
164
+
165
+ if not pixi_env:
166
+ return # No environment found
139
167
 
140
168
  if sys.platform == "win32":
141
169
  # Windows: add to PATH for DLL loading
File without changes
File without changes
File without changes