Skip to content
Permalink
Browse files

feat(installer): manually mount and scan a DMG file when installing f…

…or the .app

copy the .app file discovered from the DMG into the users Applications directory
  • Loading branch information
MarshallOfSound authored and malept committed Jan 10, 2017
1 parent 9a2f36c commit 7ea5af8abf7043cd0caf12f988169a78fe7e2ba1
Showing with 116 additions and 35 deletions.
  1. +30 −3 src/installers/darwin/dmg.js
  2. +4 −32 src/installers/darwin/zip.js
  3. +42 −0 src/util/hdiutil.js
  4. +40 −0 src/util/move-app.js
@@ -1,5 +1,32 @@
import opn from 'opn';
import { spawn } from 'child_process';
import fs from 'fs-promise';
import path from 'path';

export default async (filePath) => {
await opn(filePath, { wait: false });
import { getMountedImages, mountImage, unmountImage } from '../../util/hdiutil';
import moveApp from '../../util/move-app';

export default async (filePath, installSpinner) => {
const mounts = await getMountedImages();
let targetMount = mounts.find(mount => mount.imagePath === filePath);

if (!targetMount) {
targetMount = await mountImage(filePath);
}

try {
const volumePath = path.resolve('/Volumes', targetMount.mountPath);
const appName = (await fs.readdir(volumePath)).find(file => file.endsWith('.app'));
if (!appName) {
// eslint-disable-next-line no-throw-literal
throw 'Failed to find .app file in DMG';
}
const appPath = path.resolve(volumePath, appName);
const targetApplicationPath = `/Applications/${path.basename(appPath)}`;

await moveApp(appPath, targetApplicationPath, installSpinner, true);

spawn('open', ['-R', targetApplicationPath], { detached: true });
} finally {
await unmountImage(targetMount);
}
};
@@ -1,9 +1,7 @@
import { spawn } from 'child_process';
import fs from 'fs-promise';
import inquirer from 'inquirer';
import path from 'path';
import pify from 'pify';
import sudo from 'sudo-prompt';
import { exec, spawn } from 'child_process';
import moveApp from '../../util/move-app';

export default async (filePath, installSpinner) => {
await new Promise((resolve) => {
@@ -14,40 +12,14 @@ export default async (filePath, installSpinner) => {
child.stderr.on('data', () => {});
child.on('exit', () => resolve());
});
let writeAccess = true;
try {
await fs.access('/Applications', fs.W_OK);
} catch (err) {
writeAccess = false;
}

const appPath = (await fs.readdir(path.dirname(filePath))).filter(file => file.endsWith('.app'))
.map(file => path.resolve(path.dirname(filePath), file))
.sort((fA, fB) => fs.statSync(fA).ctime.getTime() - fs.statSync(fB).ctime.getTime())[0];

const targetApplicationPath = `/Applications/${path.basename(appPath)}`;
if (await fs.exists(targetApplicationPath)) {
installSpinner.stop();
const { confirm } = await inquirer.createPromptModule()({
type: 'confirm',
name: 'confirm',
message: `The application "${path.basename(targetApplicationPath)}" appears to already exist in /Applications. Do you want to replace it?`,
});
if (!confirm) {
throw 'Installation stopped by user';
} else {
installSpinner.start();
await fs.remove(targetApplicationPath);
}
}

const moveCommand = `mv "${appPath}" "${targetApplicationPath}"`;
if (writeAccess) {
await pify(exec)(moveCommand);
} else {
await pify(sudo.exec)(moveCommand, {
name: 'Electron Forge',
});
}
await moveApp(appPath, targetApplicationPath, installSpinner);

spawn('open', ['-R', targetApplicationPath], { detached: true });
};
@@ -0,0 +1,42 @@
import { spawnPromise } from 'spawn-rx';
import debug from 'debug';

const d = debug('electron-forge:hdiutil');

export const getMountedImages = async () => {
const output = await spawnPromise('hdiutil', ['info']);
const mounts = output.split(/====\n/g);
mounts.shift();

const mountObjects = [];

for (const mount of mounts) {
try {
const mountPath = /\/Volumes\/(.+)\n/g.exec(mount)[1];
const imagePath = /image-path +: +(.+)\n/g.exec(mount)[1];
mountObjects.push({ mountPath, imagePath });
} catch (err) {
// Ignore
}
}

d('identified active mounts', mountObjects);
return mountObjects;
};

export const mountImage = async (filePath) => {
d('mounting image:', filePath);
const output = await spawnPromise('hdiutil', ['attach', '-noautoopen', '-nobrowse', '-noverify', filePath]);
const mountPath = /\/Volumes\/(.+)\n/g.exec(output)[1];
d('mounted at:', mountPath);

return {
mountPath,
iamgePath: filePath,
};
};

export const unmountImage = async (mount) => {
d('unmounting current mount:', mount);
await spawnPromise('hdiutil', ['unmount', '-force', `/Volumes/${mount.mountPath}`]);
};
@@ -0,0 +1,40 @@
import fs from 'fs-promise';
import inquirer from 'inquirer';
import path from 'path';
import pify from 'pify';
import sudo from 'sudo-prompt';
import { exec } from 'child_process';

export default async (appPath, targetApplicationPath, spinner, copyInstead = false) => {
let writeAccess = true;
try {
await fs.access('/Applications', fs.W_OK);
} catch (err) {
writeAccess = false;
}

if (await fs.exists(targetApplicationPath)) {
spinner.stop();
const { confirm } = await inquirer.createPromptModule()({
type: 'confirm',
name: 'confirm',
message: `The application "${path.basename(targetApplicationPath)}" appears to already exist in /Applications. Do you want to replace it?`,
});
if (!confirm) {
// eslint-disable-next-line no-throw-literal
throw 'Installation stopped by user';
} else {
spinner.start();
await fs.remove(targetApplicationPath);
}
}

const moveCommand = `${copyInstead ? 'cp -r' : 'mv'} "${appPath}" "${targetApplicationPath}"`;
if (writeAccess) {
await pify(exec)(moveCommand);
} else {
await pify(sudo.exec)(moveCommand, {
name: 'Electron Forge',
});
}
};

0 comments on commit 7ea5af8

Please sign in to comment.
You can’t perform that action at this time.