From c08264b650dd23c744fd846e41f4b440c7d59cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Fern=C3=A1ndez?= Date: Thu, 22 Jul 2021 18:15:01 +0200 Subject: [PATCH] feat: detect CPU type and if Rosetta is installed on start --- src/checkCpu.ts | 21 +++++++++++++++++++++ src/checkRosetta.ts | 23 +++++++++++++++++++++++ src/execAsync.ts | 4 ++++ src/getArch.ts | 2 +- src/getDescription.ts | 10 +++++----- src/index.ts | 14 ++++++++++---- src/installRosetta.ts | 37 ++++++++++--------------------------- src/logError.ts | 2 ++ src/spawnWithArch.ts | 2 +- 9 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 src/checkCpu.ts create mode 100644 src/checkRosetta.ts create mode 100644 src/execAsync.ts create mode 100644 src/logError.ts diff --git a/src/checkCpu.ts b/src/checkCpu.ts new file mode 100644 index 0000000..6f84ee5 --- /dev/null +++ b/src/checkCpu.ts @@ -0,0 +1,21 @@ +import { execAsync } from './execAsync'; + +const hasIntelCpu = async (): Promise => { + try { + await execAsync( + '/usr/sbin/sysctl -n machdep.cpu.brand_string | grep -o "Intel"' + ); + + return true; + } catch { + return false; + } +}; + +export const checkCpu = async (): Promise => { + const intelCpu = await hasIntelCpu(); + + if (intelCpu) { + throw new Error('You need a Mac with an ARM processor to run this tool.'); + } +}; diff --git a/src/checkRosetta.ts b/src/checkRosetta.ts new file mode 100644 index 0000000..1292ad9 --- /dev/null +++ b/src/checkRosetta.ts @@ -0,0 +1,23 @@ +import inquirer from 'inquirer'; + +import { installRosetta, isRosettaInstalled } from './installRosetta'; + +export const checkRosetta = async (): Promise => { + const rosettaInstalled = await isRosettaInstalled(); + + if (rosettaInstalled) { + return; + } + + const { install }: { install: boolean } = await inquirer.prompt([ + { + type: 'confirm', + name: 'install', + message: 'Rosetta 2 is not found on this Mac, do you want to install it?' + } + ]); + + if (install) { + return installRosetta(); + } +}; diff --git a/src/execAsync.ts b/src/execAsync.ts new file mode 100644 index 0000000..203caca --- /dev/null +++ b/src/execAsync.ts @@ -0,0 +1,4 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; + +export const execAsync = promisify(exec); diff --git a/src/getArch.ts b/src/getArch.ts index cb86516..2849ea4 100644 --- a/src/getArch.ts +++ b/src/getArch.ts @@ -2,7 +2,7 @@ import { Arch, Opts } from './types'; export const getArch = ({ arm, intel }: Opts): Arch | undefined => { if (arm && intel) { - throw new Error("You can't select both ARM and Intel modes"); + throw new Error("You can't select both ARM and Intel modes."); } if (arm) { diff --git a/src/getDescription.ts b/src/getDescription.ts index f3dd87e..876ded4 100644 --- a/src/getDescription.ts +++ b/src/getDescription.ts @@ -2,11 +2,11 @@ export const getDescription = (): string => { const paths = process.argv[1].split('/'); const binName = paths[paths.length - 1]; - return ` ${binName} Prompts for mode and starts a new shell. + return ` ${binName} prompts for mode and starts a new shell - ${binName} [-i, --intel] Starts a new shell in Intel (x86_64) mode. - ${binName} [-a, --arm] Starts a new shell in ARM (arm64) mode. + ${binName} [-i, --intel] starts a new shell in Intel (x86_64) mode + ${binName} [-a, --arm] starts a new shell in ARM (arm64) mode - ${binName} [-i, --intel] Runs the commands in Intel (x86_64) mode. - ${binName} [-a, --arm] Runs the commands in ARM (arm64) mode.`; + ${binName} [-i, --intel] runs the commands in Intel (x86_64) mode + ${binName} [-a, --arm] runs the commands in ARM (arm64) mode`; }; diff --git a/src/index.ts b/src/index.ts index e07c35b..fadc777 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,14 @@ import { program } from 'commander'; import { getDescription } from './getDescription'; +import { checkCpu } from './checkCpu'; +import { checkRosetta } from './checkRosetta'; import { getArch } from './getArch'; import { archSelector } from './archSelector'; import { spawnWithArch } from './spawnWithArch'; import { getSystemShell } from './shell'; import { installRosetta } from './installRosetta'; +import { logError } from './logError'; import { Arch } from './types'; import pkg from '../package.json'; @@ -15,8 +18,10 @@ program .option('-a, --arm', 'run in ARM (arm64) mode') .passThroughOptions() .description(getDescription()) - .action((commands: string[]) => { + .action(async (commands: string[]) => { try { + await checkCpu(); + await checkRosetta(); const arch = getArch(program.opts()); if (!commands?.length) { @@ -29,18 +34,19 @@ program return spawnWithArch(arch ?? Arch.Intel, commands); } catch (err) { - console.error('❌ Error:', err?.message); + logError(err); } }); program .command('install') - .description('Installs Rosetta 2 on this Mac.') + .description('installs Rosetta 2 on this Mac') .action(async () => { try { + await checkCpu(); await installRosetta(); } catch (err) { - console.error('❌ Error:', err?.message); + logError(err); } }); diff --git a/src/installRosetta.ts b/src/installRosetta.ts index 7a93d10..3de5426 100644 --- a/src/installRosetta.ts +++ b/src/installRosetta.ts @@ -1,8 +1,7 @@ -import { exec, spawn } from 'child_process'; +import { spawn } from 'child_process'; import { access } from 'fs/promises'; -import { promisify } from 'util'; -const execAsync = promisify(exec); +import { execAsync } from './execAsync'; const fileExists = async (file: string): Promise => { try { @@ -13,17 +12,10 @@ const fileExists = async (file: string): Promise => { } }; -const hasIntelCpu = async (): Promise => { - try { - await execAsync( - '/usr/sbin/sysctl -n machdep.cpu.brand_string | grep -o "Intel"' - ); - - return true; - } catch { - return false; - } -}; +export const isRosettaInstalled = (): Promise => + fileExists( + '/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist' + ); const getMajorMacOSVersion = async (): Promise => { const { stdout: version } = await execAsync( @@ -35,17 +27,7 @@ const getMajorMacOSVersion = async (): Promise => { }; export const installRosetta = async (): Promise => { - const intelCpu = await hasIntelCpu(); - - if (intelCpu) { - return console.log( - '✅ Your Mac has an Intel CPU, no need to install Rosetta 2.' - ); - } - - const rosettaInstalled = await fileExists( - '/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist' - ); + const rosettaInstalled = await isRosettaInstalled(); if (rosettaInstalled) { return console.log('✅ Your Mac already has Rosetta 2 installed.'); @@ -54,8 +36,8 @@ export const installRosetta = async (): Promise => { const majorMacOSVersion = await getMajorMacOSVersion(); if (majorMacOSVersion < 11) { - return console.error( - '❌ You need macOS Big Sur or later in order to install Rosetta 2.' + throw new Error( + 'You need macOS Big Sur or later in order to install Rosetta 2.' ); } @@ -69,6 +51,7 @@ export const installRosetta = async (): Promise => { return new Promise((resolve, reject) => { childProcess.once('exit', code => { if (code === 0) { + console.log('✅ Rosetta 2 has been installed.'); return resolve(); } diff --git a/src/logError.ts b/src/logError.ts new file mode 100644 index 0000000..d41ec67 --- /dev/null +++ b/src/logError.ts @@ -0,0 +1,2 @@ +export const logError = (err: Error): void => + console.error('❌ Error:', err?.message); diff --git a/src/spawnWithArch.ts b/src/spawnWithArch.ts index 5b4ade4..8e14ab7 100644 --- a/src/spawnWithArch.ts +++ b/src/spawnWithArch.ts @@ -34,6 +34,6 @@ export const spawnWithArch = ( }); childProcess.on('close', () => - console.log(`✨ ${archName} (${arch}) execution finished`) + console.log(`✨ ${archName} (${arch}) execution finished.`) ); };