main.ts 6.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*---------------------------------------------------------------------------------------------
 *  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 * as fs from 'fs';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';

const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();

type AutoDetect = 'on' | 'off';
let taskProvider: vscode.Disposable | undefined;

export function activate(_context: vscode.ExtensionContext): void {
	let workspaceRoot = vscode.workspace.rootPath;
	if (!workspaceRoot) {
		return;
	}
23
	let pattern = path.join(workspaceRoot, '[Gg]runtfile.js');
24 25 26 27 28 29 30 31 32 33 34 35 36
	let detectorPromise: Thenable<vscode.Task[]> | undefined = undefined;
	let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
	fileWatcher.onDidChange(() => detectorPromise = undefined);
	fileWatcher.onDidCreate(() => detectorPromise = undefined);
	fileWatcher.onDidDelete(() => detectorPromise = undefined);

	function onConfigurationChanged() {
		let autoDetect = vscode.workspace.getConfiguration('grunt').get<AutoDetect>('autoDetect');
		if (taskProvider && autoDetect === 'off') {
			detectorPromise = undefined;
			taskProvider.dispose();
			taskProvider = undefined;
		} else if (!taskProvider && autoDetect === 'on') {
D
Dirk Baeumer 已提交
37
			taskProvider = vscode.workspace.registerTaskProvider('grunt', {
38 39 40 41 42
				provideTasks: () => {
					if (!detectorPromise) {
						detectorPromise = getGruntTasks();
					}
					return detectorPromise;
D
Dirk Baeumer 已提交
43 44 45
				},
				resolveTask(_task: vscode.Task): vscode.Task | undefined {
					return undefined;
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
				}
			});
		}
	}
	vscode.workspace.onDidChangeConfiguration(onConfigurationChanged);
	onConfigurationChanged();
}

export function deactivate(): void {
	if (taskProvider) {
		taskProvider.dispose();
	}
}

function exists(file: string): Promise<boolean> {
	return new Promise<boolean>((resolve, _reject) => {
		fs.exists(file, (value) => {
			resolve(value);
		});
	});
}

function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
	return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
		cp.exec(command, options, (error, stdout, stderr) => {
			if (error) {
				reject({ error, stdout, stderr });
			}
			resolve({ stdout, stderr });
		});
	});
}

79 80 81 82 83 84 85 86
let _channel: vscode.OutputChannel;
function getOutputChannel(): vscode.OutputChannel {
	if (!_channel) {
		_channel = vscode.window.createOutputChannel('Grunt Auto Detection');
	}
	return _channel;
}

D
Dirk Baeumer 已提交
87
interface GruntTaskDefinition extends vscode.TaskDefinition {
88 89 90 91
	task: string;
	file?: string;
}

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
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.indexOf(testName) !== -1) {
			return true;
		}
	}
	return false;
}

112 113 114 115 116 117
async function getGruntTasks(): Promise<vscode.Task[]> {
	let workspaceRoot = vscode.workspace.rootPath;
	let emptyTasks: vscode.Task[] = [];
	if (!workspaceRoot) {
		return emptyTasks;
	}
118
	if (!await exists(path.join(workspaceRoot, 'gruntfile.js')) && !await exists(path.join(workspaceRoot, 'Gruntfile.js'))) {
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
		return emptyTasks;
	}

	let command: string;
	let platform = process.platform;
	if (platform === 'win32' && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'grunt.cmd'))) {
		command = path.join('.', 'node_modules', '.bin', 'grunt.cmd');
	} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'grunt'))) {
		command = path.join('.', 'node_modules', '.bin', 'grunt');
	} else {
		command = 'grunt';
	}

	let commandLine = `${command} --help --no-color`;
	try {
		let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
		if (stderr) {
136 137
			getOutputChannel().appendLine(stderr);
			getOutputChannel().show(true);
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
		}
		let result: vscode.Task[] = [];
		if (stdout) {
			// grunt lists tasks as follows (description is wrapped into a new line if too long):
			// ...
			// Available tasks
			//         uglify  Minify files with UglifyJS. *
			//         jshint  Validate files with JSHint. *
			//           test  Alias for "jshint", "qunit" tasks.
			//        default  Alias for "jshint", "qunit", "concat", "uglify" tasks.
			//           long  Alias for "eslint", "qunit", "browserify", "sass",
			//                 "autoprefixer", "uglify", tasks.
			//
			// Tasks run in the order specified

			let lines = stdout.split(/\r{0,1}\n/);
			let tasksStart = false;
			let tasksEnd = false;
			for (let line of lines) {
				if (line.length === 0) {
					continue;
				}
				if (!tasksStart && !tasksEnd) {
					if (line.indexOf('Available tasks') === 0) {
						tasksStart = true;
					}
				} else if (tasksStart && !tasksEnd) {
					if (line.indexOf('Tasks run in the order specified') === 0) {
						tasksEnd = true;
					} else {
						let regExp = /^\s*(\S.*\S)  \S/g;
						let matches = regExp.exec(line);
						if (matches && matches.length === 2) {
171
							let name = matches[1];
D
Dirk Baeumer 已提交
172
							let kind: GruntTaskDefinition = {
173
								type: 'grunt',
174
								task: name
175
							};
176 177 178 179
							let source = 'grunt';
							let task = name.indexOf(' ') === -1
								? new vscode.Task(kind, name, source, new vscode.ShellExecution(`${command} ${name}`))
								: new vscode.Task(kind, name, source, new vscode.ShellExecution(`${command} "${name}"`));
180
							result.push(task);
181
							let lowerCaseTaskName = name.toLowerCase();
182 183 184 185
							if (isBuildTask(lowerCaseTaskName)) {
								task.group = vscode.TaskGroup.Build;
							} else if (isTestTask(lowerCaseTaskName)) {
								task.group = vscode.TaskGroup.Test;
186 187 188 189 190 191 192 193
							}
						}
					}
				}
			}
		}
		return result;
	} catch (err) {
194
		let channel = getOutputChannel();
195 196
		if (err.stderr) {
			channel.appendLine(err.stderr);
197 198 199
		}
		if (err.stdout) {
			channel.appendLine(err.stdout);
200 201
		}
		channel.appendLine(localize('execFailed', 'Auto detecting Grunt failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
202
		channel.show(true);
203 204 205
		return emptyTasks;
	}
}