diff --git a/Tasks/ShellScript/Strings/resources.resjson/de-de/resources.resjson b/Tasks/Bash/Strings/resources.resjson/de-de/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/de-de/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/de-de/resources.resjson diff --git a/Tasks/Bash/Strings/resources.resjson/en-US/resources.resjson b/Tasks/Bash/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000000..f38d7cb7eaee --- /dev/null +++ b/Tasks/Bash/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,14 @@ +{ + "loc.friendlyName": "Bash", + "loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613738)", + "loc.description": "Run a Bash script on Mac, Linux, or Windows", + "loc.instanceNameFormat": "Bash Script", + "loc.releaseNotes": "Script task consistency. Added support for multiple lines and added support for Windows.", + "loc.group.displayName.advanced": "Advanced", + "loc.input.label.script": "Script", + "loc.input.label.workingDirectory": "Working Directory", + "loc.input.label.failOnStderr": "Fail on Standard Error", + "loc.input.help.failOnStderr": "If this is true, this task will fail if any errors are written to the StandardError stream.", + "loc.messages.JS_ExitCode": "Bash exited with code '%s'.", + "loc.messages.JS_Stderr": "Bash wrote one or more lines to the standard error stream." +} \ No newline at end of file diff --git a/Tasks/ShellScript/Strings/resources.resjson/es-es/resources.resjson b/Tasks/Bash/Strings/resources.resjson/es-es/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/es-es/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/es-es/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/fr-fr/resources.resjson b/Tasks/Bash/Strings/resources.resjson/fr-fr/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/fr-fr/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/fr-fr/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/it-IT/resources.resjson b/Tasks/Bash/Strings/resources.resjson/it-IT/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/it-IT/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/it-IT/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/ja-jp/resources.resjson b/Tasks/Bash/Strings/resources.resjson/ja-jp/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/ja-jp/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/ja-jp/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/ko-KR/resources.resjson b/Tasks/Bash/Strings/resources.resjson/ko-KR/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/ko-KR/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/ko-KR/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/ru-RU/resources.resjson b/Tasks/Bash/Strings/resources.resjson/ru-RU/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/ru-RU/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/ru-RU/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/zh-CN/resources.resjson b/Tasks/Bash/Strings/resources.resjson/zh-CN/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/zh-CN/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/zh-CN/resources.resjson diff --git a/Tasks/ShellScript/Strings/resources.resjson/zh-TW/resources.resjson b/Tasks/Bash/Strings/resources.resjson/zh-TW/resources.resjson similarity index 100% rename from Tasks/ShellScript/Strings/resources.resjson/zh-TW/resources.resjson rename to Tasks/Bash/Strings/resources.resjson/zh-TW/resources.resjson diff --git a/Tasks/Bash/bash.ts b/Tasks/Bash/bash.ts new file mode 100644 index 000000000000..f2c41115ffc2 --- /dev/null +++ b/Tasks/Bash/bash.ts @@ -0,0 +1,67 @@ +import fs = require('fs'); +import path = require('path'); +import os = require('os'); +import tl = require('vsts-task-lib/task'); +import tr = require('vsts-task-lib/toolrunner'); +var uuidV4 = require('uuid/v4'); + +async function run() { + try { + tl.setResourcePath(path.join(__dirname, 'task.json')); + + // Get inputs. + let failOnStderr = tl.getBoolInput('failOnStderr', false); + let script: string = tl.getInput('script', false) || ''; + let workingDirectory = tl.getPathInput('workingDirectory', /*required*/ true, /*check*/ true); + + // Write the script to disk. + tl.assertAgent('2.115.0'); + let tempDirectory = tl.getVariable('agent.tempDirectory'); + tl.checkPath(tempDirectory, `${tempDirectory} (agent.tempDirectory)`); + let filePath = path.join(tempDirectory, uuidV4() + '.sh'); + await fs.writeFileSync( + filePath, + '\ufeff' + script, // Prepend the Unicode BOM character. + { encoding: 'utf8' }); // Since UTF8 encoding is specified, node will + // // encode the BOM into its UTF8 binary sequence. + + // Create the tool runner. + let bash = tl.tool(tl.which('bash', true)) + .arg('--noprofile') + .arg(`--norc`) + .arg(filePath); + let options = { + cwd: workingDirectory, + failOnStdErr: false, + errStream: process.stdout, // Direct all output to STDOUT, otherwise the output may appear out + outStream: process.stdout, // of order since Node buffers it's own STDOUT but not STDERR. + ignoreReturnCode: true + }; + + // Listen for stderr. + let stderrFailure = false; + if (failOnStderr) { + bash.on('stderr', (data) => { + stderrFailure = true; + }); + } + + // Run bash. + let exitCode: number = await bash.exec(options); + + // Fail on exit code. + if (exitCode !== 0) { + tl.setResult(tl.TaskResult.Failed, tl.loc('JS_ExitCode', exitCode)); + } + + // Fail on stderr. + if (stderrFailure) { + tl.setResult(tl.TaskResult.Failed, tl.loc('JS_Stderr')); + } + } + catch (err) { + tl.setResult(tl.TaskResult.Failed, err.message || 'run() failed'); + } +} + +run(); diff --git a/Tasks/ShellScript/icon.png b/Tasks/Bash/icon.png similarity index 100% rename from Tasks/ShellScript/icon.png rename to Tasks/Bash/icon.png diff --git a/Tasks/ShellScript/icon.svg b/Tasks/Bash/icon.svg similarity index 100% rename from Tasks/ShellScript/icon.svg rename to Tasks/Bash/icon.svg diff --git a/Tasks/ShellScript/package.json b/Tasks/Bash/package.json similarity index 75% rename from Tasks/ShellScript/package.json rename to Tasks/Bash/package.json index 7190a87ce59d..4cea925cd930 100644 --- a/Tasks/ShellScript/package.json +++ b/Tasks/Bash/package.json @@ -1,8 +1,8 @@ { - "name": "vsts-tasks-shellscript", + "name": "vsts-tasks-bash", "version": "1.0.0", - "description": "VSTS ShellScript Task", - "main": "shellscript.js", + "description": "VSTS Bash Task", + "main": "bash.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -17,6 +17,7 @@ }, "homepage": "https://github.com/Microsoft/vsts-tasks#readme", "dependencies": { - "vsts-task-lib": "^0.9.20" + "uuid": "^3.0.1", + "vsts-task-lib": "2.0.5" } } diff --git a/Tasks/Bash/task.json b/Tasks/Bash/task.json new file mode 100644 index 000000000000..12ec91f765fe --- /dev/null +++ b/Tasks/Bash/task.json @@ -0,0 +1,75 @@ +{ + "id": "6C731C3C-3C68-459A-A5C9-BDE6E6595B5B", + "name": "Bash", + "friendlyName": "Bash", + "description": "Run a Bash script on Mac, Linux, or Windows", + "helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613738)", + "category": "Utility", + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "author": "Microsoft Corporation", + "version": { + "Major": 3, + "Minor": 120, + "Patch": 0 + }, + "releaseNotes": "Script task consistency. Added support for multiple lines and added support for Windows.", + "preview": true, + "minimumAgentVersion": "2.115.0", + "instanceNameFormat": "Bash Script", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "script", + "type": "multiLine", + "label": "Script", + "required": true, + "defaultValue": "# Write your commands here\n# Use the Environment input below to map secret variables into environment variables", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + }, + "helpMarkDown": "" + }, + { + "name": "workingDirectory", + "type": "filePath", + "label": "Working Directory", + "defaultValue": "", + "required": false, + "groupName": "advanced" + }, + { + "name": "failOnStderr", + "type": "boolean", + "label": "Fail on Standard Error", + "defaultValue": "false", + "required": false, + "helpMarkDown": "If this is true, this task will fail if any errors are written to the StandardError stream.", + "groupName": "advanced" + } + ], + "execution": { + "Node": { + "target": "bash.js", + "argumentFormat": "" + } + }, + "messages": { + "JS_ExitCode": "Bash exited with code '%s'.", + "JS_Stderr": "Bash wrote one or more lines to the standard error stream." + } +} \ No newline at end of file diff --git a/Tasks/Bash/task.loc.json b/Tasks/Bash/task.loc.json new file mode 100644 index 000000000000..2a60ed2e4c78 --- /dev/null +++ b/Tasks/Bash/task.loc.json @@ -0,0 +1,75 @@ +{ + "id": "6C731C3C-3C68-459A-A5C9-BDE6E6595B5B", + "name": "Bash", + "friendlyName": "ms-resource:loc.friendlyName", + "description": "ms-resource:loc.description", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "category": "Utility", + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "author": "Microsoft Corporation", + "version": { + "Major": 3, + "Minor": 120, + "Patch": 0 + }, + "releaseNotes": "ms-resource:loc.releaseNotes", + "preview": true, + "minimumAgentVersion": "2.115.0", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "groups": [ + { + "name": "advanced", + "displayName": "ms-resource:loc.group.displayName.advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "script", + "type": "multiLine", + "label": "ms-resource:loc.input.label.script", + "required": true, + "defaultValue": "# Write your commands here\n# Use the Environment input below to map secret variables into environment variables", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + }, + "helpMarkDown": "" + }, + { + "name": "workingDirectory", + "type": "filePath", + "label": "ms-resource:loc.input.label.workingDirectory", + "defaultValue": "", + "required": false, + "groupName": "advanced" + }, + { + "name": "failOnStderr", + "type": "boolean", + "label": "ms-resource:loc.input.label.failOnStderr", + "defaultValue": "false", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.failOnStderr", + "groupName": "advanced" + } + ], + "execution": { + "Node": { + "target": "bash.js", + "argumentFormat": "" + } + }, + "messages": { + "JS_ExitCode": "ms-resource:loc.messages.JS_ExitCode", + "JS_Stderr": "ms-resource:loc.messages.JS_Stderr" + } +} \ No newline at end of file diff --git a/Tasks/ShellScript/tsconfig.json b/Tasks/Bash/tsconfig.json similarity index 100% rename from Tasks/ShellScript/tsconfig.json rename to Tasks/Bash/tsconfig.json diff --git a/Tasks/ShellScript/typings.json b/Tasks/Bash/typings.json similarity index 100% rename from Tasks/ShellScript/typings.json rename to Tasks/Bash/typings.json diff --git a/Tasks/ShellScript/typings/globals/mocha/index.d.ts b/Tasks/Bash/typings/globals/mocha/index.d.ts similarity index 100% rename from Tasks/ShellScript/typings/globals/mocha/index.d.ts rename to Tasks/Bash/typings/globals/mocha/index.d.ts diff --git a/Tasks/ShellScript/typings/globals/mocha/typings.json b/Tasks/Bash/typings/globals/mocha/typings.json similarity index 100% rename from Tasks/ShellScript/typings/globals/mocha/typings.json rename to Tasks/Bash/typings/globals/mocha/typings.json diff --git a/Tasks/ShellScript/typings/globals/node/index.d.ts b/Tasks/Bash/typings/globals/node/index.d.ts similarity index 100% rename from Tasks/ShellScript/typings/globals/node/index.d.ts rename to Tasks/Bash/typings/globals/node/index.d.ts diff --git a/Tasks/ShellScript/typings/globals/node/typings.json b/Tasks/Bash/typings/globals/node/typings.json similarity index 100% rename from Tasks/ShellScript/typings/globals/node/typings.json rename to Tasks/Bash/typings/globals/node/typings.json diff --git a/Tasks/ShellScript/typings/globals/q/index.d.ts b/Tasks/Bash/typings/globals/q/index.d.ts similarity index 100% rename from Tasks/ShellScript/typings/globals/q/index.d.ts rename to Tasks/Bash/typings/globals/q/index.d.ts diff --git a/Tasks/ShellScript/typings/globals/q/typings.json b/Tasks/Bash/typings/globals/q/typings.json similarity index 100% rename from Tasks/ShellScript/typings/globals/q/typings.json rename to Tasks/Bash/typings/globals/q/typings.json diff --git a/Tasks/ShellScript/typings/index.d.ts b/Tasks/Bash/typings/index.d.ts similarity index 100% rename from Tasks/ShellScript/typings/index.d.ts rename to Tasks/Bash/typings/index.d.ts diff --git a/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson b/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson index 20ee43847671..67d787789ec1 100644 --- a/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson @@ -9,8 +9,8 @@ "loc.input.label.workingDirectory": "Working Directory", "loc.input.label.failOnStderr": "Fail on Standard Error", "loc.input.help.failOnStderr": "If this is true, this task will fail if any errors are written to the StandardError stream.", - "loc.messages.JS_Stderr": "bash wrote one or more lines to the standard error stream.", - "loc.messages.JS_ExitCode": "bash exited with code '%s'.", - "loc.messages.PS_ExitCode": "cmd.exe exited with code '{0}'.", + "loc.messages.JS_ExitCode": "Bash exited with code '%s'.", + "loc.messages.JS_Stderr": "Bash wrote one or more lines to the standard error stream.", + "loc.messages.PS_ExitCode": "Cmd.exe exited with code '{0}'.", "loc.messages.PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from cmd.exe." } \ No newline at end of file diff --git a/Tasks/CmdLine/cmdlinetask.ts b/Tasks/CmdLine/cmdlinetask.ts deleted file mode 100644 index 7c2560be8897..000000000000 --- a/Tasks/CmdLine/cmdlinetask.ts +++ /dev/null @@ -1,27 +0,0 @@ -import path = require('path'); -import tl = require('vsts-task-lib/task'); -import trm = require('vsts-task-lib/toolrunner'); - -async function run() { - try { - tl.setResourcePath(path.join( __dirname, 'task.json')); - - var tool: string = tl.which(tl.getInput('filename', true), true); - var tr: trm.ToolRunner = tl.tool(tool); - - var cwd = tl.getPathInput('workingFolder', true, false); - tl.mkdirP(cwd); - tl.cd(cwd); - tr.line(tl.getInput('arguments', false)); - - var failOnStdErr: boolean = tl.getBoolInput('failOnStandardError', false); - var code: number = await tr.exec({failOnStdErr: failOnStdErr}); - tl.setResult(tl.TaskResult.Succeeded, tl.loc('CmdLineReturnCode', tool, code)); - } - catch(err) { - tl.error(err.message); - tl.setResult(tl.TaskResult.Failed, tl.loc('CmdLineFailed', tool, err.message)); - } -} - -run(); \ No newline at end of file diff --git a/Tasks/CmdLine/task.json b/Tasks/CmdLine/task.json index 2d99d552112d..a5b79e9965a3 100644 --- a/Tasks/CmdLine/task.json +++ b/Tasks/CmdLine/task.json @@ -74,9 +74,9 @@ } }, "messages": { - "JS_Stderr": "bash wrote one or more lines to the standard error stream.", - "JS_ExitCode": "bash exited with code '%s'.", - "PS_ExitCode": "cmd.exe exited with code '{0}'.", + "JS_ExitCode": "Bash exited with code '%s'.", + "JS_Stderr": "Bash wrote one or more lines to the standard error stream.", + "PS_ExitCode": "Cmd.exe exited with code '{0}'.", "PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from cmd.exe." } } \ No newline at end of file diff --git a/Tasks/CmdLine/task.loc.json b/Tasks/CmdLine/task.loc.json index 56102fd228c1..ef21b9577b5b 100644 --- a/Tasks/CmdLine/task.loc.json +++ b/Tasks/CmdLine/task.loc.json @@ -74,8 +74,8 @@ } }, "messages": { - "JS_Stderr": "ms-resource:loc.messages.JS_Stderr", "JS_ExitCode": "ms-resource:loc.messages.JS_ExitCode", + "JS_Stderr": "ms-resource:loc.messages.JS_Stderr", "PS_ExitCode": "ms-resource:loc.messages.PS_ExitCode", "PS_UnableToDetermineExitCode": "ms-resource:loc.messages.PS_UnableToDetermineExitCode" } diff --git a/Tasks/ShellScript/Strings/resources.resjson/en-US/resources.resjson b/Tasks/ShellScript/Strings/resources.resjson/en-US/resources.resjson deleted file mode 100644 index 31da27bd48a8..000000000000 --- a/Tasks/ShellScript/Strings/resources.resjson/en-US/resources.resjson +++ /dev/null @@ -1,19 +0,0 @@ -{ - "loc.friendlyName": "Shell Script", - "loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613738)", - "loc.description": "Run a shell script using bash", - "loc.instanceNameFormat": "Shell Script $(scriptPath)", - "loc.group.displayName.advanced": "Advanced", - "loc.input.label.scriptPath": "Script Path", - "loc.input.help.scriptPath": "Relative path from repo root of the shell script file to run.", - "loc.input.label.args": "Arguments", - "loc.input.help.args": "Arguments passed to the shell script", - "loc.input.label.disableAutoCwd": "Specify Working Directory", - "loc.input.help.disableAutoCwd": "The default behavior is to set the working directory to the script location. This enables you to optionally specify a different working directory.", - "loc.input.label.cwd": "Working Directory", - "loc.input.help.cwd": "Current working directory where the script is run. Empty is the root of the repo (build) or artifacts (release), which is $(System.DefaultWorkingDirectory).", - "loc.input.label.failOnStandardError": "Fail on Standard Error", - "loc.input.help.failOnStandardError": "If this is true, this task will fail if any errors are written to the StandardError stream.", - "loc.messages.BashReturnCode": "Bash exited with return code: %d", - "loc.messages.BashFailed": "Bash failed with error: %s" -} \ No newline at end of file diff --git a/Tasks/ShellScript/Tests/L0.ts b/Tasks/ShellScript/Tests/L0.ts deleted file mode 100644 index 797af6a0d3a9..000000000000 --- a/Tasks/ShellScript/Tests/L0.ts +++ /dev/null @@ -1,94 +0,0 @@ - -// npm install mocha --save-dev -// typings install dt~mocha --save --global - -import * as path from 'path'; -import * as assert from 'assert'; -import * as ttm from 'vsts-task-lib/mock-test'; - -describe('ShellScript L0 Suite', function () { - before(() => { - - }); - - after(() => { - - }); - - it('runs shellscript in cwd', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'L0runsInCwd.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - assert(tr.ran('/usr/local/bin/bash /script.sh arg1 arg2'), 'it should have run ShellScript'); - assert(tr.invokedToolCount == 1, 'should have only run ShellScript'); - assert(tr.stdout.indexOf('bash output here') >= 0, "bash stdout"); - assert(tr.stderr.length == 0, 'should not have written to stderr'); - assert(tr.succeeded, 'task should have succeeded'); - - done(); - }); - - it('fails if script returns 1', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'L0failIfReturns1.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - assert(tr.ran('/usr/local/bin/bash /script.sh arg1 arg2'), 'it should have run ShellScript'); - assert(tr.invokedToolCount == 1, 'should have only run ShellScript'); - - var expectedErr = '/usr/local/bin/bash failed with return code: 1'; - - assert(tr.stdOutContained(expectedErr), 'should have said: ' + expectedErr); - // failOnStdErr not set - assert(!tr.stderr, 'should not have written to stderr'); - assert(tr.failed, 'task should have failed'); - done(); - }) - - it('fails if failOnStdErr and script writes to stderr', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'L0failIfStdErr.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - assert(tr.ran('/usr/local/bin/bash /script.sh arg1 arg2'), 'it should have run ShellScript'); - assert(tr.invokedToolCount == 1, 'should have only run ShellScript'); - // failOnStdErr true - assert(tr.stderr.length > 0, 'should have written to stderr'); - assert(tr.failed, 'task should have failed'); - done(); - }) - - it('fails if cwd not set', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'L0failNoCwd.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - - assert(tr.invokedToolCount == 0, 'should not have run ShellScript'); - assert(tr.failed, 'task should have failed'); - done(); - }) - - it('fails if script not found', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'L0failIfScriptNotFound.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - - assert(tr.invokedToolCount == 0, 'should not have run ShellScript'); - assert(tr.failed, 'task should have failed'); - assert(tr.stdOutContained('Not found /notexistscript.sh')); - done(); - }) -}); diff --git a/Tasks/ShellScript/Tests/L0failIfReturns1.ts b/Tasks/ShellScript/Tests/L0failIfReturns1.ts deleted file mode 100644 index a4f76d852914..000000000000 --- a/Tasks/ShellScript/Tests/L0failIfReturns1.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import ma = require('vsts-task-lib/mock-answer'); -import tmrm = require('vsts-task-lib/mock-run'); -import path = require('path'); - -let taskPath = path.join(__dirname, '..', 'shellscript.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('scriptPath', '/script.sh'); -tmr.setInput('args', 'arg1 arg2'); -tmr.setInput('cwd', 'fake/wd'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "bash": "/usr/local/bin/bash", - "node": "/usr/local/bin/node" - }, - "exec": { - "/usr/local/bin/bash /script.sh arg1 arg2": { - "code": 1, - "stdout": "bash output here", - "stderr": "bash error output here" - } - }, - "checkPath" : { - "/usr/local/bin/bash": true, - "/usr/local/bin/node": true, - "/script.sh" : true - } -}; -tmr.setAnswers(a); - -// if you need to, you can mock a specific module function called in task -// tmr.registerMock('./taskmod', { -// sayHello: function() { -// console.log('Hello Mock!'); -// } -// }); - -tmr.run(); - diff --git a/Tasks/ShellScript/Tests/L0failIfScriptNotFound.ts b/Tasks/ShellScript/Tests/L0failIfScriptNotFound.ts deleted file mode 100644 index b2b1f7bbf2e8..000000000000 --- a/Tasks/ShellScript/Tests/L0failIfScriptNotFound.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import ma = require('vsts-task-lib/mock-answer'); -import tmrm = require('vsts-task-lib/mock-run'); -import path = require('path'); - -let taskPath = path.join(__dirname, '..', 'shellscript.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('scriptPath', '/notexistscript.sh'); -tmr.setInput('args', 'arg1 arg2'); -tmr.setInput('cwd', 'fake/wd'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "bash": "/usr/local/bin/bash", - "node": "/usr/local/bin/node" - }, - "exec": { - "/usr/local/bin/bash /script.sh arg1 arg2": { - "code": 0, - "stdout": "bash output here", - "stderr": "" - } - }, - "checkPath" : { - "/usr/local/bin/bash": true, - "/usr/local/bin/node": true, - "/script.sh" : true - } -}; -tmr.setAnswers(a); - -// if you need to, you can mock a specific module function called in task -// tmr.registerMock('./taskmod', { -// sayHello: function() { -// console.log('Hello Mock!'); -// } -// }); - -tmr.run(); - diff --git a/Tasks/ShellScript/Tests/L0failIfStdErr.ts b/Tasks/ShellScript/Tests/L0failIfStdErr.ts deleted file mode 100644 index 58d1d1f8d356..000000000000 --- a/Tasks/ShellScript/Tests/L0failIfStdErr.ts +++ /dev/null @@ -1,43 +0,0 @@ - -import ma = require('vsts-task-lib/mock-answer'); -import tmrm = require('vsts-task-lib/mock-run'); -import path = require('path'); - -let taskPath = path.join(__dirname, '..', 'shellscript.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('scriptPath', '/script.sh'); -tmr.setInput('args', 'arg1 arg2'); -tmr.setInput('cwd', 'fake/wd'); -tmr.setInput('failOnStandardError', 'true'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "bash": "/usr/local/bin/bash", - "node": "/usr/local/bin/node" - }, - "exec": { - "/usr/local/bin/bash /script.sh arg1 arg2": { - "code": 0, - "stdout": "bash output here", - "stderr": "bash error output here" - } - }, - "checkPath" : { - "/usr/local/bin/bash": true, - "/usr/local/bin/node": true, - "/script.sh" : true - } -}; -tmr.setAnswers(a); - -// if you need to, you can mock a specific module function called in task -// tmr.registerMock('./taskmod', { -// sayHello: function() { -// console.log('Hello Mock!'); -// } -// }); - -tmr.run(); - diff --git a/Tasks/ShellScript/Tests/L0failNoCwd.ts b/Tasks/ShellScript/Tests/L0failNoCwd.ts deleted file mode 100644 index 6079d039489a..000000000000 --- a/Tasks/ShellScript/Tests/L0failNoCwd.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import ma = require('vsts-task-lib/mock-answer'); -import tmrm = require('vsts-task-lib/mock-run'); -import path = require('path'); - -let taskPath = path.join(__dirname, '..', 'shellscript.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('scriptPath', '/script.sh'); -tmr.setInput('args', 'arg1 arg2'); -//tmr.setInput('cwd', 'fake/wd'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "bash": "/usr/local/bin/bash", - "node": "/usr/local/bin/node" - }, - "exec": { - "/usr/local/bin/bash /script.sh arg1 arg2": { - "code": 0, - "stdout": "bash output here", - "stderr": "" - } - }, - "checkPath" : { - "/usr/local/bin/bash": true, - "/usr/local/bin/node": true, - "/script.sh" : true - } -}; -tmr.setAnswers(a); - -// if you need to, you can mock a specific module function called in task -// tmr.registerMock('./taskmod', { -// sayHello: function() { -// console.log('Hello Mock!'); -// } -// }); - -tmr.run(); - diff --git a/Tasks/ShellScript/Tests/L0runsInCwd.ts b/Tasks/ShellScript/Tests/L0runsInCwd.ts deleted file mode 100644 index 9c4ff9afa60b..000000000000 --- a/Tasks/ShellScript/Tests/L0runsInCwd.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import ma = require('vsts-task-lib/mock-answer'); -import tmrm = require('vsts-task-lib/mock-run'); -import path = require('path'); - -let taskPath = path.join(__dirname, '..', 'shellscript.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('scriptPath', '/script.sh'); -tmr.setInput('args', 'arg1 arg2'); -tmr.setInput('cwd', 'fake/wd'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "bash": "/usr/local/bin/bash", - "node": "/usr/local/bin/node" - }, - "exec": { - "/usr/local/bin/bash /script.sh arg1 arg2": { - "code": 0, - "stdout": "bash output here", - "stderr": "" - } - }, - "checkPath" : { - "/usr/local/bin/bash": true, - "/usr/local/bin/node": true, - "/script.sh" : true - } -}; -tmr.setAnswers(a); - -// if you need to, you can mock a specific module function called in task -// tmr.registerMock('./taskmod', { -// sayHello: function() { -// console.log('Hello Mock!'); -// } -// }); - -tmr.run(); - diff --git a/Tasks/ShellScript/shellscript.ts b/Tasks/ShellScript/shellscript.ts deleted file mode 100644 index ac4d41ee6e30..000000000000 --- a/Tasks/ShellScript/shellscript.ts +++ /dev/null @@ -1,42 +0,0 @@ -/// - -import path = require('path'); -import tl = require('vsts-task-lib/task'); -import trm = require('vsts-task-lib/toolrunner'); - -async function run() { - try { - tl.setResourcePath(path.join( __dirname, 'task.json')); - - var bash: trm.ToolRunner = tl.tool(tl.which('bash', true)); - - var scriptPath: string = tl.getPathInput('scriptPath', true, true); - var cwd: string = tl.getPathInput('cwd', true, false); - - // if user didn't supply a cwd (advanced), then set cwd to folder script is in. - // All "script" tasks should do this - if (!tl.filePathSupplied('cwd') && !tl.getBoolInput('disableAutoCwd', false)) { - cwd = path.dirname(scriptPath); - } - tl.mkdirP(cwd); - tl.cd(cwd); - - bash.arg(scriptPath); - - // additional args should always call argString. argString() parses quoted arg strings - bash.line(tl.getInput('args', false)); - - // determines whether output to stderr will fail a task. - // some tools write progress and other warnings to stderr. scripts can also redirect. - var failOnStdErr: boolean = tl.getBoolInput('failOnStandardError', false); - - var code: number = await bash.exec({failOnStdErr: failOnStdErr}); - tl.setResult(tl.TaskResult.Succeeded, tl.loc('BashReturnCode', code)); - } - catch(err) { - tl.error(err.message); - tl.setResult(tl.TaskResult.Failed, tl.loc('BashFailed', err.message)); - } -} - -run(); diff --git a/Tasks/ShellScript/task.json b/Tasks/ShellScript/task.json deleted file mode 100644 index 8fe1ae192b9c..000000000000 --- a/Tasks/ShellScript/task.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "id": "6C731C3C-3C68-459A-A5C9-BDE6E6595B5B", - "name": "ShellScript", - "friendlyName": "Shell Script", - "description": "Run a shell script using bash", - "helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613738)", - "category": "Utility", - "visibility": [ - "Build", - "Release" - ], - "runsOn": [ - "Agent", - "DeploymentGroup" - ], - "author": "Microsoft Corporation", - "version": { - "Major": 2, - "Minor": 1, - "Patch": 3 - }, - "demands": [ - "sh" - ], - "instanceNameFormat": "Shell Script $(scriptPath)", - "groups": [ - { - "name": "advanced", - "displayName": "Advanced", - "isExpanded": false - } - ], - "inputs": [ - { - "name": "scriptPath", - "type": "filePath", - "label": "Script Path", - "defaultValue": "", - "required": true, - "helpMarkDown": "Relative path from repo root of the shell script file to run." - }, - { - "name": "args", - "type": "string", - "label": "Arguments", - "defaultValue": "", - "required": false, - "helpMarkDown": "Arguments passed to the shell script" - }, - { - "name": "disableAutoCwd", - "type": "boolean", - "label": "Specify Working Directory", - "defaultValue": "false", - "required": false, - "helpMarkDown": "The default behavior is to set the working directory to the script location. This enables you to optionally specify a different working directory.", - "groupName": "advanced" - }, - { - "name": "cwd", - "type": "filePath", - "label": "Working Directory", - "defaultValue": "", - "required": false, - "visibleRule": "disableAutoCwd = true", - "helpMarkDown": "Current working directory where the script is run. Empty is the root of the repo (build) or artifacts (release), which is $(System.DefaultWorkingDirectory).", - "groupName": "advanced" - }, - { - "name": "failOnStandardError", - "type": "boolean", - "label": "Fail on Standard Error", - "defaultValue": "false", - "required": false, - "helpMarkDown": "If this is true, this task will fail if any errors are written to the StandardError stream.", - "groupName": "advanced" - } - ], - "execution": { - "Node": { - "target": "shellscript.js", - "argumentFormat": "" - } - }, - "messages": { - "BashReturnCode": "Bash exited with return code: %d", - "BashFailed": "Bash failed with error: %s" - } -} \ No newline at end of file diff --git a/Tasks/ShellScript/task.loc.json b/Tasks/ShellScript/task.loc.json deleted file mode 100644 index d631fc0906cf..000000000000 --- a/Tasks/ShellScript/task.loc.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "id": "6C731C3C-3C68-459A-A5C9-BDE6E6595B5B", - "name": "ShellScript", - "friendlyName": "ms-resource:loc.friendlyName", - "description": "ms-resource:loc.description", - "helpMarkDown": "ms-resource:loc.helpMarkDown", - "category": "Utility", - "visibility": [ - "Build", - "Release" - ], - "runsOn": [ - "Agent", - "DeploymentGroup" - ], - "author": "Microsoft Corporation", - "version": { - "Major": 2, - "Minor": 1, - "Patch": 3 - }, - "demands": [ - "sh" - ], - "instanceNameFormat": "ms-resource:loc.instanceNameFormat", - "groups": [ - { - "name": "advanced", - "displayName": "ms-resource:loc.group.displayName.advanced", - "isExpanded": false - } - ], - "inputs": [ - { - "name": "scriptPath", - "type": "filePath", - "label": "ms-resource:loc.input.label.scriptPath", - "defaultValue": "", - "required": true, - "helpMarkDown": "ms-resource:loc.input.help.scriptPath" - }, - { - "name": "args", - "type": "string", - "label": "ms-resource:loc.input.label.args", - "defaultValue": "", - "required": false, - "helpMarkDown": "ms-resource:loc.input.help.args" - }, - { - "name": "disableAutoCwd", - "type": "boolean", - "label": "ms-resource:loc.input.label.disableAutoCwd", - "defaultValue": "false", - "required": false, - "helpMarkDown": "ms-resource:loc.input.help.disableAutoCwd", - "groupName": "advanced" - }, - { - "name": "cwd", - "type": "filePath", - "label": "ms-resource:loc.input.label.cwd", - "defaultValue": "", - "required": false, - "visibleRule": "disableAutoCwd = true", - "helpMarkDown": "ms-resource:loc.input.help.cwd", - "groupName": "advanced" - }, - { - "name": "failOnStandardError", - "type": "boolean", - "label": "ms-resource:loc.input.label.failOnStandardError", - "defaultValue": "false", - "required": false, - "helpMarkDown": "ms-resource:loc.input.help.failOnStandardError", - "groupName": "advanced" - } - ], - "execution": { - "Node": { - "target": "shellscript.js", - "argumentFormat": "" - } - }, - "messages": { - "BashReturnCode": "ms-resource:loc.messages.BashReturnCode", - "BashFailed": "ms-resource:loc.messages.BashFailed" - } -} \ No newline at end of file diff --git a/Tasks/powerShell/Strings/resources.resjson/en-US/resources.resjson b/Tasks/powerShell/Strings/resources.resjson/en-US/resources.resjson index adb9798bfb86..88bf8e4f3e8d 100644 --- a/Tasks/powerShell/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/powerShell/Strings/resources.resjson/en-US/resources.resjson @@ -14,8 +14,10 @@ "loc.input.help.ignoreLASTEXITCODE": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.", "loc.input.label.workingDirectory": "Working Directory", "loc.input.help.workingDirectory": "Working directory where the script is run.", + "loc.messages.JS_ExitCode": "PowerShell exited with code '%s'.", "loc.messages.JS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '%s'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", - "loc.messages.PS_ExitCode": "powershell exited with code '{0}'.", + "loc.messages.JS_Stderr": "PowerShell wrote one or more lines to the standard error stream.", + "loc.messages.PS_ExitCode": "PowerShell exited with code '{0}'.", "loc.messages.PS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '{0}'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", "loc.messages.PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from powershell." } \ No newline at end of file diff --git a/Tasks/powerShell/powershell.ts b/Tasks/powerShell/powershell.ts index 3f684ac6403d..d699ba081224 100644 --- a/Tasks/powerShell/powershell.ts +++ b/Tasks/powerShell/powershell.ts @@ -19,9 +19,7 @@ async function run() { default: throw new Error(tl.loc('JS_InvalidErrorActionPreference', errorActionPreference)); } - let executionPolicy: string = tl.getInput('executionPolicy', false) || 'Unrestricted'; let failOnStderr = tl.getBoolInput('failOnStderr', false); - let ignoreExitCode = tl.getBoolInput('ignoreExitCode', false); let ignoreLASTEXITCODE = tl.getBoolInput('ignoreLASTEXITCODE', false); let script: string = tl.getInput('script', false) || ''; let workingDirectory = tl.getPathInput('workingDirectory', /*required*/ true, /*check*/ true); @@ -53,19 +51,38 @@ async function run() { // Run the script. let powershell = tl.tool(tl.which('powershell', true)) .arg('-NoLogo') - .arg(`-Sta`) .arg('-NoProfile') .arg('-NonInteractive') - .arg('-ExecutionPolicy') - .arg(executionPolicy) .arg('-File') .arg(filePath); let options = { cwd: workingDirectory, - failOnStdErr: failOnStderr, - ignoreReturnCode: ignoreExitCode + failOnStdErr: false, + errStream: process.stdout, // Direct all output to STDOUT, otherwise the output may appear out + outStream: process.stdout, // of order since Node buffers it's own STDOUT but not STDERR. + ignoreReturnCode: true }; - await powershell.exec(options); + + // Listen for stderr. + let stderrFailure = false; + if (failOnStderr) { + powershell.on('stderr', (data) => { + stderrFailure = true; + }); + } + + // Run bash. + let exitCode: number = await powershell.exec(options); + + // Fail on exit code. + if (exitCode !== 0) { + tl.setResult(tl.TaskResult.Failed, tl.loc('JS_ExitCode', exitCode)); + } + + // Fail on stderr. + if (stderrFailure) { + tl.setResult(tl.TaskResult.Failed, tl.loc('JS_Stderr')); + } } catch (err) { tl.setResult(tl.TaskResult.Failed, err.message || 'run() failed'); diff --git a/Tasks/powerShell/task.json b/Tasks/powerShell/task.json index 531c24586a49..8533f4dacd22 100644 --- a/Tasks/powerShell/task.json +++ b/Tasks/powerShell/task.json @@ -98,8 +98,10 @@ } }, "messages": { + "JS_ExitCode": "PowerShell exited with code '%s'.", "JS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '%s'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", - "PS_ExitCode": "powershell exited with code '{0}'.", + "JS_Stderr": "PowerShell wrote one or more lines to the standard error stream.", + "PS_ExitCode": "PowerShell exited with code '{0}'.", "PS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '{0}'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", "PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from powershell." } diff --git a/Tasks/powerShell/task.loc.json b/Tasks/powerShell/task.loc.json index 0bc52c8b0ad9..7686977bb836 100644 --- a/Tasks/powerShell/task.loc.json +++ b/Tasks/powerShell/task.loc.json @@ -98,7 +98,9 @@ } }, "messages": { + "JS_ExitCode": "ms-resource:loc.messages.JS_ExitCode", "JS_InvalidErrorActionPreference": "ms-resource:loc.messages.JS_InvalidErrorActionPreference", + "JS_Stderr": "ms-resource:loc.messages.JS_Stderr", "PS_ExitCode": "ms-resource:loc.messages.PS_ExitCode", "PS_InvalidErrorActionPreference": "ms-resource:loc.messages.PS_InvalidErrorActionPreference", "PS_UnableToDetermineExitCode": "ms-resource:loc.messages.PS_UnableToDetermineExitCode" diff --git a/make-options.json b/make-options.json index 20dab2cf8bda..3d7df569a731 100644 --- a/make-options.json +++ b/make-options.json @@ -67,7 +67,6 @@ "ServiceFabricDeploy", "ServiceFabricPowerShell", "ServiceFabricUpdateAppVersions", - "ShellScript", "SqlAzureDacpacDeployment", "SqlDacpacDeploymentOnMachineGroup", "SSH",