diff --git a/src/commands/install/config/apps-groups/mac.ts b/src/commands/install/config/apps-groups/mac.ts index e63b6bea..e63328f4 100755 --- a/src/commands/install/config/apps-groups/mac.ts +++ b/src/commands/install/config/apps-groups/mac.ts @@ -6,6 +6,7 @@ export const MACOS: Readonly = [ description: 'Enable Touch ID for sudo (password needed)', group: 'MacOS', default: true, + first: true, commands: () => [ 'sudo -v', 'sudo cp -f /etc/pam.d/sudo_local.template /etc/pam.d/sudo_local', diff --git a/src/commands/install/install.command.ts b/src/commands/install/install.command.ts index 6477ee0e..dfafeb4e 100755 --- a/src/commands/install/install.command.ts +++ b/src/commands/install/install.command.ts @@ -1,7 +1,7 @@ import { Listr, ListrTask } from 'listr2' import { Command, CommandRunner, Option } from 'nest-commander' import { cpus } from 'node:os' -import { arch as ARCH } from 'node:process' +import { arch as ARCH, exit } from 'node:process' import ora from 'ora' import { BREW_NON_ERRORS } from '@common/constants' import { execPromise } from '@common/utils' @@ -41,8 +41,24 @@ export class InstallCommand extends CommandRunner { return true } + @Option({ + name: 'parallelCount', + flags: '-p --parallel-count ', + defaultValue: cpus().length / 2 + 1, + description: 'Amount of parallel processes for installation', + }) + private parallelCount(count: string): number { + const parsed = Number(count) + if (isNaN(parsed)) { + this.logger.error('Parallel count should be a number') + exit(1) + } + + return parsed + } + async run(inputs: string[], options: IInstallCommandOptions): Promise { - const { noParallel } = options + const { noParallel, parallelCount } = options await this.checkUpdateService.checkForUpdates() try { @@ -55,24 +71,30 @@ export class InstallCommand extends CommandRunner { const toInstall = await MULTI_SELECT_APPS_PROMPT(uniqueTags) - const order = this.resolveDeps(toInstall).sort((a, b) => { - if (a.last) { - return 1 - } + const resolvedDeps = this.resolveDeps(toInstall) - if (b.last) { - return -1 - } + this.logger.debug(`Installing apps, resolvedDeps: ${resolvedDeps.map((app) => app.name).join(', ')}`) + this.logger.debug(`Current arch ${ARCH}`) - return 0 - }) + const firstApps = resolvedDeps.filter((app) => app.first) + const lastApps = resolvedDeps.filter((app) => app.last) + const restApps = resolvedDeps.filter((app) => !app.first && !app.last) - this.logger.debug(`Installing apps: ${order.map((app) => app.name).join(', ')}`) + if (noParallel) { + this.logger.debug('No parallel installation!') - this.logger.debug(`Current arch ${ARCH}`) + this.logger.debug(`Installing apps, firstApps: ${firstApps.map((app) => app.name).join(', ')}`) + for (const app of firstApps) { + await this.installApp(app) + } - if (noParallel) { - for (const app of order) { + this.logger.debug(`Installing apps, restApps: ${restApps.map((app) => app.name).join(', ')}`) + for (const app of restApps) { + await this.installApp(app) + } + + this.logger.debug(`Installing apps, lastApps: ${lastApps.map((app) => app.name).join(', ')}`) + for (const app of lastApps) { await this.installApp(app) } @@ -82,24 +104,29 @@ export class InstallCommand extends CommandRunner { /** * Parallel installation */ - this.logger.log('Parallel installation is experimental!', 'yellow') - this.logger.log('Enter sudo password in order to have parallel installation', 'red-background') + this.logger.log( + '\n\n---------- Enter sudo password in order to have parallel installation ----------\n\n', + 'red-background', + ) await execPromise(`sudo -v`) - const cpusAmount = cpus().length - const parallelProcessAmount = cpusAmount / 2 + 1 + this.logger.debug(`Installing apps, firstApps: ${firstApps.map((app) => app.name).join(', ')}`) + for (const app of firstApps) { + await this.installApp(app) + } - const orderNoLast = order.filter((app) => !app.last) - const lastApps = order.filter((app) => app.last) + this.logger.debug(`Installing apps, restApps: ${restApps.map((app) => app.name).join(', ')}`) - const tasksChunks = this.generateParallelTasks(orderNoLast, parallelProcessAmount) + const tasksChunks = this.generateParallelTasks(restApps, parallelCount) for (const tasks of tasksChunks) { - const spinners = new Listr(tasks, TASKS_CONFIG(parallelProcessAmount)) + const spinners = new Listr(tasks, TASKS_CONFIG(parallelCount)) await spinners.run() } + this.logger.debug(`Installing apps, lastApps: ${lastApps.map((app) => app.name).join(', ')}`) + for (const app of lastApps) { await this.installApp(app) } diff --git a/src/commands/install/models/install-command.options.ts b/src/commands/install/models/install-command.options.ts index b8e62e86..36db4b12 100644 --- a/src/commands/install/models/install-command.options.ts +++ b/src/commands/install/models/install-command.options.ts @@ -4,4 +4,10 @@ export interface IInstallCommandOptions { * @default false */ noParallel: boolean + + /** + * Number of parallel install commands to run + * @default - (number of CPU cores) / 2 + 1 + */ + parallelCount: number } diff --git a/src/models/app-setup.model.ts b/src/models/app-setup.model.ts index 2a12b936..fbc4810c 100755 --- a/src/models/app-setup.model.ts +++ b/src/models/app-setup.model.ts @@ -35,6 +35,12 @@ export interface IAppSetup { */ paid?: boolean + /** + * @default false + * @description If true, the app will be installed first. + */ + first?: boolean + /** * @default false * @description If true, the app will be installed last. Cannot be a dependency! diff --git a/src/services/logger.service.ts b/src/services/logger.service.ts index 2aee04ca..67be0697 100755 --- a/src/services/logger.service.ts +++ b/src/services/logger.service.ts @@ -20,7 +20,7 @@ export class LoggerService { } public error(message: string, trace?) { const generatedMessage = this.generateMessage(message) - console.error(`\x1b[31m${message}\x1b[0m`) + console.error(this.coloredMessage(message, 'red')) appendFile(this.logPath, `ERROR | ${generatedMessage}\n`, { mode: 0o770 }) } public warn(message: string) {