Utilities to build DMG files for macOS applications, used by electron-builder.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Install with Tessl CLI
npx tessl i tessl/npm-dmg-builder