diff --git a/extensions/npm/README.md b/extensions/npm/README.md index a24a7d69d6e..44c08ff3998 100644 --- a/extensions/npm/README.md +++ b/extensions/npm/README.md @@ -35,4 +35,6 @@ The extension fetches data from https://registry.npmjs/org and https://registry. - `npm.enableScriptExplorer` - Enable an explorer view for npm scripts. - `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`. - `npm.scriptCodeLens.enable` - Enable/disable the code lenses to run a script, the default is `false`. +- `npm.enableRunFromFolderContextMenu` - Enable running npm scripts from the context menu of folders in Explorer, the default is `true`. + diff --git a/extensions/npm/package.json b/extensions/npm/package.json index a987d6082b4..ad3d9933908 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -88,6 +88,10 @@ { "command": "npm.runSelectedScript", "title": "%command.runSelectedScript%" + }, + { + "command": "npm.runScriptsFromFolder", + "title": "%command.runScriptsFromFolder%" } ], "menus": { @@ -172,7 +176,14 @@ "when": "view == npm && viewItem == script", "group": "navigation@3" } - ] + ], + "explorer/context": [ + { + "when": "config.npm.enableRunFromFolderContextMenu && explorerViewletVisible && explorerResourceIsFolder", + "command": "npm.runScriptsFromFolder", + "group": "2_workspace" + } + ] }, "configuration": { "id": "npm", @@ -222,6 +233,12 @@ "scope": "resource", "description": "%config.npm.enableScriptExplorer%" }, + "npm.enableRunFromFolderContextMenu": { + "type": "boolean", + "default": true, + "scope": "resource", + "description": "%config.npm.enableRunFromFolderContextMenu%" + }, "npm.scriptExplorerAction": { "type": "string", "enum": [ diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index 4a33bcc0bc3..c86b605ee25 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -8,6 +8,7 @@ "config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts.", "config.npm.scriptExplorerAction": "The default click action used in the scripts explorer: `open` or `run`, the default is `open`.", "config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs/org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.", + "config.npm.enableRunFromFolderContextMenu": "Enable running scripts from the context menu of a folder in Explorer", "npm.parseError": "Npm task detection: failed to parse the file {0}", "taskdef.script": "The npm script to customize.", "taskdef.path": "The path to the folder of the package.json file that provides the script. Can be omitted.", @@ -17,5 +18,6 @@ "command.debug": "Debug", "command.openScript": "Open", "command.runInstall": "Run Install", - "command.runSelectedScript": "Run Script" + "command.runSelectedScript": "Run Script", + "command.runScriptsFromFolder": "Select npm Scripts to Run..." } diff --git a/extensions/npm/src/commands.ts b/extensions/npm/src/commands.ts index 08384681708..17fdd2aa786 100644 --- a/extensions/npm/src/commands.ts +++ b/extensions/npm/src/commands.ts @@ -3,11 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { - runScript, findScriptAtPosition -} from './tasks'; import * as nls from 'vscode-nls'; +import * as vscode from 'vscode'; + +import { + detectNpmScriptsForFolder, + findScriptAtPosition, + getPackageJsonUriFromTask, + runScript +} from './tasks'; const localize = nls.loadMessageBundle(); @@ -28,4 +32,29 @@ export function runSelectedScript() { let message = localize('noScriptFound', 'Could not find a valid npm script at the selection.'); vscode.window.showErrorMessage(message); } -} \ No newline at end of file +} + +export async function selectAndRunScriptFromFolder(folderInfo: vscode.Uri) { + type TaskMap = { [id: string]: vscode.Task; }; + let taskList = await detectNpmScriptsForFolder(folderInfo.path); + + if (taskList && taskList.length > 0) { + let taskMap: TaskMap = {}; + taskList.forEach(t => { + let uri = getPackageJsonUriFromTask(t); + if (uri && uri.fsPath.length >= folderInfo.fsPath.length) { + let taskName = uri.fsPath.substring(folderInfo.fsPath.length, uri.fsPath.length - '/package.json'.length) + ' > ' + t.name.substring(0, t.name.search('-')); + taskMap[taskName] = t; + } + }); + await vscode.window.showQuickPick(Object.keys(taskMap).sort(), { + placeHolder: `Run scripts on folder ${folderInfo.fsPath}...`, + onDidSelectItem: (taskToRun: string) => { + vscode.tasks.executeTask(taskMap[taskToRun]); + } + }); + } + else { + vscode.window.showInformationMessage(`No scripts detected in folder ${folderInfo.path}`); + } +} diff --git a/extensions/npm/src/main.ts b/extensions/npm/src/main.ts index d32d245b0ed..921908468d0 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -6,10 +6,10 @@ import * as httpRequest from 'request-light'; import * as vscode from 'vscode'; import { addJSONProviders } from './features/jsonContributions'; +import { runSelectedScript, selectAndRunScriptFromFolder } from './commands'; import { NpmScriptsTreeDataProvider } from './npmView'; import { invalidateTasksCache, NpmTaskProvider, hasPackageJson } from './tasks'; import { invalidateHoverScriptsCache, NpmScriptHoverProvider } from './scriptHover'; -import { runSelectedScript } from './commands'; let treeDataProvider: NpmScriptsTreeDataProvider | undefined; @@ -45,6 +45,8 @@ export async function activate(context: vscode.ExtensionContext): Promise if (await hasPackageJson()) { vscode.commands.executeCommand('setContext', 'npm:showScriptExplorer', true); } + + context.subscriptions.push(vscode.commands.registerCommand('npm.runScriptsFromFolder', selectAndRunScriptFromFolder)); } function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined { diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 0e6a28bfc33..c7a9f52d152 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -155,6 +155,28 @@ async function detectNpmScripts(): Promise { } } + +export async function detectNpmScriptsForFolder(folder: string): Promise { + + let allTasks: Task[] = []; + let visitedPackageJsonFiles: Set = new Set(); + + try { + let relativePattern = new RelativePattern(folder, '**/package.json'); + let paths = await workspace.findFiles(relativePattern, '**/node_modules/**'); + for (const path of paths) { + if (!visitedPackageJsonFiles.has(path.fsPath)) { + let tasks = await provideNpmScriptsForFolder(path); + visitedPackageJsonFiles.add(path.fsPath); + allTasks.push(...tasks); + } + } + return allTasks; + } catch (error) { + return Promise.reject(error); + } +} + export async function provideNpmScripts(): Promise { if (!cachedTasks) { cachedTasks = await detectNpmScripts();