hyper/app/utils/cli-install.ts

160 lines
5.5 KiB
TypeScript
Raw Permalink Normal View History

2023-02-20 22:59:00 -09:00
import {existsSync, readlink, symlink} from 'fs';
2019-11-28 05:17:01 -09:00
import path from 'path';
2023-07-25 09:30:19 -08:00
import {promisify} from 'util';
import {clipboard, dialog} from 'electron';
import {mkdirpSync} from 'fs-extra';
2021-04-09 05:23:38 -08:00
import * as Registry from 'native-reg';
2021-01-10 18:58:48 -09:00
import type {ValueType} from 'native-reg';
import sudoPrompt from 'sudo-prompt';
2023-07-25 09:30:19 -08:00
import {cliScriptPath, cliLinkPath} from '../config/paths';
import notify from '../notify';
2020-02-14 23:17:45 -09:00
2023-02-20 22:59:00 -09:00
const readLink = promisify(readlink);
const symLink = promisify(symlink);
const sudoExec = promisify(sudoPrompt.exec);
const checkInstall = () => {
2023-02-20 22:59:00 -09:00
return readLink(cliLinkPath)
2020-03-25 02:15:08 -08:00
.then((link) => link === cliScriptPath)
.catch((err) => {
if (err.code === 'ENOENT') {
return false;
}
throw err;
});
};
const addSymlink = async (silent: boolean) => {
try {
const isInstalled = await checkInstall();
if (isInstalled) {
console.log('Hyper CLI already in PATH');
return;
}
console.log('Linking HyperCLI');
2023-02-20 22:59:00 -09:00
if (!existsSync(path.dirname(cliLinkPath))) {
try {
mkdirpSync(path.dirname(cliLinkPath));
} catch (err) {
throw `Failed to create directory ${path.dirname(cliLinkPath)} - ${err}`;
}
}
2023-02-20 22:59:00 -09:00
await symLink(cliScriptPath, cliLinkPath);
} catch (_err) {
const err = _err as {code: string};
// 'EINVAL' is returned by readlink,
// 'EEXIST' is returned by symlink
let error =
err.code === 'EEXIST' || err.code === 'EINVAL'
? `File already exists: ${cliLinkPath}`
: `Symlink creation failed: ${err.code}`;
// Need sudo access to create symlink
if (err.code === 'EACCES' && !silent) {
const result = await dialog.showMessageBox({
message: `You need to grant elevated privileges to add Hyper CLI to PATH
Or you can run
sudo ln -sf "${cliScriptPath}" "${cliLinkPath}"`,
type: 'info',
buttons: ['OK', 'Copy Command', 'Cancel']
});
if (result.response === 0) {
try {
await sudoExec(`ln -sf "${cliScriptPath}" "${cliLinkPath}"`, {name: 'Hyper'});
return;
} catch (_error) {
error = (_error as any[])[0];
}
} else if (result.response === 1) {
clipboard.writeText(`sudo ln -sf "${cliScriptPath}" "${cliLinkPath}"`);
}
}
throw error;
}
};
const addBinToUserPath = () => {
2020-11-22 17:43:55 -09:00
return new Promise<void>((resolve, reject) => {
2020-02-14 23:17:45 -09:00
try {
const envKey = Registry.openKey(Registry.HKCU, 'Environment', Registry.Access.ALL_ACCESS)!;
// C:\Users\<user>\AppData\Local\Programs\hyper\resources\bin
const binPath = path.dirname(cliScriptPath);
// C:\Users\<user>\AppData\Local\hyper
const oldPath = path.resolve(process.env.LOCALAPPDATA!, 'hyper');
2020-02-14 23:17:45 -09:00
const items = Registry.enumValueNames(envKey);
2020-03-25 02:15:08 -08:00
const pathItem = items.find((item) => item.toUpperCase() === 'PATH');
2020-02-14 23:17:45 -09:00
const pathItemName = pathItem || 'PATH';
let newPathValue = binPath;
2021-01-10 18:58:48 -09:00
let type: ValueType = Registry.ValueType.SZ;
if (pathItem) {
2020-02-14 23:17:45 -09:00
type = Registry.queryValueRaw(envKey, pathItem)!.type;
if (type !== Registry.ValueType.SZ && type !== Registry.ValueType.EXPAND_SZ) {
reject(`Registry key type is ${type}`);
return;
}
const value = Registry.queryValue(envKey, pathItem) as string;
let pathParts = value.split(';');
2020-02-14 23:17:45 -09:00
const existingPath = pathParts.includes(binPath);
const existingOldPath = pathParts.some((pathPart) => pathPart.startsWith(oldPath));
if (existingPath && !existingOldPath) {
console.log('Hyper CLI already in PATH');
Registry.closeKey(envKey);
resolve();
return;
}
// Because nsis install path is different from squirrel we need to remove old path if present
// and add current path if absent
if (existingOldPath) pathParts = pathParts.filter((pathPart) => !pathPart.startsWith(oldPath));
if (!pathParts.includes(binPath)) pathParts.push(binPath);
newPathValue = pathParts.join(';');
}
console.log('Adding HyperCLI path (registry)');
2020-02-14 23:17:45 -09:00
Registry.setValueRaw(envKey, pathItemName, type, Registry.formatString(newPathValue));
Registry.closeKey(envKey);
resolve();
} catch (error) {
reject(error);
}
});
};
2020-06-19 04:51:34 -08:00
const logNotify = (withNotification: boolean, title: string, body: string, details?: {error?: any}) => {
2019-12-25 00:52:32 -09:00
console.log(title, body, details);
withNotification && notify(title, body, details);
};
export const installCLI = async (withNotification: boolean) => {
if (process.platform === 'win32') {
try {
await addBinToUserPath();
logNotify(
withNotification,
'Hyper CLI installed',
'You may need to restart your computer to complete this installation process.'
);
} catch (err) {
logNotify(withNotification, 'Hyper CLI installation failed', `Failed to add Hyper CLI path to user PATH ${err}`);
}
} else if (process.platform === 'darwin' || process.platform === 'linux') {
// AppImages are mounted on run at a temporary path, don't create symlink
if (process.env['APPIMAGE']) {
console.log('Skipping CLI symlink creation as it is an AppImage install');
return;
}
try {
await addSymlink(!withNotification);
logNotify(withNotification, 'Hyper CLI installed', `Symlink created at ${cliLinkPath}`);
} catch (error) {
logNotify(withNotification, 'Hyper CLI installation failed', `${error}`);
}
} else {
logNotify(withNotification, 'Hyper CLI installation failed', `Unsupported platform ${process.platform}`);
}
};