Skip to content

Commit

Permalink
feat: detect CPU type and if Rosetta is installed on start
Browse files Browse the repository at this point in the history
  • Loading branch information
FdezRomero committed Jul 22, 2021
1 parent cf7b0e0 commit c08264b
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 38 deletions.
21 changes: 21 additions & 0 deletions src/checkCpu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { execAsync } from './execAsync';

const hasIntelCpu = async (): Promise<boolean> => {
try {
await execAsync(
'/usr/sbin/sysctl -n machdep.cpu.brand_string | grep -o "Intel"'
);

return true;
} catch {
return false;
}
};

export const checkCpu = async (): Promise<void> => {
const intelCpu = await hasIntelCpu();

if (intelCpu) {
throw new Error('You need a Mac with an ARM processor to run this tool.');
}
};
23 changes: 23 additions & 0 deletions src/checkRosetta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import inquirer from 'inquirer';

import { installRosetta, isRosettaInstalled } from './installRosetta';

export const checkRosetta = async (): Promise<void> => {
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();
}
};
4 changes: 4 additions & 0 deletions src/execAsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { exec } from 'child_process';
import { promisify } from 'util';

export const execAsync = promisify(exec);
2 changes: 1 addition & 1 deletion src/getArch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions src/getDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] <commands...> Runs the commands in Intel (x86_64) mode.
${binName} [-a, --arm] <commands...> Runs the commands in ARM (arm64) mode.`;
${binName} [-i, --intel] <commands...> runs the commands in Intel (x86_64) mode
${binName} [-a, --arm] <commands...> runs the commands in ARM (arm64) mode`;
};
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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) {
Expand All @@ -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);
}
});

Expand Down
37 changes: 10 additions & 27 deletions src/installRosetta.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> => {
try {
Expand All @@ -13,17 +12,10 @@ const fileExists = async (file: string): Promise<boolean> => {
}
};

const hasIntelCpu = async (): Promise<boolean> => {
try {
await execAsync(
'/usr/sbin/sysctl -n machdep.cpu.brand_string | grep -o "Intel"'
);

return true;
} catch {
return false;
}
};
export const isRosettaInstalled = (): Promise<boolean> =>
fileExists(
'/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist'
);

const getMajorMacOSVersion = async (): Promise<number> => {
const { stdout: version } = await execAsync(
Expand All @@ -35,17 +27,7 @@ const getMajorMacOSVersion = async (): Promise<number> => {
};

export const installRosetta = async (): Promise<void> => {
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.');
Expand All @@ -54,8 +36,8 @@ export const installRosetta = async (): Promise<void> => {
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.'
);
}

Expand All @@ -69,6 +51,7 @@ export const installRosetta = async (): Promise<void> => {
return new Promise<void>((resolve, reject) => {
childProcess.once('exit', code => {
if (code === 0) {
console.log('✅ Rosetta 2 has been installed.');
return resolve();
}

Expand Down
2 changes: 2 additions & 0 deletions src/logError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const logError = (err: Error): void =>
console.error('❌ Error:', err?.message);
2 changes: 1 addition & 1 deletion src/spawnWithArch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ export const spawnWithArch = (
});

childProcess.on('close', () =>
console.log(`✨ ${archName} (${arch}) execution finished`)
console.log(`✨ ${archName} (${arch}) execution finished.`)
);
};

0 comments on commit c08264b

Please sign in to comment.