pysfi 0.1.10__py3-none-any.whl → 0.1.12__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 (49) hide show
  1. {pysfi-0.1.10.dist-info → pysfi-0.1.12.dist-info}/METADATA +9 -7
  2. pysfi-0.1.12.dist-info/RECORD +62 -0
  3. {pysfi-0.1.10.dist-info → pysfi-0.1.12.dist-info}/entry_points.txt +13 -2
  4. sfi/__init__.py +1 -1
  5. sfi/alarmclock/alarmclock.py +40 -40
  6. sfi/bumpversion/__init__.py +1 -1
  7. sfi/cleanbuild/cleanbuild.py +155 -0
  8. sfi/condasetup/condasetup.py +116 -0
  9. sfi/docdiff/docdiff.py +238 -0
  10. sfi/docscan/__init__.py +1 -1
  11. sfi/docscan/docscan_gui.py +1 -1
  12. sfi/docscan/lang/eng.py +152 -152
  13. sfi/docscan/lang/zhcn.py +170 -170
  14. sfi/filedate/filedate.py +185 -112
  15. sfi/gittool/__init__.py +2 -0
  16. sfi/gittool/gittool.py +401 -0
  17. sfi/llmclient/llmclient.py +592 -0
  18. sfi/llmquantize/llmquantize.py +480 -0
  19. sfi/llmserver/llmserver.py +335 -0
  20. sfi/makepython/makepython.py +2 -2
  21. sfi/pdfsplit/pdfsplit.py +4 -4
  22. sfi/pyarchive/pyarchive.py +418 -0
  23. sfi/pyembedinstall/__init__.py +0 -0
  24. sfi/pyembedinstall/pyembedinstall.py +629 -0
  25. sfi/pylibpack/pylibpack.py +813 -269
  26. sfi/pylibpack/rules/numpy.json +22 -0
  27. sfi/pylibpack/rules/pymupdf.json +10 -0
  28. sfi/pylibpack/rules/pyqt5.json +19 -0
  29. sfi/pylibpack/rules/pyside2.json +23 -0
  30. sfi/pylibpack/rules/scipy.json +23 -0
  31. sfi/pylibpack/rules/shiboken2.json +24 -0
  32. sfi/pyloadergen/pyloadergen.py +271 -572
  33. sfi/pypack/pypack.py +822 -471
  34. sfi/pyprojectparse/__init__.py +0 -0
  35. sfi/pyprojectparse/pyprojectparse.py +500 -0
  36. sfi/pysourcepack/pysourcepack.py +308 -369
  37. sfi/quizbase/__init__.py +0 -0
  38. sfi/quizbase/quizbase.py +828 -0
  39. sfi/quizbase/quizbase_gui.py +987 -0
  40. sfi/regexvalidate/__init__.py +0 -0
  41. sfi/regexvalidate/regex_help.html +284 -0
  42. sfi/regexvalidate/regexvalidate.py +468 -0
  43. sfi/taskkill/taskkill.py +0 -2
  44. pysfi-0.1.10.dist-info/RECORD +0 -39
  45. sfi/embedinstall/embedinstall.py +0 -478
  46. sfi/projectparse/projectparse.py +0 -152
  47. {pysfi-0.1.10.dist-info → pysfi-0.1.12.dist-info}/WHEEL +0 -0
  48. /sfi/{embedinstall → llmclient}/__init__.py +0 -0
  49. /sfi/{projectparse → llmquantize}/__init__.py +0 -0
@@ -1,369 +1,308 @@
1
- """Source code packaging tool for projects defined in projects.json."""
2
-
3
- from __future__ import annotations
4
-
5
- import argparse
6
- import json
7
- import logging
8
- import shutil
9
- import sys
10
- from pathlib import Path
11
-
12
- __version__ = "0.1.0"
13
-
14
- cwd = Path.cwd()
15
- logging.basicConfig(level=logging.INFO, format="%(message)s")
16
- logger = logging.getLogger(__name__)
17
-
18
- # Default directories and files to exclude during packaging
19
- DEFAULT_EXCLUDE = {
20
- "__pycache__",
21
- "*.pyc",
22
- "*.pyo",
23
- ".pytest_cache",
24
- ".benchmarks",
25
- "tests",
26
- ".git",
27
- ".gitignore",
28
- "dist",
29
- "build",
30
- "*.egg-info",
31
- "node_modules",
32
- ".idea",
33
- "*.log",
34
- }
35
-
36
- # Default files to include in packaging
37
- DEFAULT_INCLUDE = {
38
- "*.py",
39
- "README.md",
40
- "LICENSE",
41
- "pyproject.toml",
42
- }
43
-
44
-
45
- def load_projects(projects_file: Path) -> dict:
46
- """Load projects from JSON file.
47
-
48
- Args:
49
- projects_file: Path to projects.json file
50
-
51
- Returns:
52
- Dictionary containing project information
53
- """
54
- if not projects_file.exists():
55
- logger.error(f"Projects file {projects_file} does not exist")
56
- return {}
57
-
58
- try:
59
- with projects_file.open("r", encoding="utf-8") as f:
60
- return json.load(f)
61
- except Exception as e:
62
- logger.error(f"Error loading projects from {projects_file}: {e}")
63
- return {}
64
-
65
-
66
- def list_projects(projects: dict) -> None:
67
- """List all available projects.
68
-
69
- Args:
70
- projects: Dictionary containing project information
71
- """
72
- if not projects:
73
- logger.warning("No projects found")
74
- return
75
-
76
- logger.info("Available projects:")
77
- for name, info in sorted(projects.items()):
78
- version = info.get("version", "N/A")
79
- description = info.get("description", "No description")
80
- logger.info(f" - {name} (v{version}): {description}")
81
-
82
-
83
- def should_exclude(path: Path, exclude_patterns: set[str]) -> bool:
84
- """Check if a path should be excluded based on patterns.
85
-
86
- Args:
87
- path: Path to check
88
- exclude_patterns: Set of patterns to exclude
89
-
90
- Returns:
91
- True if path should be excluded, False otherwise
92
- """
93
- for pattern in exclude_patterns:
94
- if pattern.startswith("*"):
95
- # Wildcard pattern (e.g., "*.pyc")
96
- if path.match(pattern):
97
- return True
98
- else:
99
- # Exact name pattern
100
- if path.name == pattern:
101
- return True
102
- if path.parts:
103
- # Check if any parent directory matches
104
- for part in path.parts:
105
- if part == pattern:
106
- return True
107
- return False
108
-
109
-
110
- def should_include(path: Path, include_patterns: set[str], exclude_patterns: set[str]) -> bool:
111
- """Check if a path should be included based on include and exclude patterns.
112
-
113
- Args:
114
- path: Path to check
115
- include_patterns: Set of patterns to include
116
- exclude_patterns: Set of patterns to exclude
117
-
118
- Returns:
119
- True if path should be included, False otherwise
120
- """
121
- # Check if should be excluded first
122
- if should_exclude(path, exclude_patterns):
123
- return False
124
-
125
- # If no include patterns, include everything not excluded
126
- if not include_patterns:
127
- return True
128
-
129
- # Check if matches any include pattern
130
- for pattern in include_patterns:
131
- if pattern.startswith("*"):
132
- # Wildcard pattern (e.g., "*.py")
133
- if path.match(pattern):
134
- return True
135
- else:
136
- # Exact name pattern
137
- if path.name == pattern:
138
- return True
139
- if path.is_dir() and path.name == pattern:
140
- # Include directory and all its contents
141
- return True
142
-
143
- return False
144
-
145
-
146
- def pack_project(
147
- project_name: str,
148
- projects: dict,
149
- base_dir: Path,
150
- output_dir: Path | None = None,
151
- include_patterns: set[str] | None = None,
152
- exclude_patterns: set[str] | None = None,
153
- ) -> bool:
154
- """Pack project source code and resources to dist/src directory.
155
-
156
- Args:
157
- project_name: Name of the project to pack
158
- projects: Dictionary containing project information
159
- base_dir: Base directory containing project folders
160
- output_dir: Output directory (default: project's dist/src)
161
- include_patterns: File patterns to include (default: DEFAULT_INCLUDE)
162
- exclude_patterns: File patterns to exclude (default: DEFAULT_EXCLUDE)
163
-
164
- Returns:
165
- True if packing succeeded, False otherwise
166
- """
167
- if project_name not in projects:
168
- logger.error(f"Project '{project_name}' not found in projects.json")
169
- return False
170
-
171
- project_path = base_dir / project_name
172
-
173
- # If project directory doesn't exist in base_dir, try to search for it
174
- if not project_path.exists():
175
- logger.debug(f"Project directory {project_path} does not exist in base_dir")
176
- # Search in parent directories
177
- search_dirs = [base_dir, base_dir.parent, base_dir.parent.parent]
178
- for search_dir in search_dirs:
179
- candidate = search_dir / project_name
180
- if candidate.exists() and candidate.is_dir():
181
- logger.info(f"Found project at {candidate}")
182
- project_path = candidate
183
- break
184
-
185
- if not project_path.exists():
186
- logger.error(f"Project directory for '{project_name}' not found")
187
- logger.debug(f"Searched in: {base_dir}, {base_dir.parent}, {base_dir.parent.parent}")
188
- return False
189
-
190
- if not project_path.is_dir():
191
- logger.error(f"{project_path} is not a directory")
192
- return False
193
-
194
- # Determine output directory
195
- output_dir = project_path / "dist" / "src" if output_dir is None else Path(output_dir) / project_name
196
-
197
- # Create output directory
198
- try:
199
- output_dir.mkdir(parents=True, exist_ok=True)
200
- except Exception as e:
201
- logger.error(f"Error creating output directory {output_dir}: {e}")
202
- return False
203
-
204
- # Use default patterns if not specified
205
- if include_patterns is None:
206
- include_patterns = DEFAULT_INCLUDE
207
- if exclude_patterns is None:
208
- exclude_patterns = DEFAULT_EXCLUDE
209
-
210
- # Copy files
211
- copied_files = 0
212
- copied_dirs = 0
213
-
214
- logger.info(f"Packing project '{project_name}' to {output_dir}")
215
-
216
- try:
217
- for item in project_path.iterdir():
218
- if item.is_file():
219
- # For files, check if should be included
220
- if should_include(item, include_patterns, exclude_patterns):
221
- dest_file = output_dir / item.name
222
- shutil.copy2(item, dest_file)
223
- logger.debug(f"Copied file: {item.name}")
224
- copied_files += 1
225
- elif item.is_dir():
226
- # For directories, exclude only if in exclude patterns
227
- if not should_exclude(item, exclude_patterns):
228
- # Copy directory recursively with exclude patterns
229
- dest_dir = output_dir / item.name
230
- if dest_dir.exists():
231
- shutil.rmtree(dest_dir)
232
- shutil.copytree(item, dest_dir, ignore=shutil.ignore_patterns(*DEFAULT_EXCLUDE))
233
- logger.debug(f"Copied directory: {item.name}")
234
- copied_dirs += 1
235
-
236
- logger.info(f"Successfully packed {project_name}: {copied_files} files, {copied_dirs} directories")
237
- return True
238
-
239
- except Exception as e:
240
- logger.error(f"Error packing project {project_name}: {e}")
241
- return False
242
-
243
-
244
- def main():
245
- parser = argparse.ArgumentParser(
246
- description="PySourcePack - Source code packaging tool",
247
- epilog="Pack source code and resources for projects defined in projects.json",
248
- )
249
- parser.add_argument(
250
- "-i",
251
- "--input",
252
- default="projects.json",
253
- help="Input projects.json file path (default: projects.json)",
254
- )
255
- parser.add_argument(
256
- "-d",
257
- "--directory",
258
- default=str(cwd),
259
- help="Base directory containing project folders (default: current directory)",
260
- )
261
- parser.add_argument(
262
- "-o",
263
- "--output-dir",
264
- default=None,
265
- help="Output directory for packed files (default: <project>/dist/src)",
266
- )
267
- parser.add_argument(
268
- "-p",
269
- "--project",
270
- default=None,
271
- help="Project name to pack (default: pack all projects)",
272
- )
273
- parser.add_argument(
274
- "-l",
275
- "--list",
276
- action="store_true",
277
- help="List all available projects",
278
- )
279
- parser.add_argument(
280
- "--include",
281
- nargs="*",
282
- default=None,
283
- help="File patterns to include (default: *.py, README.md, pyproject.toml)",
284
- )
285
- parser.add_argument(
286
- "--exclude",
287
- nargs="*",
288
- default=None,
289
- help="File patterns to exclude (default: __pycache__, *.pyc, tests, .benchmarks, etc.)",
290
- )
291
- parser.add_argument(
292
- "--debug",
293
- action="store_true",
294
- help="Enable debug mode",
295
- )
296
-
297
- args = parser.parse_args()
298
-
299
- if args.debug:
300
- logger.setLevel(logging.DEBUG)
301
-
302
- # Load projects file
303
- projects_file = Path(args.input)
304
- if not projects_file.is_absolute():
305
- # Relative to base directory
306
- base_dir = Path(args.directory)
307
- projects_file = base_dir / args.input
308
- else:
309
- base_dir = Path(args.directory)
310
-
311
- # If projects_file is relative and not found, try to find it in current directory
312
- if not projects_file.exists():
313
- logger.warning(f"Projects file {projects_file} not found, trying current directory...")
314
- alt_file = Path.cwd() / args.input
315
- if alt_file.exists():
316
- projects_file = alt_file
317
- base_dir = alt_file.parent
318
- logger.info(f"Found projects file at {projects_file}")
319
-
320
- # Check if user explicitly specified the -d parameter
321
- explicit_dir = any(arg in ["-d", "--directory"] for arg in sys.argv)
322
- if not explicit_dir and projects_file.exists():
323
- # If -d was not explicitly specified, set base_dir to projects_file's parent
324
- base_dir = projects_file.parent
325
- logger.debug(f"Auto-set base_dir to {base_dir} (parent of projects.json)")
326
-
327
- projects = load_projects(projects_file)
328
- if not projects:
329
- return
330
-
331
- # List projects if requested
332
- if args.list:
333
- list_projects(projects)
334
- return
335
-
336
- # Process include and exclude patterns
337
- include_patterns = set(args.include) if args.include else None
338
- exclude_patterns = set(args.exclude) if args.exclude else None
339
-
340
- # Pack specified project or all projects
341
- if args.project:
342
- success = pack_project(
343
- args.project,
344
- projects,
345
- base_dir,
346
- output_dir=Path(args.output_dir) if args.output_dir else None,
347
- include_patterns=include_patterns,
348
- exclude_patterns=exclude_patterns,
349
- )
350
- if success:
351
- logger.info(f"Packed project '{args.project}' successfully")
352
- else:
353
- logger.error(f"Failed to pack project '{args.project}'")
354
- else:
355
- # Pack all projects
356
- logger.info("Packing all projects...")
357
- success_count = 0
358
- for project_name in projects:
359
- if pack_project(
360
- project_name,
361
- projects,
362
- base_dir,
363
- output_dir=Path(args.output_dir) if args.output_dir else None,
364
- include_patterns=include_patterns,
365
- exclude_patterns=exclude_patterns,
366
- ):
367
- success_count += 1
368
-
369
- logger.info(f"Packed {success_count}/{len(projects)} projects successfully")
1
+ """Source code packaging tool for projects defined in projects.json."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+ import shutil
8
+ from pathlib import Path
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = [
12
+ "pack_project",
13
+ ]
14
+
15
+ cwd = Path.cwd()
16
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Default directories and files to exclude during packaging
20
+ DEFAULT_EXCLUDE = {
21
+ "__pycache__",
22
+ "*.pyc",
23
+ "*.pyo",
24
+ ".pytest_cache",
25
+ ".benchmarks",
26
+ "tests",
27
+ ".git",
28
+ ".gitignore",
29
+ "dist",
30
+ "build",
31
+ "*.egg-info",
32
+ "node_modules",
33
+ ".idea",
34
+ "*.log",
35
+ }
36
+
37
+ # Default files to include in packaging
38
+ DEFAULT_INCLUDE = {
39
+ "*.py",
40
+ "README.md",
41
+ "LICENSE",
42
+ "pyproject.toml",
43
+ }
44
+
45
+
46
+ def should_exclude(path: Path, exclude_patterns: set[str]) -> bool:
47
+ """Check if a path should be excluded based on patterns.
48
+
49
+ Args:
50
+ path: Path to check
51
+ exclude_patterns: Set of patterns to exclude
52
+
53
+ Returns:
54
+ True if path should be excluded, False otherwise
55
+ """
56
+ for pattern in exclude_patterns:
57
+ if pattern.startswith("*"):
58
+ # Wildcard pattern (e.g., "*.pyc")
59
+ if path.match(pattern):
60
+ return True
61
+ else:
62
+ # Exact name pattern
63
+ if path.name == pattern:
64
+ return True
65
+ if path.parts:
66
+ # Check if any parent directory matches
67
+ for part in path.parts:
68
+ if part == pattern:
69
+ return True
70
+ return False
71
+
72
+
73
+ def should_include(
74
+ path: Path, include_patterns: set[str], exclude_patterns: set[str]
75
+ ) -> bool:
76
+ """Check if a path should be included based on include and exclude patterns.
77
+
78
+ Args:
79
+ path: Path to check
80
+ include_patterns: Set of patterns to include
81
+ exclude_patterns: Set of patterns to exclude
82
+
83
+ Returns:
84
+ True if path should be included, False otherwise
85
+ """
86
+ # Check if should be excluded first
87
+ if should_exclude(path, exclude_patterns):
88
+ return False
89
+
90
+ # If no include patterns, include everything not excluded
91
+ if not include_patterns:
92
+ return True
93
+
94
+ # Check if matches any include pattern
95
+ for pattern in include_patterns:
96
+ if pattern.startswith("*"):
97
+ # Wildcard pattern (e.g., "*.py")
98
+ if path.match(pattern):
99
+ return True
100
+ else:
101
+ # Exact name pattern
102
+ if path.name == pattern:
103
+ return True
104
+ if path.is_dir() and path.name == pattern:
105
+ # Include directory and all its contents
106
+ return True
107
+
108
+ return False
109
+
110
+
111
+ def _check_project_path(project_path: Path) -> bool:
112
+ if not project_path.exists():
113
+ logger.error(f"Project directory {project_path} does not exist")
114
+ return False
115
+
116
+ if not project_path.is_dir():
117
+ logger.error(f"{project_path} is not a directory")
118
+ return False
119
+
120
+ return True
121
+
122
+
123
+ def pack_project(
124
+ base_dir: Path,
125
+ projects: dict,
126
+ project_name: str,
127
+ include_patterns: set[str] | None = None,
128
+ exclude_patterns: set[str] | None = None,
129
+ ) -> bool:
130
+ """Pack project source code and resources to dist/src directory.
131
+
132
+ Args:
133
+ base_dir: Base directory containing project folders
134
+ project_name: Name of the project to pack
135
+ include_patterns: File patterns to include (default: DEFAULT_INCLUDE)
136
+ exclude_patterns: File patterns to exclude (default: DEFAULT_EXCLUDE)
137
+
138
+ Returns:
139
+ True if packing succeeded, False otherwise
140
+ """
141
+ logger.debug(f"Start packing project: {project_name}")
142
+
143
+ if project_name not in projects:
144
+ logger.error(f"Project '{project_name}' not found in projects.json")
145
+ return False
146
+
147
+ if not project_name:
148
+ logger.error("Project name cannot be empty")
149
+ return False
150
+
151
+ project_path = base_dir if len(projects) == 1 else base_dir / project_name
152
+ output_dir = base_dir / "dist" / "src" / project_name
153
+
154
+ logger.debug(f"Project path: {project_path}, project_name: {project_name}")
155
+ if not _check_project_path(project_path):
156
+ return False
157
+
158
+ output_dir.mkdir(parents=True, exist_ok=True)
159
+
160
+ # Use default patterns if not specified
161
+ if include_patterns is None:
162
+ include_patterns = DEFAULT_INCLUDE
163
+ if exclude_patterns is None:
164
+ exclude_patterns = DEFAULT_EXCLUDE
165
+
166
+ # Copy files
167
+ copied_files = 0
168
+ copied_dirs = 0
169
+
170
+ logger.info(f"Packing project '{project_name}' to {output_dir}")
171
+
172
+ try:
173
+ for item in project_path.iterdir():
174
+ if item.is_file():
175
+ # For files, check if should be included
176
+ if should_include(item, include_patterns, exclude_patterns):
177
+ dest_file = output_dir / item.name
178
+ shutil.copy2(item, dest_file)
179
+ logger.debug(f"Copied file: {item.name}")
180
+ copied_files += 1
181
+ elif item.is_dir() and not should_exclude(item, exclude_patterns):
182
+ # Copy directory recursively with exclude patterns
183
+ dest_dir = output_dir / item.name
184
+ if dest_dir.exists():
185
+ shutil.rmtree(dest_dir)
186
+ shutil.copytree(
187
+ item, dest_dir, ignore=shutil.ignore_patterns(*DEFAULT_EXCLUDE)
188
+ )
189
+ logger.debug(f"Copied directory: {item.name}")
190
+ copied_dirs += 1
191
+
192
+ logger.info(
193
+ f"Successfully packed {project_name}: {copied_files} files, {copied_dirs} directories"
194
+ )
195
+ return True
196
+
197
+ except Exception as e:
198
+ logger.error(f"Error packing project {project_name}: {e}")
199
+ return False
200
+
201
+
202
+ def create_parser() -> argparse.ArgumentParser:
203
+ """Create and return an argument parser for the PySourcePack tool."""
204
+ parser = argparse.ArgumentParser(
205
+ description="PySourcePack - Source code packaging tool",
206
+ epilog="Pack source code and resources for projects defined in projects.json",
207
+ )
208
+ parser.add_argument(
209
+ "directory",
210
+ default=str(cwd),
211
+ nargs="?",
212
+ help="Base directory containing project folders (default: current directory)",
213
+ )
214
+ parser.add_argument(
215
+ "-p",
216
+ "--project",
217
+ default=None,
218
+ help="Project name to pack (default: pack all projects)",
219
+ )
220
+ parser.add_argument(
221
+ "-l",
222
+ "--list",
223
+ action="store_true",
224
+ help="List all available projects",
225
+ )
226
+ parser.add_argument(
227
+ "--include",
228
+ nargs="*",
229
+ default=None,
230
+ help="File patterns to include (default: *.py, README.md, pyproject.toml)",
231
+ )
232
+ parser.add_argument(
233
+ "--exclude",
234
+ nargs="*",
235
+ default=None,
236
+ help="File patterns to exclude (default: __pycache__, *.pyc, tests, .benchmarks, etc.)",
237
+ )
238
+ parser.add_argument(
239
+ "--debug",
240
+ "-d",
241
+ action="store_true",
242
+ help="Enable debug mode",
243
+ )
244
+ return parser
245
+
246
+
247
+ def main() -> None:
248
+ from sfi.pyprojectparse.pyprojectparse import Solution
249
+
250
+ parser = create_parser()
251
+ args = parser.parse_args()
252
+
253
+ if args.debug:
254
+ logger.setLevel(logging.DEBUG)
255
+
256
+ # Load projects file
257
+ base_dir = Path(args.directory)
258
+ project_json = base_dir / "projects.json"
259
+ projects = Solution.from_directory(base_dir, recursive=True, update=True).projects
260
+
261
+ if not project_json.exists():
262
+ logger.error("Failed to create projects.json")
263
+ return
264
+
265
+ if not projects:
266
+ logger.error("No projects found in projects.json")
267
+ return
268
+
269
+ if args.list:
270
+ logger.info("Available projects:")
271
+ for info in projects.values():
272
+ logger.info(info)
273
+ return
274
+
275
+ # Process include and exclude patterns
276
+ include_patterns = set(args.include) if args.include else None
277
+ exclude_patterns = set(args.exclude) if args.exclude else None
278
+
279
+ logger.debug(
280
+ f"Start packing, config: base_dir={base_dir}, project_name={args.project}, include_patterns={include_patterns}, exclude_patterns={exclude_patterns}"
281
+ )
282
+ # Pack specified project or all projects
283
+ if args.project:
284
+ if pack_project(
285
+ base_dir=base_dir,
286
+ projects=projects,
287
+ project_name=args.project,
288
+ include_patterns=include_patterns,
289
+ exclude_patterns=exclude_patterns,
290
+ ):
291
+ logger.info(f"Packed project '{args.project}' successfully")
292
+ else:
293
+ logger.error(f"Failed to pack project '{args.project}'")
294
+ return
295
+
296
+ # Pack all projects
297
+ logger.info("Packing all projects...")
298
+ success_count = 0
299
+ for project_name in projects:
300
+ if pack_project(
301
+ base_dir=base_dir,
302
+ projects=projects,
303
+ project_name=project_name,
304
+ include_patterns=include_patterns,
305
+ exclude_patterns=exclude_patterns,
306
+ ):
307
+ success_count += 1
308
+ logger.info(f"Packed {success_count}/{len(projects)} projects successfully")