Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/gulp/src/main.ts
5221 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as path from 'path';
7
import * as fs from 'fs';
8
import * as cp from 'child_process';
9
import * as vscode from 'vscode';
10
11
12
type AutoDetect = 'on' | 'off';
13
14
/**
15
* Check if the given filename is a file.
16
*
17
* If returns false in case the file does not exist or
18
* the file stats cannot be accessed/queried or it
19
* is no file at all.
20
*
21
* @param filename
22
* the filename to the checked
23
* @returns
24
* true in case the file exists, in any other case false.
25
*/
26
async function exists(filename: string): Promise<boolean> {
27
try {
28
29
if ((await fs.promises.stat(filename)).isFile()) {
30
return true;
31
}
32
} catch (ex) {
33
// In case requesting the file statistics fail.
34
// we assume it does not exist.
35
return false;
36
}
37
38
return false;
39
}
40
41
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
42
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
43
cp.exec(command, options, (error, stdout, stderr) => {
44
if (error) {
45
reject({ error, stdout, stderr });
46
}
47
resolve({ stdout, stderr });
48
});
49
});
50
}
51
52
const buildNames: string[] = ['build', 'compile', 'watch'];
53
function isBuildTask(name: string): boolean {
54
for (const buildName of buildNames) {
55
if (name.indexOf(buildName) !== -1) {
56
return true;
57
}
58
}
59
return false;
60
}
61
62
const testNames: string[] = ['test'];
63
function isTestTask(name: string): boolean {
64
for (const testName of testNames) {
65
if (name.indexOf(testName) !== -1) {
66
return true;
67
}
68
}
69
return false;
70
}
71
72
let _channel: vscode.OutputChannel;
73
function getOutputChannel(): vscode.OutputChannel {
74
if (!_channel) {
75
_channel = vscode.window.createOutputChannel('Gulp Auto Detection');
76
}
77
return _channel;
78
}
79
80
function showError() {
81
vscode.window.showWarningMessage(vscode.l10n.t("Problem finding gulp tasks. See the output for more information."),
82
vscode.l10n.t("Go to output")).then((choice) => {
83
if (choice !== undefined) {
84
_channel.show(true);
85
}
86
});
87
}
88
89
async function findGulpCommand(rootPath: string): Promise<string> {
90
const platform = process.platform;
91
92
if (platform === 'win32' && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp.cmd'))) {
93
const globalGulp = path.join(process.env.APPDATA ? process.env.APPDATA : '', 'npm', 'gulp.cmd');
94
if (await exists(globalGulp)) {
95
return `"${globalGulp}"`;
96
}
97
98
return path.join('.', 'node_modules', '.bin', 'gulp.cmd');
99
100
}
101
102
if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp'))) {
103
return path.join('.', 'node_modules', '.bin', 'gulp');
104
}
105
106
return 'gulp';
107
}
108
109
interface GulpTaskDefinition extends vscode.TaskDefinition {
110
task: string;
111
file?: string;
112
}
113
114
class FolderDetector {
115
116
private fileWatcher: vscode.FileSystemWatcher | undefined;
117
private promise: Thenable<vscode.Task[]> | undefined;
118
119
constructor(
120
private _workspaceFolder: vscode.WorkspaceFolder,
121
private _gulpCommand: Promise<string>) {
122
}
123
124
public get workspaceFolder(): vscode.WorkspaceFolder {
125
return this._workspaceFolder;
126
}
127
128
public isEnabled(): boolean {
129
return vscode.workspace.getConfiguration('gulp', this._workspaceFolder.uri).get<AutoDetect>('autoDetect') === 'on';
130
}
131
132
public start(): void {
133
const pattern = path.join(this._workspaceFolder.uri.fsPath, '{node_modules,gulpfile{.babel.js,.esm.js,.js,.mjs,.cjs,.ts}}');
134
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
135
this.fileWatcher.onDidChange(() => this.promise = undefined);
136
this.fileWatcher.onDidCreate(() => this.promise = undefined);
137
this.fileWatcher.onDidDelete(() => this.promise = undefined);
138
}
139
140
public async getTasks(): Promise<vscode.Task[]> {
141
if (!this.isEnabled()) {
142
return [];
143
}
144
145
if (!this.promise) {
146
this.promise = this.computeTasks();
147
}
148
149
return this.promise;
150
}
151
152
public async getTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
153
const gulpTask = _task.definition.task;
154
if (gulpTask) {
155
const kind = _task.definition as GulpTaskDefinition;
156
const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
157
const task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options));
158
return task;
159
}
160
return undefined;
161
}
162
163
/**
164
* Searches for a gulp entry point inside the given folder.
165
*
166
* Typically the entry point is a file named "gulpfile.js"
167
*
168
* It can also be a transposed gulp entry points, like gulp.babel.js or gulp.esm.js
169
*
170
* Additionally recent node version prefer the .mjs or .cjs extension over the .js.
171
*
172
* @param root
173
* the folder which should be checked.
174
*/
175
private async hasGulpfile(root: string): Promise<boolean | undefined> {
176
177
for (const filename of await fs.promises.readdir(root)) {
178
179
const ext = path.extname(filename);
180
if (ext !== '.js' && ext !== '.mjs' && ext !== '.cjs' && ext !== '.ts') {
181
continue;
182
}
183
184
if (!exists(filename)) {
185
continue;
186
}
187
188
const basename = path.basename(filename, ext).toLowerCase();
189
if (basename === 'gulpfile') {
190
return true;
191
}
192
if (basename === 'gulpfile.esm') {
193
return true;
194
}
195
if (basename === 'gulpfile.babel') {
196
return true;
197
}
198
}
199
200
return false;
201
}
202
203
private async computeTasks(): Promise<vscode.Task[]> {
204
const rootPath = this._workspaceFolder.uri.scheme === 'file' ? this._workspaceFolder.uri.fsPath : undefined;
205
const emptyTasks: vscode.Task[] = [];
206
if (!rootPath) {
207
return emptyTasks;
208
}
209
210
if (!await this.hasGulpfile(rootPath)) {
211
return emptyTasks;
212
}
213
214
const commandLine = `${await this._gulpCommand} --tasks-simple --no-color`;
215
try {
216
const { stdout, stderr } = await exec(commandLine, { cwd: rootPath });
217
if (stderr && stderr.length > 0) {
218
// Filter out "No license field"
219
const errors = stderr.split('\n');
220
errors.pop(); // The last line is empty.
221
if (!errors.every(value => value.indexOf('No license field') >= 0)) {
222
getOutputChannel().appendLine(stderr);
223
showError();
224
}
225
}
226
const result: vscode.Task[] = [];
227
if (stdout) {
228
const lines = stdout.split(/\r{0,1}\n/);
229
for (const line of lines) {
230
if (line.length === 0) {
231
continue;
232
}
233
const kind: GulpTaskDefinition = {
234
type: 'gulp',
235
task: line
236
};
237
const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
238
const task = new vscode.Task(kind, this.workspaceFolder, line, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [line], options));
239
result.push(task);
240
const lowerCaseLine = line.toLowerCase();
241
if (isBuildTask(lowerCaseLine)) {
242
task.group = vscode.TaskGroup.Build;
243
} else if (isTestTask(lowerCaseLine)) {
244
task.group = vscode.TaskGroup.Test;
245
}
246
}
247
}
248
return result;
249
} catch (err) {
250
const channel = getOutputChannel();
251
if (err.stderr) {
252
channel.appendLine(err.stderr);
253
}
254
if (err.stdout) {
255
channel.appendLine(err.stdout);
256
}
257
channel.appendLine(vscode.l10n.t("Auto detecting gulp for folder {0} failed with error: {1}', this.workspaceFolder.name, err.error ? err.error.toString() : 'unknown"));
258
showError();
259
return emptyTasks;
260
}
261
}
262
263
public dispose() {
264
this.promise = undefined;
265
if (this.fileWatcher) {
266
this.fileWatcher.dispose();
267
}
268
}
269
}
270
271
class TaskDetector {
272
273
private taskProvider: vscode.Disposable | undefined;
274
private detectors: Map<string, FolderDetector> = new Map();
275
276
constructor() {
277
}
278
279
public start(): void {
280
const folders = vscode.workspace.workspaceFolders;
281
if (folders) {
282
this.updateWorkspaceFolders(folders, []);
283
}
284
vscode.workspace.onDidChangeWorkspaceFolders((event) => this.updateWorkspaceFolders(event.added, event.removed));
285
vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this);
286
}
287
288
public dispose(): void {
289
if (this.taskProvider) {
290
this.taskProvider.dispose();
291
this.taskProvider = undefined;
292
}
293
this.detectors.clear();
294
}
295
296
private updateWorkspaceFolders(added: readonly vscode.WorkspaceFolder[], removed: readonly vscode.WorkspaceFolder[]): void {
297
for (const remove of removed) {
298
const detector = this.detectors.get(remove.uri.toString());
299
if (detector) {
300
detector.dispose();
301
this.detectors.delete(remove.uri.toString());
302
}
303
}
304
for (const add of added) {
305
const detector = new FolderDetector(add, findGulpCommand(add.uri.fsPath));
306
this.detectors.set(add.uri.toString(), detector);
307
if (detector.isEnabled()) {
308
detector.start();
309
}
310
}
311
this.updateProvider();
312
}
313
314
private updateConfiguration(): void {
315
for (const detector of this.detectors.values()) {
316
detector.dispose();
317
this.detectors.delete(detector.workspaceFolder.uri.toString());
318
}
319
const folders = vscode.workspace.workspaceFolders;
320
if (folders) {
321
for (const folder of folders) {
322
if (!this.detectors.has(folder.uri.toString())) {
323
const detector = new FolderDetector(folder, findGulpCommand(folder.uri.fsPath));
324
this.detectors.set(folder.uri.toString(), detector);
325
if (detector.isEnabled()) {
326
detector.start();
327
}
328
}
329
}
330
}
331
this.updateProvider();
332
}
333
334
private updateProvider(): void {
335
if (!this.taskProvider && this.detectors.size > 0) {
336
const thisCapture = this;
337
this.taskProvider = vscode.tasks.registerTaskProvider('gulp', {
338
provideTasks(): Promise<vscode.Task[]> {
339
return thisCapture.getTasks();
340
},
341
resolveTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
342
return thisCapture.getTask(_task);
343
}
344
});
345
}
346
else if (this.taskProvider && this.detectors.size === 0) {
347
this.taskProvider.dispose();
348
this.taskProvider = undefined;
349
}
350
}
351
352
public getTasks(): Promise<vscode.Task[]> {
353
return this.computeTasks();
354
}
355
356
private computeTasks(): Promise<vscode.Task[]> {
357
if (this.detectors.size === 0) {
358
return Promise.resolve([]);
359
} else if (this.detectors.size === 1) {
360
return this.detectors.values().next().value!.getTasks();
361
} else {
362
const promises: Promise<vscode.Task[]>[] = [];
363
for (const detector of this.detectors.values()) {
364
promises.push(detector.getTasks().then((value) => value, () => []));
365
}
366
return Promise.all(promises).then((values) => {
367
const result: vscode.Task[] = [];
368
for (const tasks of values) {
369
if (tasks && tasks.length > 0) {
370
result.push(...tasks);
371
}
372
}
373
return result;
374
});
375
}
376
}
377
378
public async getTask(task: vscode.Task): Promise<vscode.Task | undefined> {
379
if (this.detectors.size === 0) {
380
return undefined;
381
} else if (this.detectors.size === 1) {
382
return this.detectors.values().next().value!.getTask(task);
383
} else {
384
if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) {
385
// Not supported, we don't have enough info to create the task.
386
return undefined;
387
} else if (task.scope) {
388
const detector = this.detectors.get(task.scope.uri.toString());
389
if (detector) {
390
return detector.getTask(task);
391
}
392
}
393
return undefined;
394
}
395
}
396
}
397
398
let detector: TaskDetector;
399
export function activate(_context: vscode.ExtensionContext): void {
400
detector = new TaskDetector();
401
detector.start();
402
}
403
404
export function deactivate(): void {
405
detector.dispose();
406
}
407
408