Skip to content

Commit a6dde81

Browse files
committed
Fixing microsoft#31217 npm script not detected if the package.json is not in root folder
1 parent 86d5aba commit a6dde81

5 files changed

Lines changed: 100 additions & 16 deletions

File tree

extensions/npm/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ To run scripts as tasks you use the `Tasks` menu.
1111
- `npm.autoDetect` enable detecting scripts as tasks, the default is `on`.
1212
- `npm.runSilent` run npm script with the `--silent` option, the default is `false`.
1313
- `npm.packageManager` the package manager used to run the scripts: `npm` or `yarn`, the default is `npm`.
14+
- `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 user '**/test/**.

extensions/npm/package.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
},
1818
"dependencies": {
1919
"jsonc-parser": "^1.0.0",
20+
"minimatch": "^3.0.4",
2021
"request-light": "^0.2.2",
2122
"vscode-nls": "^3.2.1"
2223
},
2324
"devDependencies": {
25+
"@types/minimatch": "^3.0.3",
2426
"@types/node": "7.0.43"
2527
},
2628
"main": "./out/main",
@@ -59,6 +61,17 @@
5961
],
6062
"default": "npm",
6163
"description": "%config.npm.packageManager%"
64+
},
65+
"npm.exclude": {
66+
"type": [
67+
"string",
68+
"array"
69+
],
70+
"items": {
71+
"type": "string"
72+
},
73+
"description": "%config.npm.exclude%",
74+
"scope": "window"
6275
}
6376
}
6477
},
@@ -91,4 +104,4 @@
91104
}
92105
]
93106
}
94-
}
107+
}

extensions/npm/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"config.npm.autoDetect": "Controls whether auto detection of npm scripts is on or off. Default is on.",
55
"config.npm.runSilent": "Run npm commands with the `--silent` option.",
66
"config.npm.packageManager": "The package manager used to run scripts.",
7+
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
78
"npm.parseError": "Npm task detection: failed to parse the file {0}",
89
"taskdef.script": "The npm script to customize.",
910
"taskdef.path": "The path to the folder of the package.json file that provides the script. Can be ommitted."

extensions/npm/src/main.ts

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import * as fs from 'fs';
99
import * as httpRequest from 'request-light';
1010
import * as vscode from 'vscode';
1111
import * as nls from 'vscode-nls';
12+
import * as minimatch from 'minimatch';
13+
1214
const localize = nls.loadMessageBundle();
1315

1416
import { addJSONProviders } from './features/jsonContributions';
@@ -97,15 +99,17 @@ function isNotPreOrPostScript(script: string): boolean {
9799
async function provideNpmScripts(): Promise<vscode.Task[]> {
98100
let emptyTasks: vscode.Task[] = [];
99101
let allTasks: vscode.Task[] = [];
100-
let folders = vscode.workspace.workspaceFolders;
101102

102-
if (!folders) {
103+
let paths = await vscode.workspace.findFiles('**/package.json', '**/node_modules/**');
104+
if (paths.length === 0) {
103105
return emptyTasks;
104106
}
107+
105108
try {
106-
for (let i = 0; i < folders.length; i++) {
107-
if (isEnabled(folders[i])) {
108-
let tasks = await provideNpmScriptsForFolder(folders[i]);
109+
for (let i = 0; i < paths.length; i++) {
110+
let folder = vscode.workspace.getWorkspaceFolder(paths[i]);
111+
if (folder && isEnabled(folder) && !isExcluded(folder, paths[i])) {
112+
let tasks = await provideNpmScriptsForFolder(paths[i]);
109113
allTasks.push(...tasks);
110114
}
111115
}
@@ -119,19 +123,45 @@ function isEnabled(folder: vscode.WorkspaceFolder): boolean {
119123
return vscode.workspace.getConfiguration('npm', folder.uri).get<AutoDetect>('autoDetect') === 'on';
120124
}
121125

122-
async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder): Promise<vscode.Task[]> {
126+
function isExcluded(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri) {
127+
function testForExclusionPattern(path: string, pattern: string): boolean {
128+
return minimatch(path, pattern, { dot: true });
129+
}
130+
131+
let exclude = vscode.workspace.getConfiguration('npm', folder.uri).get<string | string[]>('exclude');
132+
133+
if (exclude) {
134+
if (Array.isArray(exclude)) {
135+
for (let pattern of exclude) {
136+
if (testForExclusionPattern(packageJsonUri.fsPath, pattern)) {
137+
return true;
138+
}
139+
}
140+
} else if (testForExclusionPattern(packageJsonUri.fsPath, exclude)) {
141+
return true;
142+
}
143+
}
144+
return false;
145+
}
146+
147+
async function provideNpmScriptsForFolder(packageJsonUri: vscode.Uri): Promise<vscode.Task[]> {
123148
let emptyTasks: vscode.Task[] = [];
124149

125-
if (folder.uri.scheme !== 'file') {
150+
if (packageJsonUri.scheme !== 'file') {
126151
return emptyTasks;
127152
}
128-
let rootPath = folder.uri.fsPath;
129153

130-
let packageJson = path.join(rootPath, 'package.json');
154+
let packageJson = packageJsonUri.fsPath;
155+
131156
if (!await exists(packageJson)) {
132157
return emptyTasks;
133158
}
134159

160+
let folder = vscode.workspace.getWorkspaceFolder(packageJsonUri);
161+
if (!folder) {
162+
return emptyTasks;
163+
}
164+
135165
try {
136166
var contents = await readFile(packageJson);
137167
var json = JSON.parse(contents);
@@ -141,7 +171,7 @@ async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder): Promi
141171

142172
const result: vscode.Task[] = [];
143173
Object.keys(json.scripts).filter(isNotPreOrPostScript).forEach(each => {
144-
const task = createTask(each, `run ${each}`, rootPath, folder);
174+
const task = createTask(each, `run ${each}`, folder!, packageJsonUri);
145175
const lowerCaseTaskName = each.toLowerCase();
146176
if (isBuildTask(lowerCaseTaskName)) {
147177
task.group = vscode.TaskGroup.Build;
@@ -154,14 +184,17 @@ async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder): Promi
154184
// result.push(createTask('install', 'install', rootPath, folder, []));
155185
return result;
156186
} catch (e) {
157-
let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJson);
187+
let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJsonUri);
158188
throw new Error(localizedParseError);
159189
}
160190
}
161191

162-
function createTask(script: string, cmd: string, rootPath: string, folder: vscode.WorkspaceFolder, matcher?: any): vscode.Task {
192+
function createTask(script: string, cmd: string, folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri, matcher?: any): vscode.Task {
163193

164-
function getTaskName(script: string) {
194+
function getTaskName(script: string, file: string) {
195+
if (file.length) {
196+
return `${script} - ${file.substring(0, file.length - 1)}`;
197+
}
165198
return script;
166199
}
167200

@@ -173,10 +206,21 @@ function createTask(script: string, cmd: string, rootPath: string, folder: vscod
173206
return `${packageManager} ${cmd}`;
174207
}
175208

209+
function getRelativePath(folder: vscode.WorkspaceFolder, packageJsonUri: vscode.Uri): string {
210+
let rootUri = folder.uri;
211+
let absolutePath = packageJsonUri.fsPath;
212+
return absolutePath.substring(rootUri.fsPath.length + 1, absolutePath.length - 'package.json'.length);
213+
}
214+
176215
let kind: NpmTaskDefinition = {
177216
type: 'npm',
178217
script: script
179218
};
180-
let taskName = getTaskName(script);
181-
return new vscode.Task(kind, folder, taskName, 'npm', new vscode.ShellExecution(getCommandLine(folder, cmd), { cwd: rootPath }), matcher);
219+
let relativePackageJson = getRelativePath(folder, packageJsonUri);
220+
if (relativePackageJson.length) {
221+
kind.file = getRelativePath(folder, packageJsonUri);
222+
}
223+
let taskName = getTaskName(script, relativePackageJson);
224+
let cwd = path.dirname(packageJsonUri.fsPath);
225+
return new vscode.Task(kind, folder, taskName, 'npm', new vscode.ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
182226
}

extensions/npm/yarn.lock

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
# yarn lockfile v1
33

44

5+
"@types/minimatch@^3.0.3":
6+
version "3.0.3"
7+
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
8+
59
"@types/node@7.0.43":
610
version "7.0.43"
711
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
@@ -12,6 +16,21 @@ agent-base@4, agent-base@^4.1.0:
1216
dependencies:
1317
es6-promisify "^5.0.0"
1418

19+
balanced-match@^1.0.0:
20+
version "1.0.0"
21+
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
22+
23+
brace-expansion@^1.1.7:
24+
version "1.1.11"
25+
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
26+
dependencies:
27+
balanced-match "^1.0.0"
28+
concat-map "0.0.1"
29+
30+
concat-map@0.0.1:
31+
version "0.0.1"
32+
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
33+
1534
debug@2:
1635
version "2.6.9"
1736
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -52,6 +71,12 @@ jsonc-parser@^1.0.0:
5271
version "1.0.1"
5372
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.1.tgz#7f8f296414e6e7c4a33b9e4914fc8c47e4421675"
5473

74+
minimatch@^3.0.4:
75+
version "3.0.4"
76+
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
77+
dependencies:
78+
brace-expansion "^1.1.7"
79+
5580
ms@2.0.0:
5681
version "2.0.0"
5782
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"

0 commit comments

Comments
 (0)