Skip to content

Commit 436ccc0

Browse files
refactor: replace Q with native promises (#905)
* refactor: replace `Q` with native promises * Changed how promises were deferred to fix failing tests * Bumped version to 5.0.0 * Switched to using promise constructors instead of custom Deferred class * Continue support of Q until the release of v5. Marked the methods that make use of Q as deprecated for now * Added JSDoc deprecation annotation * Updated version and changelog * Updated changelog message * Apply review suggestion Co-authored-by: Jamie Magee <[email protected]> * Tests for execAsync --------- Co-authored-by: Aleksandr Levochkin <[email protected]> Co-authored-by: Aleksandr Levochkin <[email protected]> Co-authored-by: Aleksandr Levockin (Akvelon INC) <[email protected]>
1 parent 4099e05 commit 436ccc0

11 files changed

+2159
-69
lines changed

node/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ Backported from ver.`3.4.0`:
4848
## 4.4.0
4949

5050
- Add `getBoolFeatureFlag` [#936](https://github.com/microsoft/azure-pipelines-task-lib/pull/936)
51+
52+
## 4.5.0
53+
54+
- Added `execAsync` methods that return native promises. Marked `exec` methods that return promises from the Q library as deprecated [#905](https://github.com/microsoft/azure-pipelines-task-lib/pull/905)

node/mock-task.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import Q = require('q');
32
import path = require('path');
43
import fs = require('fs');
@@ -335,6 +334,18 @@ export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Pro
335334
return tr.exec(options);
336335
}
337336

337+
//-----------------------------------------------------
338+
// Exec convenience wrapper
339+
//-----------------------------------------------------
340+
export function execAsync(tool: string, args: any, options?: trm.IExecOptions): Promise<number> {
341+
var toolPath = which(tool, true);
342+
var tr: trm.ToolRunner = this.tool(toolPath);
343+
if (args) {
344+
tr.arg(args);
345+
}
346+
return tr.execAsync(options);
347+
}
348+
338349
export function execSync(tool: string, args: any, options?: trm.IExecSyncOptions): trm.IExecSyncResult {
339350
var toolPath = which(tool, true);
340351
var tr: trm.ToolRunner = this.tool(toolPath);

node/mock-toolrunner.ts

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import Q = require('q');
32
import os = require('os');
43
import events = require('events');
@@ -162,6 +161,113 @@ export class ToolRunner extends events.EventEmitter {
162161
// Exec - use for long running tools where you need to stream live output as it runs
163162
// returns a promise with return code.
164163
//
164+
public execAsync(options?: IExecOptions): Promise<number> {
165+
this._debug('exec tool: ' + this.toolPath);
166+
this._debug('Arguments:');
167+
this.args.forEach((arg) => {
168+
this._debug(' ' + arg);
169+
});
170+
171+
var success = true;
172+
options = options || <IExecOptions>{};
173+
174+
var ops: IExecOptions = {
175+
cwd: options.cwd || process.cwd(),
176+
env: options.env || process.env,
177+
silent: options.silent || false,
178+
outStream: options.outStream || process.stdout,
179+
errStream: options.errStream || process.stderr,
180+
failOnStdErr: options.failOnStdErr || false,
181+
ignoreReturnCode: options.ignoreReturnCode || false,
182+
windowsVerbatimArguments: options.windowsVerbatimArguments
183+
};
184+
185+
var argString = this.args.join(' ') || '';
186+
var cmdString = this.toolPath;
187+
if (argString) {
188+
cmdString += (' ' + argString);
189+
}
190+
191+
// Using split/join to replace the temp path
192+
cmdString = this.ignoreTempPath(cmdString);
193+
194+
if (!ops.silent) {
195+
if(this.pipeOutputToTool) {
196+
var pipeToolArgString = this.pipeOutputToTool.args.join(' ') || '';
197+
var pipeToolCmdString = this.ignoreTempPath(this.pipeOutputToTool.toolPath);
198+
if(pipeToolArgString) {
199+
pipeToolCmdString += (' ' + pipeToolArgString);
200+
}
201+
202+
cmdString += ' | ' + pipeToolCmdString;
203+
}
204+
205+
ops.outStream.write('[command]' + cmdString + os.EOL);
206+
}
207+
208+
// TODO: filter process.env
209+
var res = mock.getResponse('exec', cmdString, debug);
210+
if (res.stdout) {
211+
this.emit('stdout', res.stdout);
212+
if (!ops.silent) {
213+
ops.outStream.write(res.stdout + os.EOL);
214+
}
215+
const stdLineArray = res.stdout.split(os.EOL);
216+
for (const line of stdLineArray.slice(0, -1)) {
217+
this.emit('stdline', line);
218+
}
219+
if(stdLineArray.length > 0 && stdLineArray[stdLineArray.length - 1].length > 0) {
220+
this.emit('stdline', stdLineArray[stdLineArray.length - 1]);
221+
}
222+
}
223+
224+
if (res.stderr) {
225+
this.emit('stderr', res.stderr);
226+
227+
success = !ops.failOnStdErr;
228+
if (!ops.silent) {
229+
var s = ops.failOnStdErr ? ops.errStream : ops.outStream;
230+
s.write(res.stderr + os.EOL);
231+
}
232+
const stdErrArray = res.stderr.split(os.EOL);
233+
for (const line of stdErrArray.slice(0, -1)) {
234+
this.emit('errline', line);
235+
}
236+
if (stdErrArray.length > 0 && stdErrArray[stdErrArray.length - 1].length > 0) {
237+
this.emit('errline', stdErrArray[stdErrArray.length - 1]);
238+
}
239+
}
240+
241+
242+
var code = res.code;
243+
244+
if (!ops.silent) {
245+
ops.outStream.write('rc:' + res.code + os.EOL);
246+
}
247+
248+
if (code != 0 && !ops.ignoreReturnCode) {
249+
success = false;
250+
}
251+
252+
if (!ops.silent) {
253+
ops.outStream.write('success:' + success + os.EOL);
254+
}
255+
256+
return new Promise((resolve, reject) => {
257+
if (success) {
258+
resolve(code);
259+
}
260+
else {
261+
reject(new Error(this.toolPath + ' failed with return code: ' + code));
262+
}
263+
});
264+
}
265+
266+
/**
267+
* Exec - use for long running tools where you need to stream live output as it runs
268+
* @deprecated use `execAsync` instead
269+
* @returns a promise with return code.
270+
*/
165271
public exec(options?: IExecOptions): Q.Promise<number> {
166272
var defer = Q.defer<number>();
167273

@@ -270,8 +376,6 @@ export class ToolRunner extends events.EventEmitter {
270376
// but also has limits. For example, no live output and limited to max buffer
271377
//
272378
public execSync(options?: IExecSyncOptions): IExecSyncResult {
273-
var defer = Q.defer();
274-
275379
this._debug('exec tool: ' + this.toolPath);
276380
this._debug('Arguments:');
277381
this.args.forEach((arg) => {

node/package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "azure-pipelines-task-lib",
3-
"version": "4.4.0",
3+
"version": "4.5.0",
44
"description": "Azure Pipelines Task SDK",
55
"main": "./task.js",
66
"typings": "./task.d.ts",
@@ -39,7 +39,7 @@
3939
"@types/minimatch": "3.0.3",
4040
"@types/mocha": "^9.1.1",
4141
"@types/mockery": "^1.4.29",
42-
"@types/node": "^16.11.39",
42+
"@types/node": "^10.17.0",
4343
"@types/q": "^1.5.4",
4444
"@types/semver": "^7.3.4",
4545
"@types/shelljs": "^0.8.8",

node/task.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,34 @@ export function rmRF(inputPath: string): void {
14471447
* @param options optional exec options. See IExecOptions
14481448
* @returns number
14491449
*/
1450+
export function execAsync(tool: string, args: any, options?: trm.IExecOptions): Promise<number> {
1451+
let tr: trm.ToolRunner = this.tool(tool);
1452+
tr.on('debug', (data: string) => {
1453+
debug(data);
1454+
});
1455+
1456+
if (args) {
1457+
if (args instanceof Array) {
1458+
tr.arg(args);
1459+
}
1460+
else if (typeof (args) === 'string') {
1461+
tr.line(args)
1462+
}
1463+
}
1464+
return tr.execAsync(options);
1465+
}
1466+
1467+
/**
1468+
* Exec a tool. Convenience wrapper over ToolRunner to exec with args in one call.
1469+
* Output will be streamed to the live console.
1470+
* Returns promise with return code
1471+
*
1472+
* @deprecated Use the {@link execAsync} method that returns a native Javascript Promise instead
1473+
* @param tool path to tool to exec
1474+
* @param args an arg string or array of args
1475+
* @param options optional exec options. See IExecOptions
1476+
* @returns number
1477+
*/
14501478
export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Promise<number> {
14511479
let tr: trm.ToolRunner = this.tool(tool);
14521480
tr.on('debug', (data: string) => {

0 commit comments

Comments
 (0)