diff --git a/src/main.ts b/src/main.ts index 2e9c819..6352b53 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,22 @@ import {scanFiles} from './stages/scan-files.js'; import {fixFiles} from './stages/fix-files.js'; import {traverseFiles} from './stages/traverse-files.js'; import {scanDependencies} from './stages/scan-dependencies.js'; +import {availableParallelism} from 'node:os'; + +function getWantedThreads(scanSpeed: string): number { + const threads = availableParallelism(); + switch (scanSpeed) { + case 'slow': + return threads * 0.25; + case 'medium': + return threads * 0.5; + case 'fast': + return threads * 0.75; + case 'fastest': + return threads; + } + return 1; +} const availableManifests: Record = { native: modReplacements.nativeReplacements, @@ -118,6 +134,38 @@ async function runModuleReplacements(): Promise { cl.confirm({ message: 'Automatically uninstall packages?', initialValue: false + }), + scanSpeed: () => + cl.select({ + message: 'Preferred scan speed', + options: [ + { + value: 'fastest', + label: 'Fastest', + hint: 'uses all the threads, pushes cpu to 100%' + }, + { + value: 'fast', + label: 'Fast', + hint: '75% of available threads' + }, + { + value: 'medium', + label: 'Medium', + hint: '50% of available threads' + }, + { + value: 'slow', + label: 'Slow', + hint: '25% of available threads' + }, + { + value: 'slowest', + label: 'Slowest', + hint: 'disables parallelism, 1 thread' + } + ], + initialValue: 'fast' }) }, { @@ -198,13 +246,15 @@ async function runModuleReplacements(): Promise { try { const files = await traverseFiles(options.filesDir); - + const threads = getWantedThreads(options.scanSpeed); + console.time('scan'); const scanFilesResult = await scanFiles( files, manifestReplacements, + threads, scanSpinner ); - + console.timeEnd('scan'); if (scanFilesResult.length > 0) { dependenciesFound = true; } diff --git a/src/stages/scan-files.ts b/src/stages/scan-files.ts index f3d2a94..e4ab066 100644 --- a/src/stages/scan-files.ts +++ b/src/stages/scan-files.ts @@ -13,6 +13,7 @@ const available = availableParallelism(); export function scanFiles( files: string[], replacements: modReplacements.ModuleReplacement[], + threads: number, spinner: ReturnType ): Promise { return new Promise((resolve, reject) => { @@ -21,7 +22,7 @@ export function scanFiles( const filesLength = files.length; const results: FileReplacement[] = []; - for (const file of files.splice(0, available)) { + for (const file of files.splice(0, threads)) { const worker = new Worker(`${__dirname}/workers/scan-file.js`); // todo, what todo with the errors? worker.on('error', (error) => reject(error.message)); diff --git a/src/stages/workers/scan-file.ts b/src/stages/workers/scan-file.ts index 013a936..b9670dd 100644 --- a/src/stages/workers/scan-file.ts +++ b/src/stages/workers/scan-file.ts @@ -1,11 +1,11 @@ import * as modReplacements from 'module-replacements'; import {ts as sg} from '@ast-grep/napi'; -import {readFile} from 'node:fs/promises'; import dedent from 'dedent'; import pc from 'picocolors'; import {type FileReplacement} from './../../shared-types.js'; import {suggestReplacement} from './../../suggest-replacement.js'; import {parentPort} from 'node:worker_threads'; +import {open} from 'node:fs/promises'; async function scanFile( filePath: string, @@ -85,9 +85,11 @@ async function scanTask( replacements: modReplacements.ModuleReplacement[] ) { try { - const contents = await readFile(file, 'utf8'); + const fd = await open(file); + const contents = await fd.readFile({encoding: 'utf-8'}); const lines = contents.split('\n'); const scanResult = await scanFile(file, contents, lines, replacements); + await fd.close(); parentPort?.postMessage({type: 'result', value: scanResult}); } catch (err) {