python-package-folder 4.1.3__py3-none-any.whl → 4.3.0__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.
- python_package_folder/_hatch_build.py +40 -10
- python_package_folder/scripts/get-next-version.cjs +553 -0
- {python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/METADATA +1 -1
- {python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/RECORD +7 -6
- {python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/WHEEL +0 -0
- {python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/entry_points.txt +0 -0
- {python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,13 +18,29 @@ class CustomBuildHook(BuildHookInterface):
|
|
|
18
18
|
|
|
19
19
|
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
20
20
|
"""Initialize the build hook and add scripts directory files."""
|
|
21
|
-
# Get the source directory for the package
|
|
22
|
-
source_dir = Path(self.root) / "src" / "python_package_folder"
|
|
23
|
-
scripts_dir = source_dir / "scripts"
|
|
24
|
-
|
|
25
21
|
# Debug: Print to stderr so it shows in build output
|
|
26
22
|
print(f"[DEBUG] Build hook called. Root: {self.root}", file=sys.stderr)
|
|
27
|
-
|
|
23
|
+
|
|
24
|
+
# Try multiple possible locations for the scripts directory
|
|
25
|
+
# 1. Source layout: src/python_package_folder/scripts
|
|
26
|
+
# 2. Sdist layout: python_package_folder/scripts (after extraction)
|
|
27
|
+
# 3. Alternative sdist layout: scripts/ (if extracted differently)
|
|
28
|
+
possible_scripts_dirs = [
|
|
29
|
+
Path(self.root) / "src" / "python_package_folder" / "scripts",
|
|
30
|
+
Path(self.root) / "python_package_folder" / "scripts",
|
|
31
|
+
Path(self.root) / "scripts",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
scripts_dir = None
|
|
35
|
+
for possible_dir in possible_scripts_dirs:
|
|
36
|
+
if possible_dir.exists() and possible_dir.is_dir():
|
|
37
|
+
scripts_dir = possible_dir
|
|
38
|
+
print(f"[DEBUG] Found scripts dir at: {scripts_dir}", file=sys.stderr)
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if scripts_dir is None:
|
|
42
|
+
print(f"[DEBUG] Scripts directory not found. Tried: {[str(d) for d in possible_scripts_dirs]}", file=sys.stderr)
|
|
43
|
+
return
|
|
28
44
|
|
|
29
45
|
# If scripts directory exists, include all files from it
|
|
30
46
|
if scripts_dir.exists() and scripts_dir.is_dir():
|
|
@@ -32,9 +48,25 @@ class CustomBuildHook(BuildHookInterface):
|
|
|
32
48
|
# This ensures they're included in the wheel at the correct location
|
|
33
49
|
for script_file in scripts_dir.iterdir():
|
|
34
50
|
if script_file.is_file():
|
|
35
|
-
# Calculate relative paths
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
# Calculate relative paths from project root
|
|
52
|
+
try:
|
|
53
|
+
source_path = script_file.relative_to(self.root)
|
|
54
|
+
except ValueError:
|
|
55
|
+
# If relative_to fails, try to construct path manually
|
|
56
|
+
# This can happen with sdist layouts
|
|
57
|
+
if "python_package_folder" in str(script_file):
|
|
58
|
+
# Extract the part after python_package_folder
|
|
59
|
+
parts = script_file.parts
|
|
60
|
+
try:
|
|
61
|
+
idx = parts.index("python_package_folder")
|
|
62
|
+
source_path = Path(*parts[idx:])
|
|
63
|
+
except (ValueError, IndexError):
|
|
64
|
+
# Fallback: use the filename
|
|
65
|
+
source_path = Path("python_package_folder") / "scripts" / script_file.name
|
|
66
|
+
else:
|
|
67
|
+
source_path = Path("python_package_folder") / "scripts" / script_file.name
|
|
68
|
+
|
|
69
|
+
# Target path inside the wheel package (always the same)
|
|
38
70
|
target_path = f"python_package_folder/scripts/{script_file.name}"
|
|
39
71
|
|
|
40
72
|
print(f"[DEBUG] Adding {source_path} -> {target_path}", file=sys.stderr)
|
|
@@ -46,8 +78,6 @@ class CustomBuildHook(BuildHookInterface):
|
|
|
46
78
|
build_data["force_include"][str(source_path)] = target_path
|
|
47
79
|
|
|
48
80
|
print(f"[DEBUG] force_include now has {len(build_data.get('force_include', {}))} entries", file=sys.stderr)
|
|
49
|
-
else:
|
|
50
|
-
print(f"[DEBUG] Scripts directory not found at {scripts_dir}", file=sys.stderr)
|
|
51
81
|
|
|
52
82
|
|
|
53
83
|
# Export the hook class (hatchling might need this)
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Get next version using semantic-release.
|
|
4
|
+
*
|
|
5
|
+
* This script runs semantic-release in dry-run mode to determine the next version
|
|
6
|
+
* for a package. It supports both subfolder builds (per-package tags) and main
|
|
7
|
+
* package builds (repo-level tags).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/get-next-version.cjs <project_root> [subfolder_path] [package_name] [repository] [repository_url]
|
|
11
|
+
*
|
|
12
|
+
* Args:
|
|
13
|
+
* - project_root: Root directory of the project (absolute or relative path)
|
|
14
|
+
* - subfolder_path: Optional. Path to subfolder relative to project_root (for Workflow 1)
|
|
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)
|
|
18
|
+
*
|
|
19
|
+
* Output:
|
|
20
|
+
* - Version string (e.g., "1.2.3") if a release is determined
|
|
21
|
+
* - "none" if semantic-release determines no release is needed
|
|
22
|
+
* - Exits with non-zero code on error
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const https = require('https');
|
|
28
|
+
const http = require('http');
|
|
29
|
+
|
|
30
|
+
// Parse command line arguments
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
if (args.length < 1) {
|
|
33
|
+
console.error('Error: project_root is required');
|
|
34
|
+
console.error('Usage: node get-next-version.cjs <project_root> [subfolder_path] [package_name] [repository] [repository_url]');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const projectRoot = path.resolve(args[0]);
|
|
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;
|
|
43
|
+
|
|
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]');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// Note: package_name can be provided without subfolder_path for main package registry queries
|
|
53
|
+
|
|
54
|
+
// Check if project root exists
|
|
55
|
+
if (!fs.existsSync(projectRoot)) {
|
|
56
|
+
console.error(`Error: Project root does not exist: ${projectRoot}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
|
63
|
+
const isSubfolderBuild = subfolderPath !== null && packageName !== null;
|
|
64
|
+
const workingDir = isSubfolderBuild
|
|
65
|
+
? path.resolve(projectRoot, subfolderPath)
|
|
66
|
+
: projectRoot;
|
|
67
|
+
|
|
68
|
+
// Check if working directory exists
|
|
69
|
+
if (!fs.existsSync(workingDir)) {
|
|
70
|
+
console.error(`Error: Working directory does not exist: ${workingDir}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// For subfolder builds, ensure package.json exists with correct name
|
|
75
|
+
let tempPackageJson = null;
|
|
76
|
+
let backupCreatedByScript = false;
|
|
77
|
+
let fileCreatedByScript = false;
|
|
78
|
+
let originalPackageJsonContent = null; // Track original content for restoration
|
|
79
|
+
if (isSubfolderBuild) {
|
|
80
|
+
const packageJsonPath = path.join(workingDir, 'package.json');
|
|
81
|
+
const hadPackageJson = fs.existsSync(packageJsonPath);
|
|
82
|
+
|
|
83
|
+
if (!hadPackageJson) {
|
|
84
|
+
// Create temporary package.json for semantic-release-commit-filter
|
|
85
|
+
const packageJsonContent = JSON.stringify({
|
|
86
|
+
name: packageName,
|
|
87
|
+
version: '0.0.0'
|
|
88
|
+
}, null, 2);
|
|
89
|
+
fs.writeFileSync(packageJsonPath, packageJsonContent, 'utf8');
|
|
90
|
+
tempPackageJson = packageJsonPath;
|
|
91
|
+
fileCreatedByScript = true;
|
|
92
|
+
} else {
|
|
93
|
+
// Read existing package.json and ensure name matches
|
|
94
|
+
try {
|
|
95
|
+
const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
96
|
+
const backup = packageJsonPath + '.backup';
|
|
97
|
+
const backupExists = fs.existsSync(backup);
|
|
98
|
+
|
|
99
|
+
// Store original content before any modifications
|
|
100
|
+
originalPackageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
101
|
+
|
|
102
|
+
if (existing.name !== packageName) {
|
|
103
|
+
// Need to modify the name
|
|
104
|
+
// Check if backup is stale (from a previous crashed run)
|
|
105
|
+
// A backup is stale if it contains the same name we're trying to set
|
|
106
|
+
let isStaleBackup = false;
|
|
107
|
+
if (backupExists) {
|
|
108
|
+
try {
|
|
109
|
+
const backupContent = JSON.parse(fs.readFileSync(backup, 'utf8'));
|
|
110
|
+
// If backup has the name we're trying to set, it's stale from a previous run
|
|
111
|
+
if (backupContent.name === packageName) {
|
|
112
|
+
isStaleBackup = true;
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// If we can't read the backup, treat it as potentially stale
|
|
116
|
+
isStaleBackup = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If backup is stale, restore from it first, then create a fresh backup
|
|
121
|
+
if (isStaleBackup) {
|
|
122
|
+
try {
|
|
123
|
+
fs.copyFileSync(backup, packageJsonPath);
|
|
124
|
+
// Re-read after restoration and update original content
|
|
125
|
+
originalPackageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
126
|
+
const restored = JSON.parse(originalPackageJsonContent);
|
|
127
|
+
// Now create a fresh backup of the restored original
|
|
128
|
+
fs.copyFileSync(packageJsonPath, backup);
|
|
129
|
+
backupCreatedByScript = true;
|
|
130
|
+
// Update the restored content with the new name
|
|
131
|
+
restored.name = packageName;
|
|
132
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(restored, null, 2), 'utf8');
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// If restoration fails, create a new backup of current state
|
|
135
|
+
fs.copyFileSync(packageJsonPath, backup);
|
|
136
|
+
backupCreatedByScript = true;
|
|
137
|
+
existing.name = packageName;
|
|
138
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
// Backup doesn't exist or is valid (preserves user's original)
|
|
142
|
+
// If backup exists, it's user's backup - we'll restore from originalPackageJsonContent
|
|
143
|
+
// If backup doesn't exist, create one
|
|
144
|
+
if (!backupExists) {
|
|
145
|
+
fs.copyFileSync(packageJsonPath, backup);
|
|
146
|
+
backupCreatedByScript = true;
|
|
147
|
+
}
|
|
148
|
+
// Modify the file
|
|
149
|
+
existing.name = packageName;
|
|
150
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
151
|
+
}
|
|
152
|
+
tempPackageJson = packageJsonPath;
|
|
153
|
+
} else if (backupExists) {
|
|
154
|
+
// Name already matches, but check if backup is stale
|
|
155
|
+
// If backup has the same name, it's from a previous crashed run
|
|
156
|
+
try {
|
|
157
|
+
const backupContent = JSON.parse(fs.readFileSync(backup, 'utf8'));
|
|
158
|
+
if (backupContent.name === packageName) {
|
|
159
|
+
// Stale backup from previous run - restore it
|
|
160
|
+
fs.copyFileSync(backup, packageJsonPath);
|
|
161
|
+
// Update original content after restoration
|
|
162
|
+
originalPackageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
163
|
+
// Remove stale backup since we've restored
|
|
164
|
+
fs.unlinkSync(backup);
|
|
165
|
+
// Re-check if we need to modify after restoration
|
|
166
|
+
const restored = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
167
|
+
if (restored.name !== packageName) {
|
|
168
|
+
// After restoration, name doesn't match - need to modify
|
|
169
|
+
fs.copyFileSync(packageJsonPath, backup);
|
|
170
|
+
backupCreatedByScript = true;
|
|
171
|
+
restored.name = packageName;
|
|
172
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(restored, null, 2), 'utf8');
|
|
173
|
+
tempPackageJson = packageJsonPath;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// If we can't read backup, leave it as-is (might be user's backup)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error(`Error reading package.json: ${e.message}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
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;
|
|
319
|
+
try {
|
|
320
|
+
const semanticReleasePath = require.resolve('semantic-release', { paths: [projectRoot] });
|
|
321
|
+
semanticRelease = require(semanticReleasePath);
|
|
322
|
+
} catch (resolveError) {
|
|
323
|
+
try {
|
|
324
|
+
semanticRelease = require('semantic-release');
|
|
325
|
+
} catch (e) {
|
|
326
|
+
console.error('Error: semantic-release is not installed.');
|
|
327
|
+
console.error('Please install it with: npm install -g semantic-release');
|
|
328
|
+
console.error('Or install it as a devDependency: npm install --save-dev semantic-release');
|
|
329
|
+
if (isSubfolderBuild) {
|
|
330
|
+
console.error('For subfolder builds, also install: npm install -g semantic-release-commit-filter');
|
|
331
|
+
console.error('Or as devDependency: npm install --save-dev semantic-release-commit-filter');
|
|
332
|
+
}
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// For subfolder builds, require semantic-release-commit-filter
|
|
338
|
+
// (required only to verify it's installed; the plugin is used via options.plugins)
|
|
339
|
+
// First try resolving from project root (for devDependencies), then fall back to global
|
|
340
|
+
if (isSubfolderBuild) {
|
|
341
|
+
try {
|
|
342
|
+
const commitFilterPath = require.resolve('semantic-release-commit-filter', { paths: [projectRoot] });
|
|
343
|
+
require(commitFilterPath);
|
|
344
|
+
} catch (resolveError) {
|
|
345
|
+
try {
|
|
346
|
+
require('semantic-release-commit-filter');
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.error('Error: semantic-release-commit-filter is not installed.');
|
|
349
|
+
console.error('Please install it with: npm install -g semantic-release-commit-filter');
|
|
350
|
+
console.error('Or install it as a devDependency: npm install --save-dev semantic-release-commit-filter');
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
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
|
+
|
|
372
|
+
// Configure semantic-release options
|
|
373
|
+
const options = {
|
|
374
|
+
dryRun: true,
|
|
375
|
+
ci: false,
|
|
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
|
+
}
|
|
391
|
+
|
|
392
|
+
// For subfolder builds, configure commit filter and per-package tags
|
|
393
|
+
if (isSubfolderBuild) {
|
|
394
|
+
// Get relative path from project root to subfolder for commit filtering
|
|
395
|
+
const relPath = path.relative(projectRoot, workingDir).replace(/\\/g, '/');
|
|
396
|
+
|
|
397
|
+
options.plugins = [
|
|
398
|
+
['@semantic-release/commit-analyzer', {
|
|
399
|
+
preset: 'angular',
|
|
400
|
+
}],
|
|
401
|
+
['semantic-release-commit-filter', {
|
|
402
|
+
cwd: workingDir,
|
|
403
|
+
path: relPath,
|
|
404
|
+
}],
|
|
405
|
+
['@semantic-release/release-notes-generator', {
|
|
406
|
+
preset: 'angular',
|
|
407
|
+
}],
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
// Use per-package tag format: {package-name}-v{version}
|
|
411
|
+
options.tagFormat = `${packageName}-v\${version}`;
|
|
412
|
+
} else {
|
|
413
|
+
// Main package: use default tag format v{version}
|
|
414
|
+
options.plugins = [
|
|
415
|
+
['@semantic-release/commit-analyzer', {
|
|
416
|
+
preset: 'angular',
|
|
417
|
+
}],
|
|
418
|
+
['@semantic-release/release-notes-generator', {
|
|
419
|
+
preset: 'angular',
|
|
420
|
+
}],
|
|
421
|
+
];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Run semantic-release (returns a promise)
|
|
425
|
+
semanticRelease(options, {
|
|
426
|
+
cwd: workingDir,
|
|
427
|
+
env: {
|
|
428
|
+
...process.env,
|
|
429
|
+
// Ensure git commands run from project root for subfolder builds
|
|
430
|
+
GIT_DIR: path.join(projectRoot, '.git'),
|
|
431
|
+
GIT_WORK_TREE: projectRoot,
|
|
432
|
+
},
|
|
433
|
+
}).then((result) => {
|
|
434
|
+
// Clean up temporary package.json if we created or modified it
|
|
435
|
+
if (tempPackageJson && fs.existsSync(tempPackageJson)) {
|
|
436
|
+
const backup = tempPackageJson + '.backup';
|
|
437
|
+
if (backupCreatedByScript && fs.existsSync(backup)) {
|
|
438
|
+
// Restore original (only if we created the backup)
|
|
439
|
+
fs.copyFileSync(backup, tempPackageJson);
|
|
440
|
+
fs.unlinkSync(backup);
|
|
441
|
+
} else if (fileCreatedByScript) {
|
|
442
|
+
// Remove temporary file (only if we created it, not if it existed before)
|
|
443
|
+
fs.unlinkSync(tempPackageJson);
|
|
444
|
+
} else if (originalPackageJsonContent !== null) {
|
|
445
|
+
// We modified an existing file but didn't create a backup (user's backup exists)
|
|
446
|
+
// Restore from the original content we stored, but don't delete user's backup
|
|
447
|
+
fs.writeFileSync(tempPackageJson, originalPackageJsonContent, 'utf8');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Output result
|
|
452
|
+
if (result && result.nextRelease && result.nextRelease.version) {
|
|
453
|
+
console.log(result.nextRelease.version);
|
|
454
|
+
process.exit(0);
|
|
455
|
+
} else {
|
|
456
|
+
console.log('none');
|
|
457
|
+
process.exit(0);
|
|
458
|
+
}
|
|
459
|
+
}).catch((error) => {
|
|
460
|
+
// Clean up temporary package.json on error
|
|
461
|
+
if (tempPackageJson && fs.existsSync(tempPackageJson)) {
|
|
462
|
+
const backup = tempPackageJson + '.backup';
|
|
463
|
+
if (backupCreatedByScript && fs.existsSync(backup)) {
|
|
464
|
+
try {
|
|
465
|
+
// Restore original (only if we created the backup)
|
|
466
|
+
fs.copyFileSync(backup, tempPackageJson);
|
|
467
|
+
fs.unlinkSync(backup);
|
|
468
|
+
} catch (e) {
|
|
469
|
+
// Ignore cleanup errors
|
|
470
|
+
}
|
|
471
|
+
} else if (fileCreatedByScript) {
|
|
472
|
+
try {
|
|
473
|
+
// Remove temporary file (only if we created it, not if it existed before)
|
|
474
|
+
fs.unlinkSync(tempPackageJson);
|
|
475
|
+
} catch (e) {
|
|
476
|
+
// Ignore cleanup errors
|
|
477
|
+
}
|
|
478
|
+
} else if (originalPackageJsonContent !== null) {
|
|
479
|
+
// We modified an existing file but didn't create a backup (user's backup exists)
|
|
480
|
+
// Restore from the original content we stored, but don't delete user's backup
|
|
481
|
+
try {
|
|
482
|
+
fs.writeFileSync(tempPackageJson, originalPackageJsonContent, 'utf8');
|
|
483
|
+
} catch (e) {
|
|
484
|
+
// Ignore cleanup errors
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Check if it's a "no release" case (common, not an error)
|
|
490
|
+
if (error.message && (
|
|
491
|
+
error.message.includes('no release') ||
|
|
492
|
+
error.message.includes('No release') ||
|
|
493
|
+
error.code === 'ENOCHANGE'
|
|
494
|
+
)) {
|
|
495
|
+
console.log('none');
|
|
496
|
+
process.exit(0);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Other errors
|
|
500
|
+
console.error(`Error running semantic-release: ${error.message}`);
|
|
501
|
+
if (error.stack) {
|
|
502
|
+
console.error(error.stack);
|
|
503
|
+
}
|
|
504
|
+
process.exit(1);
|
|
505
|
+
});
|
|
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
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
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
|
+
}
|
|
545
|
+
|
|
546
|
+
// Other errors
|
|
547
|
+
console.error(`Error: ${error.message}`);
|
|
548
|
+
if (error.stack) {
|
|
549
|
+
console.error(error.stack);
|
|
550
|
+
}
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
})();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.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>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
python_package_folder/__init__.py,sha256=DQt-uldOEKfh0MUqCvKdeNKOnpuOvpb7blYvXMyO9Wc,719
|
|
2
2
|
python_package_folder/__main__.py,sha256=a-__-VLhYw-J7S7CsHdhtEvQr3RiAZxiYDvKhKTgMX4,291
|
|
3
|
-
python_package_folder/_hatch_build.py,sha256=
|
|
3
|
+
python_package_folder/_hatch_build.py,sha256=Y50LAM-Jn6f_2M48UZRu-ZIWMY0_nsV8XQyqlRUKoqY,4146
|
|
4
4
|
python_package_folder/analyzer.py,sha256=cmTNUDCWBIh3XZ_mShlQVG1P9NN_oe3FUBTirVtYfTQ,16709
|
|
5
5
|
python_package_folder/finder.py,sha256=RPidZ7LKCFuQ_KgCFIZdHWPXsZIDor3M4C0hKeYW7EI,11799
|
|
6
6
|
python_package_folder/manager.py,sha256=Z9RPg0ZQ7jZhmEXfCzX9OrD_oiA5p2Pnm5Y9tgW3ObQ,55970
|
|
@@ -11,8 +11,9 @@ python_package_folder/subfolder_build.py,sha256=oH_KKLJIMByUZCl8y3AyohUO6Om0OvsI
|
|
|
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-
|
|
15
|
-
python_package_folder-4.
|
|
16
|
-
python_package_folder-4.
|
|
17
|
-
python_package_folder-4.
|
|
18
|
-
python_package_folder-4.
|
|
14
|
+
python_package_folder/scripts/get-next-version.cjs,sha256=5gImk8Yyg0_TZ9ZtL6hXx8lSRT4Dn_sdkDl6mynlqTs,21535
|
|
15
|
+
python_package_folder-4.3.0.dist-info/METADATA,sha256=5SxWRMB_PR-_s3P9JaoIQmwE9Eg5srFAEwoesDziop0,37517
|
|
16
|
+
python_package_folder-4.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
17
|
+
python_package_folder-4.3.0.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
|
|
18
|
+
python_package_folder-4.3.0.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
|
|
19
|
+
python_package_folder-4.3.0.dist-info/RECORD,,
|
|
File without changes
|
{python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_package_folder-4.1.3.dist-info → python_package_folder-4.3.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|