提交 df891cc4 编写于 作者: E Erich Gamma

Merge branch 'egamma/npmExplorer'

......@@ -14,3 +14,4 @@ For more information about auto detection of Tasks pls see the [documentation](h
- `npm.runSilent` run npm script with the `--silent` option, the default is `false`.
- `npm.packageManager` the package manager used to run the scripts: `npm` or `yarn`, the default is `npm`.
- `npm.exclude` glob patterns for folders that should be excluded from automatic script detection. The pattern is matched against the **absolute path** of the package.json. For example, to exclude all test folders use '**/test/**'.
- `npm.enableScriptExplorer` enable an explorer view for npm scripts when the workspace contains a 'package.json' file.
......@@ -7,6 +7,7 @@
"engines": {
"vscode": "0.10.x"
},
"enableProposedApi": true,
"icon": "images/npm_icon.png",
"categories": [
"Other"
......@@ -28,9 +29,73 @@
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask",
"onLanguage:json"
"onLanguage:json",
"onView:npm",
"workspaceContains:**/package.json"
],
"contributes": {
"views": {
"explorer": [
{
"id": "npm",
"name": "%view.name%",
"when": "hasNpmScripts"
}
]
},
"commands": [
{
"command": "npm.runScript",
"title": "%command.run%"
},
{
"command": "npm.debugScript",
"title": "%command.debug%"
},
{
"command": "npm.openScript",
"title": "%command.openScript%"
},
{
"command": "npm.refresh",
"title": "%command.refresh%",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
}
],
"menus": {
"view/title": [
{
"command": "npm.refresh",
"when": "view == npm",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "npm.openScript",
"when": "view == npm && viewItem == packageJSON",
"group": "navigation"
},
{
"command": "npm.openScript",
"when": "view == npm && viewItem == script",
"group": "navigation@1"
},
{
"command": "npm.runScript",
"when": "view == npm && viewItem == script",
"group": "navigation@2"
},
{
"command": "npm.debugScript",
"when": "view == npm && viewItem == script",
"group": "navigation@3"
}
]
},
"configuration": {
"id": "npm",
"type": "object",
......@@ -72,6 +137,12 @@
},
"description": "%config.npm.exclude%",
"scope": "resource"
},
"npm.enableScriptExplorer": {
"type": "boolean",
"default": false,
"scope": "resource",
"description": "%config.npm.enableScriptExplorer%"
}
}
},
......
......@@ -5,7 +5,15 @@
"config.npm.runSilent": "Run npm commands with the `--silent` option.",
"config.npm.packageManager": "The package manager used to run scripts.",
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
"config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts, when the workspace contains a 'package.json' file.",
"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 ommitted."
"taskdef.path": "The path to the folder of the package.json file that provides the script. Can be ommitted.",
"view.name": "Npm Scripts",
"command.refresh": "Refresh",
"command.run": "Run",
"command.debug": "Debug",
"command.openScript": "Open",
"npm.scriptInvalid": "Could not find the script '{0}'. Try to refresh the view.",
"npm.noDebugOptions": "Could not launch '{0}' for debugging, the script needs to include the node debug options: '--nolazy --inspect-brk=port', [learn more](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-support-for-npm-and-other-tools).`"
}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 5.5a5.5 5.5 0 0 1-5.5 5.5c-.275 0-.543-.027-.807-.066l-.079-.012a5.429 5.429 0 0 1-.81-.192l-4.537 4.537c-.472.473-1.1.733-1.767.733s-1.295-.26-1.768-.732a2.502 2.502 0 0 1 0-3.535l4.537-4.537a5.452 5.452 0 0 1-.191-.812c-.005-.025-.008-.051-.012-.077A5.503 5.503 0 0 1 5 5.5a5.5 5.5 0 1 1 11 0z" id="outline"/><path class="icon-vs-bg" d="M15 5.5a4.5 4.5 0 0 1-4.5 4.5c-.693 0-1.342-.17-1.929-.45l-5.01 5.01c-.293.294-.677.44-1.061.44s-.768-.146-1.061-.439a1.5 1.5 0 0 1 0-2.121l5.01-5.01A4.483 4.483 0 0 1 6 5.5 4.5 4.5 0 0 1 10.5 1c.693 0 1.342.17 1.929.45L9.636 4.243l2.121 2.121 2.793-2.793c.28.587.45 1.236.45 1.929z" id="iconBg"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 5.5a5.5 5.5 0 0 1-5.5 5.5c-.275 0-.543-.027-.807-.066l-.079-.012a5.429 5.429 0 0 1-.81-.192l-4.537 4.537c-.472.473-1.1.733-1.767.733s-1.295-.26-1.768-.732a2.502 2.502 0 0 1 0-3.535l4.537-4.537a5.452 5.452 0 0 1-.191-.812c-.005-.025-.008-.051-.012-.077A5.503 5.503 0 0 1 5 5.5a5.5 5.5 0 1 1 11 0z" id="outline"/><path class="icon-vs-bg" d="M15 5.5a4.5 4.5 0 0 1-4.5 4.5c-.693 0-1.342-.17-1.929-.45l-5.01 5.01c-.293.294-.677.44-1.061.44s-.768-.146-1.061-.439a1.5 1.5 0 0 1 0-2.121l5.01-5.01A4.483 4.483 0 0 1 6 5.5 4.5 4.5 0 0 1 10.5 1c.693 0 1.342.17 1.929.45L9.636 4.243l2.121 2.121 2.793-2.793c.28.587.45 1.236.45 1.929z" id="iconBg"/></svg>
\ No newline at end of file
......@@ -4,37 +4,50 @@
*--------------------------------------------------------------------------------------------*/
'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';
import * as minimatch from 'minimatch';
const localize = nls.loadMessageBundle();
import { addJSONProviders } from './features/jsonContributions';
import { NpmScriptsTreeDataProvider } from './npmView';
import { provideNpmScripts, hasNpmScripts, explorerIsEnabled } from './tasks';
type AutoDetect = 'on' | 'off';
let taskProvider: vscode.Disposable | undefined;
export function activate(context: vscode.ExtensionContext): void {
if (!vscode.workspace.workspaceFolders) {
return;
}
export async function activate(context: vscode.ExtensionContext): Promise<void> {
taskProvider = registerTaskProvider(context);
configureHttpRequest();
vscode.workspace.onDidChangeConfiguration(() => configureHttpRequest());
context.subscriptions.push(addJSONProviders(httpRequest.xhr));
}
taskProvider = vscode.workspace.registerTaskProvider('npm', {
function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined {
if (vscode.workspace.workspaceFolders) {
let provider: vscode.TaskProvider = {
provideTasks: () => {
return provideNpmScripts();
return provideNpmScripts(localize);
},
resolveTask(_task: vscode.Task): vscode.Task | undefined {
return undefined;
}
});
configureHttpRequest();
vscode.workspace.onDidChangeConfiguration(() => configureHttpRequest());
};
let disposable = vscode.workspace.registerTaskProvider('npm', provider);
registerExplorer(context, provider);
return disposable;
}
return undefined;
}
context.subscriptions.push(addJSONProviders(httpRequest.xhr));
async function registerExplorer(context: vscode.ExtensionContext, provider: vscode.TaskProvider) {
if (explorerIsEnabled()) {
let treeDataProvider = vscode.window.registerTreeDataProvider('npm', new NpmScriptsTreeDataProvider(context, provider, localize));
context.subscriptions.push(treeDataProvider);
if (await hasNpmScripts()) {
vscode.commands.executeCommand('setContext', 'hasNpmScripts', true);
}
}
}
function configureHttpRequest() {
......@@ -47,185 +60,3 @@ export function deactivate(): void {
taskProvider.dispose();
}
}
async function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
async function readFile(file: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
resolve(data.toString());
});
});
}
interface NpmTaskDefinition extends vscode.TaskDefinition {
script: string;
path?: string;
}
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
if (name.indexOf(buildName) !== -1) {
return true;
}
}
return false;
}
const testNames: string[] = ['test'];
function isTestTask(name: string): boolean {
for (let testName of testNames) {
if (name === testName) {
return true;
}
}
return false;
}
function isNotPreOrPostScript(script: string): boolean {
return !(script.startsWith('pre') || script.startsWith('post'));
}
async function provideNpmScripts(): Promise<vscode.Task[]> {
let emptyTasks: vscode.Task[] = [];
let allTasks: vscode.Task[] = [];
let folders = vscode.workspace.workspaceFolders;
if (!folders) {
return emptyTasks;
}
try {
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
if (isEnabled(folder)) {
let relativePattern = new vscode.RelativePattern(folder, '**/package.json');
let paths = await vscode.workspace.findFiles(relativePattern, '**/node_modules/**');
for (let j = 0; j < paths.length; j++) {
if (!isExcluded(folder, paths[j])) {
let tasks = await provideNpmScriptsForFolder(paths[j]);
allTasks.push(...tasks);
}
}
}
}
return allTasks;
} catch (error) {
return Promise.reject(error);
}
}
function isEnabled(folder: vscode.WorkspaceFolder): boolean {
return vscode.workspace.getConfiguration('npm', folder.uri).get<AutoDetect>('autoDetect') === 'on';
}
function isExcluded(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri) {
function testForExclusionPattern(path: string, pattern: string): boolean {
return minimatch(path, pattern, { dot: true });
}
let exclude = vscode.workspace.getConfiguration('npm', folder.uri).get<string | string[]>('exclude');
if (exclude) {
if (Array.isArray(exclude)) {
for (let pattern of exclude) {
if (testForExclusionPattern(packageJsonUri.fsPath, pattern)) {
return true;
}
}
} else if (testForExclusionPattern(packageJsonUri.fsPath, exclude)) {
return true;
}
}
return false;
}
async function provideNpmScriptsForFolder(packageJsonUri: vscode.Uri): Promise<vscode.Task[]> {
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);
}
}
function createTask(script: string, cmd: string, folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri, matcher?: any): vscode.Task {
function getTaskName(script: string, file: string) {
if (file.length) {
return `${script} - ${file.substring(0, file.length - 1)}`;
}
return script;
}
function getCommandLine(folder: vscode.WorkspaceFolder, cmd: string): string {
let packageManager = vscode.workspace.getConfiguration('npm', folder.uri).get<string>('packageManager', 'npm');
if (vscode.workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
return `${packageManager} --silent ${cmd}`;
}
return `${packageManager} ${cmd}`;
}
function getRelativePath(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri): string {
let rootUri = folder.uri;
let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
return absolutePath.substring(rootUri.path.length + 1);
}
let kind: NpmTaskDefinition = {
type: 'npm',
script: script
};
let relativePackageJson = getRelativePath(folder, packageJsonUri);
if (relativePackageJson.length) {
kind.path = getRelativePath(folder, packageJsonUri);
}
let taskName = getTaskName(script, relativePackageJson);
let cwd = path.dirname(packageJsonUri.fsPath);
return new vscode.Task(kind, folder, taskName, 'npm', new vscode.ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
}
/*---------------------------------------------------------------------------------------------
* 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 * as path from 'path';
import {
DebugConfiguration, Event, EventEmitter, ExtensionContext, Task, TaskProvider,
TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri,
WorkspaceFolder, commands, debug, window, workspace, Selection
} from 'vscode';
import { visit, JSONVisitor } from 'jsonc-parser';
import { NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, isWorkspaceFolder, getPackageManager, getTaskName } from './tasks';
class Folder extends TreeItem {
packages: PackageJSON[] = [];
workspaceFolder: WorkspaceFolder;
constructor(folder: WorkspaceFolder) {
super(folder.name, TreeItemCollapsibleState.Expanded);
this.contextValue = 'folder';
this.resourceUri = folder.uri;
this.workspaceFolder = folder;
this.iconPath = ThemeIcon.Folder;
}
addPackage(packageJson: PackageJSON) {
this.packages.push(packageJson);
}
}
const packageName = 'package.json';
class PackageJSON extends TreeItem {
path: string;
folder: Folder;
scripts: NpmScript[] = [];
static getLabel(folderName: string, relativePath: string): string {
if (relativePath.length > 0) {
return path.join(relativePath, packageName);
}
return path.join(folderName, packageName);
}
constructor(folder: Folder, relativePath: string) {
super(PackageJSON.getLabel(folder.label!, relativePath), TreeItemCollapsibleState.Expanded);
this.folder = folder;
this.path = relativePath;
this.contextValue = 'packageJSON';
if (relativePath) {
this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, relativePath, packageName));
} else {
this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, packageName));
}
this.iconPath = ThemeIcon.File;
}
addScript(script: NpmScript) {
this.scripts.push(script);
}
}
class NpmScript extends TreeItem {
task: Task;
package: PackageJSON;
constructor(context: ExtensionContext, packageJson: PackageJSON, task: Task) {
super(task.name, TreeItemCollapsibleState.None);
this.contextValue = 'script';
this.package = packageJson;
this.task = task;
this.command = {
title: 'Run Script',
command: 'npm.openScript',
arguments: [this]
};
this.iconPath = {
light: context.asAbsolutePath(path.join('resources', 'light', 'script.svg')),
dark: context.asAbsolutePath(path.join('resources', 'dark', 'script.svg'))
};
}
getFolder(): WorkspaceFolder {
return this.package.folder.workspaceFolder;
}
}
export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
private taskTree: Folder[] | PackageJSON[] | null = null;
private taskProvider: TaskProvider;
private localize: any;
private extensionContext: ExtensionContext;
private _onDidChangeTreeData: EventEmitter<TreeItem | null> = new EventEmitter<TreeItem | null>();
readonly onDidChangeTreeData: Event<TreeItem | null> = this._onDidChangeTreeData.event;
constructor(context: ExtensionContext, taskProvider: TaskProvider, localize: any) {
const subscriptions = context.subscriptions;
this.taskProvider = taskProvider;
this.localize = localize;
this.extensionContext = context;
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 scriptIsValid(scripts: any, task: Task): boolean {
for (const script in scripts) {
let label = getTaskName(script, task.definition.path);
if (task.name === label) {
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 (!this.scriptIsValid(scripts, task)) {
this.scriptNotValid(task);
return;
}
workspace.executeTask(script.task);
}
private async extractDebugArg(scripts: any, task: Task): Promise<[string, number] | undefined> {
let script: string = scripts[task.name];
let match = script.match(/--(inspect|debug)(-brk)?(=(\d*))?/);
if (match) {
if (match[4]) {
return [match[1], parseInt(match[4])];
}
if (match[1] === 'inspect') {
return [match[1], 9229];
}
if (match[1] === 'debug') {
return [match[1], 5858];
}
}
return undefined;
}
private async debugScript(script: NpmScript) {
let task = script.task;
let uri = getPackageJsonUriFromTask(task);
let scripts = await getScripts(uri!, this.localize);
if (!this.scriptIsValid(scripts, task)) {
this.scriptNotValid(task);
return;
}
let debugArg = await this.extractDebugArg(scripts, task);
if (!debugArg) {
let message = this.localize('npm.noDebugOptions', 'Could not launch "{0}" for debugging, the script needs to include the node debug options: "--nolazy --inspect-brk=port", [learn more](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-support-for-npm-and-other-tools).', task.name);
window.showErrorMessage(message);
return;
}
let packageManager = getPackageManager(script.getFolder());
const config: DebugConfiguration = {
type: 'node',
request: 'launch',
name: `Debug ${task.name}`,
runtimeExecutable: packageManager,
runtimeArgs: [
'run-script',
task.name,
],
port: debugArg[1]
};
if (isWorkspaceFolder(task.scope)) {
debug.startDebugging(task.scope, config);
}
}
private scriptNotValid(task: Task) {
let message = this.localize('npm.scriptInvalid', 'Could not find the script "{0}". Try to refresh the view.', task.name);
window.showErrorMessage(message);
}
private findScript(document: TextDocument, script?: NpmScript): number {
let scriptOffset = 0;
let inScripts = false;
let visitor: JSONVisitor = {
onError() {
return scriptOffset;
},
onObjectEnd() {
if (inScripts) {
inScripts = false;
}
},
onObjectProperty(property: string, offset: number, _length: number) {
if (property === 'scripts') {
inScripts = true;
if (!script) { // select the script section
scriptOffset = offset;
}
}
else if (inScripts && script) {
let label = getTaskName(property, script.task.definition.path);
if (script.task.name === label) {
scriptOffset = offset;
}
}
}
};
visit(document.getText(), visitor);
return scriptOffset;
}
private async openScript(selection: PackageJSON | NpmScript) {
let uri: Uri | undefined = undefined;
if (selection instanceof PackageJSON) {
uri = selection.resourceUri!;
} else if (selection instanceof NpmScript) {
uri = selection.package.resourceUri;
}
if (!uri) {
return;
}
let document: TextDocument = await workspace.openTextDocument(uri);
let offset = this.findScript(document, selection instanceof NpmScript ? selection : undefined);
let position = document.positionAt(offset);
await window.showTextDocument(document, { selection: new Selection(position, position) });
}
private refresh() {
this.taskTree = null;
this._onDidChangeTreeData.fire();
}
getTreeItem(element: TreeItem): TreeItem {
return element;
}
getParent(element: TreeItem): TreeItem | null {
if (element instanceof Folder) {
return null;
}
if (element instanceof PackageJSON) {
return element.folder;
}
if (element instanceof NpmScript) {
return element.package;
}
return null;
}
async getChildren(element?: TreeItem): Promise<TreeItem[]> {
if (!this.taskTree) {
let tasks = await this.taskProvider.provideTasks();
if (tasks) {
this.taskTree = this.buildTaskTree(tasks);
}
}
if (element instanceof Folder) {
return element.packages;
}
if (element instanceof PackageJSON) {
return element.scripts;
}
if (element instanceof NpmScript) {
return [];
}
if (!element) {
if (this.taskTree) {
return this.taskTree;
}
}
return [];
}
private buildTaskTree(tasks: Task[]): Folder[] | PackageJSON[] {
let folders: Map<String, Folder> = new Map();
let packages: Map<String, PackageJSON> = new Map();
let folder = null;
let packageJson = null;
tasks.forEach(each => {
if (isWorkspaceFolder(each.scope)) {
folder = folders.get(each.scope.name);
if (!folder) {
folder = new Folder(each.scope);
folders.set(each.scope.name, folder);
}
let definition: NpmTaskDefinition = <NpmTaskDefinition>each.definition;
let relativePath = definition.path ? definition.path : '';
let fullPath = path.join(each.scope.name, relativePath);
packageJson = packages.get(fullPath);
if (!packageJson) {
packageJson = new PackageJSON(folder, relativePath);
folder.addPackage(packageJson);
packages.set(fullPath, packageJson);
}
let script = new NpmScript(this.extensionContext, packageJson, each);
packageJson.addScript(script);
}
});
if (folders.size === 1) {
return [...packages.values()];
}
return [...folders.values()];
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace } from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as minimatch from 'minimatch';
export interface NpmTaskDefinition extends TaskDefinition {
script: string;
path?: string;
}
type AutoDetect = 'on' | 'off';
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
if (name.indexOf(buildName) !== -1) {
return true;
}
}
return false;
}
const testNames: string[] = ['test'];
function isTestTask(name: string): boolean {
for (let testName of testNames) {
if (name === testName) {
return true;
}
}
return false;
}
function isNotPreOrPostScript(script: string): boolean {
return !(script.startsWith('pre') || script.startsWith('post'));
}
export function isWorkspaceFolder(value: any): value is WorkspaceFolder {
return value && typeof value !== 'number';
}
export function getPackageManager(folder: WorkspaceFolder): string {
return workspace.getConfiguration('npm', folder.uri).get<string>('packageManager', 'npm');
}
export function explorerIsEnabled(): boolean {
let folders = workspace.workspaceFolders;
if (!folders) {
return false;
}
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
if (workspace.getConfiguration('npm', folder.uri).get<boolean>('enableScriptExplorer') === true) {
return true;
}
}
return false;
}
export async function hasNpmScripts(): Promise<boolean> {
let folders = workspace.workspaceFolders;
if (!folders) {
return false;
}
try {
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
if (isAutoDetectionEnabled(folder)) {
let relativePattern = new RelativePattern(folder, '**/package.json');
let paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
if (paths.length > 0) {
return true;
}
}
}
return false;
} catch (error) {
return Promise.reject(error);
}
}
export async function provideNpmScripts(localize: any): Promise<Task[]> {
let emptyTasks: Task[] = [];
let allTasks: Task[] = [];
let folders = workspace.workspaceFolders;
if (!folders) {
return emptyTasks;
}
try {
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
if (isAutoDetectionEnabled(folder)) {
let relativePattern = new RelativePattern(folder, '**/package.json');
let paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
for (let j = 0; j < paths.length; j++) {
if (!isExcluded(folder, paths[j])) {
let tasks = await provideNpmScriptsForFolder(localize, paths[j]);
allTasks.push(...tasks);
}
}
}
}
return allTasks;
} catch (error) {
return Promise.reject(error);
}
}
function isAutoDetectionEnabled(folder: WorkspaceFolder): boolean {
return workspace.getConfiguration('npm', folder.uri).get<AutoDetect>('autoDetect') === 'on';
}
function isExcluded(folder: WorkspaceFolder, packageJsonUri: Uri) {
function testForExclusionPattern(path: string, pattern: string): boolean {
return minimatch(path, pattern, { dot: true });
}
let exclude = workspace.getConfiguration('npm', folder.uri).get<string | string[]>('exclude');
if (exclude) {
if (Array.isArray(exclude)) {
for (let pattern of exclude) {
if (testForExclusionPattern(packageJsonUri.fsPath, pattern)) {
return true;
}
}
} else if (testForExclusionPattern(packageJsonUri.fsPath, exclude)) {
return true;
}
}
return false;
}
async function provideNpmScriptsForFolder(localize: any, packageJsonUri: Uri): Promise<Task[]> {
let emptyTasks: Task[] = [];
let folder = workspace.getWorkspaceFolder(packageJsonUri);
if (!folder) {
return emptyTasks;
}
let scripts = await getScripts(packageJsonUri, localize);
if (!scripts) {
return emptyTasks;
}
const result: 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 = TaskGroup.Build;
} else if (isTestTask(lowerCaseTaskName)) {
task.group = TaskGroup.Test;
}
result.push(task);
});
// always add npm install (without a problem matcher)
// result.push(createTask('install', 'install', rootPath, folder, []));
return result;
}
export function getTaskName(script: string, relativePath: string | undefined) {
if (relativePath && relativePath.length) {
return `${script} - ${relativePath.substring(0, relativePath.length - 1)}`;
}
return script;
}
function createTask(script: string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, matcher?: any): Task {
function getCommandLine(folder: WorkspaceFolder, cmd: string): string {
let packageManager = getPackageManager(folder);
if (workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
return `${packageManager} --silent ${cmd}`;
}
return `${packageManager} ${cmd}`;
}
function getRelativePath(folder: WorkspaceFolder, packageJsonUri: Uri): string {
let rootUri = folder.uri;
let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
return absolutePath.substring(rootUri.path.length + 1);
}
let kind: NpmTaskDefinition = {
type: 'npm',
script: script
};
let relativePackageJson = getRelativePath(folder, packageJsonUri);
if (relativePackageJson.length) {
kind.path = getRelativePath(folder, packageJsonUri);
}
let taskName = getTaskName(script, relativePackageJson);
let cwd = path.dirname(packageJsonUri.fsPath);
return new Task(kind, folder, taskName, 'npm', new ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
}
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;
}
async function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
async function readFile(file: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
resolve(data.toString());
});
});
}
export async function getScripts(packageJsonUri: Uri, localize: any): Promise<any> {
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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册