diff --git a/extensions/npm/README.md b/extensions/npm/README.md index 215ca927ff434a..acc7d9872389b9 100644 --- a/extensions/npm/README.md +++ b/extensions/npm/README.md @@ -34,7 +34,8 @@ The extension fetches data from and context.subscriptions.push(vscode.commands.registerCommand('npm.refresh', () => { invalidateScriptCaches(); })); + context.subscriptions.push(vscode.commands.registerCommand('npm.scriptRunner', (args) => { + if (args instanceof vscode.Uri) { + return getScriptRunner(context, args, true); + } + return ''; + })); context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => { if (args instanceof vscode.Uri) { - return getPackageManager(context, args); + return getPackageManager(context, args, true); } return ''; })); diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 34113f4155326c..01f7ff0416ed7f 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -13,9 +13,10 @@ import { } from 'vscode'; import { readScripts } from './readScripts'; import { - createInstallationTask, getPackageManager, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition, + createInstallationTask, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition, NpmTaskProvider, startDebugging, + detectPackageManager, ITaskWithLocation, INSTALL_SCRIPT } from './tasks'; @@ -150,8 +151,8 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { } private async runScript(script: NpmScript) { - // Call getPackageManager to trigger the multiple lock files warning. - await getPackageManager(this.context, script.getFolder().uri); + // Call detectPackageManager to trigger the multiple lock files warning. + await detectPackageManager(this.context, script.getFolder().uri, true); tasks.executeTask(script.task); } @@ -181,7 +182,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { if (!uri) { return; } - const task = await createInstallationTask(await getPackageManager(this.context, selection.folder.workspaceFolder.uri, true), selection.folder.workspaceFolder, uri); + const task = await createInstallationTask(this.context, selection.folder.workspaceFolder, uri); tasks.executeTask(task); } diff --git a/extensions/npm/src/scriptHover.ts b/extensions/npm/src/scriptHover.ts index 46f076b517efb6..33f2346e815882 100644 --- a/extensions/npm/src/scriptHover.ts +++ b/extensions/npm/src/scriptHover.ts @@ -13,7 +13,7 @@ import { import { INpmScriptInfo, readScripts } from './readScripts'; import { createScriptRunnerTask, - getPackageManager, startDebugging + startDebugging } from './tasks'; @@ -114,7 +114,7 @@ export class NpmScriptHoverProvider implements HoverProvider { const documentUri = args.documentUri; const folder = workspace.getWorkspaceFolder(documentUri); if (folder) { - const task = await createScriptRunnerTask(await getPackageManager(this.context, folder.uri), script, folder, documentUri); + const task = await createScriptRunnerTask(this.context, script, folder, documentUri); await tasks.executeTask(task); } } diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 8e3d65827b4803..44b38a1c5a9d81 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -72,9 +72,9 @@ export class NpmTaskProvider implements TaskProvider { packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' }); } if (kind.script === INSTALL_SCRIPT) { - return createInstallationTask(await getPackageManager(this.context, _task.scope.uri), _task.scope, packageJsonUri); + return createInstallationTask(this.context, _task.scope, packageJsonUri); } - return createScriptRunnerTask(await getPackageManager(this.context, _task.scope.uri), kind.script, _task.scope, packageJsonUri); + return createScriptRunnerTask(this.context, kind.script, _task.scope, packageJsonUri); } return undefined; } @@ -125,27 +125,43 @@ export function isWorkspaceFolder(value: any): value is WorkspaceFolder { return value && typeof value !== 'number'; } -export async function getPackageManager(extensionContext: ExtensionContext, folder: Uri, showWarning: boolean = true): Promise { - let packageManagerName = workspace.getConfiguration('npm', folder).get('packageManager', 'npm'); - - if (packageManagerName === 'auto') { - const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath); - packageManagerName = name; - const neverShowWarning = 'npm.multiplePMWarning.neverShow'; - if (showWarning && multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) { - const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}. To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', packageManagerName, folder.fsPath); - const neverShowAgain = l10n.t("Do not show again"); - const learnMore = l10n.t("Learn more"); - window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => { - switch (result) { - case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break; - case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json')); - } - }); - } +export async function getScriptRunner(context: ExtensionContext, folder: Uri, showWarning: boolean): Promise { + let scriptRunner = workspace.getConfiguration('npm', folder).get('scriptRunner', 'npm'); + + if (scriptRunner === 'auto') { + scriptRunner = await detectPackageManager(context, folder, showWarning); } - return packageManagerName; + return scriptRunner; +} + +export async function getPackageManager(context: ExtensionContext, folder: Uri, showWarning: boolean): Promise { + let packageManager = workspace.getConfiguration('npm', folder).get('packageManager', 'npm'); + + if (packageManager === 'auto') { + packageManager = await detectPackageManager(context, folder, showWarning); + } + + return packageManager; +} + +export async function detectPackageManager(extensionContext: ExtensionContext, folder: Uri, showWarning: boolean): Promise { + const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath); + const neverShowWarning = 'npm.multiplePMWarning.neverShow'; + if (showWarning && multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) { + // todo: add text for npm.scriptRunner? + const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}. To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', name, folder.fsPath); + const neverShowAgain = l10n.t("Do not show again"); + const learnMore = l10n.t("Learn more"); + window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => { + switch (result) { + case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break; + case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json')); + } + }); + } + + return name; } export async function hasNpmScripts(): Promise { @@ -169,15 +185,13 @@ export async function hasNpmScripts(): Promise { } } -async function detectNpmScripts(context: ExtensionContext, showWarning: boolean): Promise { +async function* findNpmPackages(): AsyncGenerator { - const emptyTasks: ITaskWithLocation[] = []; - const allTasks: ITaskWithLocation[] = []; const visitedPackageJsonFiles: Set = new Set(); const folders = workspace.workspaceFolders; if (!folders) { - return emptyTasks; + return; } try { for (const folder of folders) { @@ -186,14 +200,12 @@ async function detectNpmScripts(context: ExtensionContext, showWarning: boolean) const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**'); for (const path of paths) { if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) { - const tasks = await provideNpmScriptsForFolder(context, path, showWarning); + yield path; visitedPackageJsonFiles.add(path.fsPath); - allTasks.push(...tasks); } } } } - return allTasks; } catch (error) { return Promise.reject(error); } @@ -227,7 +239,12 @@ export async function detectNpmScriptsForFolder(context: ExtensionContext, folde export async function provideNpmScripts(context: ExtensionContext, showWarning: boolean): Promise { if (!cachedTasks) { - cachedTasks = await detectNpmScripts(context, showWarning); + const allTasks: ITaskWithLocation[] = []; + for await (const path of findNpmPackages()) { + const tasks = await provideNpmScriptsForFolder(context, path, showWarning); + allTasks.push(...tasks); + } + cachedTasks = allTasks; } return cachedTasks; } @@ -277,15 +294,13 @@ async function provideNpmScriptsForFolder(context: ExtensionContext, packageJson const result: ITaskWithLocation[] = []; - const packageManager = await getPackageManager(context, folder.uri, showWarning); - for (const { name, value, nameRange } of scripts.scripts) { - const task = await createScriptRunnerTask(packageManager, name, folder!, packageJsonUri, value); + const task = await createScriptRunnerTask(context, name, folder!, packageJsonUri, value, showWarning); result.push({ task, location: new Location(packageJsonUri, nameRange) }); } if (!workspace.getConfiguration('npm', folder).get('scriptExplorerExclude', []).find(e => e.includes(INSTALL_SCRIPT))) { - result.push({ task: await createInstallationTask(packageManager, folder, packageJsonUri, 'install dependencies from package') }); + result.push({ task: await createInstallationTask(context, folder, packageJsonUri, 'install dependencies from package', showWarning) }); } return result; } @@ -317,7 +332,8 @@ function getRelativePath(rootUri: Uri, packageJsonUri: Uri): string { return absolutePath.substring(rootUri.path.length + 1); } -export async function createScriptRunnerTask(packageManager: string, script: string, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string): Promise { +export async function createScriptRunnerTask(context: ExtensionContext, script: string, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, showWarning = true): Promise { + const scriptRunner = await getScriptRunner(context, folder.uri, showWarning); let kind: INpmTaskDefinition = { type: 'npm', script }; const relativePackageJson = getRelativePath(folder.uri, packageJsonUri); @@ -326,7 +342,7 @@ export async function createScriptRunnerTask(packageManager: string, script: str } const taskName = getTaskName(script, relativePackageJson); const cwd = path.dirname(packageJsonUri.fsPath); - const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(packageManager, getCommandLine(folder.uri, ['run', script]), { cwd: cwd })); + const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(scriptRunner, getCommandLine(folder.uri, ['run', script]), { cwd: cwd })); task.detail = scriptValue; const lowerCaseTaskName = script.toLowerCase(); @@ -343,7 +359,8 @@ export async function createScriptRunnerTask(packageManager: string, script: str return task; } -export async function createInstallationTask(packageManager: string, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string): Promise { +export async function createInstallationTask(context: ExtensionContext, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, showWarning = true): Promise { + const packageManager = await getPackageManager(context, folder.uri, showWarning); const kind: INpmTaskDefinition = { type: 'npm', script: INSTALL_SCRIPT }; const relativePackageJson = getRelativePath(folder.uri, packageJsonUri); @@ -411,15 +428,17 @@ export async function runScript(context: ExtensionContext, script: string, docum const uri = document.uri; const folder = workspace.getWorkspaceFolder(uri); if (folder) { - const task = await createScriptRunnerTask(await getPackageManager(context, folder.uri), script, folder, uri); + const task = await createScriptRunnerTask(context, script, folder, uri); tasks.executeTask(task); } } export async function startDebugging(context: ExtensionContext, scriptName: string, cwd: string, folder: WorkspaceFolder) { + let scriptRunner = await getScriptRunner(context, folder.uri, true); + commands.executeCommand( 'extension.js-debug.createDebuggerTerminal', - `${await getPackageManager(context, folder.uri)} run ${scriptName}`, + `${scriptRunner} run ${scriptName}`, folder, { cwd }, );