Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions npm_modules/cli/src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { BazelClient } from '../utils/BazelClient';
import { checkCommandExists, runCliCommand } from '../utils/cliUtils';
import { makeCommandHandler } from '../utils/errorUtils';
import { wrapInColor } from '../utils/logUtils';
import { getLinuxPackageManager } from '../setup/linuxSetup';

/** Discord support link for troubleshooting */
const DISCORD_SUPPORT_URL = 'https://discord.gg/uJyNEeYX2U';
Expand Down Expand Up @@ -238,7 +239,7 @@ class ValdiDoctor {
* - Output structured JSON for machine processing (--json flag)
* - Display formatted, colored output for human consumption
*

*
* @example
* ```typescript
Expand Down Expand Up @@ -631,6 +632,20 @@ class ValdiDoctor {
}
}

private async getLinuxJavaInstallCommand() {
const pm = await getLinuxPackageManager();

if (pm === 'apt') {
return 'sudo apt install openjdk-17-jdk';
} else if (pm === 'yum') {
return 'sudo yum install java-17-openjdk-devel';
} else if (pm === 'pacman') {
return 'sudo pacman -S jdk17-openjdk && sudo archlinux-java set jdk17-openjdk';
} else {
return 'Please install Java JDK manually';
}
}

/**
* Validates Java JDK installation and configuration as set up by dev_setup.
*
Expand All @@ -650,7 +665,7 @@ class ValdiDoctor {
const { stdout, stderr } = await runCliCommand('java -version');
// java -version typically outputs to stderr, but check both
const versionInfo = stderr || stdout || '';

// Try multiple version string formats:
// 1. version "17.0.16" (older format)
// 2. openjdk 17.0.16 (OpenJDK format)
Expand All @@ -662,7 +677,7 @@ class ValdiDoctor {
if (!versionMatch) {
versionMatch = versionInfo.match(/java\s+(\d+\.\d+\.\d+)/i);
}

const version = versionMatch?.[1] ?? 'Unknown version';

// Check if Java version is 17 or higher
Expand All @@ -683,7 +698,7 @@ class ValdiDoctor {
message: `Java ${version} is outdated. Java 17+ is recommended`,
details: 'dev_setup now installs Java 17 for better compatibility',
fixable: true,
fixCommand: os.platform() === 'darwin' ? 'brew install openjdk@17' : 'sudo apt install openjdk-17-jdk',
fixCommand: os.platform() === 'darwin' ? 'brew install openjdk@17' : await this.getLinuxJavaInstallCommand(),
category: 'Java installation',
});
} else {
Expand Down Expand Up @@ -711,7 +726,7 @@ class ValdiDoctor {
message: 'Java not found in PATH',
details: 'dev_setup installs Java JDK for Android development',
fixable: true,
fixCommand: os.platform() === 'darwin' ? 'brew install openjdk@17' : 'sudo apt install openjdk-17-jdk',
fixCommand: os.platform() === 'darwin' ? 'brew install openjdk@17' : await this.getLinuxJavaInstallCommand(),
category: 'Java installation',
});
}
Expand Down Expand Up @@ -1054,7 +1069,7 @@ class ValdiDoctor {
});
}
} else {
const fixCommand = this.getFixCommandForDependency(dep);
const fixCommand = await this.getFixCommandForDependency(dep);
this.addResult({
name: `${dep} installation`,
status: failureLevel,
Expand Down Expand Up @@ -1163,19 +1178,22 @@ class ValdiDoctor {
* @returns Platform-appropriate installation command or instruction
* @private
*/
private getFixCommandForDependency(dep: string): string {
private async getFixCommandForDependency(dep: string): Promise<string> {
const pm = await getLinuxPackageManager();
const pmInstall = (pm === 'pacman') ? 'sudo pacman -S' : `sudo ${pm} install`;

switch (dep) {
case 'git': {
return os.platform() === 'darwin' ? 'brew install git' : 'sudo apt-get install git';
return os.platform() === 'darwin' ? 'brew install git' : `${pmInstall} git`;
}
case 'npm': {
return 'Install Node.js from https://nodejs.org (includes npm)';
}
case 'watchman': {
return os.platform() === 'darwin' ? 'brew install watchman' : 'sudo apt-get install watchman';
return os.platform() === 'darwin' ? 'brew install watchman' : `${pmInstall} watchman`;
}
case 'git-lfs': {
return os.platform() === 'darwin' ? 'brew install git-lfs' : 'sudo apt-get install git-lfs';
return os.platform() === 'darwin' ? 'brew install git-lfs' : `${pmInstall} git-lfs`;
}
case 'bazelisk': {
return os.platform() === 'darwin' ? 'brew install bazelisk' : 'valdi dev_setup';
Expand Down Expand Up @@ -1332,7 +1350,7 @@ class ValdiDoctor {
*
* @param argv - Resolved command line arguments
* @returns Promise that resolves when the doctor command completes
*
*
* @example
* ```bash
* valdi doctor --verbose --fix --json
Expand Down Expand Up @@ -1360,13 +1378,13 @@ async function valdiDoctor(argv: ArgumentsResolver<CommandParameters>): Promise<

/**
* The command name as it appears in the CLI.

*/
export const command = 'doctor';

/**
* Human-readable description of the command for help output.

*/
export const describe = 'Check your Valdi development environment for common issues';

Expand All @@ -1379,7 +1397,7 @@ export const describe = 'Check your Valdi development environment for common iss
* - `--json` (-j): Output results in JSON format for automation
*
* @param yargs - The yargs instance to configure

*/
export const builder = (yargs: Argv<CommandParameters>): void => {
yargs
Expand Down Expand Up @@ -1417,6 +1435,6 @@ export const builder = (yargs: Argv<CommandParameters>): void => {

/**
* The command handler wrapped with error handling and logging.

*/
export const handler = makeCommandHandler(valdiDoctor);
81 changes: 72 additions & 9 deletions npm_modules/cli/src/setup/linuxSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,86 @@ import path from 'path';
import { checkCommandExists } from '../utils/cliUtils';
import { DevSetupHelper, HOME_DIR } from './DevSetupHelper';
import { ANDROID_LINUX_COMMANDLINE_TOOLS } from './versions';
import { wrapInColor } from '../utils/logUtils';
import { ANSI_COLORS } from '../core/constants';
import { execSync } from 'child_process';

const BAZELISK_URL = 'https://github.com/bazelbuild/bazelisk/releases/download/v1.26.0/bazelisk-linux-amd64';

export async function getLinuxPackageManager(): Promise<string | null> {
let pm: string | null = null;
const distro = await fs.promises.readFile('/etc/os-release', 'utf8');
const match = distro.match(/^ID="?([^"\n]*)"?$/m);

const distroToPackageManager: { [key: string]: string } = {
ubuntu: "apt",
fedora: "dnf",
debian: "apt",
arch: "pacman",
linuxmint: "apt",
opensuse: "zypper",
rhel: "dnf",
centos: "dnf",
manjaro: "pacman",
};

if (match && match[1]) {
return distroToPackageManager[match[1]] || null;
} else {
if (checkCommandExists('pacman')) {
pm = 'pacman';
} else if (checkCommandExists('dnf')) {
pm = 'dnf';
} else if (checkCommandExists('yum')) {
pm = 'yum';
} else if (checkCommandExists('apt')) {
pm = 'apt';
}
}

return pm;
}

export async function linuxSetup(): Promise<void> {
const devSetup = new DevSetupHelper();
const pm = await getLinuxPackageManager();

if (pm === "apt") {
await devSetup.runShell('Installing dependencies from apt', [
`sudo apt-get install zlib1g-dev git-lfs watchman libfontconfig-dev adb`,
]);

await devSetup.runShell('Installing libtinfo5', [
`wget https://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb`,
`sudo apt install ./libtinfo5_6.3-2ubuntu0.1_amd64.deb`,
]);

await devSetup.runShell('Installing dependencies from apt', [
`sudo apt-get install zlib1g-dev git-lfs watchman libfontconfig-dev adb`,
]);
if (!checkCommandExists('java')) {
await devSetup.runShell('Installing Java Runtime Environment', ['sudo apt install default-jre']);
}
} else if (pm === "pacman") {
await devSetup.runShell('Installing dependencies from pacman', [
`sudo pacman -S zlib git-lfs watchman libfontconfig-dev adb`,
]);

await devSetup.runShell('Installing libtinfo5', [
`wget http://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb`,
`sudo apt install ./libtinfo5_6.3-2ubuntu0.1_amd64.deb`,
]);
try {
execSync("pacman -Q ncurses5-compat-libs", { stdio: 'ignore' });
} catch {
await devSetup.runShell('Installing libtinfo5', [
`git clone https://aur.archlinux.org/ncurses5-compat-libs.git`,
`cd ncurses5-compat-libs`,
`makepkg -sic`,
`cd ..`
]);
}

if (!checkCommandExists('java')) {
await devSetup.runShell('Installing Java Runtime Environment', ['sudo apt install default-jre']);
if (!checkCommandExists('java')) {
await devSetup.runShell('Installing Java Runtime Environment', ['sudo pacman -S jdk17-openjdk']);
}
} else {
console.log(wrapInColor('Unsupported package manager, please install the required dependencies manually:', ANSI_COLORS.RED_COLOR));
console.log(wrapInColor('zlib git-lfs watchman libfontconfig-dev adb libtinfo5 openjdk-17', ANSI_COLORS.RED_COLOR));
console.log()
}

const bazeliskPathSuffix = '.valdi/bin/bazelisk';
Expand Down