flipper-doctor
Version:
Utility for checking for issues with a flipper installation
477 lines • 22.9 kB
JavaScript
;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runHealthchecks = exports.getHealthchecks = exports.getEnvInfo = void 0;
const child_process_1 = require("child_process");
const os_1 = __importDefault(require("os"));
const util_1 = require("util");
const environmentInfo_1 = require("./environmentInfo");
var environmentInfo_2 = require("./environmentInfo");
Object.defineProperty(exports, "getEnvInfo", { enumerable: true, get: function () { return environmentInfo_2.getEnvInfo; } });
const watchman = __importStar(require("fb-watchman"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const fs_extra = __importStar(require("fs-extra"));
const validateSelectedXcodeVersion_1 = require("./fb-stubs/validateSelectedXcodeVersion");
function getHealthchecks(isProduction) {
return {
common: {
label: 'Common',
isRequired: true,
isSkipped: false,
healthchecks: [
{
key: 'common.openssl',
label: 'OpenSSL Installed',
run: async (_) => {
const result = await tryExecuteCommand('openssl version');
return {
hasProblem: result.fail,
message: result.fail
? ['common.openssl--not_installed', { output: result.message }]
: ['common.openssl--installed', { output: result.message }],
};
},
},
...(!isProduction
? [
{
key: 'common.watchman',
label: 'Watchman Installed',
run: async (_) => {
const isAvailable = await isWatchmanAvailable();
return {
hasProblem: !isAvailable,
message: isAvailable
? ['common.watchman--installed']
: ['common.watchman--not_installed'],
};
},
},
]
: []),
],
},
android: {
label: 'Android',
isRequired: false,
isSkipped: false,
healthchecks: [
...(process.platform === 'darwin'
? [
{
key: 'android.android-studio',
label: 'Android Studio Installed',
isRequired: false,
run: async (_) => {
const hasProblem = !fs.existsSync('/Applications/Android Studio.app');
return {
hasProblem,
message: hasProblem
? [
'android.android-studio--not_installed',
{ platform: os_1.default.arch() },
]
: ['android.android-studio--installed'],
};
},
},
]
: []),
{
key: 'android.sdk',
label: 'SDK Installed',
isRequired: true,
run: async (_) => {
const androidHome = process.env.ANDROID_HOME;
if (!androidHome) {
return {
hasProblem: true,
message: ['android.sdk--no_ANDROID_HOME'],
};
}
else if (!fs.existsSync(androidHome)) {
const androidStudioAndroidHome = `${os_1.default.homedir()}/Library/Android/sdk`;
const globalAndroidHome = '/opt/android_sdk';
const existingAndroidHome = (await fs_extra.exists(androidStudioAndroidHome))
? androidStudioAndroidHome
: (await fs_extra.exists(globalAndroidHome))
? globalAndroidHome
: null;
return {
hasProblem: true,
message: [
'android.sdk--invalid_ANDROID_HOME',
{ androidHome, existingAndroidHome },
],
};
}
else {
const platformToolsDir = path.join(androidHome, 'platform-tools');
if (!fs.existsSync(platformToolsDir)) {
return {
hasProblem: true,
message: ['android.sdk--no_android_sdk', { platformToolsDir }],
};
}
else {
const versionResult = await tryExecuteCommand(`"${path.join(platformToolsDir, 'adb')}" version`);
if (versionResult.fail === false) {
return {
hasProblem: false,
message: [
'android.sdk--installed',
{ output: versionResult.stdout },
],
};
}
else {
return {
hasProblem: true,
message: [
'android.sdk--not_installed',
{ output: versionResult.message },
],
};
}
}
}
},
},
],
},
ios: {
label: 'iOS',
...(process.platform === 'darwin'
? {
isRequired: false,
isSkipped: false,
healthchecks: [
{
key: 'ios.idb',
label: 'IDB installed',
isRequired: true,
run: async (_, settings) => {
if (!settings) {
return {
hasProblem: false,
message: ['ios.idb--no_context'],
};
}
if (!settings.enablePhysicalIOS) {
return {
hasProblem: false,
message: ['ios.idb--physical_device_disabled'],
};
}
const result = await tryExecuteCommand(`${settings?.idbPath} --help`);
const hasIdbCompanion = await tryExecuteCommand(`idbCompanion --help`);
if (result.fail) {
const hasIdbInPath = await tryExecuteCommand(`which idb`);
if (!hasIdbInPath.fail) {
return {
hasProblem: true,
message: [
'ios.idb--not_installed_but_present',
{
idbPath: settings.idbPath,
idbInPath: hasIdbInPath.stdout.trim(),
},
],
};
}
return {
hasProblem: true,
message: [
'ios.idb--not_installed',
{
idbPath: settings.idbPath,
hasIdbCompanion: !hasIdbCompanion.fail,
},
],
};
}
return {
hasProblem: false,
message: ['ios.idb--installed'],
};
},
},
{
key: 'ios.xcode',
label: 'XCode Installed',
isRequired: true,
run: async (e) => {
const hasProblem = e.IDEs == null ||
e.IDEs.Xcode == null ||
// error/edgecase in EnvironmentInfo
e.IDEs.Xcode.version === '/undefined';
return {
hasProblem,
message: hasProblem
? ['ios.xcode--not_installed']
: [
`ios.xcode--installed`,
{
version: e.IDEs.Xcode.version,
path: e.IDEs.Xcode.path,
},
],
};
},
},
{
key: 'ios.xcode-select',
label: 'xcode-select set',
isRequired: true,
run: async (_) => {
const allApps = await fs_extra.promises.readdir('/Applications');
// Xcode_14.2.0_xxxxxxx.app
// Xcode_14.3.1_xxxxxxxxxx.app
// Xcode_15.0.0_xxxxxxxxxx.app
// Xcode.app
const latestXCode = allApps
.filter((a) => a.startsWith('Xcode'))
.sort()
.pop();
const availableXcode = latestXCode
? path.join('/Applications', latestXCode)
: null;
const result = await tryExecuteCommand('xcode-select -p');
if (result.fail) {
return {
hasProblem: true,
message: [
'ios.xcode-select--not_set',
{ message: result.message, availableXcode },
],
};
}
const selectedXcode = result.stdout.toString().trim();
if (selectedXcode == '/Library/Developer/CommandLineTools') {
return {
hasProblem: true,
message: [
'ios.xcode-select--no_xcode_selected',
{ availableXcode },
],
};
}
if ((await fs_extra.pathExists(selectedXcode)) == false) {
return {
hasProblem: true,
message: [
'ios.xcode-select--nonexisting_selected',
{ selected: selectedXcode, availableXcode },
],
};
}
const validatedXcodeVersion = await (0, validateSelectedXcodeVersion_1.validateSelectedXcodeVersion)(selectedXcode, availableXcode);
if (validatedXcodeVersion.hasProblem) {
return validatedXcodeVersion;
}
return {
hasProblem: false,
message: [
'ios.xcode-select--set',
{ selected: selectedXcode },
],
};
},
},
{
key: 'ios.sdk',
label: 'SDK Installed',
isRequired: true,
run: async (e) => {
const hasProblem = !e.SDKs['iOS SDK'] ||
!e.SDKs['iOS SDK'].Platforms ||
!e.SDKs['iOS SDK'].Platforms.length;
return {
hasProblem,
message: hasProblem
? ['ios.sdk--not_installed']
: [
'ios.sdk--installed',
{ platforms: e.SDKs['iOS SDK'].Platforms },
],
};
},
},
{
key: 'ios.has-simulators',
label: 'Simulators are available',
isRequired: true,
run: async (_e, settings) => {
const result = await tryExecuteCommand(`${settings?.idbPath ?? 'idb'} list-targets --json`);
if (result.fail) {
return {
hasProblem: true,
message: [
'ios.has-simulators--idb-failed',
{ message: result.message },
],
};
}
const devices = result.stdout
.trim()
.split('\n')
.map((x) => {
try {
return JSON.parse(x);
}
catch (e) {
return null;
}
})
.filter((x) => x != null && x.type === 'simulator');
if (devices.length === 0) {
return {
hasProblem: true,
message: ['ios.has-simulators--no-devices'],
};
}
return {
hasProblem: false,
message: [
'ios.has-simulators--ok',
{ count: devices.length },
],
};
},
},
{
key: 'ios.xctrace',
label: 'xctrace exists',
isRequired: true,
run: async (_) => {
const result = await tryExecuteCommand('xcrun xctrace version');
if (result.fail) {
return {
hasProblem: true,
message: [
'ios.xctrace--not_installed',
{ message: result.message.trim() },
],
};
}
return {
hasProblem: false,
message: [
'ios.xctrace--installed',
{ output: result.stdout.trim() },
],
};
},
},
],
}
: {
isSkipped: true,
skipReason: `Healthcheck is skipped, because iOS development is not supported on the current platform "${process.platform}".`,
}),
},
};
}
exports.getHealthchecks = getHealthchecks;
async function runHealthchecks(isProduction) {
const environmentInfo = await (0, environmentInfo_1.getEnvInfo)();
const healthchecks = getHealthchecks(isProduction);
const results = await Promise.all(Object.entries(healthchecks).map(async ([key, category]) => {
if (category.isSkipped) {
return category;
}
const categoryResult = [
key,
{
label: category.label,
results: await Promise.all(category.healthchecks.map(async ({ key, label, run, isRequired }) => ({
key,
label,
isRequired: isRequired ?? true,
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
result: await run(environmentInfo).catch((e) => {
console.warn(`Health check ${key}/${label} failed with:`, e);
// TODO Improve result type to be: OK | Problem(message, fix...)
return {
hasProblem: true,
};
}),
}))),
},
];
return categoryResult;
}));
return results;
}
exports.runHealthchecks = runHealthchecks;
async function tryExecuteCommand(command) {
try {
const output = await (0, util_1.promisify)(child_process_1.exec)(command);
return {
fail: false,
message: `Command "${command}" successfully executed with output: ${output.stdout}`,
stdout: output.stdout,
};
}
catch (err) {
return {
fail: true,
message: `Command "${command}" failed to execute with output: ${err.message}`,
error: err,
};
}
}
async function isWatchmanAvailable() {
const client = new watchman.Client();
return new Promise((resolve) => {
const complete = (result) => {
resolve(result);
client.removeAllListeners('error');
client.end();
};
client.once('error', () => complete(false));
client.capabilityCheck({ optional: [], required: ['relative_root'] }, (error) => {
if (error) {
complete(false);
return;
}
complete(true);
});
});
}
//# sourceMappingURL=index.js.map