@controlplane/cli
Version:
Control Plane Corporation CLI
199 lines • 7.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.realDmgOps = void 0;
exports.extractMembersFromDmg = extractMembersFromDmg;
const fs = require("fs");
const os = require("os");
const path = require("path");
const child_process_1 = require("child_process");
// ANCHOR - Constants
/** Time budget for a single hdiutil invocation, so a wedged mount cannot hang the CLI. */
const HDIUTIL_TIMEOUT_MS = 2 * 60 * 1000;
/** The macOS tool that mounts and unmounts disk images. */
const HDIUTIL = 'hdiutil';
/** The prefix of the temporary directory holding the image and its mount point. */
const WORK_DIR_PREFIX = 'cpln-upgrade-';
/** The file name the downloaded image is staged under. */
const IMAGE_FILE_NAME = 'release.dmg';
/** The directory the image is mounted at, inside the working directory. */
const MOUNT_DIR_NAME = 'mnt';
// SECTION - Functions
/**
* Extracts executables from a macOS disk image by mounting it read-only, copying
* the requested members out, and unmounting it again.
*
* The image is mounted at a private mount point rather than at its volume name, so
* an identically named volume that is already mounted can never be read by mistake.
*
* @param {Buffer} archive - The raw .dmg bytes.
* @param {Set<string>} wanted - The member base names to collect.
* @param {DmgOps} ops - The filesystem and process operations to use.
* @returns {Map<string, Buffer>} The collected members (only those found).
* @throws {Error} When the image cannot be mounted or cannot be unmounted afterwards.
*/
function extractMembersFromDmg(archive, wanted, ops) {
const workDir = ops.makeTempDir(WORK_DIR_PREFIX);
const imagePath = path.join(workDir, IMAGE_FILE_NAME);
const mountPoint = path.join(workDir, MOUNT_DIR_NAME);
try {
// hdiutil reads the image from a path, so the downloaded bytes are staged on disk
ops.writeFile(imagePath, archive);
ops.makeDir(mountPoint);
attachImage(imagePath, mountPoint, ops);
let found;
try {
found = readMountedMembers(mountPoint, wanted, ops);
}
finally {
// The image must be detached before the working directory can be removed
detachImage(mountPoint, ops);
}
return found;
}
finally {
tryRemoveDir(workDir, ops);
}
}
/**
* Mounts a disk image read-only at a private mount point.
*
* @param {string} imagePath - The staged .dmg file.
* @param {string} mountPoint - The directory to mount the image at.
* @param {DmgOps} ops - The operations to use.
* @returns {void}
* @throws {Error} When hdiutil cannot be run or the image cannot be mounted.
*/
function attachImage(imagePath, mountPoint, ops) {
// The image's own checksum is redundant: these bytes were already verified against SHA256SUMS
const args = ['attach', '-nobrowse', '-readonly', '-noverify', '-mountpoint', mountPoint, imagePath];
runHdiutil(args, 'mount the downloaded disk image', ops);
}
/**
* Runs hdiutil, turning any failure into an error naming the attempted action.
*
* @param {string[]} args - The hdiutil arguments.
* @param {string} action - The action being attempted, used in the error message.
* @param {DmgOps} ops - The operations to use.
* @returns {void}
* @throws {Error} When hdiutil cannot be run or exits non-zero.
*/
function runHdiutil(args, action, ops) {
const result = ops.run(HDIUTIL, args);
if (result.error) {
throw new Error(`Unable to ${action}: could not run ${HDIUTIL}: ${result.error.message}`);
}
if (result.status !== 0) {
// A null status means the process was killed rather than exiting on its own
const outcome = result.status === null ? 'did not complete' : `exited with status ${result.status}`;
const detail = result.stderr.trim();
throw new Error(`Unable to ${action}: ${HDIUTIL} ${outcome}${detail === '' ? '' : ` (${detail})`}.`);
}
}
/**
* Reads the requested members from the root of a mounted volume.
*
* @param {string} mountPoint - The directory the image is mounted at.
* @param {Set<string>} wanted - The member base names to collect.
* @param {DmgOps} ops - The operations to use.
* @returns {Map<string, Buffer>} The collected members.
*/
function readMountedMembers(mountPoint, wanted, ops) {
const found = new Map();
for (const name of wanted) {
// The image stores these members at the volume root, so only the base name matters
const base = path.basename(name);
const filePath = path.join(mountPoint, base);
// Only a regular file carries executable bytes: a directory or a symlink of the
// same name must never satisfy a member
if (ops.isFile(filePath)) {
found.set(base, readMember(filePath, base, ops));
}
}
return found;
}
/**
* Reads one member off the mounted volume, naming it when the read fails.
*
* @param {string} filePath - The member's path on the mounted volume.
* @param {string} name - The member's base name, used in the error message.
* @param {DmgOps} ops - The operations to use.
* @returns {Buffer} The member's contents.
* @throws {Error} When the member cannot be read.
*/
function readMember(filePath, name, ops) {
try {
return ops.readFile(filePath);
}
catch (error) {
// A failure mid-read carries no path of its own, so it has to be named here
throw new Error(`Unable to read '${name}' from the mounted disk image at ${filePath}: ${describeError(error)}`);
}
}
/**
* Unmounts a disk image, reporting how to release it by hand if that fails.
*
* @param {string} mountPoint - The directory the image is mounted at.
* @param {DmgOps} ops - The operations to use.
* @returns {void}
* @throws {Error} When the image cannot be unmounted.
*/
function detachImage(mountPoint, ops) {
try {
runHdiutil(['detach', '-force', mountPoint], 'unmount the downloaded disk image', ops);
}
catch (error) {
throw new Error(`${describeError(error)} The image is still mounted; run '${HDIUTIL} detach -force ${mountPoint}' to release it.`);
}
}
/**
* Renders an unknown thrown value as a message safe to embed in another error.
*
* @param {unknown} error - The caught value.
* @returns {string} The error's message, or a placeholder for a non-Error value.
*/
function describeError(error) {
return error instanceof Error ? error.message : 'Unknown error';
}
/**
* Removes a directory tree, ignoring any error.
*
* @param {string} directory - The directory to remove.
* @param {DmgOps} ops - The operations to use.
* @returns {void}
*/
function tryRemoveDir(directory, ops) {
try {
ops.removeDir(directory);
}
catch (_a) {
// Ignore — cleanup is best-effort
}
}
/**
* The default operations, backed by the real fs module and hdiutil.
*/
exports.realDmgOps = {
isFile: (filePath) => {
try {
// lstat, not stat: a symlink must be rejected rather than followed
return fs.lstatSync(filePath).isFile();
}
catch (_a) {
return false;
}
},
makeTempDir: (prefix) => fs.mkdtempSync(path.join(os.tmpdir(), prefix)),
makeDir: (directory) => {
fs.mkdirSync(directory, { recursive: true });
},
readFile: (filePath) => fs.readFileSync(filePath),
writeFile: (filePath, data) => fs.writeFileSync(filePath, data),
removeDir: (directory) => fs.rmSync(directory, { recursive: true, force: true }),
run: (program, args) => {
var _a;
const result = (0, child_process_1.spawnSync)(program, args, { encoding: 'utf-8', timeout: HDIUTIL_TIMEOUT_MS });
return { status: result.status, stderr: (_a = result.stderr) !== null && _a !== void 0 ? _a : '', error: result.error };
},
};
// !SECTION
//# sourceMappingURL=dmg-archive.js.map