Robust error handling for hdiutil operations with detailed error explanations and retry logic for transient failures. Provides comprehensive support for managing macOS disk utility operations and DMG-related errors.
High-level wrapper for hdiutil commands with automatic retry logic and error handling.
/**
* Execute hdiutil command with retry logic for transient errors
* Automatically retries failed operations for known transient error codes
* @param args - Array of hdiutil command arguments
* @returns Promise resolving to command output or null
* @throws Error for non-transient failures after retry attempts
*/
function hdiUtil(args: string[]): Promise<string | null>;Usage Examples:
import { hdiUtil } from "dmg-builder";
// Attach DMG with automatic retry
const attachResult = await hdiUtil([
"attach", "-noverify", "-noautoopen", "/path/to/file.dmg"
]);
// Create DMG from folder
await hdiUtil([
"create", "-srcfolder", "/source/folder",
"-format", "UDZO", "/output/file.dmg"
]);
// Get detailed system information
const info = await hdiUtil(["info"]);
// Detach volume with retry logic
await hdiUtil(["detach", "-quiet", "/dev/disk2"]);Comprehensive error code explanation system for hdiutil operations.
/**
* Provide human-readable explanation for hdiutil error codes
* Includes common causes and suggested solutions
* @param errorCode - Numeric error code from hdiutil
* @returns Descriptive error message with troubleshooting guidance
*/
function explainHdiutilError(errorCode: number): string;Usage Examples:
import { explainHdiutilError } from "dmg-builder";
try {
await hdiUtil(["attach", "/invalid/path.dmg"]);
} catch (error) {
const explanation = explainHdiutilError(error.code);
console.error(`hdiutil failed: ${explanation}`);
// Output: "No such file or directory: Check if the specified path exists."
}
// Get explanation for specific error codes
console.log(explainHdiutilError(16));
// "Resource busy: The volume is in use. Try closing files or processes and retry."
console.log(explainHdiutilError(35));
// "Operation timed out: The system was too slow or unresponsive. Try again."Set of error codes that indicate transient failures suitable for automatic retry.
/**
* Set of hdiutil error codes that are transient and should be retried
* These codes typically indicate temporary resource conflicts or timing issues
*/
const hdiutilTransientExitCodes: Set<number>;Transient Error Codes:
1 - Generic error (can occur from brief race conditions)16 - Resource busy (volume in use, often resolves after retry)35 - Operation timed out (system delay, retry after short delay)256 - Volume in use or unmount failure (similar to 16)49153 - Volume not mounted yet (attach may be too fast)/**
* Comprehensive mapping of hdiutil error codes to explanations
* Covers common and uncommon hdiutil failure scenarios
*/
const errorCodeMappings = {
0: "Success: The hdiutil command completed without error.",
1: "Generic error: The operation failed, but the reason is not specific. Check command syntax or permissions.",
2: "No such file or directory: Check if the specified path exists.",
6: "Disk image to resize is not currently attached or not recognized as a valid block device by macOS.",
8: "Exec format error: The file might not be a valid disk image.",
16: "Resource busy: The volume is in use. Try closing files or processes and retry.",
22: "Invalid argument: One or more arguments passed to hdiutil are incorrect.",
35: "Operation timed out: The system was too slow or unresponsive. Try again.",
36: "I/O error: There was a problem reading or writing to disk. Check disk health.",
100: "Image-related error: The disk image may be corrupted or invalid.",
256: "Volume is busy or could not be unmounted. Try again after closing files.",
49153: "Volume not mounted yet: The image may not have been fully attached.",
"-5341": "Disk image too small: hdiutil could not fit the contents. Increase the image size.",
"-5342": "Specified size too small: Disk image creation failed due to insufficient size."
};Temporary/Retryable Errors:
Permanent/Non-Retryable Errors:
The retry system uses exponential backoff with configurable parameters:
const retryConfig = {
retries: 5, // Maximum retry attempts
interval: 5000, // Initial delay (5 seconds)
backoff: 2000, // Additional delay per retry (2 seconds)
shouldRetry: (error) => hdiutilTransientExitCodes.has(error.code)
};// Internal implementation of retry logic
const shouldRetry = (args: string[]) => (error: any) => {
const code = error.code ?? -1;
const stderr = error.stderr?.toString() || "";
const stdout = error.stdout?.toString() || "";
const output = `${stdout} ${stderr}`.trim();
const willRetry = hdiutilTransientExitCodes.has(code);
// Log retry decision with context
log.warn({
willRetry,
args,
code,
output
}, `hdiutil error: ${explainHdiutilError(code)}`);
return willRetry;
};Enable verbose hdiutil output for troubleshooting:
// Set environment variable for debug output
process.env.DEBUG_DMG = "true";
// This adds -verbose flag to hdiutil commands instead of -quiet
// Provides detailed operation logs for diagnosisVolume Busy (Code 16, 256):
// Problem: Volume is in use by another process
// Solution: Automatic retry with delay, eventually force detach
await hdiUtil(["detach", "-quiet", devicePath])
.catch(async (error) => {
if (hdiutilTransientExitCodes.has(error.code)) {
await new Promise(resolve => setTimeout(resolve, 3000));
return hdiUtil(["detach", "-force", devicePath]);
}
throw error;
});Timeout Issues (Code 35):
// Problem: System overloaded or slow disk operations
// Solution: Automatic retry with exponential backoff
// The retry system handles this automaticallyFile Not Found (Code 2):
// Problem: Invalid DMG path or missing file
// Solution: Validate paths before hdiutil operations
import { existsSync } from "fs";
if (!existsSync(dmgPath)) {
throw new Error(`DMG file not found: ${dmgPath}`);
}Disk Image Too Small (Code -5341, -5342):
// Problem: Insufficient space allocated for DMG contents
// Solution: Calculate required size and recreate with larger size
// This typically requires recreating the DMG with proper size calculation
const requiredSize = calculateContentSize(sourceFolder);
const dmgSize = Math.ceil(requiredSize * 1.1); // 10% paddingThe error handling system provides comprehensive logging:
// Error logs include:
// - Original hdiutil command and arguments
// - Error code and stderr/stdout output
// - Retry decision and timing
// - Final outcome (success/failure)
log.warn({
command: "hdiutil",
args: ["attach", "/path/to/file.dmg"],
exitCode: 16,
stderr: "hdiutil: attach failed - Resource busy",
willRetry: true,
retryAttempt: 2
}, "hdiutil operation failed, retrying...");Error handling is tightly integrated with volume operations:
This comprehensive error handling ensures reliable DMG operations even in challenging system conditions.