python-package-folder 4.0.0__tar.gz → 4.1.0__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 (52) hide show
  1. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/PKG-INFO +18 -1
  2. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/README.md +17 -0
  3. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/coverage.svg +2 -2
  4. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/pyproject.toml +1 -1
  5. {python_package_folder-4.0.0 → python_package_folder-4.1.0/python_package_folder}/scripts/get-next-version.cjs +224 -54
  6. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/python_package_folder.py +44 -3
  7. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.copier-answers.yml +0 -0
  8. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  9. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.cursor/rules/general.mdc +0 -0
  10. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.cursor/rules/python.mdc +0 -0
  11. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.github/workflows/ci.yml +0 -0
  12. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.github/workflows/publish.yml +0 -0
  13. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.gitignore +0 -0
  14. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/.vscode/settings.json +0 -0
  15. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/LICENSE +0 -0
  16. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/Makefile +0 -0
  17. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/development.md +0 -0
  18. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/installation.md +0 -0
  19. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/publishing.md +0 -0
  20. {python_package_folder-4.0.0/python_package_folder → python_package_folder-4.1.0}/scripts/get-next-version.cjs +0 -0
  21. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/__init__.py +0 -0
  22. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/__main__.py +0 -0
  23. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/_hatch_build.py +0 -0
  24. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/analyzer.py +0 -0
  25. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/finder.py +0 -0
  26. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/manager.py +0 -0
  27. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/publisher.py +0 -0
  28. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/py.typed +0 -0
  29. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/subfolder_build.py +0 -0
  30. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/types.py +0 -0
  31. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/utils.py +0 -0
  32. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/src/python_package_folder/version.py +0 -0
  33. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/conftest.py +0 -0
  34. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/some_globals.py +0 -0
  35. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  36. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  37. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  38. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  39. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  40. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  41. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_build_with_external_deps.py +0 -0
  42. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_linting.py +0 -0
  43. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_preserve_directory_structure.py +0 -0
  44. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_publisher.py +0 -0
  45. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_shared_subdirectory_imports.py +0 -0
  46. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_spreadsheet_creation_imports.py +0 -0
  47. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_subfolder_build.py +0 -0
  48. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_third_party_dependencies.py +0 -0
  49. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_utils.py +0 -0
  50. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/test_version_manager.py +0 -0
  51. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/tests/tests.py +0 -0
  52. {python_package_folder-4.0.0 → python_package_folder-4.1.0}/uv.lock +0 -0
@@ -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.0
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
@@ -455,15 +455,32 @@ The `--version` option:
455
455
 
456
456
  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.
457
457
 
458
+ **Version Detection:**
459
+ - **Baseline version**:
460
+ - **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.
461
+ - **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.
462
+ - **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.
463
+
458
464
  **For subfolder builds (Workflow 1):**
459
465
  - Uses per-package tags: `{package-name}-v{version}` (e.g., `my-package-v1.2.3`)
466
+ - Queries the target registry for the latest published version of the subfolder package
460
467
  - Filters commits to only those affecting the subfolder path
468
+ - **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:
469
+ - `fix: update my_subfolder/foo.py` → **Included** (affects subfolder)
470
+ - `feat: add feature to other_package/bar.py` → **Excluded** (doesn't affect subfolder)
471
+ - `fix: update my_subfolder/baz.py and shared/utils.py` → **Included** (affects subfolder, even if it also touches files outside)
461
472
  - Requires `semantic-release-commit-filter` plugin
462
473
 
463
474
  **For main package builds (Workflow 2):**
464
475
  - Uses repo-level tags: `v{version}` (e.g., `v1.2.3`)
476
+ - Queries the target registry for the latest published version when publishing
465
477
  - Analyzes all commits in the repository
466
478
 
479
+ **Registry Support:**
480
+ - **PyPI**: Fully supported via JSON API (`https://pypi.org/pypi/{package-name}/json`)
481
+ - **TestPyPI**: Fully supported via JSON API (`https://test.pypi.org/pypi/{package-name}/json`)
482
+ - **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.
483
+
467
484
  **Setup:**
468
485
  ```bash
469
486
  # Install semantic-release globally
@@ -14,7 +14,7 @@
14
14
  <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
15
15
  <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
16
16
  <text x="31.5" y="14">coverage</text>
17
- <text x="81" y="15" fill="#010101" fill-opacity=".3">66%</text>
18
- <text x="81" y="14">66%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">65%</text>
18
+ <text x="81" y="14">65%</text>
19
19
  </g>
20
20
  </svg>
@@ -42,7 +42,7 @@ dependencies = [
42
42
 
43
43
  # ---- Dev dependencies ----
44
44
 
45
- version = "4.0.0"
45
+ version = "4.1.0"
46
46
  [dependency-groups]
47
47
  dev = [
48
48
  "pytest>=8.3.5",
@@ -7,12 +7,14 @@
7
7
  * package builds (repo-level tags).
8
8
  *
9
9
  * Usage:
10
- * node scripts/get-next-version.cjs <project_root> [subfolder_path] [package_name]
10
+ * node scripts/get-next-version.cjs <project_root> [subfolder_path] [package_name] [repository] [repository_url]
11
11
  *
12
12
  * Args:
13
13
  * - project_root: Root directory of the project (absolute or relative path)
14
14
  * - subfolder_path: Optional. Path to subfolder relative to project_root (for Workflow 1)
15
15
  * - package_name: Optional. Package name for subfolder builds (for per-package tags)
16
+ * - repository: Optional. Target repository ('pypi', 'testpypi', or 'azure')
17
+ * - repository_url: Optional. Repository URL (required for Azure Artifacts)
16
18
  *
17
19
  * Output:
18
20
  * - Version string (e.g., "1.2.3") if a release is determined
@@ -22,25 +24,32 @@
22
24
 
23
25
  const path = require('path');
24
26
  const fs = require('fs');
27
+ const https = require('https');
28
+ const http = require('http');
25
29
 
26
30
  // Parse command line arguments
27
31
  const args = process.argv.slice(2);
28
32
  if (args.length < 1) {
29
33
  console.error('Error: project_root is required');
30
- console.error('Usage: node get-next-version.cjs <project_root> [subfolder_path] [package_name]');
34
+ console.error('Usage: node get-next-version.cjs <project_root> [subfolder_path] [package_name] [repository] [repository_url]');
31
35
  process.exit(1);
32
36
  }
33
37
 
34
38
  const projectRoot = path.resolve(args[0]);
35
- const subfolderPath = args[1] || null;
36
- const packageName = args[2] || null;
39
+ const subfolderPath = args[1] && args[1] !== 'null' && args[1] !== '' ? args[1] : null;
40
+ const packageName = args[2] && args[2] !== 'null' && args[2] !== '' ? args[2] : null;
41
+ const repository = args[3] && args[3] !== 'null' && args[3] !== '' ? args[3] : null;
42
+ const repositoryUrl = args[4] && args[4] !== 'null' && args[4] !== '' ? args[4] : null;
37
43
 
38
- // Validate argument combination: both-or-neither for subfolder builds
39
- if ((subfolderPath !== null && packageName === null) || (subfolderPath === null && packageName !== null)) {
40
- console.error('Error: subfolder_path and package_name must be provided together (both or neither).');
41
- console.error('Usage: node get-next-version.cjs <project_root> [subfolder_path] [package_name]');
44
+ // Validate argument combination
45
+ // - For subfolder builds: both subfolder_path and package_name are required
46
+ // - For main package builds: package_name can be provided alone (for registry queries)
47
+ if (subfolderPath !== null && packageName === null) {
48
+ console.error('Error: package_name is required when subfolder_path is provided.');
49
+ console.error('Usage: node get-next-version.cjs <project_root> [subfolder_path] [package_name] [repository] [repository_url]');
42
50
  process.exit(1);
43
51
  }
52
+ // Note: package_name can be provided without subfolder_path for main package registry queries
44
53
 
45
54
  // Check if project root exists
46
55
  if (!fs.existsSync(projectRoot)) {
@@ -49,6 +58,8 @@ if (!fs.existsSync(projectRoot)) {
49
58
  }
50
59
 
51
60
  // Determine if this is a subfolder build
61
+ // A subfolder build requires both subfolder_path and package_name
62
+ // package_name alone (without subfolder_path) indicates a main package build with registry query
52
63
  const isSubfolderBuild = subfolderPath !== null && packageName !== null;
53
64
  const workingDir = isSubfolderBuild
54
65
  ? path.resolve(projectRoot, subfolderPath)
@@ -173,10 +184,138 @@ if (isSubfolderBuild) {
173
184
  }
174
185
  }
175
186
 
176
- try {
177
- // Try to require semantic-release
178
- // First try resolving from project root (for devDependencies), then fall back to global
179
- let semanticRelease;
187
+ /**
188
+ * Query PyPI or TestPyPI JSON API for the latest version of a package.
189
+ * @param {string} packageName - Package name to query
190
+ * @param {string} registry - 'pypi' or 'testpypi'
191
+ * @returns {Promise<string|null>} Latest version string or null if not found
192
+ */
193
+ async function queryPyPIVersion(packageName, registry) {
194
+ const baseUrl = registry === 'testpypi'
195
+ ? 'https://test.pypi.org'
196
+ : 'https://pypi.org';
197
+ const url = `${baseUrl}/pypi/${packageName}/json`;
198
+
199
+ return new Promise((resolve, reject) => {
200
+ https.get(url, (res) => {
201
+ let data = '';
202
+
203
+ res.on('data', (chunk) => {
204
+ data += chunk;
205
+ });
206
+
207
+ res.on('end', () => {
208
+ if (res.statusCode === 404) {
209
+ // Package doesn't exist yet (first release)
210
+ resolve(null);
211
+ } else if (res.statusCode === 200) {
212
+ try {
213
+ const json = JSON.parse(data);
214
+ // Get latest version from info.version or releases
215
+ const version = json.info?.version || Object.keys(json.releases || {}).pop() || null;
216
+ resolve(version);
217
+ } catch (e) {
218
+ reject(new Error(`Failed to parse PyPI response: ${e.message}`));
219
+ }
220
+ } else {
221
+ reject(new Error(`PyPI API returned status ${res.statusCode}`));
222
+ }
223
+ });
224
+ }).on('error', (err) => {
225
+ reject(err);
226
+ });
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Query Azure Artifacts for the latest version of a package.
232
+ * Azure Artifacts uses a simple index format (HTML) which is more complex to parse.
233
+ * For now, we'll attempt to query but fall back gracefully if it fails.
234
+ * @param {string} packageName - Package name to query
235
+ * @param {string} repositoryUrl - Azure Artifacts repository URL
236
+ * @returns {Promise<string|null>} Latest version string or null if not found/unsupported
237
+ */
238
+ async function queryAzureArtifactsVersion(packageName, repositoryUrl) {
239
+ // Convert upload URL to simple index URL
240
+ // Upload: https://pkgs.dev.azure.com/ORG/PROJECT/_packaging/FEED/pypi/upload
241
+ // Simple: https://pkgs.dev.azure.com/ORG/PROJECT/_packaging/FEED/pypi/simple/{package}/
242
+ let simpleIndexUrl;
243
+ try {
244
+ const url = new URL(repositoryUrl);
245
+ if (url.pathname.endsWith('/upload')) {
246
+ simpleIndexUrl = repositoryUrl.replace('/upload', `/simple/${packageName}/`);
247
+ } else {
248
+ // Try to construct from common patterns
249
+ simpleIndexUrl = repositoryUrl.replace(/\/upload$/, `/simple/${packageName}/`);
250
+ }
251
+ } catch (e) {
252
+ // Invalid URL format, return null to fall back to git tags
253
+ return null;
254
+ }
255
+
256
+ return new Promise((resolve) => {
257
+ // Azure Artifacts may require authentication and returns HTML
258
+ // For now, we'll attempt the request but gracefully fall back if it fails
259
+ // This is a limitation - Azure Artifacts API is more complex than PyPI
260
+ const url = new URL(simpleIndexUrl);
261
+ const client = url.protocol === 'https:' ? https : http;
262
+
263
+ const req = client.get(simpleIndexUrl, (res) => {
264
+ // Azure Artifacts simple index returns HTML, not JSON
265
+ // Parsing HTML is complex and may require authentication
266
+ // For now, we'll return null to fall back to git tags
267
+ // This can be enhanced later with proper HTML parsing or API endpoint discovery
268
+ resolve(null);
269
+ });
270
+
271
+ req.on('error', () => {
272
+ // Network error or authentication required - fall back to git tags
273
+ resolve(null);
274
+ });
275
+
276
+ req.setTimeout(5000, () => {
277
+ req.destroy();
278
+ resolve(null);
279
+ });
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Query the package registry for the latest published version.
285
+ * @param {string} packageName - Package name to query
286
+ * @param {string|null} repository - Repository type ('pypi', 'testpypi', 'azure', or null)
287
+ * @param {string|null} repositoryUrl - Repository URL (for Azure)
288
+ * @returns {Promise<string|null>} Latest version or null if not found/unsupported
289
+ */
290
+ async function queryRegistryVersion(packageName, repository, repositoryUrl) {
291
+ if (!repository || !packageName) {
292
+ return null;
293
+ }
294
+
295
+ try {
296
+ if (repository === 'pypi' || repository === 'testpypi') {
297
+ return await queryPyPIVersion(packageName, repository);
298
+ } else if (repository === 'azure') {
299
+ if (!repositoryUrl) {
300
+ return null;
301
+ }
302
+ return await queryAzureArtifactsVersion(packageName, repositoryUrl);
303
+ }
304
+ } catch (error) {
305
+ // Log error but don't fail - fall back to git tags
306
+ console.error(`Warning: Failed to query ${repository} for latest version: ${error.message}`);
307
+ console.error('Falling back to git tags for version detection.');
308
+ }
309
+
310
+ return null;
311
+ }
312
+
313
+ // Main execution
314
+ (async () => {
315
+ try {
316
+ // Try to require semantic-release
317
+ // First try resolving from project root (for devDependencies), then fall back to global
318
+ let semanticRelease;
180
319
  try {
181
320
  const semanticReleasePath = require.resolve('semantic-release', { paths: [projectRoot] });
182
321
  semanticRelease = require(semanticReleasePath);
@@ -214,11 +353,41 @@ try {
214
353
  }
215
354
  }
216
355
 
356
+ // Query registry for latest version if repository info is provided
357
+ let registryVersion = null;
358
+ if (repository && packageName) {
359
+ try {
360
+ registryVersion = await queryRegistryVersion(packageName, repository, repositoryUrl);
361
+ if (registryVersion) {
362
+ console.error(`Found latest version on ${repository}: ${registryVersion}`);
363
+ } else {
364
+ console.error(`Package not found on ${repository} or query failed, using git tags as fallback`);
365
+ }
366
+ } catch (error) {
367
+ console.error(`Warning: Registry query failed: ${error.message}`);
368
+ console.error('Falling back to git tags for version detection.');
369
+ }
370
+ }
371
+
217
372
  // Configure semantic-release options
218
373
  const options = {
219
374
  dryRun: true,
220
375
  ci: false,
221
376
  };
377
+
378
+ // If we have a registry version, we can use it to set lastRelease in semantic-release context
379
+ // This ensures semantic-release uses the registry version as baseline instead of git tags
380
+ if (registryVersion) {
381
+ // Set lastRelease in options to use registry version as baseline
382
+ // This tells semantic-release to analyze commits since this version
383
+ options.lastRelease = {
384
+ version: registryVersion,
385
+ gitTag: isSubfolderBuild
386
+ ? `${packageName}-v${registryVersion}`
387
+ : `v${registryVersion}`,
388
+ gitHead: null, // We don't know the commit, but semantic-release will find it
389
+ };
390
+ }
222
391
 
223
392
  // For subfolder builds, configure commit filter and per-package tags
224
393
  if (isSubfolderBuild) {
@@ -334,50 +503,51 @@ try {
334
503
  }
335
504
  process.exit(1);
336
505
  });
337
- } catch (error) {
338
- // Clean up temporary package.json on error
339
- if (tempPackageJson && fs.existsSync(tempPackageJson)) {
340
- const backup = tempPackageJson + '.backup';
341
- if (backupCreatedByScript && fs.existsSync(backup)) {
342
- try {
343
- // Restore original (only if we created the backup)
344
- fs.copyFileSync(backup, tempPackageJson);
345
- fs.unlinkSync(backup);
346
- } catch (e) {
347
- // Ignore cleanup errors
348
- }
349
- } else if (fileCreatedByScript) {
350
- try {
351
- // Remove temporary file (only if we created it, not if it existed before)
352
- fs.unlinkSync(tempPackageJson);
353
- } catch (e) {
354
- // Ignore cleanup errors
355
- }
356
- } else if (originalPackageJsonContent !== null) {
357
- // We modified an existing file but didn't create a backup (user's backup exists)
358
- // Restore from the original content we stored, but don't delete user's backup
359
- try {
360
- fs.writeFileSync(tempPackageJson, originalPackageJsonContent, 'utf8');
361
- } catch (e) {
362
- // Ignore cleanup errors
506
+ } catch (error) {
507
+ // Clean up temporary package.json on error
508
+ if (tempPackageJson && fs.existsSync(tempPackageJson)) {
509
+ const backup = tempPackageJson + '.backup';
510
+ if (backupCreatedByScript && fs.existsSync(backup)) {
511
+ try {
512
+ // Restore original (only if we created the backup)
513
+ fs.copyFileSync(backup, tempPackageJson);
514
+ fs.unlinkSync(backup);
515
+ } catch (e) {
516
+ // Ignore cleanup errors
517
+ }
518
+ } else if (fileCreatedByScript) {
519
+ try {
520
+ // Remove temporary file (only if we created it, not if it existed before)
521
+ fs.unlinkSync(tempPackageJson);
522
+ } catch (e) {
523
+ // Ignore cleanup errors
524
+ }
525
+ } else if (originalPackageJsonContent !== null) {
526
+ // We modified an existing file but didn't create a backup (user's backup exists)
527
+ // Restore from the original content we stored, but don't delete user's backup
528
+ try {
529
+ fs.writeFileSync(tempPackageJson, originalPackageJsonContent, 'utf8');
530
+ } catch (e) {
531
+ // Ignore cleanup errors
532
+ }
363
533
  }
364
534
  }
365
- }
366
535
 
367
- // Check if it's a "no release" case (common, not an error)
368
- if (error.message && (
369
- error.message.includes('no release') ||
370
- error.message.includes('No release') ||
371
- error.code === 'ENOCHANGE'
372
- )) {
373
- console.log('none');
374
- process.exit(0);
375
- }
536
+ // Check if it's a "no release" case (common, not an error)
537
+ if (error.message && (
538
+ error.message.includes('no release') ||
539
+ error.message.includes('No release') ||
540
+ error.code === 'ENOCHANGE'
541
+ )) {
542
+ console.log('none');
543
+ process.exit(0);
544
+ }
376
545
 
377
- // Other errors
378
- console.error(`Error: ${error.message}`);
379
- if (error.stack) {
380
- console.error(error.stack);
546
+ // Other errors
547
+ console.error(`Error: ${error.message}`);
548
+ if (error.stack) {
549
+ console.error(error.stack);
550
+ }
551
+ process.exit(1);
381
552
  }
382
- process.exit(1);
383
- }
553
+ })();
@@ -27,6 +27,8 @@ def resolve_version_via_semantic_release(
27
27
  project_root: Path,
28
28
  subfolder_path: Path | None = None,
29
29
  package_name: str | None = None,
30
+ repository: str | None = None,
31
+ repository_url: str | None = None,
30
32
  ) -> str | None:
31
33
  """
32
34
  Resolve the next version using semantic-release via Node.js script.
@@ -35,6 +37,8 @@ def resolve_version_via_semantic_release(
35
37
  project_root: Root directory of the project
36
38
  subfolder_path: Optional path to subfolder (relative to project_root) for Workflow 1
37
39
  package_name: Optional package name for subfolder builds
40
+ repository: Optional target repository ('pypi', 'testpypi', or 'azure')
41
+ repository_url: Optional repository URL (required for Azure Artifacts)
38
42
 
39
43
  Returns:
40
44
  Version string if a release is determined, None if no release or error
@@ -98,7 +102,17 @@ def resolve_version_via_semantic_release(
98
102
  else subfolder_path
99
103
  )
100
104
  cmd.extend([str(rel_path), package_name])
101
- # Workflow 2: main package (no additional args needed)
105
+ elif package_name:
106
+ # Main package build with package_name (for registry queries)
107
+ # Pass null for subfolder_path, then package_name
108
+ cmd.extend(["", package_name])
109
+ # Workflow 2: main package without package_name (no additional args needed)
110
+
111
+ # Add repository information if provided
112
+ if repository:
113
+ cmd.append(repository)
114
+ if repository_url:
115
+ cmd.append(repository_url)
102
116
 
103
117
  result = subprocess.run(
104
118
  cmd,
@@ -297,6 +311,10 @@ def main() -> int:
297
311
  # Version is needed for subfolder builds or when publishing main package
298
312
  if is_subfolder or args.publish:
299
313
  print("No --version provided, attempting to resolve via semantic-release...")
314
+ # Get repository info if publishing
315
+ repository = args.publish if args.publish else None
316
+ repository_url = args.repository_url if args.publish else None
317
+
300
318
  if is_subfolder:
301
319
  # Workflow 1: subfolder build
302
320
  # src_dir is guaranteed to be relative to project_root due to is_subfolder check
@@ -305,11 +323,34 @@ def main() -> int:
305
323
  ).lower().strip("-")
306
324
  subfolder_rel_path = src_dir.relative_to(project_root)
307
325
  resolved_version = resolve_version_via_semantic_release(
308
- project_root, subfolder_rel_path, package_name
326
+ project_root,
327
+ subfolder_rel_path,
328
+ package_name,
329
+ repository=repository,
330
+ repository_url=repository_url,
309
331
  )
310
332
  else:
311
333
  # Workflow 2: main package
312
- resolved_version = resolve_version_via_semantic_release(project_root)
334
+ # For main package, we need package_name from pyproject.toml for registry queries
335
+ package_name_for_registry = None
336
+ if repository:
337
+ try:
338
+ import tomllib
339
+ pyproject_path = project_root / "pyproject.toml"
340
+ if pyproject_path.exists():
341
+ with open(pyproject_path, "rb") as f:
342
+ data = tomllib.load(f)
343
+ package_name_for_registry = data.get("project", {}).get("name")
344
+ except Exception:
345
+ pass
346
+
347
+ resolved_version = resolve_version_via_semantic_release(
348
+ project_root,
349
+ subfolder_path=None,
350
+ package_name=package_name_for_registry,
351
+ repository=repository,
352
+ repository_url=repository_url,
353
+ )
313
354
 
314
355
  if resolved_version:
315
356
  print(f"Resolved version via semantic-release: {resolved_version}")