diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 1f5c6526cd3..cf413e1ec40 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -44,11 +44,15 @@ "commands": [ { "command": "npm.runScript", - "title": "Run Script" + "title": "Run" + }, + { + "command": "npm.debugScript", + "title": "Debug" }, { "command": "npm.openScript", - "title": "Open Script" + "title": "Open" }, { "command": "npm.refresh", @@ -72,6 +76,16 @@ "command": "npm.openScript", "when": "view == npm && viewItem == packageJSON", "group": "1_navigation" + }, + { + "command": "npm.runScript", + "when": "view == npm && viewItem == script", + "group": "1_navigation" + }, + { + "command": "npm.debugScript", + "when": "view == npm && viewItem == script", + "group": "1_navigation" } ] }, diff --git a/extensions/npm/src/main.ts b/extensions/npm/src/main.ts index 38935c2c68a..b3109903ee6 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -5,7 +5,6 @@ 'use strict'; import * as path from 'path'; -import * as fs from 'fs'; import * as httpRequest from 'request-light'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; @@ -15,40 +14,11 @@ const localize = nls.loadMessageBundle(); import { addJSONProviders } from './features/jsonContributions'; import { NpmScriptsTreeDataProvider } from './npmView'; -import { NpmTaskDefinition, ScriptValidator, isWorkspaceFolder } from './tasks'; +import { NpmTaskDefinition, getScripts } from './tasks'; type AutoDetect = 'on' | 'off'; let taskProvider: vscode.Disposable | undefined; -class Validator implements ScriptValidator { - async scriptIsValid(task: vscode.Task): Promise { - let uri: vscode.Uri | null = this.getPackageJsonUri(task); - if (uri) { - let tasks = await provideNpmScriptsForFolder(uri); - for (let i = 0; i < tasks.length; i++) { - const t = tasks[i]; - if (isWorkspaceFolder(task.scope) && isWorkspaceFolder(task.scope)) { - if (t.name === task.name && t.scope === task.scope && ((t.execution)).commandLine === ((task.execution)).commandLine) { - return true; - } - } - } - } - return false; - } - - getPackageJsonUri(task: vscode.Task): vscode.Uri | null { - if (isWorkspaceFolder(task.scope)) { - if (task.definition.path) { - return vscode.Uri.file(path.join(task.scope.uri.fsPath, task.definition.path, 'package.json')); - } else { - return vscode.Uri.file(path.join(task.scope.uri.fsPath, 'package.json')); - } - } - return null; - } -} - export function activate(context: vscode.ExtensionContext): void { let provider: vscode.TaskProvider = { provideTasks: () => { @@ -60,7 +30,7 @@ export function activate(context: vscode.ExtensionContext): void { }; taskProvider = vscode.workspace.registerTaskProvider('npm', provider); - vscode.window.registerTreeDataProvider('npm', new NpmScriptsTreeDataProvider(context, provider, new Validator())); + vscode.window.registerTreeDataProvider('npm', new NpmScriptsTreeDataProvider(context, provider, localize)); if (!vscode.workspace.workspaceFolders) { return; @@ -83,25 +53,6 @@ export function deactivate(): void { } } -async function exists(file: string): Promise { - return new Promise((resolve, _reject) => { - fs.exists(file, (value) => { - resolve(value); - }); - }); -} - -async function readFile(file: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(file, (err, data) => { - if (err) { - reject(err); - } - resolve(data.toString()); - }); - }); -} - const buildNames: string[] = ['build', 'compile', 'watch']; function isBuildTask(name: string): boolean { for (let buildName of buildNames) { @@ -182,46 +133,29 @@ function isExcluded(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri) async function provideNpmScriptsForFolder(packageJsonUri: vscode.Uri): Promise { let emptyTasks: vscode.Task[] = []; - if (packageJsonUri.scheme !== 'file') { - return emptyTasks; - } - - let packageJson = packageJsonUri.fsPath; - - if (!await exists(packageJson)) { - return emptyTasks; - } - let folder = vscode.workspace.getWorkspaceFolder(packageJsonUri); if (!folder) { return emptyTasks; } - - try { - var contents = await readFile(packageJson); - var json = JSON.parse(contents); - if (!json.scripts) { - return emptyTasks; - } - - const result: vscode.Task[] = []; - Object.keys(json.scripts).filter(isNotPreOrPostScript).forEach(each => { - const task = createTask(each, `run ${each}`, folder!, packageJsonUri); - const lowerCaseTaskName = each.toLowerCase(); - if (isBuildTask(lowerCaseTaskName)) { - task.group = vscode.TaskGroup.Build; - } else if (isTestTask(lowerCaseTaskName)) { - task.group = vscode.TaskGroup.Test; - } - result.push(task); - }); - // always add npm install (without a problem matcher) - // result.push(createTask('install', 'install', rootPath, folder, [])); - return result; - } catch (e) { - let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJsonUri); - throw new Error(localizedParseError); + let scripts = await getScripts(packageJsonUri, localize); + if (!scripts) { + return emptyTasks; } + + const result: vscode.Task[] = []; + Object.keys(scripts).filter(isNotPreOrPostScript).forEach(each => { + const task = createTask(each, `run ${each}`, folder!, packageJsonUri); + const lowerCaseTaskName = each.toLowerCase(); + if (isBuildTask(lowerCaseTaskName)) { + task.group = vscode.TaskGroup.Build; + } else if (isTestTask(lowerCaseTaskName)) { + task.group = vscode.TaskGroup.Test; + } + result.push(task); + }); + // always add npm install (without a problem matcher) + // result.push(createTask('install', 'install', rootPath, folder, [])); + return result; } function createTask(script: string, cmd: string, folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri, matcher?: any): vscode.Task { diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index cb0f455c048..fe03679c5b2 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -2,14 +2,15 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +'use strict'; -import { - ExtensionContext, Task, TreeDataProvider, TreeItem, TreeItemCollapsibleState, - WorkspaceFolder, workspace, commands, window, EventEmitter, Event, - ThemeIcon, Uri, TextDocument, TaskProvider -} from 'vscode'; -import { NpmTaskDefinition, ScriptValidator, isWorkspaceFolder } from './tasks'; import * as path from 'path'; +import { + DebugConfiguration, Event, EventEmitter, ExtensionContext, Task, TaskProvider, + TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, + WorkspaceFolder, commands, debug, window, workspace +} from 'vscode'; +import { NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, isWorkspaceFolder } from './tasks'; class Folder extends TreeItem { packages: PackageJSON[] = []; @@ -70,35 +71,95 @@ class NpmScript extends TreeItem { this.command = { title: 'Run Script', command: 'npm.runScript', - arguments: [task] + arguments: [this] }; } } export class NpmScriptsTreeDataProvider implements TreeDataProvider { private taskTree: Folder[] | PackageJSON[] | null = null; - private validator: ScriptValidator; private taskProvider: TaskProvider; + private localize: any; private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - constructor(context: ExtensionContext, taskProvider: TaskProvider, validator: ScriptValidator) { + constructor(context: ExtensionContext, taskProvider: TaskProvider, localize: any) { const subscriptions = context.subscriptions; - this.validator = validator; this.taskProvider = taskProvider; + this.localize = localize; subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this)); + subscriptions.push(commands.registerCommand('npm.debugScript', this.debugScript, this)); subscriptions.push(commands.registerCommand('npm.openScript', this.openScript, this)); subscriptions.push(commands.registerCommand('npm.refresh', this.refresh, this)); } - private async runScript(task: Task) { - if (!await this.validator.scriptIsValid(task)) { - window.showErrorMessage(`Could not find script '${task.name}' or the script has changed. Try to refresh the view.`); + private async scriptIsValid(scripts: any, task: Task): Promise { + if (scripts[task.name]) { + return true; + } + return false; + } + + private async runScript(script: NpmScript) { + let task = script.task; + let uri = getPackageJsonUriFromTask(task); + let scripts = await getScripts(uri!, this.localize); + + if (!await this.scriptIsValid(scripts, task)) { + window.showErrorMessage(`Could not find script '${task.name}'. Try to refresh the view.`); return; } - workspace.executeTask(task); + workspace.executeTask(script.task); + } + + private async extractPort(scripts: any, task: Task): Promise { + let script: string = scripts[task.name]; + let match = script.match(/--inspect-brk=(\d*)/); + if (match && match.length === 2) { + return parseInt(match[1]); + } + return null; + } + + private async debugScript(script: NpmScript) { + let task = script.task; + let uri = getPackageJsonUriFromTask(task); + let scripts = await getScripts(uri!, this.localize); + + if (!await this.scriptIsValid(scripts, task)) { + window.showErrorMessage(`Could not find script '${task.name}'. Try to refresh the view.`); + return; + } + + let port = await this.extractPort(scripts, task); + // let debugArgs = null; + // if (!port) { + // port = 9229; + // debugArgs = ['--', '--nolazy', `--inspect-brk=${port}`]; + // } + if (!port) { + window.showErrorMessage(`Could not launch for debugging, the script does not define --inspect-brk=port.`); + return; + } + const config: DebugConfiguration = { + type: 'node', + request: 'launch', + name: `Debug ${task.name}`, + runtimeExecutable: 'npm', + runtimeArgs: [ + 'run-script', + task.name, + ], + port: port + }; + // if (debugArgs) { + // config.runtimeArgs.push(...debugArgs); + // } + if (isWorkspaceFolder(task.scope)) { + debug.startDebugging(task.scope, config); + } } private async openScript(packageJSON: PackageJSON) { diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index cb443e387b6..c443ee2f252 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -2,18 +2,68 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +'use strict'; -import { TaskDefinition, Task, WorkspaceFolder } from 'vscode'; +import { TaskDefinition, Task, WorkspaceFolder, Uri } from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; export interface NpmTaskDefinition extends TaskDefinition { script: string; path?: string; } -export interface ScriptValidator { - scriptIsValid(task: Task): Promise; -} - export function isWorkspaceFolder(value: any): value is WorkspaceFolder { return value && typeof value !== 'number'; -} \ No newline at end of file +} + +export function getPackageJsonUriFromTask(task: Task): Uri | null { + if (isWorkspaceFolder(task.scope)) { + if (task.definition.path) { + return Uri.file(path.join(task.scope.uri.fsPath, task.definition.path, 'package.json')); + } else { + return Uri.file(path.join(task.scope.uri.fsPath, 'package.json')); + } + } + return null; +} + +export async function exists(file: string): Promise { + return new Promise((resolve, _reject) => { + fs.exists(file, (value) => { + resolve(value); + }); + }); +} + +export async function readFile(file: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(file, (err, data) => { + if (err) { + reject(err); + } + resolve(data.toString()); + }); + }); +} + +export async function getScripts(packageJsonUri: Uri, localize: any): Promise { + + if (packageJsonUri.scheme !== 'file') { + return null; + } + + let packageJson = packageJsonUri.fsPath; + if (!await exists(packageJson)) { + return null; + } + + try { + var contents = await readFile(packageJson); + var json = JSON.parse(contents); + return json.scripts; + } catch (e) { + let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJsonUri); + throw new Error(localizedParseError); + } +}