fix(npm): add retry logic for Windows zip extraction
On Windows, the downloaded ZIP file may remain locked by antivirus software or Node.js file handle release delays. This causes Expand-Archive to fail with "being used by another process" errors. Added exponential backoff retry logic (5 attempts, 500ms-8s delays) that detects file lock errors and retries the extraction. Non-lock errors still fail immediately. Fixes GH#889 (bd-5dlz) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,28 +120,53 @@ function extractTarGz(tarGzPath, destDir, binaryName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract zip file (for Windows)
|
// Sleep helper for retry logic
|
||||||
function extractZip(zipPath, destDir, binaryName) {
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract zip file (for Windows) with retry logic
|
||||||
|
async function extractZip(zipPath, destDir, binaryName) {
|
||||||
console.log(`Extracting ${zipPath}...`);
|
console.log(`Extracting ${zipPath}...`);
|
||||||
|
|
||||||
try {
|
const maxRetries = 5;
|
||||||
// Use unzip command or powershell on Windows
|
const baseDelayMs = 500;
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'inherit' });
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
} else {
|
try {
|
||||||
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'inherit' });
|
// Use unzip command or powershell on Windows
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
|
execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'inherit' });
|
||||||
|
} else {
|
||||||
|
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The binary should now be in destDir
|
||||||
|
const extractedBinary = path.join(destDir, binaryName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(extractedBinary)) {
|
||||||
|
throw new Error(`Binary not found after extraction: ${extractedBinary}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Binary extracted to: ${extractedBinary}`);
|
||||||
|
return; // Success
|
||||||
|
} catch (err) {
|
||||||
|
const isFileLockError = err.message && (
|
||||||
|
err.message.includes('being used by another process') ||
|
||||||
|
err.message.includes('Access is denied') ||
|
||||||
|
err.message.includes('cannot access the file')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isFileLockError && attempt < maxRetries) {
|
||||||
|
const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
|
||||||
|
console.log(`File may be locked (attempt ${attempt}/${maxRetries}). Retrying in ${delayMs}ms...`);
|
||||||
|
await sleep(delayMs);
|
||||||
|
} else if (attempt === maxRetries) {
|
||||||
|
throw new Error(`Failed to extract archive after ${maxRetries} attempts: ${err.message}`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to extract archive: ${err.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The binary should now be in destDir
|
|
||||||
const extractedBinary = path.join(destDir, binaryName);
|
|
||||||
|
|
||||||
if (!fs.existsSync(extractedBinary)) {
|
|
||||||
throw new Error(`Binary not found after extraction: ${extractedBinary}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Binary extracted to: ${extractedBinary}`);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`Failed to extract archive: ${err.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +200,7 @@ async function install() {
|
|||||||
|
|
||||||
// Extract the archive based on platform
|
// Extract the archive based on platform
|
||||||
if (platformName === 'windows') {
|
if (platformName === 'windows') {
|
||||||
extractZip(archivePath, binDir, binaryName);
|
await extractZip(archivePath, binDir, binaryName);
|
||||||
} else {
|
} else {
|
||||||
extractTarGz(archivePath, binDir, binaryName);
|
extractTarGz(archivePath, binDir, binaryName);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user