python-package-folder 4.0.0__py3-none-any.whl → 4.1.1__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.
@@ -10,6 +10,8 @@ It can be invoked via:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import os
14
+ import shutil
13
15
  import subprocess
14
16
  import sys
15
17
  from pathlib import Path
@@ -23,11 +25,23 @@ from .manager import BuildManager
23
25
  from .utils import find_project_root, find_source_directory
24
26
 
25
27
 
28
+ def is_github_actions() -> bool:
29
+ """Check if running in GitHub Actions."""
30
+ return os.getenv("GITHUB_ACTIONS") == "true"
31
+
32
+
33
+ def check_node_available() -> bool:
34
+ """Check if Node.js is available."""
35
+ return shutil.which("node") is not None
36
+
37
+
26
38
  def resolve_version_via_semantic_release(
27
39
  project_root: Path,
28
40
  subfolder_path: Path | None = None,
29
41
  package_name: str | None = None,
30
- ) -> str | None:
42
+ repository: str | None = None,
43
+ repository_url: str | None = None,
44
+ ) -> tuple[str | None, str | None]:
31
45
  """
32
46
  Resolve the next version using semantic-release via Node.js script.
33
47
 
@@ -35,10 +49,38 @@ def resolve_version_via_semantic_release(
35
49
  project_root: Root directory of the project
36
50
  subfolder_path: Optional path to subfolder (relative to project_root) for Workflow 1
37
51
  package_name: Optional package name for subfolder builds
52
+ repository: Optional target repository ('pypi', 'testpypi', or 'azure')
53
+ repository_url: Optional repository URL (required for Azure Artifacts)
38
54
 
39
55
  Returns:
40
- Version string if a release is determined, None if no release or error
56
+ Tuple of (version string if a release is determined, error message if any)
57
+ Returns (None, None) if no release or no error, (None, error_msg) on error
41
58
  """
59
+ # Note: Node.js availability should be checked before calling this function
60
+ # This check is a safety fallback
61
+ if not check_node_available():
62
+ if is_github_actions():
63
+ error_msg = """Node.js is not available in this GitHub Actions workflow.
64
+
65
+ To fix this, add the following steps BEFORE running python-package-folder:
66
+
67
+ - name: Setup Node.js
68
+ uses: actions/setup-node@v4
69
+ with:
70
+ node-version: '20'
71
+
72
+ - name: Install semantic-release
73
+ run: |
74
+ npm install -g semantic-release semantic-release-commit-filter
75
+
76
+ Alternatively, provide --version explicitly to skip automatic version resolution."""
77
+ print(f"Error: {error_msg}", file=sys.stderr)
78
+ return None, error_msg
79
+ else:
80
+ error_msg = "Node.js not found. Cannot resolve version via semantic-release."
81
+ print(f"Error: {error_msg}", file=sys.stderr)
82
+ return None, error_msg
83
+
42
84
  # Try to find the script in multiple locations:
43
85
  # 1. Project root / scripts (for development or when script is in repo)
44
86
  # 2. Package installation directory / scripts (for installed package)
@@ -86,7 +128,9 @@ def resolve_version_via_semantic_release(
86
128
  script_path = fallback_script
87
129
 
88
130
  if not script_path:
89
- return None
131
+ error_msg = "Could not locate get-next-version.cjs script"
132
+ print(f"Error: {error_msg}", file=sys.stderr)
133
+ return None, error_msg
90
134
 
91
135
  # Build command arguments
92
136
  cmd = ["node", str(script_path), str(project_root)]
@@ -98,7 +142,17 @@ def resolve_version_via_semantic_release(
98
142
  else subfolder_path
99
143
  )
100
144
  cmd.extend([str(rel_path), package_name])
101
- # Workflow 2: main package (no additional args needed)
145
+ elif package_name:
146
+ # Main package build with package_name (for registry queries)
147
+ # Pass null for subfolder_path, then package_name
148
+ cmd.extend(["", package_name])
149
+ # Workflow 2: main package without package_name (no additional args needed)
150
+
151
+ # Add repository information if provided
152
+ if repository:
153
+ cmd.append(repository)
154
+ if repository_url:
155
+ cmd.append(repository_url)
102
156
 
103
157
  result = subprocess.run(
104
158
  cmd,
@@ -109,38 +163,53 @@ def resolve_version_via_semantic_release(
109
163
  )
110
164
 
111
165
  if result.returncode != 0:
112
- # Log error details for debugging
166
+ # Collect error details
167
+ error_details = []
113
168
  if result.stderr:
114
- print(
115
- f"Warning: semantic-release version resolution failed: {result.stderr}",
116
- file=sys.stderr,
117
- )
118
- elif result.stdout:
119
- print(
120
- f"Warning: semantic-release version resolution failed: {result.stdout}",
121
- file=sys.stderr,
122
- )
123
- return None
169
+ error_details.append(f"stderr: {result.stderr}")
170
+ if result.stdout:
171
+ error_details.append(f"stdout: {result.stdout}")
172
+
173
+ error_msg = "semantic-release version resolution failed"
174
+ if error_details:
175
+ error_msg += f": {'; '.join(error_details)}"
176
+
177
+ print(f"Error: {error_msg}", file=sys.stderr)
178
+ return None, error_msg
124
179
 
125
180
  version = result.stdout.strip()
126
181
  if version and version != "none":
127
- return version
182
+ return version, None
128
183
 
129
- return None
184
+ return None, None
130
185
  except FileNotFoundError:
131
- # Node.js not found
132
- print(
133
- "Warning: Node.js not found. Cannot resolve version via semantic-release.",
134
- file=sys.stderr,
135
- )
136
- return None
186
+ # Node.js not found (shouldn't happen if check_node_available() passed, but handle gracefully)
187
+ if is_github_actions():
188
+ error_msg = """Node.js is not available in this GitHub Actions workflow.
189
+
190
+ To fix this, add the following steps BEFORE running python-package-folder:
191
+
192
+ - name: Setup Node.js
193
+ uses: actions/setup-node@v4
194
+ with:
195
+ node-version: '20'
196
+
197
+ - name: Install semantic-release
198
+ run: |
199
+ npm install -g semantic-release semantic-release-commit-filter
200
+
201
+ Alternatively, provide --version explicitly to skip automatic version resolution."""
202
+ print(f"Error: {error_msg}", file=sys.stderr)
203
+ return None, error_msg
204
+ else:
205
+ error_msg = "Node.js not found. Cannot resolve version via semantic-release."
206
+ print(f"Error: {error_msg}", file=sys.stderr)
207
+ return None, error_msg
137
208
  except Exception as e:
138
209
  # Other errors (e.g., permission issues, script not found)
139
- print(
140
- f"Warning: Error resolving version via semantic-release: {e}",
141
- file=sys.stderr,
142
- )
143
- return None
210
+ error_msg = f"Error resolving version via semantic-release: {e}"
211
+ print(f"Error: {error_msg}", file=sys.stderr)
212
+ return None, error_msg
144
213
  finally:
145
214
  # Clean up temporary file if we extracted from zip/pex
146
215
  # This must be at function level to ensure cleanup even on early return
@@ -297,6 +366,50 @@ def main() -> int:
297
366
  # Version is needed for subfolder builds or when publishing main package
298
367
  if is_subfolder or args.publish:
299
368
  print("No --version provided, attempting to resolve via semantic-release...")
369
+
370
+ # Check Node.js availability upfront
371
+ if not check_node_available():
372
+ if is_github_actions():
373
+ error_msg = """Node.js is not available in this GitHub Actions workflow.
374
+
375
+ To fix this, add the following steps BEFORE running python-package-folder:
376
+
377
+ - name: Setup Node.js
378
+ uses: actions/setup-node@v4
379
+ with:
380
+ node-version: '20'
381
+
382
+ - name: Install semantic-release
383
+ run: |
384
+ npm install -g semantic-release semantic-release-commit-filter
385
+
386
+ Alternatively, provide --version explicitly to skip automatic version resolution."""
387
+ print(f"Error: {error_msg}", file=sys.stderr)
388
+ else:
389
+ print(
390
+ "Error: Node.js is not available. Cannot resolve version via semantic-release.",
391
+ file=sys.stderr,
392
+ )
393
+ print(
394
+ "Please install Node.js or provide --version explicitly.",
395
+ file=sys.stderr,
396
+ )
397
+ return 1
398
+
399
+ # Log that Node.js is available (for debugging)
400
+ node_version = subprocess.run(
401
+ ["node", "--version"],
402
+ capture_output=True,
403
+ text=True,
404
+ check=False,
405
+ )
406
+ if node_version.returncode == 0:
407
+ print(f"Node.js detected: {node_version.stdout.strip()}")
408
+
409
+ # Get repository info if publishing
410
+ repository = args.publish if args.publish else None
411
+ repository_url = args.repository_url if args.publish else None
412
+
300
413
  if is_subfolder:
301
414
  # Workflow 1: subfolder build
302
415
  # src_dir is guaranteed to be relative to project_root due to is_subfolder check
@@ -304,12 +417,35 @@ def main() -> int:
304
417
  " ", "-"
305
418
  ).lower().strip("-")
306
419
  subfolder_rel_path = src_dir.relative_to(project_root)
307
- resolved_version = resolve_version_via_semantic_release(
308
- project_root, subfolder_rel_path, package_name
420
+ resolved_version, error_details = resolve_version_via_semantic_release(
421
+ project_root,
422
+ subfolder_rel_path,
423
+ package_name,
424
+ repository=repository,
425
+ repository_url=repository_url,
309
426
  )
310
427
  else:
311
428
  # Workflow 2: main package
312
- resolved_version = resolve_version_via_semantic_release(project_root)
429
+ # For main package, we need package_name from pyproject.toml for registry queries
430
+ package_name_for_registry = None
431
+ if repository:
432
+ try:
433
+ import tomllib
434
+ pyproject_path = project_root / "pyproject.toml"
435
+ if pyproject_path.exists():
436
+ with open(pyproject_path, "rb") as f:
437
+ data = tomllib.load(f)
438
+ package_name_for_registry = data.get("project", {}).get("name")
439
+ except Exception:
440
+ pass
441
+
442
+ resolved_version, error_details = resolve_version_via_semantic_release(
443
+ project_root,
444
+ subfolder_path=None,
445
+ package_name=package_name_for_registry,
446
+ repository=repository,
447
+ repository_url=repository_url,
448
+ )
313
449
 
314
450
  if resolved_version:
315
451
  print(f"Resolved version via semantic-release: {resolved_version}")
@@ -319,8 +455,11 @@ def main() -> int:
319
455
  "This could mean:\n"
320
456
  " - No release is needed (no relevant commits)\n"
321
457
  " - semantic-release is not installed or configured\n"
322
- " - Node.js is not available\n\n"
323
- "Please either:\n"
458
+ )
459
+ if error_details:
460
+ error_msg += f"\nDetails: {error_details}\n"
461
+ error_msg += (
462
+ "\nPlease either:\n"
324
463
  " - Install semantic-release: npm install -g semantic-release"
325
464
  )
326
465
  if is_subfolder:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 4.0.0
3
+ Version: 4.1.1
4
4
  Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
5
5
  Project-URL: Repository, https://github.com/alelom/python-package-folder
6
6
  Author-email: Alessio Lombardi <work@alelom.com>
@@ -475,15 +475,32 @@ The `--version` option:
475
475
 
476
476
  When `--version` is not provided, the tool can automatically determine the next version using semantic-release. This requires Node.js, npm, and semantic-release to be installed.
477
477
 
478
+ **Version Detection:**
479
+ - **Baseline version**:
480
+ - **Registry Query (Preferred)**: When publishing to a repository (PyPI, TestPyPI, or Azure Artifacts), the tool queries the target registry for the latest published version and uses it as the baseline for version calculation. This ensures version calculations are based on what's actually published, not just git tags.
481
+ - **Git Tags (Fallback)**: If the package doesn't exist on the registry yet (first release) or if registry query fails, the tool falls back to using git tags to determine the starting version.
482
+ - **New version to publish**: After determining the baseline version, [`semantic-release`](https://semantic-release.gitbook.io/semantic-release/) analyzes commits since that version to calculate the next version bump (major, minor, or patch) based on [_conventional commit_](https://www.conventionalcommits.org/en/v1.0.0/) messages.
483
+
478
484
  **For subfolder builds (Workflow 1):**
479
485
  - Uses per-package tags: `{package-name}-v{version}` (e.g., `my-package-v1.2.3`)
486
+ - Queries the target registry for the latest published version of the subfolder package
480
487
  - Filters commits to only those affecting the subfolder path
488
+ - **Commit filtering behavior**: Only commits that modify files within the subfolder path are considered for version calculation. Commits that only target files outside the subfolder are excluded. For example:
489
+ - `fix: update my_subfolder/foo.py` → **Included** (affects subfolder)
490
+ - `feat: add feature to other_package/bar.py` → **Excluded** (doesn't affect subfolder)
491
+ - `fix: update my_subfolder/baz.py and shared/utils.py` → **Included** (affects subfolder, even if it also touches files outside)
481
492
  - Requires `semantic-release-commit-filter` plugin
482
493
 
483
494
  **For main package builds (Workflow 2):**
484
495
  - Uses repo-level tags: `v{version}` (e.g., `v1.2.3`)
496
+ - Queries the target registry for the latest published version when publishing
485
497
  - Analyzes all commits in the repository
486
498
 
499
+ **Registry Support:**
500
+ - **PyPI**: Fully supported via JSON API (`https://pypi.org/pypi/{package-name}/json`)
501
+ - **TestPyPI**: Fully supported via JSON API (`https://test.pypi.org/pypi/{package-name}/json`)
502
+ - **Azure Artifacts**: Basic support with fallback to git tags. Azure Artifacts uses a different API format and may require authentication, so if the query fails, the tool automatically falls back to git tags.
503
+
487
504
  **Setup:**
488
505
  ```bash
489
506
  # Install semantic-release globally
@@ -6,13 +6,13 @@ python_package_folder/finder.py,sha256=RPidZ7LKCFuQ_KgCFIZdHWPXsZIDor3M4C0hKeYW7
6
6
  python_package_folder/manager.py,sha256=Z9RPg0ZQ7jZhmEXfCzX9OrD_oiA5p2Pnm5Y9tgW3ObQ,55970
7
7
  python_package_folder/publisher.py,sha256=TSjdOvxvnWLbJCnduTK_xZBRfvsrq9kpEH-sfebeWkU,13507
8
8
  python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- python_package_folder/python_package_folder.py,sha256=yqwAFKjP8eAq5-cTiJJktndji5GYXPz6-uXb7b4ZwfI,15912
9
+ python_package_folder/python_package_folder.py,sha256=HOJQOgDiPhwVta4iudehuy1QLu4mrdjUNV6z0zaSTxs,21820
10
10
  python_package_folder/subfolder_build.py,sha256=oH_KKLJIMByUZCl8y3AyohUO6Om0OvsIQ7Xg1fkd3jE,38782
11
11
  python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
12
12
  python_package_folder/utils.py,sha256=lIkWsFKeAYAJ9TDUM99T4pUBHJVbUvCdUgkWQN-LUho,3111
13
13
  python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
14
- python_package_folder-4.0.0.dist-info/METADATA,sha256=nEAxlP6arQTeduyXCqtfR8ZdR5rLAAAEqGaPpoJtNyM,35525
15
- python_package_folder-4.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
- python_package_folder-4.0.0.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
17
- python_package_folder-4.0.0.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
18
- python_package_folder-4.0.0.dist-info/RECORD,,
14
+ python_package_folder-4.1.1.dist-info/METADATA,sha256=tP7YhDnLYlnSzoDTKIjjpVcuCyIs4dGwtuedDFbajUc,37517
15
+ python_package_folder-4.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
+ python_package_folder-4.1.1.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
17
+ python_package_folder-4.1.1.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
18
+ python_package_folder-4.1.1.dist-info/RECORD,,