Skip to content

Commit df891cc

Browse files
committed
Merge branch 'egamma/npmExplorer'
2 parents 5246984 + 6220b39 commit df891cc

10 files changed

Lines changed: 685 additions & 201 deletions

File tree

extensions/npm/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ For more information about auto detection of Tasks pls see the [documentation](h
1414
- `npm.runSilent` run npm script with the `--silent` option, the default is `false`.
1515
- `npm.packageManager` the package manager used to run the scripts: `npm` or `yarn`, the default is `npm`.
1616
- `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/**'.
17+
- `npm.enableScriptExplorer` enable an explorer view for npm scripts when the workspace contains a 'package.json' file.

extensions/npm/package.json

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"engines": {
88
"vscode": "0.10.x"
99
},
10+
"enableProposedApi": true,
1011
"icon": "images/npm_icon.png",
1112
"categories": [
1213
"Other"
@@ -28,9 +29,73 @@
2829
"main": "./out/main",
2930
"activationEvents": [
3031
"onCommand:workbench.action.tasks.runTask",
31-
"onLanguage:json"
32+
"onLanguage:json",
33+
"onView:npm",
34+
"workspaceContains:**/package.json"
3235
],
3336
"contributes": {
37+
"views": {
38+
"explorer": [
39+
{
40+
"id": "npm",
41+
"name": "%view.name%",
42+
"when": "hasNpmScripts"
43+
}
44+
]
45+
},
46+
"commands": [
47+
{
48+
"command": "npm.runScript",
49+
"title": "%command.run%"
50+
},
51+
{
52+
"command": "npm.debugScript",
53+
"title": "%command.debug%"
54+
},
55+
{
56+
"command": "npm.openScript",
57+
"title": "%command.openScript%"
58+
},
59+
{
60+
"command": "npm.refresh",
61+
"title": "%command.refresh%",
62+
"icon": {
63+
"light": "resources/light/refresh.svg",
64+
"dark": "resources/dark/refresh.svg"
65+
}
66+
}
67+
],
68+
"menus": {
69+
"view/title": [
70+
{
71+
"command": "npm.refresh",
72+
"when": "view == npm",
73+
"group": "navigation"
74+
}
75+
],
76+
"view/item/context": [
77+
{
78+
"command": "npm.openScript",
79+
"when": "view == npm && viewItem == packageJSON",
80+
"group": "navigation"
81+
},
82+
{
83+
"command": "npm.openScript",
84+
"when": "view == npm && viewItem == script",
85+
"group": "navigation@1"
86+
},
87+
{
88+
"command": "npm.runScript",
89+
"when": "view == npm && viewItem == script",
90+
"group": "navigation@2"
91+
},
92+
{
93+
"command": "npm.debugScript",
94+
"when": "view == npm && viewItem == script",
95+
"group": "navigation@3"
96+
}
97+
]
98+
},
3499
"configuration": {
35100
"id": "npm",
36101
"type": "object",
@@ -72,6 +137,12 @@
72137
},
73138
"description": "%config.npm.exclude%",
74139
"scope": "resource"
140+
},
141+
"npm.enableScriptExplorer": {
142+
"type": "boolean",
143+
"default": false,
144+
"scope": "resource",
145+
"description": "%config.npm.enableScriptExplorer%"
75146
}
76147
}
77148
},
@@ -104,4 +175,4 @@
104175
}
105176
]
106177
}
107-
}
178+
}

extensions/npm/package.nls.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55
"config.npm.runSilent": "Run npm commands with the `--silent` option.",
66
"config.npm.packageManager": "The package manager used to run scripts.",
77
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
8+
"config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts, when the workspace contains a 'package.json' file.",
89
"npm.parseError": "Npm task detection: failed to parse the file {0}",
910
"taskdef.script": "The npm script to customize.",
10-
"taskdef.path": "The path to the folder of the package.json file that provides the script. Can be ommitted."
11+
"taskdef.path": "The path to the folder of the package.json file that provides the script. Can be ommitted.",
12+
"view.name": "Npm Scripts",
13+
"command.refresh": "Refresh",
14+
"command.run": "Run",
15+
"command.debug": "Debug",
16+
"command.openScript": "Open",
17+
"npm.scriptInvalid": "Could not find the script '{0}'. Try to refresh the view.",
18+
"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).`"
1119
}
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

extensions/npm/src/main.ts

Lines changed: 29 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -4,228 +4,59 @@
44
*--------------------------------------------------------------------------------------------*/
55
'use strict';
66

7-
import * as path from 'path';
8-
import * as fs from 'fs';
97
import * as httpRequest from 'request-light';
108
import * as vscode from 'vscode';
119
import * as nls from 'vscode-nls';
12-
import * as minimatch from 'minimatch';
1310

1411
const localize = nls.loadMessageBundle();
1512

1613
import { addJSONProviders } from './features/jsonContributions';
14+
import { NpmScriptsTreeDataProvider } from './npmView';
15+
import { provideNpmScripts, hasNpmScripts, explorerIsEnabled } from './tasks';
1716

18-
type AutoDetect = 'on' | 'off';
1917
let taskProvider: vscode.Disposable | undefined;
2018

21-
export function activate(context: vscode.ExtensionContext): void {
22-
if (!vscode.workspace.workspaceFolders) {
23-
return;
24-
}
25-
26-
taskProvider = vscode.workspace.registerTaskProvider('npm', {
27-
provideTasks: () => {
28-
return provideNpmScripts();
29-
},
30-
resolveTask(_task: vscode.Task): vscode.Task | undefined {
31-
return undefined;
32-
}
33-
});
19+
export async function activate(context: vscode.ExtensionContext): Promise<void> {
20+
taskProvider = registerTaskProvider(context);
3421
configureHttpRequest();
3522
vscode.workspace.onDidChangeConfiguration(() => configureHttpRequest());
36-
3723
context.subscriptions.push(addJSONProviders(httpRequest.xhr));
3824
}
3925

40-
function configureHttpRequest() {
41-
const httpSettings = vscode.workspace.getConfiguration('http');
42-
httpRequest.configure(httpSettings.get<string>('proxy', ''), httpSettings.get<boolean>('proxyStrictSSL', true));
43-
}
44-
45-
export function deactivate(): void {
46-
if (taskProvider) {
47-
taskProvider.dispose();
48-
}
49-
}
50-
51-
async function exists(file: string): Promise<boolean> {
52-
return new Promise<boolean>((resolve, _reject) => {
53-
fs.exists(file, (value) => {
54-
resolve(value);
55-
});
56-
});
57-
}
58-
59-
async function readFile(file: string): Promise<string> {
60-
return new Promise<string>((resolve, reject) => {
61-
fs.readFile(file, (err, data) => {
62-
if (err) {
63-
reject(err);
64-
}
65-
resolve(data.toString());
66-
});
67-
});
68-
}
69-
70-
interface NpmTaskDefinition extends vscode.TaskDefinition {
71-
script: string;
72-
path?: string;
73-
}
74-
75-
const buildNames: string[] = ['build', 'compile', 'watch'];
76-
function isBuildTask(name: string): boolean {
77-
for (let buildName of buildNames) {
78-
if (name.indexOf(buildName) !== -1) {
79-
return true;
80-
}
81-
}
82-
return false;
83-
}
84-
85-
const testNames: string[] = ['test'];
86-
function isTestTask(name: string): boolean {
87-
for (let testName of testNames) {
88-
if (name === testName) {
89-
return true;
90-
}
91-
}
92-
return false;
93-
}
94-
95-
function isNotPreOrPostScript(script: string): boolean {
96-
return !(script.startsWith('pre') || script.startsWith('post'));
97-
}
98-
99-
async function provideNpmScripts(): Promise<vscode.Task[]> {
100-
let emptyTasks: vscode.Task[] = [];
101-
let allTasks: vscode.Task[] = [];
102-
103-
let folders = vscode.workspace.workspaceFolders;
104-
if (!folders) {
105-
return emptyTasks;
106-
}
107-
try {
108-
for (let i = 0; i < folders.length; i++) {
109-
let folder = folders[i];
110-
if (isEnabled(folder)) {
111-
let relativePattern = new vscode.RelativePattern(folder, '**/package.json');
112-
let paths = await vscode.workspace.findFiles(relativePattern, '**/node_modules/**');
113-
for (let j = 0; j < paths.length; j++) {
114-
if (!isExcluded(folder, paths[j])) {
115-
let tasks = await provideNpmScriptsForFolder(paths[j]);
116-
allTasks.push(...tasks);
117-
}
118-
}
26+
function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined {
27+
if (vscode.workspace.workspaceFolders) {
28+
let provider: vscode.TaskProvider = {
29+
provideTasks: () => {
30+
return provideNpmScripts(localize);
31+
},
32+
resolveTask(_task: vscode.Task): vscode.Task | undefined {
33+
return undefined;
11934
}
120-
}
121-
return allTasks;
122-
} catch (error) {
123-
return Promise.reject(error);
35+
};
36+
let disposable = vscode.workspace.registerTaskProvider('npm', provider);
37+
registerExplorer(context, provider);
38+
return disposable;
12439
}
40+
return undefined;
12541
}
12642

127-
function isEnabled(folder: vscode.WorkspaceFolder): boolean {
128-
return vscode.workspace.getConfiguration('npm', folder.uri).get<AutoDetect>('autoDetect') === 'on';
129-
}
130-
131-
function isExcluded(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri) {
132-
function testForExclusionPattern(path: string, pattern: string): boolean {
133-
return minimatch(path, pattern, { dot: true });
134-
}
135-
136-
let exclude = vscode.workspace.getConfiguration('npm', folder.uri).get<string | string[]>('exclude');
137-
138-
if (exclude) {
139-
if (Array.isArray(exclude)) {
140-
for (let pattern of exclude) {
141-
if (testForExclusionPattern(packageJsonUri.fsPath, pattern)) {
142-
return true;
143-
}
144-
}
145-
} else if (testForExclusionPattern(packageJsonUri.fsPath, exclude)) {
146-
return true;
43+
async function registerExplorer(context: vscode.ExtensionContext, provider: vscode.TaskProvider) {
44+
if (explorerIsEnabled()) {
45+
let treeDataProvider = vscode.window.registerTreeDataProvider('npm', new NpmScriptsTreeDataProvider(context, provider, localize));
46+
context.subscriptions.push(treeDataProvider);
47+
if (await hasNpmScripts()) {
48+
vscode.commands.executeCommand('setContext', 'hasNpmScripts', true);
14749
}
14850
}
149-
return false;
15051
}
15152

152-
async function provideNpmScriptsForFolder(packageJsonUri: vscode.Uri): Promise<vscode.Task[]> {
153-
let emptyTasks: vscode.Task[] = [];
154-
155-
if (packageJsonUri.scheme !== 'file') {
156-
return emptyTasks;
157-
}
158-
159-
let packageJson = packageJsonUri.fsPath;
160-
161-
if (!await exists(packageJson)) {
162-
return emptyTasks;
163-
}
164-
165-
let folder = vscode.workspace.getWorkspaceFolder(packageJsonUri);
166-
if (!folder) {
167-
return emptyTasks;
168-
}
169-
170-
try {
171-
var contents = await readFile(packageJson);
172-
var json = JSON.parse(contents);
173-
if (!json.scripts) {
174-
return emptyTasks;
175-
}
176-
177-
const result: vscode.Task[] = [];
178-
Object.keys(json.scripts).filter(isNotPreOrPostScript).forEach(each => {
179-
const task = createTask(each, `run ${each}`, folder!, packageJsonUri);
180-
const lowerCaseTaskName = each.toLowerCase();
181-
if (isBuildTask(lowerCaseTaskName)) {
182-
task.group = vscode.TaskGroup.Build;
183-
} else if (isTestTask(lowerCaseTaskName)) {
184-
task.group = vscode.TaskGroup.Test;
185-
}
186-
result.push(task);
187-
});
188-
// always add npm install (without a problem matcher)
189-
// result.push(createTask('install', 'install', rootPath, folder, []));
190-
return result;
191-
} catch (e) {
192-
let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJsonUri);
193-
throw new Error(localizedParseError);
194-
}
53+
function configureHttpRequest() {
54+
const httpSettings = vscode.workspace.getConfiguration('http');
55+
httpRequest.configure(httpSettings.get<string>('proxy', ''), httpSettings.get<boolean>('proxyStrictSSL', true));
19556
}
19657

197-
function createTask(script: string, cmd: string, folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri, matcher?: any): vscode.Task {
198-
199-
function getTaskName(script: string, file: string) {
200-
if (file.length) {
201-
return `${script} - ${file.substring(0, file.length - 1)}`;
202-
}
203-
return script;
204-
}
205-
206-
function getCommandLine(folder: vscode.WorkspaceFolder, cmd: string): string {
207-
let packageManager = vscode.workspace.getConfiguration('npm', folder.uri).get<string>('packageManager', 'npm');
208-
if (vscode.workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
209-
return `${packageManager} --silent ${cmd}`;
210-
}
211-
return `${packageManager} ${cmd}`;
212-
}
213-
214-
function getRelativePath(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri): string {
215-
let rootUri = folder.uri;
216-
let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
217-
return absolutePath.substring(rootUri.path.length + 1);
218-
}
219-
220-
let kind: NpmTaskDefinition = {
221-
type: 'npm',
222-
script: script
223-
};
224-
let relativePackageJson = getRelativePath(folder, packageJsonUri);
225-
if (relativePackageJson.length) {
226-
kind.path = getRelativePath(folder, packageJsonUri);
58+
export function deactivate(): void {
59+
if (taskProvider) {
60+
taskProvider.dispose();
22761
}
228-
let taskName = getTaskName(script, relativePackageJson);
229-
let cwd = path.dirname(packageJsonUri.fsPath);
230-
return new vscode.Task(kind, folder, taskName, 'npm', new vscode.ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
23162
}

0 commit comments

Comments
 (0)