diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..90f5aacd6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global rule: +* @microsoft/akvelon-build-task-team @microsoft/azure-pipelines-platform diff --git a/.github/workflows/autoAssignABTT.yml b/.github/workflows/autoAssignABTT.yml new file mode 100644 index 000000000..ceff0146e --- /dev/null +++ b/.github/workflows/autoAssignABTT.yml @@ -0,0 +1,25 @@ +name: Auto Assign ABTT to Project Board + +on: + issues: + types: + - opened + +jobs: + assign_one_project: + runs-on: ubuntu-latest + name: Assign to ABTT Project + steps: + - name: "Add triage and area labels" + uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Area: TaskLib + triage + + - name: "Assign newly opened issues to project board" + uses: actions/add-to-project@v0.4.1 + with: + project-url: https://github.com/orgs/microsoft/projects/755 + github-token: ${{ secrets.ABTT_TOKEN }} diff --git a/.github/workflows/localization-automerge.yml b/.github/workflows/localization-automerge.yml new file mode 100644 index 000000000..eca1b651c --- /dev/null +++ b/.github/workflows/localization-automerge.yml @@ -0,0 +1,25 @@ +name: 'LEGO automerge' + +on: + pull_request: + types: + - opened + branches: + - Localization + +jobs: + worker: + runs-on: ubuntu-latest + + if: github.actor == 'csigs' + steps: + - uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.pulls.merge({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + pull_number: context.payload.pull_request.number, + merge_method: 'squash' + }) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..33a5792f3 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ + +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 * * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue has had no activity in 90 days. Please comment if it is not actually stale' + stale-issue-label: 'stale' + days-before-stale: 90 + days-before-close: 7 + exempt-pr-label: 'no-stale' diff --git a/.gitignore b/.gitignore index 08bdb1e03..2319b712f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,7 @@ node/.taskKey # powershell compiled helper powershell/CompiledHelpers/bin -powershell/CompiledHelpers/obj \ No newline at end of file +powershell/CompiledHelpers/obj + +# generate third party notice script +!generate-third-party-notice.js \ No newline at end of file diff --git a/.vsts-ci.yml b/.vsts-ci.yml deleted file mode 100644 index a397266ef..000000000 --- a/.vsts-ci.yml +++ /dev/null @@ -1,80 +0,0 @@ -steps: - - ################################################################################ - # vsts-task-lib - ################################################################################ - - # npm install - - task: CmdLine@1 - name: (vsts-task-lib) npm install - inputs: - filename: npm - arguments: install - workingFolder: node - # - task: Npm@1.* - # name: npm install - # inputs: - # command: install - # workingDir: node - - # use node 5 - - task: NodeTool@0 - name: (vsts-task-lib) use node 5.10.1 - condition: and(succeeded(), ne(variables.os, 'windows')) - inputs: - versionSpec: "5.10.1" - - task: CmdLine@1 - name: (vsts-task-lib) use node 5.10.1 - condition: and(succeeded(), eq(variables.os, 'windows')) - inputs: - filename: powershell.exe - arguments: -noninteractive -noprofile -file "$(build.sourcesDirectory)/res/UseNode5.ps1" - - # build/test - - task: CmdLine@1 - name: (vsts-task-lib) node make.js test - inputs: - filename: node - arguments: make.js test - workingFolder: node - - # use node 6 - - task: NodeTool@0 - name: (vsts-task-lib) use node 6.10.3 - inputs: - versionSpec: "6.10.3" - - # build/test - - task: CmdLine@1 - name: (vsts-task-lib) node make.js test - inputs: - filename: node - arguments: make.js test - workingFolder: node - - ################################################################################ - # VstsTaskSdk - ################################################################################ - - # npm install - - task: CmdLine@1 - name: (VstsTaskSdk) npm install - condition: and(succeeded(), eq(variables.os, 'windows')) - inputs: - filename: npm - arguments: install - workingFolder: powershell - # - task: Npm@1.* - # name: npm install - # inputs: - # command: install - # workingDir: powershell - - # npm test - - task: CmdLine@1 - name: (VstsTaskSdk) npm test - condition: and(succeeded(), eq(variables.os, 'windows')) - inputs: - filename: npm - arguments: test - workingFolder: powershell diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..0f3481b6a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Instructions for Contributing Code + +## Contributing bug fixes + +We are currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. + +## Contributing features + +Features (things that add new or improved functionality) may be accepted, but will need to first be approved in the form of a suggestion issue. + +Design changes will not be accepted at this time. If you have a design change proposal, please log a suggestion issue. + +## Legal + +You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies that you are granting us permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under appropriate copyright. + +Please submit a Contributor License Agreement (CLA) before submitting a pull request. You may visit to sign digitally. + +## Housekeeping + +Your pull request should: + +* Include a description of what your change intends to do +* Be a child commit of a reasonably recent commit in the **master** branch + * Requests need not be a single commit, but should be a linear sequence of commits (i.e. no merge commits in your PR) +* It is desirable, but not necessary, for the tests to pass at each commit +* Have clear commit messages + * e.g. "Refactor feature", "Fix issue", "Add tests for issue" +* Include adequate tests + * At least one test should fail in the absence of your non-test code changes. If your PR does not match this criteria, please specify why + * Tests should include reasonable permutations of the target fix/change + * Include baseline changes with your change + * All changed code must have 100% code coverage diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..764b797f3 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +Please check our current Issues to see if someone already reported this https://github.com/Microsoft/azure-pipelines-task-lib/issues + +### Environment +azure-pipelines-task-lib version: + +### Issue Description + + +### Expected behaviour + + +### Actual behaviour + + +### Steps to reproduce +1. +2. +3. +4. +5. + +### Logs + \ No newline at end of file diff --git a/README.md b/README.md index 1b5028b6a..f916a057a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ -# VSTS DevOps Task SDK +# Azure Pipelines Task SDK -Libraries for writing [Visual Studio Team Services](https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx) build and deployment tasks +Libraries for writing [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) tasks -Reference examples of our in the box tasks [are here](https://github.com/Microsoft/vsts-tasks) +Reference examples of our in the box tasks [are here](https://github.com/microsoft/azure-pipelines-tasks) ## Status + | | Build & Test | |---|:-----:| -|![Win](res/win_med.png) **Windows**|![Build & Test](https://mseng.visualstudio.com/DefaultCollection/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/2553/badge?branch=master)| -|![Apple](res/apple_med.png) **OSX**|![Build & Test](https://mseng.visualstudio.com/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/5471/badge?branch=master)| -|![Ubuntu14](res/ubuntu_med.png) **Ubuntu 14.04**|![Build & Test](https://mseng.visualstudio.com/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/4123/badge?branch=master)| +|![Win-x64](res/win_med.png) **Windows**|[![Build & Test][win-build-badge]][build]| +|![macOS](res/apple_med.png) **macOS**|[![Build & Test][macOS-build-badge]][build]| +|![Linux-x64](res/ubuntu_med.png) **Linux**|[![Build & Test][linux-build-badge]][build]| + +[win-build-badge]: https://dev.azure.com/mseng/PipelineTools/_apis/build/status/azure-pipelines-task-lib-ci?branchName=master&jobname=windows +[macOS-build-badge]: https://dev.azure.com/mseng/PipelineTools/_apis/build/status/azure-pipelines-task-lib-ci?branchName=master&jobname=macOS +[linux-build-badge]: https://dev.azure.com/mseng/PipelineTools/_apis/build/status/azure-pipelines-task-lib-ci?branchName=master&jobname=linux +[build]: https://dev.azure.com/mseng/PipelineTools/_build/latest?definitionId=7623 ## Highlights @@ -20,9 +26,9 @@ Reference examples of our in the box tasks [are here](https://github.com/Microso * __Consistent API:__ The TypeScript and PowerShell libs are largely consistent. They only differ where it makes sense (being true to the platform). * __Tracing for free:__ Tracing has been built-in to many of the commands. Use the SDK and get some debug tracing for free. -## Typescript Tasks +## TypeScript Tasks -Cross platform tasks are written in Typescript. It is the preferred way to write tasks once. +Cross platform tasks are written in TypeScript. It is the preferred way to write tasks once. [![NPM version][npm-lib-image]][npm-lib-url] ![VSTS](https://mseng.visualstudio.com/DefaultCollection/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/2553/badge) @@ -34,8 +40,16 @@ A task which automates Powershell technologies can be written with our Powershel Documentation: [PowerShell API](powershell/Docs/README.md) +## Notes on authoring Tasks + +Starting from [version v2.141.0](https://github.com/Microsoft/azure-pipelines-agent/releases/tag/v2.141.0), the agent can now run on three OS architectures: x86, x64, and 32-bit ARM. When authoring a new task, you can check agent variable: `Agent.OSArchitecture` (possible values: X86, X64, ARM) to restrict running said task to a particular set of OS architectures. -[npm-lib-image]: https://img.shields.io/npm/v/vsts-task-lib.svg?style=flat -[npm-lib-url]: https://www.npmjs.com/package/vsts-task-lib + +[npm-lib-image]: https://img.shields.io/npm/v/azure-pipelines-task-lib.svg?style=flat +[npm-lib-url]: https://www.npmjs.com/package/azure-pipelines-task-lib [npm-sdk-image]: https://img.shields.io/npm/v/vsts-task-sdk.svg?style=flat [npm-sdk-url]: https://www.npmjs.com/package/vsts-task-sdk + +## Security issues + +Do you think there might be a security issue? Have you been phished or identified a security vulnerability? Please don't report it here - let us know by sending an email to secure@microsoft.com. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..869fdfe2b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/azure-pipelines-steps-node.yml b/azure-pipelines-steps-node.yml new file mode 100644 index 000000000..a7444bfb8 --- /dev/null +++ b/azure-pipelines-steps-node.yml @@ -0,0 +1,16 @@ +parameters: +- name: nodeVersion + type: string + +steps: + # npm install +- task: Npm@1 + displayName: npm install + inputs: + command: install + workingDir: node + +- task: NodeTool@0 + displayName: use node ${{parameters.nodeVersion}} + inputs: + versionSpec: ${{parameters.nodeVersion}} diff --git a/azure-pipelines-steps-test-build.yml b/azure-pipelines-steps-test-build.yml new file mode 100644 index 000000000..0789bb88a --- /dev/null +++ b/azure-pipelines-steps-test-build.yml @@ -0,0 +1,10 @@ +steps: + # test +- script: node make.js test + workingDirectory: node + displayName: node make.js test + + # build +- script: node make.js build + displayName: node make.js build + workingDirectory: node diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..13dbb8179 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,67 @@ +trigger: +- master +- features/* +- releases/* + +variables: + - group: npm-tokens + - name: nodeVersion + value: '16.13.0' + +jobs: +################################################# +- job: windows +################################################# + displayName: windows + pool: + vmImage: windows-2022 + + steps: + - template: azure-pipelines-steps-node.yml + parameters: + nodeVersion: $(nodeVersion) + + - template: azure-pipelines-steps-test-build.yml + +################################################# +- job: linux +################################################# + displayName: Linux + pool: + vmImage: ubuntu-22.04 + + steps: + - template: azure-pipelines-steps-node.yml + parameters: + nodeVersion: $(nodeVersion) + - template: azure-pipelines-steps-test-build.yml + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: 'node/_build' + artifactType: 'pipeline' + artifactName: 'npm-package' + + # For CI runs on master, automatically publish packages + - bash: | + echo //registry.npmjs.org/:_authToken=\${NPM_TOKEN} > .npmrc + npm publish || true # Ignore publish failures, usually will happen because package already exists + displayName: npm publish + workingDirectory: node/_build + condition: and(succeeded(), in(variables['build.reason'], 'IndividualCI', 'BatchedCI', 'Manual'), in(variables['build.sourcebranch'], 'refs/heads/master')) + env: + NPM_TOKEN: $(npm-automation.token) + +################################################# +- job: macOS +################################################# + displayName: macOS + pool: + vmImage: macOS-12 + + steps: + - template: azure-pipelines-steps-node.yml + parameters: + nodeVersion: $(nodeVersion) + + - template: azure-pipelines-steps-test-build.yml diff --git a/node/CHANGELOG.md b/node/CHANGELOG.md new file mode 100644 index 000000000..2e79dd0c6 --- /dev/null +++ b/node/CHANGELOG.md @@ -0,0 +1,54 @@ +# Node.js task lib changes + +## 3.x + +### 3.3.1 + +- Update minimatch to version 3.0.5 to fix vulnerability - [#836](https://github.com/microsoft/azure-pipelines-task-lib/pull/836) + +### 3.4.0 + +- Updated mockery and mocha dependencies - [#875](https://github.com/microsoft/azure-pipelines-task-lib/pull/875) + +- Include uncought exceptions stack trace to the output logs - [#895](https://github.com/microsoft/azure-pipelines-task-lib/pull/895) + +## 4.x + +### 4.0.0-preview + +- Introduced support for node 16 task handler - [#844](https://github.com/microsoft/azure-pipelines-task-lib/pull/844) + +### 4.0.1-preview + +- Added node16 to task.schema.json - [#852](https://github.com/microsoft/azure-pipelines-task-lib/pull/852) +- fix ToolRunner - _getSpawnSyncOptions - [#873](https://github.com/microsoft/azure-pipelines-task-lib/pull/873) + +### 4.0.2 + +- Updated mockery because of vulnerabilities - [#878](https://github.com/microsoft/azure-pipelines-task-lib/pull/878) + +## 4.1.0 + +Backported from ver.`3.4.0`: + +- Include uncought exceptions stack trace to the output logs - [#895](https://github.com/microsoft/azure-pipelines-task-lib/pull/895) + +## 4.2.0 + +- Added unhandledRejection event - [#912](https://github.com/microsoft/azure-pipelines-task-lib/pull/912) + +## 4.3.0 + +- Described types for `argIf` - [#920](https://github.com/microsoft/azure-pipelines-task-lib/pull/920) + +## 4.3.1 + +- Resolve CVE-2022-24999 in qs 6.9.4 [#924](https://github.com/microsoft/azure-pipelines-task-lib/pull/924) + +## 4.4.0 + +- Add `getBoolFeatureFlag` [#936](https://github.com/microsoft/azure-pipelines-task-lib/pull/936) + +## 4.5.0 + +- 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) diff --git a/node/README.md b/node/README.md index d00755f46..5cb32dd49 100644 --- a/node/README.md +++ b/node/README.md @@ -1,31 +1,47 @@ -# VSTS DevOps Task SDK +# Azure Pipelines Task SDK -Libraries for writing [Visual Studio Team Services](https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx) build and deployment tasks +Libraries for writing [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines) tasks -![VSTS](https://mseng.visualstudio.com/DefaultCollection/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/2553/badge) +Reference examples of our in the box tasks [are here](https://github.com/Microsoft/azure-pipelines-tasks) -Reference examples of our in the box tasks [are here](https://github.com/Microsoft/vsts-tasks) +## TypeScript Tasks -## Typescript Tasks - -Cross platform tasks are written in Typescript. It is the preferred way to write tasks once. +Cross platform tasks are written in TypeScript. It is the preferred way to write tasks once. [![NPM version][npm-lib-image]][npm-lib-url] -Step by Step: [Create Task](docs/stepbystep.md) +Step by Step: [Create Task](https://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=vsts) + +Documentation: [TypeScript API](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/node/docs/azure-pipelines-task-lib.md), [task JSON schema](https://aka.ms/vsts-tasks.schema.json) + +Guidance: [Finding Files](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/node/docs/findingfiles.md), [Minimum agent version](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/node/docs/minagent.md), [Proxy](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/node/docs/proxy.md), [Certificate](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/node/docs/cert.md) + +## Node 10 Upgrade Notice -Documentation: [Typescript API](docs/vsts-task-lib.md) +Azure DevOps is currently working to establish Node 10 as the new preferred runtime for tasks, upgrading from Node 6. +Relevant work is happening in the `master` branch and the major version should be used with Node 10 is 3. +Previous major version is stored in the `releases/2.x` -Guidance: [Finding Files](docs/findingfiles.md), [Minimum agent version](docs/minagent.md), [Proxy](docs/proxy.md) +### Upgrading to Node 10 + +Upgrading your tasks from Node 6 should be relatively painless, however there are some things to note: +* Typescript has been upgraded to TS 4. Older versions of TS may or may not work with Node 14 or the 3.x branch. We recommend upgrading to TS 4 when upgrading to task-lib 3.x. +* Node has made some changes to `fs` between Node 6 and Node 10. It is worth reviewing and testing your tasks thoroughly before publishing updates to Node 10. ## Reference Examples -The [ShellScript Task](https://github.com/Microsoft/vsts-tasks/tree/master/Tasks/ShellScript) and the [XCode Task](https://github.com/Microsoft/vsts-tasks/tree/master/Tasks/Xcode) are good examples. +The [ShellScript Task](https://github.com/Microsoft/azure-pipelines-tasks/tree/master/Tasks/ShellScriptV2) and the [XCode Task](https://github.com/Microsoft/azure-pipelines-tasks/tree/master/Tasks/XcodeV5) are good examples. ## Contributing -### Node +We are accepting contributions and we try to stay on top of issues. + +[Contribution Guide](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/CONTRIBUTING.md). + +[Logging Issues](https://github.com/Microsoft/azure-pipelines-task-lib/issues) + +## Building the library Once: ```bash @@ -40,5 +56,19 @@ $ npm test Set environment variable TASK_TEST_TRACE=1 to display test output. -[npm-lib-image]: https://img.shields.io/npm/v/vsts-task-lib.svg?style=flat -[npm-lib-url]: https://www.npmjs.com/package/vsts-task-lib +[npm-lib-image]: https://img.shields.io/npm/v/azure-pipelines-task-lib.svg?style=flat +[npm-lib-url]: https://www.npmjs.com/package/azure-pipelines-task-lib + +## Powershell + +We also maintain a PowerShell library for Windows task development. + +Library: [Powershell Library](https://github.com/microsoft/azure-pipelines-task-lib/tree/master/powershell) + +Usage: [Consuming the SDK](https://github.com/microsoft/azure-pipelines-task-lib/blob/master/powershell/Docs/Consuming.md) + +## Third Party Notices +To generate/update third party notice file run: +```bash +$ node generate-third-party-notice.js +``` diff --git a/node/Strings/resources.resjson/de-de/resources.resjson b/node/Strings/resources.resjson/de-DE/resources.resjson similarity index 78% rename from node/Strings/resources.resjson/de-de/resources.resjson rename to node/Strings/resources.resjson/de-DE/resources.resjson index 19e594b36..efd6ee72c 100644 --- a/node/Strings/resources.resjson/de-de/resources.resjson +++ b/node/Strings/resources.resjson/de-DE/resources.resjson @@ -5,10 +5,15 @@ "loc.messages.LIB_MkdirFailedFileExists": "Das Verzeichnis \"%s\" kann nicht erstellt werden. Eine in Konflikt stehende Datei ist vorhanden: \"%s\"", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Das Verzeichnis \"%s\" kann nicht erstellt werden. Das Stammverzeichnis ist nicht vorhanden: %s", "loc.messages.LIB_MkdirFailedInvalidShare": "Das Verzeichnis \"%s\" kann nicht erstellt werden. Es kann nicht überprüft werden, ob das Verzeichnis vorhanden ist: {%s}. Wenn das Verzeichnis eine Dateifreigabe ist, stellen Sie sicher, dass der Freigabename richtig, die Freigabe online und der aktuelle Prozess berechtigt ist, auf die Freigabe zuzugreifen.", + "loc.messages.LIB_MultilineSecret": "Geheimnisse dürfen nicht mehrere Zeilen enthalten", + "loc.messages.LIB_ProcessError": "Fehler beim Ausführen des Prozesses \"%s\". Möglicherweise konnte der Prozess nicht gestartet werden. Fehler: %s", + "loc.messages.LIB_ProcessExitCode": "Fehler beim Prozess \"%s\" mit Exitcode %s.", + "loc.messages.LIB_ProcessStderr": "Fehler beim Prozess \"%s\": Mindestens eine Zeile wurde in den STDERR-Datenstrom geschrieben.", "loc.messages.LIB_ReturnCode": "Rückgabecode: %d", "loc.messages.LIB_ResourceFileNotExist": "Die Ressourcendatei ist nicht vorhanden: %s", "loc.messages.LIB_ResourceFileAlreadySet": "Die Ressourcendatei wurde bereits festgelegt auf: %s", "loc.messages.LIB_ResourceFileNotSet": "Die Ressourcendatei wurde nicht festgelegt. Die Lokalisierungszeichenfolge für den folgenden Schlüssel wurde nicht gefunden: %s", + "loc.messages.LIB_StdioNotClosed": "Die STDIO-Datenströme wurden nicht innerhalb von %s Sekunden nach dem Beendigungsereignis aus dem Prozess \"%s\" geschlossen. Möglicherweise hat ein untergeordneter Prozess die STDIO-Datenströme geerbt und wurde noch nicht beendet.", "loc.messages.LIB_WhichNotFound_Linux": "Ausführbare Datei nicht gefunden: '%s'. Prüfen Sie, ob der Dateipfad vorhanden ist oder sich die Datei in einem von der PATH-Umgebungsvariablen angegebenen Verzeichnis befindet. Prüfen Sie zudem den Dateimodus, um sicherzustellen, dass die Datei ausführbar ist.", "loc.messages.LIB_WhichNotFound_Win": "Ausführbare Datei nicht gefunden: \"%s\". Prüfen Sie, ob der Dateipfad vorhanden ist oder sich die Datei in einem von der PATH-Umgebungsvariablen angegebenen Verzeichnis befindet. Prüfen Sie zudem, ob die Datei eine gültige Erweiterung für eine ausführbare Datei aufweist.", "loc.messages.LIB_LocStringNotFound": "Die Lokalisierungszeichenfolge für den folgenden Schlüssel wurde nicht gefunden: %s", @@ -24,5 +29,7 @@ "loc.messages.LIB_PathHasNullByte": "Der Pfad darf keine NULL-Bytes enthalten.", "loc.messages.LIB_OperationFailed": "Fehler %s: %s", "loc.messages.LIB_UseFirstGlobMatch": "Mehrere Arbeitsbereichübereinstimmungen. Die erste Übereinstimmung wird verwendet.", - "loc.messages.LIB_MergeTestResultNotSupported": "Das Mergen von Testergebnissen aus mehreren Dateien in einen Testlauf wird von dieser Version des Build-Agents für OSX/Linux nicht unterstützt. Jede Testergebnisdatei wird als separater Testlauf in VSO/TFS veröffentlicht." + "loc.messages.LIB_MergeTestResultNotSupported": "Das Mergen von Testergebnissen aus mehreren Dateien in einen Testlauf wird von dieser Version des Build-Agents für OSX/Linux nicht unterstützt. Jede Testergebnisdatei wird als separater Testlauf in VSO/TFS veröffentlicht.", + "loc.messages.LIB_PlatformNotSupported": "Plattform wird nicht unterstützt: %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/en-US/resources.resjson b/node/Strings/resources.resjson/en-US/resources.resjson index 46f8afe04..5abc0b4e7 100644 --- a/node/Strings/resources.resjson/en-US/resources.resjson +++ b/node/Strings/resources.resjson/en-US/resources.resjson @@ -5,10 +5,15 @@ "loc.messages.LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", "loc.messages.LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", + "loc.messages.LIB_MultilineSecret": "Secrets cannot contain multiple lines", + "loc.messages.LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", + "loc.messages.LIB_ProcessExitCode": "The process '%s' failed with exit code %s", + "loc.messages.LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", "loc.messages.LIB_ReturnCode": "Return code: %d", "loc.messages.LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", "loc.messages.LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", "loc.messages.LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", + "loc.messages.LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", "loc.messages.LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", "loc.messages.LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", "loc.messages.LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", @@ -24,5 +29,7 @@ "loc.messages.LIB_PathHasNullByte": "Path cannot contain null bytes", "loc.messages.LIB_OperationFailed": "Failed %s: %s", "loc.messages.LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", - "loc.messages.LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS." + "loc.messages.LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", + "loc.messages.LIB_PlatformNotSupported": "Platform not supported: %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/es-es/resources.resjson b/node/Strings/resources.resjson/es-ES/resources.resjson similarity index 77% rename from node/Strings/resources.resjson/es-es/resources.resjson rename to node/Strings/resources.resjson/es-ES/resources.resjson index d1c9e314a..24090d0fc 100644 --- a/node/Strings/resources.resjson/es-es/resources.resjson +++ b/node/Strings/resources.resjson/es-ES/resources.resjson @@ -5,10 +5,15 @@ "loc.messages.LIB_MkdirFailedFileExists": "No se puede crear el directorio '%s'. Hay un conflicto entre archivos: '%s'", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "No se puede crear el directorio '%s'. El directorio raíz no existe: '%s'", "loc.messages.LIB_MkdirFailedInvalidShare": "No se puede crear el directorio '%s'. No se puede comprobar si el directorio existe: '%s'. Si el directorio es un recurso compartido de archivos, compruebe que el nombre es correcto, que está en línea y que el proceso actual tiene permiso de acceso a este.", + "loc.messages.LIB_MultilineSecret": "Los secretos no pueden contener varias líneas.", + "loc.messages.LIB_ProcessError": "Error al intentar ejecutar el proceso \"%s\". Esto puede indicar que no se pudo iniciar el proceso. Error: %s", + "loc.messages.LIB_ProcessExitCode": "Error del proceso \"%s\" con el código de salida %s", + "loc.messages.LIB_ProcessStderr": "Error del proceso \"%s\" porque se escribieron una o varias líneas en la secuencia STDERR", "loc.messages.LIB_ReturnCode": "Código de retorno: %d", "loc.messages.LIB_ResourceFileNotExist": "El archivo de recursos no existe: %s", "loc.messages.LIB_ResourceFileAlreadySet": "El archivo de recursos se ha establecido ya en: %s", "loc.messages.LIB_ResourceFileNotSet": "No se ha establecido el archivo de recursos. No se encuentra la cadena localizada para la clave: %s", + "loc.messages.LIB_StdioNotClosed": "Las secuencias STDIO no se cerraron en un plazo de %s segundos desde el evento de salida del proceso \"%s\". Esto puede indicar que un proceso secundario ha heredado las secuencias STDIO y aún no se ha cerrado.", "loc.messages.LIB_WhichNotFound_Linux": "No se puede encontrar el archivo ejecutable: \"%s\". Compruebe que la ruta de acceso del archivo existe o que el archivo se puede encontrar en un directorio especificado en la variable de entorno PATH. Revise también el modo de archivo para comprobar que el archivo es ejecutable.", "loc.messages.LIB_WhichNotFound_Win": "No se puede encontrar el archivo ejecutable: \"%s\". Compruebe que la ruta de acceso del archivo existe o que el archivo se puede encontrar en un directorio especificado en la variable de entorno PATH. Revise también que el archivo tenga una extensión válida para un archivo ejecutable.", "loc.messages.LIB_LocStringNotFound": "No se encuentra la cadena localizada para la clave: %s", @@ -24,5 +29,7 @@ "loc.messages.LIB_PathHasNullByte": "La ruta de acceso no puede tener bytes nulos", "loc.messages.LIB_OperationFailed": "Error de %s: %s", "loc.messages.LIB_UseFirstGlobMatch": "Hay varias coincidencias en el área de trabajo. Se usará la primera.", - "loc.messages.LIB_MergeTestResultNotSupported": "Esta versión del agente de compilación para OSX/Linux no admite la fusión mediante combinación de resultados de pruebas de varios archivos en una serie de pruebas. Cada archivo de resultados de pruebas se publicará como una serie de pruebas diferente en VSO/TFS." + "loc.messages.LIB_MergeTestResultNotSupported": "Esta versión del agente de compilación para OSX/Linux no admite la fusión mediante combinación de resultados de pruebas de varios archivos en una serie de pruebas. Cada archivo de resultados de pruebas se publicará como una serie de pruebas diferente en VSO/TFS.", + "loc.messages.LIB_PlatformNotSupported": "No se admite la plataforma: %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/fr-fr/resources.resjson b/node/Strings/resources.resjson/fr-FR/resources.resjson similarity index 78% rename from node/Strings/resources.resjson/fr-fr/resources.resjson rename to node/Strings/resources.resjson/fr-FR/resources.resjson index ad3cbd090..0893f6e3f 100644 --- a/node/Strings/resources.resjson/fr-fr/resources.resjson +++ b/node/Strings/resources.resjson/fr-FR/resources.resjson @@ -5,10 +5,15 @@ "loc.messages.LIB_MkdirFailedFileExists": "Impossible de créer le répertoire '%s'. Présence d'un fichier en conflit : '%s'", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Impossible de créer le répertoire '%s'. Le répertoire racine n'existe pas : '%s'", "loc.messages.LIB_MkdirFailedInvalidShare": "Impossible de créer le répertoire '%s'. Impossible de vérifier l'existence du répertoire : '%s'. Si le répertoire est un partage de fichiers, vérifiez que le nom du partage est correct, que le partage est en ligne, et que le processus actuel est autorisé à accéder au partage.", + "loc.messages.LIB_MultilineSecret": "Les secrets ne peuvent pas contenir plusieurs lignes", + "loc.messages.LIB_ProcessError": "Erreur durant la tentative d'exécution du processus '%s'. Cela peut indiquer que le processus n'a pas réussi à démarrer. Erreur : %s", + "loc.messages.LIB_ProcessExitCode": "Échec du processus '%s'. Code de sortie : %s", + "loc.messages.LIB_ProcessStderr": "Échec du processus '%s', car une ou plusieurs lignes ont été écrites dans le flux STDERR", "loc.messages.LIB_ReturnCode": "Code de retour : %d", "loc.messages.LIB_ResourceFileNotExist": "Le fichier de ressources n'existe pas : %s", "loc.messages.LIB_ResourceFileAlreadySet": "Le fichier de ressources est déjà défini : %s", "loc.messages.LIB_ResourceFileNotSet": "Le fichier de ressources n'est pas défini. La chaîne localisée de la clé est introuvable : %s", + "loc.messages.LIB_StdioNotClosed": "Les flux STDIO ne se sont pas fermés dans les %s secondes qui ont suivi l'événement exit du processus '%s'. Cela peut indiquer qu'un processus enfant a hérité des flux STDIO et qu'il n'est pas encore sorti.", "loc.messages.LIB_WhichNotFound_Linux": "Impossible de localiser le fichier exécutable : '%s'. Vérifiez si le chemin du fichier existe ou si le fichier peut se trouver dans un répertoire spécifié par la variable d'environnement PATH. Vérifiez également le Mode de Fichier pour déterminer si le fichier est exécutable.", "loc.messages.LIB_WhichNotFound_Win": "Impossible de localiser le fichier exécutable : '%s'. Vérifiez si le chemin du fichier existe ou si le fichier peut se trouver dans un répertoire spécifié par la variable d'environnement PATH. Vérifiez également si le fichier a une extension de fichier exécutable valide.", "loc.messages.LIB_LocStringNotFound": "Chaîne localisée introuvable pour la clé : %s", @@ -24,5 +29,7 @@ "loc.messages.LIB_PathHasNullByte": "Le chemin ne peut pas contenir d'octets de valeur Null", "loc.messages.LIB_OperationFailed": "Échec de %s : %s", "loc.messages.LIB_UseFirstGlobMatch": "Plusieurs espaces de travail correspondants. Utilisation du premier d'entre eux.", - "loc.messages.LIB_MergeTestResultNotSupported": "La fusion des résultats des tests de plusieurs fichiers en une seule série de tests n'est pas prise en charge dans cette version de l'agent de build pour OSX/Linux. Chaque fichier de résultats des tests est publié en tant que série de tests distincte dans VSO/TFS." + "loc.messages.LIB_MergeTestResultNotSupported": "La fusion des résultats des tests de plusieurs fichiers en une seule série de tests n'est pas prise en charge dans cette version de l'agent de build pour OSX/Linux. Chaque fichier de résultats des tests est publié en tant que série de tests distincte dans VSO/TFS.", + "loc.messages.LIB_PlatformNotSupported": "Plateforme non prise en charge : %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/it-IT/resources.resjson b/node/Strings/resources.resjson/it-IT/resources.resjson index 064a08727..a24444fe1 100644 --- a/node/Strings/resources.resjson/it-IT/resources.resjson +++ b/node/Strings/resources.resjson/it-IT/resources.resjson @@ -1,28 +1,35 @@ -{ - "loc.messages.LIB_UnhandledEx": "Eccezione non gestita: %s", - "loc.messages.LIB_FailOnCode": "Codice restituito dell'errore: %d", - "loc.messages.LIB_MkdirFailed": "Non è possibile creare la directory '%s'. %s", - "loc.messages.LIB_MkdirFailedFileExists": "Non è possibile creare la directory '%s'. Esiste un file in conflitto: '%s'", - "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Non è possibile creare la directory '%s'. La directory radice non esiste: '%s'", - "loc.messages.LIB_MkdirFailedInvalidShare": "Non è possibile creare la directory '%s' perché non è possibile verificarne l'esistenza: '%s'. Se la directory è una condivisione file, verificare che il nome della condivisione sia corretto, che la condivisione sia online e che il processo corrente sia autorizzato ad accedervi.", - "loc.messages.LIB_ReturnCode": "Codice restituito: %d", - "loc.messages.LIB_ResourceFileNotExist": "Il file di risorse non esiste: %s", - "loc.messages.LIB_ResourceFileAlreadySet": "Il file di risorse è già stato impostato su %s", - "loc.messages.LIB_ResourceFileNotSet": "Il file di risorse non è stato impostato. La stringa localizzata per la chiave %s non è stata trovata", - "loc.messages.LIB_WhichNotFound_Linux": "Il file eseguibile '%s' non è stato trovato. Verificare se il percorso di file esiste o il file è presente in una directory specificata dalla variabile di ambiente PATH. Controllare anche la modalità file per verificare che il file sia eseguibile.", - "loc.messages.LIB_WhichNotFound_Win": "Il file eseguibile '%s' non è stato trovato. Verificare se il percorso di file esiste o il file è presente in una directory specificata dalla variabile di ambiente PATH. Controllare anche che l'estensione sia valida per un file eseguibile.", - "loc.messages.LIB_LocStringNotFound": "La stringa localizzata per la chiave %s non è stata trovata", - "loc.messages.LIB_ParameterIsRequired": "Parametro %s non fornito", - "loc.messages.LIB_InputRequired": "Input richiesto: %s", - "loc.messages.LIB_InvalidPattern": "Criterio non valido: '%s'", - "loc.messages.LIB_EndpointNotExist": "Endpoint non presente: %s", - "loc.messages.LIB_EndpointDataNotExist": "Il parametro %s dei dati dell'endpoint non è presente: %s", - "loc.messages.LIB_EndpointAuthNotExist": "I dati di autenticazione endpoint non sono presenti: %s", - "loc.messages.LIB_InvalidEndpointAuth": "Autenticazione endpoint non valida: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "L'input del file protetto non è valido: %s", - "loc.messages.LIB_PathNotFound": "Percorso %s non trovato: %s", - "loc.messages.LIB_PathHasNullByte": "Il percorso non può contenere byte Null", - "loc.messages.LIB_OperationFailed": "Operazione %s non riuscita: %s", - "loc.messages.LIB_UseFirstGlobMatch": "Sono presenti più corrispondenze dell'area di lavoro. Verrà usata la prima.", - "loc.messages.LIB_MergeTestResultNotSupported": "L'unione di più file risultanti da un'unica esecuzione dei test non è supportata in questa versione dell'agente di compilazione per OS X/Linux. Ogni file dei risultati del test verrà pubblicato come esecuzione dei test separata in VSO/TFS." +{ + "loc.messages.LIB_UnhandledEx": "Eccezione non gestita: %s", + "loc.messages.LIB_FailOnCode": "Codice restituito dell'errore: %d", + "loc.messages.LIB_MkdirFailed": "Non è possibile creare la directory '%s'. %s", + "loc.messages.LIB_MkdirFailedFileExists": "Non è possibile creare la directory '%s'. Esiste un file in conflitto: '%s'", + "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Non è possibile creare la directory '%s'. La directory radice non esiste: '%s'", + "loc.messages.LIB_MkdirFailedInvalidShare": "Non è possibile creare la directory '%s' perché non è possibile verificarne l'esistenza: '%s'. Se la directory è una condivisione file, verificare che il nome della condivisione sia corretto, che la condivisione sia online e che il processo corrente sia autorizzato ad accedervi.", + "loc.messages.LIB_MultilineSecret": "I segreti non possono contenere più righe", + "loc.messages.LIB_ProcessError": "Si è verificato un errore durante il tentativo di eseguire il processo '%s'. Questo errore può indicare che non è stato possibile avviare il processo. Errore: %s", + "loc.messages.LIB_ProcessExitCode": "Il processo '%s' non è riuscito. Codice di uscita: %s", + "loc.messages.LIB_ProcessStderr": "Il processo '%s' non è riuscito perché una o più righe sono state scritte nel flusso STDERR", + "loc.messages.LIB_ReturnCode": "Codice restituito: %d", + "loc.messages.LIB_ResourceFileNotExist": "Il file di risorse non esiste: %s", + "loc.messages.LIB_ResourceFileAlreadySet": "Il file di risorse è già stato impostato su %s", + "loc.messages.LIB_ResourceFileNotSet": "Il file di risorse non è stato impostato. La stringa localizzata per la chiave %s non è stata trovata", + "loc.messages.LIB_StdioNotClosed": "I flussi STDIO non si sono chiusi entro %s secondi dall'evento di uscita dal processo '%s'. Questa condizione può indicare che un processo figlio ha ereditato i flussi STDIO e non è ancora stato terminato.", + "loc.messages.LIB_WhichNotFound_Linux": "Il file eseguibile '%s' non è stato trovato. Verificare se il percorso di file esiste o il file è presente in una directory specificata dalla variabile di ambiente PATH. Controllare anche la modalità file per verificare che il file sia eseguibile.", + "loc.messages.LIB_WhichNotFound_Win": "Il file eseguibile '%s' non è stato trovato. Verificare se il percorso di file esiste o il file è presente in una directory specificata dalla variabile di ambiente PATH. Controllare anche che l'estensione sia valida per un file eseguibile.", + "loc.messages.LIB_LocStringNotFound": "La stringa localizzata per la chiave %s non è stata trovata", + "loc.messages.LIB_ParameterIsRequired": "Parametro %s non fornito", + "loc.messages.LIB_InputRequired": "Input richiesto: %s", + "loc.messages.LIB_InvalidPattern": "Criterio non valido: '%s'", + "loc.messages.LIB_EndpointNotExist": "Endpoint non presente: %s", + "loc.messages.LIB_EndpointDataNotExist": "Il parametro %s dei dati dell'endpoint non è presente: %s", + "loc.messages.LIB_EndpointAuthNotExist": "I dati di autenticazione endpoint non sono presenti: %s", + "loc.messages.LIB_InvalidEndpointAuth": "Autenticazione endpoint non valida: %s", + "loc.messages.LIB_InvalidSecureFilesInput": "L'input del file protetto non è valido: %s", + "loc.messages.LIB_PathNotFound": "Percorso %s non trovato: %s", + "loc.messages.LIB_PathHasNullByte": "Il percorso non può contenere byte Null", + "loc.messages.LIB_OperationFailed": "Operazione %s non riuscita: %s", + "loc.messages.LIB_UseFirstGlobMatch": "Sono presenti più corrispondenze dell'area di lavoro. Verrà usata la prima.", + "loc.messages.LIB_MergeTestResultNotSupported": "L'unione di più file risultanti da un'unica esecuzione dei test non è supportata in questa versione dell'agente di compilazione per OS X/Linux. Ogni file dei risultati del test verrà pubblicato come esecuzione dei test separata in VSO/TFS.", + "loc.messages.LIB_PlatformNotSupported": "Piattaforma non supportata: %s", + "loc.messages.LIB_CopyFileFailed": "Si è verificato un errore durante la copia del file. Tentativi rimasti: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/ja-jp/resources.resjson b/node/Strings/resources.resjson/ja-JP/resources.resjson similarity index 74% rename from node/Strings/resources.resjson/ja-jp/resources.resjson rename to node/Strings/resources.resjson/ja-JP/resources.resjson index 7532d97c5..e52e13056 100644 --- a/node/Strings/resources.resjson/ja-jp/resources.resjson +++ b/node/Strings/resources.resjson/ja-JP/resources.resjson @@ -5,10 +5,15 @@ "loc.messages.LIB_MkdirFailedFileExists": "ディレクトリ '%s' を作成できません。競合するファイルが存在します: '%s'", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "ディレクトリ '%s' を作成できません。ルート ディレクトリが存在しません: '%s'", "loc.messages.LIB_MkdirFailedInvalidShare": "ディレクトリ '%s' を作成できません。ディレクトリが存在することを確認できません: '%s'。ディレクトリがファイル共有である場合、その共有名が正しいこと、その共有がオンラインであること、そして現在のプロセスにその共有へのアクセス許可があることをご確認ください。", + "loc.messages.LIB_MultilineSecret": "シークレットに複数の行を含めることはできません", + "loc.messages.LIB_ProcessError": "プロセス '%s' を実行しようとしているときにエラーが発生しました。プロセスの開始に失敗したおそれがあります。エラー: %s", + "loc.messages.LIB_ProcessExitCode": "プロセス '%s' が終了コード %s で失敗しました", + "loc.messages.LIB_ProcessStderr": "1 つ以上の行が STDERR ストリームに書き込まれたため、プロセス '%s' が失敗しました", "loc.messages.LIB_ReturnCode": "リターン コード: %d", "loc.messages.LIB_ResourceFileNotExist": "リソース ファイルが存在しません: %s", "loc.messages.LIB_ResourceFileAlreadySet": "リソース ファイルは既に %s に設定されています", "loc.messages.LIB_ResourceFileNotSet": "リソース ファイルが設定されておらず、キーの loc 文字列が見つかりません: %s", + "loc.messages.LIB_StdioNotClosed": "STDIO ストリームが、プロセス '%s' の終了イベントから %s 秒以内に終了しませんでした。これは、子プロセスが STDIO ストリームを継承し、まだ終了していないことを示している可能性があります。", "loc.messages.LIB_WhichNotFound_Linux": "実行可能ファイルが見つかりません: '%s'。ファイル パスが存在すること、またはそのファイルが PATH 環境変数で指定されたディレクトリ内にあることをご確認ください。ファイルが実行可能かどうかについてファイル モードもご確認ください。", "loc.messages.LIB_WhichNotFound_Win": "実行可能ファイルが見つかりません: '%s'。ファイル パスが存在すること、またはそのファイルが PATH 環境変数で指定されたディレクトリ内にあることをご確認ください。そのファイルに実行可能ファイルの有効な拡張子がついていることもご確認ください。", "loc.messages.LIB_LocStringNotFound": "キーの loc 文字列が見つかりません: %s", @@ -19,10 +24,12 @@ "loc.messages.LIB_EndpointDataNotExist": "エンドポイントのデータ パラメーター %s がありません: %s", "loc.messages.LIB_EndpointAuthNotExist": "エンドポイントの認証データがありません: %s", "loc.messages.LIB_InvalidEndpointAuth": "エンドポイントの認証が無効です: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "セキュリティ保護された無効なファイル入力: %s", + "loc.messages.LIB_InvalidSecureFilesInput": "無効なセキュア ファイル入力: %s", "loc.messages.LIB_PathNotFound": "見つかりませんでした %s: %s", "loc.messages.LIB_PathHasNullByte": "パスに null バイトを含めることはできません", "loc.messages.LIB_OperationFailed": "失敗しました %s: %s", "loc.messages.LIB_UseFirstGlobMatch": "複数のワークスペースが一致します。最初のワークスペースが使用されます。", - "loc.messages.LIB_MergeTestResultNotSupported": "複数のファイルからのテスト結果を 1 つのテスト実行にマージする処理は、OSX/Linux 用のビルド エージェントのこのバージョンではサポートされていません。各テスト結果ファイルが VSO/TFS で別個のテスト実行として発行されます。" + "loc.messages.LIB_MergeTestResultNotSupported": "複数のファイルからのテスト結果を 1 つのテスト実行にマージする処理は、OSX/Linux 用のビルド エージェントのこのバージョンではサポートされていません。各テスト結果ファイルが VSO/TFS で別個のテスト実行として発行されます。", + "loc.messages.LIB_PlatformNotSupported": "プラットフォームがサポートされていません: %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/ko-KR/resources.resjson b/node/Strings/resources.resjson/ko-KR/resources.resjson index 4d5dc0bb1..a5010b122 100644 --- a/node/Strings/resources.resjson/ko-KR/resources.resjson +++ b/node/Strings/resources.resjson/ko-KR/resources.resjson @@ -1,28 +1,35 @@ -{ - "loc.messages.LIB_UnhandledEx": "처리되지 않음: %s", - "loc.messages.LIB_FailOnCode": "실패 반환 코드: %d", - "loc.messages.LIB_MkdirFailed": "'%s' 디렉터리를 만들 수 없습니다. %s", - "loc.messages.LIB_MkdirFailedFileExists": "'%s' 디렉터리를 만들 수 없습니다. 충돌하는 파일 '%s'이(가) 있습니다.", - "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "'%s' 디렉터리를 만들 수 없습니다. 루트 디렉터리 '%s'이(가) 없습니다.", - "loc.messages.LIB_MkdirFailedInvalidShare": "'%s' 디렉터리를 만들 수 없습니다. '%s' 디렉터리가 있는지 확인할 수 없습니다. 디렉터리가 파일 공유인 경우 공유 이름이 올바르고, 공유가 온라인 상태이며, 현재 프로세스에 공유에 액세스할 수 있는 권한이 있는지 확인하세요.", - "loc.messages.LIB_ReturnCode": "반환 코드: %d", - "loc.messages.LIB_ResourceFileNotExist": "리소스 파일이 없음: %s", - "loc.messages.LIB_ResourceFileAlreadySet": "리소스 파일이 이미 다음으로 설정됨: %s", - "loc.messages.LIB_ResourceFileNotSet": "리소스 파일이 설정되지 않았고 키에 대한 loc 문자열을 찾을 수 없음: %s", - "loc.messages.LIB_WhichNotFound_Linux": "실행 파일 '%s'을(를) 찾을 수 없습니다. 파일 경로가 있는지 또는 PATH 환경 변수에서 지정한 디렉터리 내에서 파일을 찾을 수 있는지 확인하세요. 또한 파일 모드를 확인하여 파일을 실행할 수 있는지 확인하세요.", - "loc.messages.LIB_WhichNotFound_Win": "실행 파일 '%s'을(를) 찾을 수 없습니다. 파일 경로가 있는지 또는 PATH 환경 변수에서 지정한 디렉터리 내에서 파일을 찾을 수 있는지 확인하세요. 또한 파일이 실행 파일에 대해 올바른 확장명을 가지고 있는지 확인하세요.", - "loc.messages.LIB_LocStringNotFound": "키에 대한 loc 문자열을 찾을 수 없음: %s", - "loc.messages.LIB_ParameterIsRequired": "%s이(가) 제공되지 않음", - "loc.messages.LIB_InputRequired": "입력 필요: %s", - "loc.messages.LIB_InvalidPattern": "잘못된 패턴: '%s'", - "loc.messages.LIB_EndpointNotExist": "끝점이 없음: %s", - "loc.messages.LIB_EndpointDataNotExist": "끝점 데이터 매개 변수 %s이(가) 없음: %s", - "loc.messages.LIB_EndpointAuthNotExist": "끝점 인증 데이터가 없음: %s", - "loc.messages.LIB_InvalidEndpointAuth": "잘못된 끝점 인증: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "보안 파일 입력이 잘못됨: %s", - "loc.messages.LIB_PathNotFound": "%s을(를) 찾을 수 없음: %s", - "loc.messages.LIB_PathHasNullByte": "경로에 null 바이트를 포함할 수 없음", - "loc.messages.LIB_OperationFailed": "%s 실패: %s", - "loc.messages.LIB_UseFirstGlobMatch": "여러 작업 영역이 일치합니다. 첫 번째 작업 영역을 사용하세요.", - "loc.messages.LIB_MergeTestResultNotSupported": "이 OSX/Linux용 빌드 에이전트 버전에서 여러 파일의 테스트 결과를 하나의 테스트 실행으로 병합하는 것을 지원하지 않습니다. 각 테스트 결과 파일은 VSO/TFS에서 별도의 테스트 실행으로 게시됩니다." +{ + "loc.messages.LIB_UnhandledEx": "처리되지 않음: %s", + "loc.messages.LIB_FailOnCode": "실패 반환 코드: %d", + "loc.messages.LIB_MkdirFailed": "'%s' 디렉터리를 만들 수 없습니다. %s", + "loc.messages.LIB_MkdirFailedFileExists": "'%s' 디렉터리를 만들 수 없습니다. 충돌하는 파일 '%s'이(가) 있습니다.", + "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "'%s' 디렉터리를 만들 수 없습니다. 루트 디렉터리 '%s'이(가) 없습니다.", + "loc.messages.LIB_MkdirFailedInvalidShare": "'%s' 디렉터리를 만들 수 없습니다. '%s' 디렉터리가 있는지 확인할 수 없습니다. 디렉터리가 파일 공유인 경우 공유 이름이 올바르고, 공유가 온라인 상태이며, 현재 프로세스에 공유에 액세스할 수 있는 권한이 있는지 확인하세요.", + "loc.messages.LIB_MultilineSecret": "비밀에 여러 줄을 사용할 수 없습니다.", + "loc.messages.LIB_ProcessError": "'%s' 프로세스를 실행할 때 오류가 발생했습니다. 이는 프로세스를 시작하지 못했음을 나타낼 수 있습니다. 오류: %s", + "loc.messages.LIB_ProcessExitCode": "'%s' 프로세스가 실패함(종료 코드 %s)", + "loc.messages.LIB_ProcessStderr": "하나 이상의 줄이 STDERR 스트림에 쓰였으므로 '%s' 프로세스가 실패함", + "loc.messages.LIB_ReturnCode": "반환 코드: %d", + "loc.messages.LIB_ResourceFileNotExist": "리소스 파일이 없음: %s", + "loc.messages.LIB_ResourceFileAlreadySet": "리소스 파일이 이미 다음으로 설정됨: %s", + "loc.messages.LIB_ResourceFileNotSet": "리소스 파일이 설정되지 않았고 키에 대한 loc 문자열을 찾을 수 없음: %s", + "loc.messages.LIB_StdioNotClosed": "STDIO 스트림이 %s초 이내('%s' 프로세스의 종료 이벤트 후)에 닫히지 않았습니다. 이는 자식 프로세스가 STDIO 스트림을 상속했으며 아직 종료되지 않았음을 나타낼 수 있습니다.", + "loc.messages.LIB_WhichNotFound_Linux": "실행 파일 '%s'을(를) 찾을 수 없습니다. 파일 경로가 있는지 또는 PATH 환경 변수에서 지정한 디렉터리 내에서 파일을 찾을 수 있는지 확인하세요. 또한 파일 모드를 확인하여 파일을 실행할 수 있는지 확인하세요.", + "loc.messages.LIB_WhichNotFound_Win": "실행 파일 '%s'을(를) 찾을 수 없습니다. 파일 경로가 있는지 또는 PATH 환경 변수에서 지정한 디렉터리 내에서 파일을 찾을 수 있는지 확인하세요. 또한 파일이 실행 파일에 대해 올바른 확장명을 가지고 있는지 확인하세요.", + "loc.messages.LIB_LocStringNotFound": "키에 대한 loc 문자열을 찾을 수 없음: %s", + "loc.messages.LIB_ParameterIsRequired": "%s이(가) 제공되지 않음", + "loc.messages.LIB_InputRequired": "입력 필요: %s", + "loc.messages.LIB_InvalidPattern": "잘못된 패턴: '%s'", + "loc.messages.LIB_EndpointNotExist": "엔드포인트가 없음: %s", + "loc.messages.LIB_EndpointDataNotExist": "엔드포인트 데이터 매개 변수 %s이(가) 없음: %s", + "loc.messages.LIB_EndpointAuthNotExist": "엔드포인트 인증 데이터가 없음: %s", + "loc.messages.LIB_InvalidEndpointAuth": "잘못된 엔드포인트 인증: %s", + "loc.messages.LIB_InvalidSecureFilesInput": "보안 파일 입력이 잘못됨: %s", + "loc.messages.LIB_PathNotFound": "%s을(를) 찾을 수 없음: %s", + "loc.messages.LIB_PathHasNullByte": "경로에 null 바이트를 포함할 수 없음", + "loc.messages.LIB_OperationFailed": "%s 실패: %s", + "loc.messages.LIB_UseFirstGlobMatch": "여러 작업 영역이 일치합니다. 첫 번째 작업 영역을 사용하세요.", + "loc.messages.LIB_MergeTestResultNotSupported": "이 OSX/Linux용 빌드 에이전트 버전에서 여러 파일의 테스트 결과를 하나의 테스트 실행으로 병합하는 것을 지원하지 않습니다. 각 테스트 결과 파일은 VSO/TFS에서 별도의 테스트 실행으로 게시됩니다.", + "loc.messages.LIB_PlatformNotSupported": "지원되지 않는 플랫폼: %s", + "loc.messages.LIB_CopyFileFailed": "파일을 복사하는 동안 오류가 발생했습니다. 남은 시도 횟수: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/ru-RU/resources.resjson b/node/Strings/resources.resjson/ru-RU/resources.resjson index d2ae77920..d2d2ceb90 100644 --- a/node/Strings/resources.resjson/ru-RU/resources.resjson +++ b/node/Strings/resources.resjson/ru-RU/resources.resjson @@ -1,28 +1,35 @@ -{ - "loc.messages.LIB_UnhandledEx": "Не обработано: %s", - "loc.messages.LIB_FailOnCode": "Код возврата при сбое: %d.", - "loc.messages.LIB_MkdirFailed": "Не удается создать каталог \"%s\". %s", - "loc.messages.LIB_MkdirFailedFileExists": "Не удается создать каталог \"%s\". Существует конфликтующий файл: \"%s\"", - "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Не удалось создать каталог \"%s\". Корневой каталог не существует: \"%s\"", - "loc.messages.LIB_MkdirFailedInvalidShare": "Не удалось создать каталог \"%s\". Не удалось проверить, существует ли каталог \"%s\". Если каталог является файловым ресурсом, убедитесь, что имя ресурса указано правильно, он работает и текущий процесс имеет разрешение на доступ к нему.", - "loc.messages.LIB_ReturnCode": "Код возврата: %d", - "loc.messages.LIB_ResourceFileNotExist": "Файл ресурсов не существует: %s.", - "loc.messages.LIB_ResourceFileAlreadySet": "Файл ресурсов уже задан: %s.", - "loc.messages.LIB_ResourceFileNotSet": "Файл ресурсов не задан, не удается найти локализованную строку для ключа: %s.", - "loc.messages.LIB_WhichNotFound_Linux": "Не удалось найти исполняемый файл: \"%s\". Убедитесь, что путь к файлу существует и файл можно найти в каталоге, указанном переменной окружения PATH. Также проверьте режим файла, чтобы убедиться, что файл является исполняемым.", - "loc.messages.LIB_WhichNotFound_Win": "Не удалось найти исполняемый файл: \"%s\". Убедитесь, что путь к файлу существует и что файл можно найти в каталоге, указанном переменной окружения PATH. Также убедитесь, что у файла есть допустимое расширение для исполняемого файла.", - "loc.messages.LIB_LocStringNotFound": "Не удается найти локализованную строку для ключа: %s.", - "loc.messages.LIB_ParameterIsRequired": "Не указан %s.", - "loc.messages.LIB_InputRequired": "Требуется ввести данные: %s.", - "loc.messages.LIB_InvalidPattern": "Недопустимый шаблон: \"%s\"", - "loc.messages.LIB_EndpointNotExist": "Конечная точка отсутствует: %s.", - "loc.messages.LIB_EndpointDataNotExist": "Отсутствует параметр %s данных конечной точки: %s", - "loc.messages.LIB_EndpointAuthNotExist": "Отсутствуют данные для проверки подлинности конечной точки: %s", - "loc.messages.LIB_InvalidEndpointAuth": "Недопустимая проверка подлинности конечной точки: %s.", - "loc.messages.LIB_InvalidSecureFilesInput": "Недопустимые входные данные защищенного файла: %s", - "loc.messages.LIB_PathNotFound": "Не найден %s: %s.", - "loc.messages.LIB_PathHasNullByte": "Путь не может содержать пустые байты.", - "loc.messages.LIB_OperationFailed": "Сбой %s: %s.", - "loc.messages.LIB_UseFirstGlobMatch": "Несколько рабочих областей совпадает, использована первая рабочая область.", - "loc.messages.LIB_MergeTestResultNotSupported": "Объединение результатов тестов из нескольких файлов в один тестовый запуск не поддерживается в этой версии агента сборки для OSX/Linux. Каждый файл результатов теста будет опубликован в качестве отдельного тестового запуска в VSO/TFS." +{ + "loc.messages.LIB_UnhandledEx": "Не обработано: %s", + "loc.messages.LIB_FailOnCode": "Код возврата при сбое: %d.", + "loc.messages.LIB_MkdirFailed": "Не удается создать каталог \"%s\". %s", + "loc.messages.LIB_MkdirFailedFileExists": "Не удается создать каталог \"%s\". Существует конфликтующий файл: \"%s\"", + "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Не удалось создать каталог \"%s\". Корневой каталог не существует: \"%s\"", + "loc.messages.LIB_MkdirFailedInvalidShare": "Не удалось создать каталог \"%s\". Не удалось проверить, существует ли каталог \"%s\". Если каталог является файловым ресурсом, убедитесь, что имя ресурса указано правильно, он работает и текущий процесс имеет разрешение на доступ к нему.", + "loc.messages.LIB_MultilineSecret": "Секрет не может быть многострочным", + "loc.messages.LIB_ProcessError": "Произошла ошибка при попытке выполнить процесс \"%s\". Это может означать, что процесс не удалось запустить. Ошибка: %s", + "loc.messages.LIB_ProcessExitCode": "Произошел сбой процесса \"%s\" с кодом выхода %s.", + "loc.messages.LIB_ProcessStderr": "Произошел сбой процесса \"%s\", так как одна или несколько строк были записаны в поток STDERR.", + "loc.messages.LIB_ReturnCode": "Код возврата: %d", + "loc.messages.LIB_ResourceFileNotExist": "Файл ресурсов не существует: %s.", + "loc.messages.LIB_ResourceFileAlreadySet": "Файл ресурсов уже задан: %s.", + "loc.messages.LIB_ResourceFileNotSet": "Файл ресурсов не задан, не удается найти локализованную строку для ключа: %s.", + "loc.messages.LIB_StdioNotClosed": "Потоки STDIO не были закрыты в течение %s с после получения события выхода от процесса \"%s\". Это может свидетельствовать о том, что дочерний процесс унаследовал потоки STDIO и еще не завершил работу.", + "loc.messages.LIB_WhichNotFound_Linux": "Не удалось найти исполняемый файл: \"%s\". Убедитесь, что путь к файлу существует и файл можно найти в каталоге, указанном переменной окружения PATH. Также проверьте режим файла, чтобы убедиться, что файл является исполняемым.", + "loc.messages.LIB_WhichNotFound_Win": "Не удалось найти исполняемый файл: \"%s\". Убедитесь, что путь к файлу существует и что файл можно найти в каталоге, указанном переменной окружения PATH. Также убедитесь, что у файла есть допустимое расширение для исполняемого файла.", + "loc.messages.LIB_LocStringNotFound": "Не удается найти локализованную строку для ключа: %s.", + "loc.messages.LIB_ParameterIsRequired": "Не указан %s.", + "loc.messages.LIB_InputRequired": "Требуется ввести данные: %s.", + "loc.messages.LIB_InvalidPattern": "Недопустимый шаблон: \"%s\"", + "loc.messages.LIB_EndpointNotExist": "Конечная точка отсутствует: %s.", + "loc.messages.LIB_EndpointDataNotExist": "Отсутствует параметр %s данных конечной точки: %s", + "loc.messages.LIB_EndpointAuthNotExist": "Отсутствуют данные для проверки подлинности конечной точки: %s", + "loc.messages.LIB_InvalidEndpointAuth": "Недопустимая проверка подлинности конечной точки: %s.", + "loc.messages.LIB_InvalidSecureFilesInput": "Недопустимые входные данные защитного файла: %s", + "loc.messages.LIB_PathNotFound": "Не найден %s: %s.", + "loc.messages.LIB_PathHasNullByte": "Путь не может содержать пустые байты.", + "loc.messages.LIB_OperationFailed": "Сбой %s: %s.", + "loc.messages.LIB_UseFirstGlobMatch": "Несколько рабочих областей совпадает, использована первая рабочая область.", + "loc.messages.LIB_MergeTestResultNotSupported": "Объединение результатов тестов из нескольких файлов в один тестовый запуск не поддерживается в этой версии агента сборки для OSX/Linux. Каждый файл результатов теста будет опубликован в качестве отдельного тестового запуска в VSO/TFS.", + "loc.messages.LIB_PlatformNotSupported": "Платформа не поддерживается: %s", + "loc.messages.LIB_CopyFileFailed": "Ошибка при копировании файла. Оставшееся число попыток: %s." } \ No newline at end of file diff --git a/node/Strings/resources.resjson/zh-CN/resources.resjson b/node/Strings/resources.resjson/zh-CN/resources.resjson index 6afdcc58d..b30eb1d81 100644 --- a/node/Strings/resources.resjson/zh-CN/resources.resjson +++ b/node/Strings/resources.resjson/zh-CN/resources.resjson @@ -1,28 +1,35 @@ -{ - "loc.messages.LIB_UnhandledEx": "未处理: %s", - "loc.messages.LIB_FailOnCode": "故障返回代码: %d", - "loc.messages.LIB_MkdirFailed": "无法创建目录“%s”。%s", - "loc.messages.LIB_MkdirFailedFileExists": "无法创建目录“%s”。存在冲突文件:“%s”", - "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "无法创建目录“%s”。根目录不存在:“%s”", - "loc.messages.LIB_MkdirFailedInvalidShare": "无法创建目录“%s”。无法验证“%s”目录是否存在。如果目录是文件共享,请验证共享名称是否正确、共享是否已联机以及当前进程是否有权访问该共享。", - "loc.messages.LIB_ReturnCode": "返回代码: %d", - "loc.messages.LIB_ResourceFileNotExist": "资源文件不存在: %s", - "loc.messages.LIB_ResourceFileAlreadySet": "资源文件已被设置为 %s", - "loc.messages.LIB_ResourceFileNotSet": "资源文件尚未设置,无法找到关键字的本地字符串: %s", - "loc.messages.LIB_WhichNotFound_Linux": "无法定位可执行文件: \"%s\"。请验证文件路径是否存在或文件是否可在 PATH 环境变量指定的目录内找到。另请检查文件模式以验证文件是否可执行。", - "loc.messages.LIB_WhichNotFound_Win": "无法定位可执行文件: \"%s\"。请验证文件路径是否存在或文件是否可在 PATH 环境变量指定的目录内找到。另请验证该文件是否具有可执行文件的有效扩展名。", - "loc.messages.LIB_LocStringNotFound": "无法找到关键字的本地字符串: %s", - "loc.messages.LIB_ParameterIsRequired": "未提供 %s", - "loc.messages.LIB_InputRequired": "输入必需项: %s", - "loc.messages.LIB_InvalidPattern": "无效的模式: \"%s\"", - "loc.messages.LIB_EndpointNotExist": "终结点不存在: %s", - "loc.messages.LIB_EndpointDataNotExist": "终结点数据参数 %s 不存在: %s", - "loc.messages.LIB_EndpointAuthNotExist": "终结点授权数据不存在: %s", - "loc.messages.LIB_InvalidEndpointAuth": "终结点验证无效: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "无效的安全文件输入: %s", - "loc.messages.LIB_PathNotFound": "找不到 %s: %s", - "loc.messages.LIB_PathHasNullByte": "路径不能包含 null 字节", - "loc.messages.LIB_OperationFailed": "%s 失败: %s", - "loc.messages.LIB_UseFirstGlobMatch": "出现多个工作区匹配时,使用第一个。", - "loc.messages.LIB_MergeTestResultNotSupported": "此版本的 OSX/Linux 生成代理不支持来自某个测试运行的多文件合并测试结果,在 VSO/TFS 中,每个测试结果文件都将作为单独的测试运行进行发布。" +{ + "loc.messages.LIB_UnhandledEx": "未处理: %s", + "loc.messages.LIB_FailOnCode": "故障返回代码: %d", + "loc.messages.LIB_MkdirFailed": "无法创建目录“%s”。%s", + "loc.messages.LIB_MkdirFailedFileExists": "无法创建目录“%s”。存在冲突文件:“%s”", + "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "无法创建目录“%s”。根目录不存在:“%s”", + "loc.messages.LIB_MkdirFailedInvalidShare": "无法创建目录“%s”。无法验证“%s”目录是否存在。如果目录是文件共享,请验证共享名称是否正确、共享是否已联机以及当前进程是否有权访问该共享。", + "loc.messages.LIB_MultilineSecret": "密码不能包含多个行", + "loc.messages.LIB_ProcessError": "尝试执行进程“%s”时出错。这可能表示进程启动失败。错误: %s", + "loc.messages.LIB_ProcessExitCode": "进程“%s”失败,退出代码为 %s", + "loc.messages.LIB_ProcessStderr": "进程“%s”失败,因为已将一行或多行写入 STDERR 流", + "loc.messages.LIB_ReturnCode": "返回代码: %d", + "loc.messages.LIB_ResourceFileNotExist": "资源文件不存在: %s", + "loc.messages.LIB_ResourceFileAlreadySet": "资源文件已被设置为 %s", + "loc.messages.LIB_ResourceFileNotSet": "资源文件尚未设置,无法找到关键字的本地字符串: %s", + "loc.messages.LIB_StdioNotClosed": "STDIO 流在进程“%s”中发生退出事件 %s 秒内未关闭 。这可能表示子进程继承了 STDIO 流且尚未退出。", + "loc.messages.LIB_WhichNotFound_Linux": "无法定位可执行文件: \"%s\"。请验证文件路径是否存在或文件是否可在 PATH 环境变量指定的目录内找到。另请检查文件模式以验证文件是否可执行。", + "loc.messages.LIB_WhichNotFound_Win": "无法定位可执行文件: \"%s\"。请验证文件路径是否存在或文件是否可在 PATH 环境变量指定的目录内找到。另请验证该文件是否具有可执行文件的有效扩展名。", + "loc.messages.LIB_LocStringNotFound": "无法找到关键字的本地字符串: %s", + "loc.messages.LIB_ParameterIsRequired": "未提供 %s", + "loc.messages.LIB_InputRequired": "输入必需项: %s", + "loc.messages.LIB_InvalidPattern": "无效的模式: \"%s\"", + "loc.messages.LIB_EndpointNotExist": "终结点不存在: %s", + "loc.messages.LIB_EndpointDataNotExist": "终结点数据参数 %s 不存在: %s", + "loc.messages.LIB_EndpointAuthNotExist": "终结点授权数据不存在: %s", + "loc.messages.LIB_InvalidEndpointAuth": "终结点验证无效: %s", + "loc.messages.LIB_InvalidSecureFilesInput": "无效的安全文件输入: %s", + "loc.messages.LIB_PathNotFound": "找不到 %s: %s", + "loc.messages.LIB_PathHasNullByte": "路径不能包含 null 字节", + "loc.messages.LIB_OperationFailed": "%s 失败: %s", + "loc.messages.LIB_UseFirstGlobMatch": "出现多个工作区匹配时,使用第一个。", + "loc.messages.LIB_MergeTestResultNotSupported": "此版本的 OSX/Linux 生成代理不支持来自某个测试运行的多文件合并测试结果,在 VSO/TFS 中,每个测试结果文件都将作为单独的测试运行进行发布。", + "loc.messages.LIB_PlatformNotSupported": "平台不受支持: %s", + "loc.messages.LIB_CopyFileFailed": "复制文件时出错。剩余尝试次数: %s" } \ No newline at end of file diff --git a/node/Strings/resources.resjson/zh-TW/resources.resjson b/node/Strings/resources.resjson/zh-TW/resources.resjson index 378f5574c..ecf4ee743 100644 --- a/node/Strings/resources.resjson/zh-TW/resources.resjson +++ b/node/Strings/resources.resjson/zh-TW/resources.resjson @@ -1,28 +1,35 @@ -{ - "loc.messages.LIB_UnhandledEx": "未經處理: %s", - "loc.messages.LIB_FailOnCode": "傳回程式碼失敗: %d", - "loc.messages.LIB_MkdirFailed": "無法建立目錄 '%s'。%s", - "loc.messages.LIB_MkdirFailedFileExists": "無法建立目錄 '%s'。存在衝突的檔案: '%s'", - "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "無法建立目錄 '%s'。根目錄不存在: '%s'", - "loc.messages.LIB_MkdirFailedInvalidShare": "無法建立目錄 '%s'。無法驗證目錄是否存在: '%s'。如果目錄是檔案共用,請驗證共用名稱正確、共用在線上,而且目前的流程具有存取共用的權限。", - "loc.messages.LIB_ReturnCode": "傳回程式碼: %d", - "loc.messages.LIB_ResourceFileNotExist": "資源檔案不存在: %s", - "loc.messages.LIB_ResourceFileAlreadySet": "資源檔案已設定至: %s", - "loc.messages.LIB_ResourceFileNotSet": "尚未設定資源檔案,找不到索引鍵的 loc 字串: %s", - "loc.messages.LIB_WhichNotFound_Linux": "找不到可執行檔: '%s'。請確認檔案路徑存在,或檔案可以在 PATH 環境變數指定的目錄中找到。另請檢查檔案模式,確認檔案可以執行。", - "loc.messages.LIB_WhichNotFound_Win": "找不到可執行檔: '%s'。請確認檔案路徑存在,或檔案可以在 PATH 環境變數指定的目錄中找到。另請確認檔案具備有效的可執行檔副檔名。", - "loc.messages.LIB_LocStringNotFound": "找不到索引鍵的 loc 字串: %s", - "loc.messages.LIB_ParameterIsRequired": "未提供 %s", - "loc.messages.LIB_InputRequired": "需要輸入內容: %s", - "loc.messages.LIB_InvalidPattern": "模式無效: '%s'", - "loc.messages.LIB_EndpointNotExist": "端點不存在: %s", - "loc.messages.LIB_EndpointDataNotExist": "端點資料參數 %s 不存在: %s", - "loc.messages.LIB_EndpointAuthNotExist": "端點驗證資料不存在: %s", - "loc.messages.LIB_InvalidEndpointAuth": "端點驗證無效: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "安全檔案輸入無效: %s", - "loc.messages.LIB_PathNotFound": "找不到 %s: %s", - "loc.messages.LIB_PathHasNullByte": "路徑不能包含 null 位元組", - "loc.messages.LIB_OperationFailed": "%s 失敗: %s", - "loc.messages.LIB_UseFirstGlobMatch": "多個工作區相符。請先使用。", - "loc.messages.LIB_MergeTestResultNotSupported": "OSX/Linux 的此版本組建代理程式不支援將多個檔案的測試結果合併至單一測試回合,每個測試結果檔案將作為個別測試回合在 VSO/TFS 中發行。" +{ + "loc.messages.LIB_UnhandledEx": "未經處理: %s", + "loc.messages.LIB_FailOnCode": "傳回程式碼失敗: %d", + "loc.messages.LIB_MkdirFailed": "無法建立目錄 '%s'。%s", + "loc.messages.LIB_MkdirFailedFileExists": "無法建立目錄 '%s'。存在衝突的檔案: '%s'", + "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "無法建立目錄 '%s'。根目錄不存在: '%s'", + "loc.messages.LIB_MkdirFailedInvalidShare": "無法建立目錄 '%s'。無法驗證目錄是否存在: '%s'。如果目錄是檔案共用,請驗證共用名稱正確、共用在線上,而且目前的流程具有存取共用的權限。", + "loc.messages.LIB_MultilineSecret": "祕密不得包含多個行", + "loc.messages.LIB_ProcessError": "嘗試執行處理序 '%s' 時發生錯誤。這可能表示處理序無法啟動。錯誤: %s", + "loc.messages.LIB_ProcessExitCode": "處理序 '%s' 失敗,結束代碼為 %s", + "loc.messages.LIB_ProcessStderr": "因為 STDERR 資料流中寫入了一或多行程式碼,所以處理序 '%s' 失敗", + "loc.messages.LIB_ReturnCode": "傳回程式碼: %d", + "loc.messages.LIB_ResourceFileNotExist": "資源檔案不存在: %s", + "loc.messages.LIB_ResourceFileAlreadySet": "資源檔案已設定至: %s", + "loc.messages.LIB_ResourceFileNotSet": "尚未設定資源檔案,找不到索引鍵的 loc 字串: %s", + "loc.messages.LIB_StdioNotClosed": "STDIO 資料流未在 %s 秒內關閉 (從處理序 '%s' 結束事件發生後算起)。這可能表示子處理序繼承了 STDIO 資料流且尚未結束。", + "loc.messages.LIB_WhichNotFound_Linux": "找不到可執行檔: '%s'。請確認檔案路徑存在,或檔案可以在 PATH 環境變數指定的目錄中找到。另請檢查檔案模式,確認檔案可以執行。", + "loc.messages.LIB_WhichNotFound_Win": "找不到可執行檔: '%s'。請確認檔案路徑存在,或檔案可以在 PATH 環境變數指定的目錄中找到。另請確認檔案具備有效的可執行檔副檔名。", + "loc.messages.LIB_LocStringNotFound": "找不到索引鍵的 loc 字串: %s", + "loc.messages.LIB_ParameterIsRequired": "未提供 %s", + "loc.messages.LIB_InputRequired": "需要輸入內容: %s", + "loc.messages.LIB_InvalidPattern": "模式無效: '%s'", + "loc.messages.LIB_EndpointNotExist": "端點不存在: %s", + "loc.messages.LIB_EndpointDataNotExist": "端點資料參數 %s 不存在: %s", + "loc.messages.LIB_EndpointAuthNotExist": "端點驗證資料不存在: %s", + "loc.messages.LIB_InvalidEndpointAuth": "端點驗證無效: %s", + "loc.messages.LIB_InvalidSecureFilesInput": "安全檔案輸入無效: %s", + "loc.messages.LIB_PathNotFound": "找不到 %s: %s", + "loc.messages.LIB_PathHasNullByte": "路徑不能包含 null 位元組", + "loc.messages.LIB_OperationFailed": "%s 失敗: %s", + "loc.messages.LIB_UseFirstGlobMatch": "多個工作區相符。請先使用。", + "loc.messages.LIB_MergeTestResultNotSupported": "OSX/Linux 的此版本組建代理程式不支援將多個檔案的測試結果合併至單一測試回合,每個測試結果檔案將作為個別測試回合在 VSO/TFS 中發行。", + "loc.messages.LIB_PlatformNotSupported": "不支援的平台: %s", + "loc.messages.LIB_CopyFileFailed": "複製檔案時發生錯誤。剩餘嘗試次數: %s" } \ No newline at end of file diff --git a/node/ThirdPartyNotice.txt b/node/ThirdPartyNotice.txt new file mode 100644 index 000000000..ddde2acda --- /dev/null +++ b/node/ThirdPartyNotice.txt @@ -0,0 +1,1114 @@ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +Do Not Translate or Localize + +This Azure Pipelines extension (azure-pipelines-task-lib) is based on or incorporates material from the projects listed below (Third Party IP). The original copyright notice and the license under which Microsoft received such Third Party IP, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party IP to you under the licensing terms for the Visual Studio Team Services extension. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. + +1. asap (git+https://github.com/kriskowal/asap.git) +2. balanced-match (git://github.com/juliangruber/balanced-match.git) +3. brace-expansion (git://github.com/juliangruber/brace-expansion.git) +4. browser-stdout (git+ssh://git@github.com/kumavis/browser-stdout.git) +5. caseless (git+https://github.com/mikeal/caseless.git) +6. concat-map (git://github.com/substack/node-concat-map.git) +7. concat-stream (git+ssh://git@github.com/maxogden/concat-stream.git) +8. core-util-is (git://github.com/isaacs/core-util-is.git) +9. fs.realpath (git+https://github.com/isaacs/fs.realpath.git) +10. has-flag (git+https://github.com/sindresorhus/has-flag.git) +11. he (git+https://github.com/mathiasbynens/he.git) +12. http-basic (git+https://github.com/ForbesLindesay/http-basic.git) +13. http-response-object (git+https://github.com/ForbesLindesay/http-response-object.git) +14. inflight (git+https://github.com/npm/inflight.git) +15. inherits (git://github.com/isaacs/inherits.git) +16. isarray (git://github.com/juliangruber/isarray.git) +17. minimatch (git://github.com/isaacs/minimatch.git) +18. minimist (git://github.com/substack/minimist.git) +19. mkdirp (git+https://github.com/substack/node-mkdirp.git) +20. mocha (git+https://github.com/mochajs/mocha.git) +21. mockery (git://github.com/mfncooper/mockery.git) +22. once (git://github.com/isaacs/once.git) +23. path-is-absolute (git+https://github.com/sindresorhus/path-is-absolute.git) +24. process-nextick-args (git+https://github.com/calvinmetcalf/process-nextick-args.git) +25. promise (git+https://github.com/then/promise.git) +26. q (git://github.com/kriskowal/q.git) +27. qs (git+https://github.com/ljharb/qs.git) +28. readable-stream (git://github.com/nodejs/readable-stream.git) +29. safe-buffer (git://github.com/feross/safe-buffer.git) +30. semver (git+https://github.com/npm/node-semver.git) +31. shelljs (git://github.com/arturadib/shelljs.git) +32. string_decoder (git://github.com/rvagg/string_decoder.git) +33. sync-request (git+https://github.com/ForbesLindesay/sync-request.git) +34. then-request (git+https://github.com/then/then-request.git) +35. typedarray (git://github.com/substack/typedarray.git) +36. typescript (git+https://github.com/Microsoft/TypeScript.git) +37. util-deprecate (git://github.com/TooTallNate/util-deprecate.git) +38. uuid (git+https://github.com/kelektiv/node-uuid.git) +39. wrappy (git+https://github.com/npm/wrappy.git) + + +%% asap NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright 2009–2014 Contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +========================================= +END OF asap NOTICES, INFORMATION, AND LICENSE + +%% balanced-match NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF balanced-match NOTICES, INFORMATION, AND LICENSE + +%% brace-expansion NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +MIT License + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF brace-expansion NOTICES, INFORMATION, AND LICENSE + +%% browser-stdout NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright 2018 kumavis + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF browser-stdout NOTICES, INFORMATION, AND LICENSE + +%% caseless NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +1. Definitions. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. +END OF TERMS AND CONDITIONS +========================================= +END OF caseless NOTICES, INFORMATION, AND LICENSE + +%% concat-map NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF concat-map NOTICES, INFORMATION, AND LICENSE + +%% concat-stream NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License + +Copyright (c) 2013 Max Ogden + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF concat-stream NOTICES, INFORMATION, AND LICENSE + +%% core-util-is NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +========================================= +END OF core-util-is NOTICES, INFORMATION, AND LICENSE + +%% fs.realpath NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---- + +This library bundles a version of the `fs.realpath` and `fs.realpathSync` +methods from Node.js v0.10 under the terms of the Node.js MIT license. + +Node's license follows, also included at the header of `old.js` which contains +the licensed code: + + Copyright Joyent, Inc. and other Node contributors. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +========================================= +END OF fs.realpath NOTICES, INFORMATION, AND LICENSE + +%% has-flag NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF has-flag NOTICES, INFORMATION, AND LICENSE + +%% he NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF he NOTICES, INFORMATION, AND LICENSE + +%% http-basic NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF http-basic NOTICES, INFORMATION, AND LICENSE + +%% http-response-object NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF http-response-object NOTICES, INFORMATION, AND LICENSE + +%% inflight NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF inflight NOTICES, INFORMATION, AND LICENSE + +%% inherits NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF inherits NOTICES, INFORMATION, AND LICENSE + +%% isarray NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +No license text available. +========================================= +END OF isarray NOTICES, INFORMATION, AND LICENSE + +%% minimatch NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF minimatch NOTICES, INFORMATION, AND LICENSE + +%% minimist NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF minimist NOTICES, INFORMATION, AND LICENSE + +%% mkdirp NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF mkdirp NOTICES, INFORMATION, AND LICENSE + +%% mocha NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2011-2018 JS Foundation and contributors, https://js.foundation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF mocha NOTICES, INFORMATION, AND LICENSE + +%% mockery NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyrights for code authored by Yahoo! Inc. is licensed under the following + terms: + + MIT License + + Copyright (c) 2011 Yahoo! Inc. All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +========================================= +END OF mockery NOTICES, INFORMATION, AND LICENSE + +%% once NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF once NOTICES, INFORMATION, AND LICENSE + +%% path-is-absolute NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF path-is-absolute NOTICES, INFORMATION, AND LICENSE + +%% process-nextick-args NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +# Copyright (c) 2015 Calvin Metcalf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.** +========================================= +END OF process-nextick-args NOTICES, INFORMATION, AND LICENSE + +%% promise NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF promise NOTICES, INFORMATION, AND LICENSE + +%% q NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright 2009–2017 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +========================================= +END OF q NOTICES, INFORMATION, AND LICENSE + +%% qs NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Nathan LaFreniere and other contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of any contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + * * * + +The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors +========================================= +END OF qs NOTICES, INFORMATION, AND LICENSE + +%% readable-stream NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" +========================================= +END OF readable-stream NOTICES, INFORMATION, AND LICENSE + +%% safe-buffer NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF safe-buffer NOTICES, INFORMATION, AND LICENSE + +%% semver NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF semver NOTICES, INFORMATION, AND LICENSE + +%% shelljs NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2012, Artur Adib +All rights reserved. + +You may use this project under the terms of the New BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Artur Adib nor the + names of the contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF shelljs NOTICES, INFORMATION, AND LICENSE + +%% string_decoder NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" +========================================= +END OF string_decoder NOTICES, INFORMATION, AND LICENSE + +%% sync-request NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF sync-request NOTICES, INFORMATION, AND LICENSE + +%% then-request NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF then-request NOTICES, INFORMATION, AND LICENSE + +%% typedarray NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +/* + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2012, Joshua Bell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + $/LicenseInfo$ + */ + +// Original can be found at: +// https://bitbucket.org/lindenlab/llsd +// Modifications by Joshua Bell inexorabletash@gmail.com +// https://github.com/inexorabletash/polyfill + +// ES3/ES5 implementation of the Krhonos Typed Array Specification +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ +// Date: 2011-02-01 +// +// Variations: +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) +========================================= +END OF typedarray NOTICES, INFORMATION, AND LICENSE + +%% typescript NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS +========================================= +END OF typescript NOTICES, INFORMATION, AND LICENSE + +%% util-deprecate NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF util-deprecate NOTICES, INFORMATION, AND LICENSE + +%% uuid NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2010-2016 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF uuid NOTICES, INFORMATION, AND LICENSE + +%% wrappy NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF wrappy NOTICES, INFORMATION, AND LICENSE + diff --git a/node/buildutils.js b/node/buildutils.js index fe6b90b2d..1ecd94bbe 100644 --- a/node/buildutils.js +++ b/node/buildutils.js @@ -33,8 +33,9 @@ exports.getExternals = function () { // download the same version of node used by the agent // and add node to the PATH - var nodeUrl = 'https://nodejs.org/dist'; - var nodeVersion = 'v5.10.1'; + var nodeUrl = process.env['TASK_NODE_URL'] || 'https://nodejs.org/dist'; + nodeUrl = nodeUrl.replace(/\/$/, ''); // ensure there is no trailing slash on the base URL + var nodeVersion = 'v16.13.0'; switch (platform) { case 'darwin': var nodeArchivePath = downloadArchive(nodeUrl + '/' + nodeVersion + '/node-' + nodeVersion + '-darwin-x64.tar.gz'); diff --git a/node/dependencies/typings.json b/node/dependencies/typings.json deleted file mode 100644 index 4d3fecabe..000000000 --- a/node/dependencies/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "vsts-task-lib", - "main": "task.d.ts", - "globalDependencies": { - "node": "registry:dt/node#6.0.0+20160709114037", - "q": "registry:dt/q#0.0.0+20160613154756" - } -} diff --git a/node/docs/TypeScriptSourceToJson.ts b/node/docs/TypeScriptSourceToJson.ts index f0b1e6c73..a6e7c8ee6 100644 --- a/node/docs/TypeScriptSourceToJson.ts +++ b/node/docs/TypeScriptSourceToJson.ts @@ -26,11 +26,10 @@ export function generate(filePaths: string[], options: ts.CompilerOptions): DocE program = ts.createProgram(filePaths, options); checker = program.getTypeChecker(); - let files: ts.SourceFile[] = program.getSourceFiles(); for (const sourceFile of program.getSourceFiles()) { // only document files we specified. dependency files may be in program if (filePaths.indexOf(sourceFile.fileName) >= 0) { - let name = path.basename(sourceFile.fileName, '.ts'); + let name = path.basename(sourceFile.fileName, '.ts'); console.log('Processing:', name); let fd: DocEntry = { @@ -38,14 +37,14 @@ export function generate(filePaths: string[], options: ts.CompilerOptions): DocE kind: 'file', members: {} as { string: [DocEntry]} }; - + doc.members[name] = fd; push(fd); ts.forEachChild(sourceFile, visit); } } - + return doc; } @@ -80,7 +79,7 @@ function visit(node: ts.Node): void { else if (node.kind == ts.SyntaxKind.InterfaceDeclaration) { if (!isNodeExported(node)) { return; - } + } let id: ts.InterfaceDeclaration = node; let symbol = checker.getSymbolAtLocation(id.name); if (symbol) { @@ -100,7 +99,7 @@ function visit(node: ts.Node): void { let memberDeclarations: ts.Declaration[] = s.getDeclarations(); if (memberDeclarations.length > 0) { let memberDoc: DocEntry = {}; - memberDoc.documentation = ts.displayPartsToString(s.getDocumentationComment()); + memberDoc.documentation = ts.displayPartsToString(s.getDocumentationComment(checker)); memberDoc.name = memberName; memberDoc.return = checker.typeToString(checker.getTypeAtLocation(memberDeclarations[0])) doc.members[memberName] = memberDoc; @@ -109,7 +108,7 @@ function visit(node: ts.Node): void { current.members[doc.name] = doc; push(doc); - } + } } if (node.kind == ts.SyntaxKind.EndOfFileToken) { inClass = false; @@ -118,7 +117,7 @@ function visit(node: ts.Node): void { else if (node.kind == ts.SyntaxKind.MethodDeclaration) { let m: ts.MethodDeclaration = node; let symbol = checker.getSymbolAtLocation(m.name); - + if (symbol) { let doc: DocEntry = getDocEntryFromSymbol(symbol); doc.kind = 'method'; @@ -156,7 +155,7 @@ function visit(node: ts.Node): void { } } // handle re-export from internal.ts - else if (node.kind === ts.SyntaxKind.VariableStatement && node.flags === ts.NodeFlags.Export) { + else if (node.kind === ts.SyntaxKind.VariableStatement && node.flags === ts.NodeFlags.ExportContext) { let statement = node; let list: ts.VariableDeclarationList = statement.declarationList; for (let declaration of list.declarations) { @@ -171,13 +170,12 @@ function visit(node: ts.Node): void { } } - ts.forEachChild(node, visit); + ts.forEachChild(node, visit); } function getDocEntryFromSignature(signature: ts.Signature): DocEntry { let paramEntries: DocEntry[] = []; - let params: ts.Symbol[] = signature.parameters; - params.forEach((ps: ts.Symbol) => { + signature.parameters.forEach((ps: ts.Symbol) => { let de = {} as DocEntry; de.name = ps.getName(); @@ -185,7 +183,7 @@ function getDocEntryFromSignature(signature: ts.Signature): DocEntry { let paramType: ts.Type = checker.getTypeAtLocation(decls[0]); de.type = checker.typeToString(paramType); de.optional = checker.isOptionalParameter(ps.declarations[0] as ts.ParameterDeclaration); - de.documentation = ts.displayPartsToString(ps.getDocumentationComment()); + de.documentation = ts.displayPartsToString(ps.getDocumentationComment(checker)); paramEntries.push(de); }); @@ -193,7 +191,7 @@ function getDocEntryFromSignature(signature: ts.Signature): DocEntry { parameters: paramEntries, members: {} as { string: [DocEntry]}, return: checker.typeToString(signature.getReturnType()), - documentation: ts.displayPartsToString(signature.getDocumentationComment()) + documentation: ts.displayPartsToString(signature.getDocumentationComment(checker)) }; return e; @@ -203,19 +201,19 @@ function getDocEntryFromSymbol(symbol: ts.Symbol): DocEntry { return { name: symbol.getName(), members: {} as { string: [DocEntry]}, - documentation: ts.displayPartsToString(symbol.getDocumentationComment()), - + documentation: ts.displayPartsToString(symbol.getDocumentationComment(checker)), + //type: checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)) }; } /** True if this is visible outside this file, false otherwise */ function isNodeExported(node: ts.Node): boolean { - return (node.flags & ts.NodeFlags.Export) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile); -} + return (node.flags & ts.NodeFlags.ExportContext) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile); +} // -// convenience stack +// convenience stack // let push = function(entry: DocEntry) { diff --git a/node/docs/vsts-task-lib.json b/node/docs/azure-pipelines-task-lib.json similarity index 86% rename from node/docs/vsts-task-lib.json rename to node/docs/azure-pipelines-task-lib.json index 91ef1f065..16e076153 100644 --- a/node/docs/vsts-task-lib.json +++ b/node/docs/azure-pipelines-task-lib.json @@ -35,7 +35,7 @@ "return": "{ [key: string]: string; }" }, "silent": { - "documentation": "optional. defaults to fales ", + "documentation": "optional. defaults to false ", "name": "silent", "return": "boolean" }, @@ -157,6 +157,39 @@ } ] }, + "_processLineBuffer": { + "name": "_processLineBuffer", + "members": {}, + "documentation": "", + "kind": "method", + "signatures": [ + { + "parameters": [ + { + "name": "data", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "strBuffer", + "type": "string", + "optional": false, + "documentation": "" + }, + { + "name": "onLine", + "type": "(line: string) => void", + "optional": false, + "documentation": "" + } + ], + "members": {}, + "return": "void", + "documentation": "" + } + ] + }, "_getSpawnFileName": { "name": "_getSpawnFileName", "members": {}, @@ -393,6 +426,12 @@ "type": "ToolRunner", "optional": false, "documentation": "" + }, + { + "name": "file", + "type": "string", + "optional": true, + "documentation": "optional filename to additionally stream the output to." } ], "members": {}, @@ -601,7 +640,7 @@ } ], "members": {}, - "return": "string", + "return": "string | undefined", "documentation": "Gets a variable value that is defined on the build/release definition or set at runtime.\n\n@returns string" } ] @@ -641,6 +680,20 @@ } ] }, + "getAgentMode": { + "name": "getAgentMode", + "members": {}, + "documentation": "Gets a agent hosted mode: Unknown, SelfHosted or MsHosted.\nRequires a 2.212.0 agent or higher for full functionality. With lower version returns AgentHostedMode.Unknown value. \n\n@returns AgentHostedMode", + "kind": "function", + "signatures": [ + { + "parameters": [], + "members": {}, + "return": "AgentHostedMode", + "documentation": "Gets a agent hosted mode: Unknown, SelfHosted or MsHosted.\nRequires a 2.212.0 agent or higher for full functionality. With lower version returns AgentHostedMode.Unknown value. \n\n@returns AgentHostedMode" + } + ] + }, "setVariable": { "name": "setVariable", "members": {}, @@ -665,7 +718,7 @@ "name": "secret", "type": "boolean", "optional": true, - "documentation": "whether variable is secret. optional, defaults to false" + "documentation": "whether variable is secret. Multi-line secrets are not allowed. Optional, defaults to false" } ], "members": {}, @@ -674,6 +727,27 @@ } ] }, + "setSecret": { + "name": "setSecret", + "members": {}, + "documentation": "Registers a value with the logger, so the value will be masked from the logs. Multi-line secrets are not allowed.", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "val", + "type": "string", + "optional": false, + "documentation": "value to register" + } + ], + "members": {}, + "return": "void", + "documentation": "Registers a value with the logger, so the value will be masked from the logs. Multi-line secrets are not allowed." + } + ] + }, "VariableInfo": { "name": "VariableInfo", "members": { @@ -699,7 +773,7 @@ "getInput": { "name": "getInput", "members": {}, - "documentation": "Gets the value of an input. The value is also trimmed.\nIf required is true and the value is not set, it will throw.\n\n@returns string", + "documentation": "Gets the value of an input. The value is also trimmed.\nIf required is true and the value is not set, it will throw.\n\n@returns string | undefined", "kind": "function", "signatures": [ { @@ -718,8 +792,29 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "Gets the value of an input. The value is also trimmed.\nIf required is true and the value is not set, it will throw.\n\n@returns string | undefined" + } + ] + }, + "getInputRequired": { + "name": "getInputRequired", + "members": {}, + "documentation": "Gets the value of an input. The value is also trimmed.\nIf the value is not set, it will throw.\n\n@returns string", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "name", + "type": "string", + "optional": false, + "documentation": "name of the input to get" + } + ], + "members": {}, "return": "string", - "documentation": "Gets the value of an input. The value is also trimmed.\nIf required is true and the value is not set, it will throw.\n\n@returns string" + "documentation": "Gets the value of an input. The value is also trimmed.\nIf the value is not set, it will throw.\n\n@returns string" } ] }, @@ -807,7 +902,7 @@ "getPathInput": { "name": "getPathInput", "members": {}, - "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf required is true and the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string", + "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf required is true and the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string | undefined", "kind": "function", "signatures": [ { @@ -832,15 +927,42 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf required is true and the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string | undefined" + } + ] + }, + "getPathInputRequired": { + "name": "getPathInputRequired", + "members": {}, + "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "name", + "type": "string", + "optional": false, + "documentation": "name of the input to get" + }, + { + "name": "check", + "type": "boolean", + "optional": true, + "documentation": "whether path is checked. optional, defaults to false " + } + ], + "members": {}, "return": "string", - "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf required is true and the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string" + "documentation": "Gets the value of a path input\nIt will be quoted for you if it isn't already and contains spaces\nIf the value is not set, it will throw.\nIf check is true and the path does not exist, it will throw.\n\n@returns string" } ] }, "getEndpointUrl": { "name": "getEndpointUrl", "members": {}, - "documentation": "Gets the url for a service endpoint\nIf the url was not set and is not optional, it will throw.\n\n@returns string", + "documentation": "Gets the url for a service endpoint\nIf the url was not set and is not optional, it will throw.\n\n@returns string | undefined", "kind": "function", "signatures": [ { @@ -859,8 +981,29 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "Gets the url for a service endpoint\nIf the url was not set and is not optional, it will throw.\n\n@returns string | undefined" + } + ] + }, + "getEndpointUrlRequired": { + "name": "getEndpointUrlRequired", + "members": {}, + "documentation": "Gets the url for a service endpoint\nIf the url was not set, it will throw.\n\n@returns string", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "id", + "type": "string", + "optional": false, + "documentation": "name of the service endpoint" + } + ], + "members": {}, "return": "string", - "documentation": "Gets the url for a service endpoint\nIf the url was not set and is not optional, it will throw.\n\n@returns string" + "documentation": "Gets the url for a service endpoint\nIf the url was not set, it will throw.\n\n@returns string" } ] }, @@ -892,6 +1035,33 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "" + } + ] + }, + "getEndpointDataParameterRequired": { + "name": "getEndpointDataParameterRequired", + "members": {}, + "documentation": "", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "id", + "type": "string", + "optional": false, + "documentation": "" + }, + { + "name": "key", + "type": "string", + "optional": false, + "documentation": "" + } + ], + "members": {}, "return": "string", "documentation": "" } @@ -900,7 +1070,7 @@ "getEndpointAuthorizationScheme": { "name": "getEndpointAuthorizationScheme", "members": {}, - "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization scheme", + "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization scheme or undefined", "kind": "function", "signatures": [ { @@ -919,15 +1089,36 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization scheme or undefined" + } + ] + }, + "getEndpointAuthorizationSchemeRequired": { + "name": "getEndpointAuthorizationSchemeRequired", + "members": {}, + "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set, it will throw.\n\n@returns {string} value of the endpoint authorization scheme", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "id", + "type": "string", + "optional": false, + "documentation": "name of the service endpoint" + } + ], + "members": {}, "return": "string", - "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization scheme" + "documentation": "Gets the endpoint authorization scheme for a service endpoint\nIf the endpoint authorization scheme is not set, it will throw.\n\n@returns {string} value of the endpoint authorization scheme" } ] }, "getEndpointAuthorizationParameter": { "name": "getEndpointAuthorizationParameter", "members": {}, - "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value", + "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value or undefined", "kind": "function", "signatures": [ { @@ -952,8 +1143,35 @@ } ], "members": {}, + "return": "string | undefined", + "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value or undefined" + } + ] + }, + "getEndpointAuthorizationParameterRequired": { + "name": "getEndpointAuthorizationParameterRequired", + "members": {}, + "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value", + "kind": "function", + "signatures": [ + { + "parameters": [ + { + "name": "id", + "type": "string", + "optional": false, + "documentation": "name of the service endpoint" + }, + { + "name": "key", + "type": "string", + "optional": false, + "documentation": "key to find the endpoint authorization parameter" + } + ], + "members": {}, "return": "string", - "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set and is not optional, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value" + "documentation": "Gets the endpoint authorization parameter value for a service endpoint with specified key\nIf the endpoint authorization parameter is not set, it will throw.\n\n@returns {string} value of the endpoint authorization parameter value" } ] }, @@ -1611,6 +1829,11 @@ "FindOptions": { "name": "FindOptions", "members": { + "allowBrokenSymbolicLinks": { + "documentation": "When true, broken symbolic link will not cause an error.", + "name": "allowBrokenSymbolicLinks", + "return": "boolean" + }, "followSpecifiedSymbolicLink": { "documentation": "Equivalent to the -H command line option. Indicates whether to traverse descendants if\nthe specified path is a symbolic link directory. Does not cause nested symbolic link\ndirectories to be traversed.", "name": "followSpecifiedSymbolicLink", @@ -2136,13 +2359,66 @@ "kind": "function", "signatures": [ { - "parameters": [], + "parameters": [ + { + "name": "requestUrl", + "type": "string", + "optional": true, + "documentation": "" + } + ], "members": {}, "return": "ProxyConfiguration", "documentation": "Gets http proxy configuration used by Build/Release agent\n\n@return ProxyConfiguration" } ] }, + "CertConfiguration": { + "name": "CertConfiguration", + "members": { + "caFile": { + "documentation": "", + "name": "caFile", + "return": "string" + }, + "certFile": { + "documentation": "", + "name": "certFile", + "return": "string" + }, + "keyFile": { + "documentation": "", + "name": "keyFile", + "return": "string" + }, + "certArchiveFile": { + "documentation": "", + "name": "certArchiveFile", + "return": "string" + }, + "passphrase": { + "documentation": "", + "name": "passphrase", + "return": "string" + } + }, + "documentation": "", + "kind": "interface" + }, + "getHttpCertConfiguration": { + "name": "getHttpCertConfiguration", + "members": {}, + "documentation": "Gets http certificate configuration used by Build/Release agent\n\n@return CertConfiguration", + "kind": "function", + "signatures": [ + { + "parameters": [], + "members": {}, + "return": "CertConfiguration", + "documentation": "Gets http certificate configuration used by Build/Release agent\n\n@return CertConfiguration" + } + ] + }, "TestPublisher": { "name": "TestPublisher", "members": { @@ -2194,6 +2470,55 @@ "members": {}, "return": "void", "documentation": "" + }, + { + "parameters": [ + { + "name": "resultsFiles", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "mergeResults", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "platform", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "config", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "runTitle", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "publishRunAttachments", + "type": "any", + "optional": false, + "documentation": "" + }, + { + "name": "testRunSystem", + "type": "any", + "optional": false, + "documentation": "" + } + ], + "members": {}, + "return": "void", + "documentation": "" } ] } @@ -2318,41 +2643,8 @@ "documentation": "" } ] - }, - "publishTelemetry": { - "name": "publishTelemetry", - "members": {}, - "documentation": "Publishes the telemetry data\n\n@returns void", - "kind": "function", - "signatures": [ - { - "parameters": [ - { - "name": "area", - "type": "string", - "optional": false, - "documentation": "Area path where the telemetry will be published" - }, - { - "name": "feature", - "type": "string", - "optional": false, - "documentation": "Feature group where the telemetry will be published" - }, - { - "name": "properties", - "type": "{ [key: string]: any; }", - "optional": false, - "documentation": "telemetry data which will be published" - } - ], - "members": {}, - "return": "void", - "documentation": "Publishes the telemetry data\n\n@returns void" - } - ] } } } } -} \ No newline at end of file +} diff --git a/node/docs/vsts-task-lib.md b/node/docs/azure-pipelines-task-lib.md similarity index 79% rename from node/docs/vsts-task-lib.md rename to node/docs/azure-pipelines-task-lib.md index df8812fe2..f94a8913f 100644 --- a/node/docs/vsts-task-lib.md +++ b/node/docs/azure-pipelines-task-lib.md @@ -1,30 +1,32 @@ -# VSTS-TASK-LIB TYPESCRIPT API - +# AZURE-PIPELINES-TASK-LIB TYPESCRIPT API + ## Dependencies A [cross platform agent](https://github.com/Microsoft/vso-agent) OR a TFS 2015 Update 2 Windows agent (or higher) is required to run a Node task end-to-end. However, an agent is not required for interactively testing the task. - + ## Importing -For now, the built vsts-task-lib (in _build) should be packaged with your task in a node_modules folder - -The build generates a vsts-task-lib.d.ts file for use when compiling tasks +For now, the built azure-pipelines-task-lib (in _build) should be packaged with your task in a node_modules folder + +The build generates a azure-pipelines-task-lib.d.ts file for use when compiling tasks In the example below, it is in a folder named definitions above the tasks lib - + ``` -/// -import tl = require('vsts-task-lib/task') +/// +import tl = require('azure-pipelines-task-lib/task') ``` - + ## [Release notes](releases.md) - +
- + ## Index - + ### Input Functions (v) - + getInput
+getInputRequired
getBoolInput
getPathInput
+getPathInputRequired
filePathSupplied
getDelimitedInput
getVariable
@@ -33,9 +35,10 @@ import tl = require('vsts-task-lib/task') setVariable
getTaskVariable
setTaskVariable
- +getAgentMode
+ ### Execution (v) - + tool
ToolRunner.arg
ToolRunner.line
@@ -48,23 +51,31 @@ import tl = require('vsts-task-lib/task') exec
execSync
setResult
- -### Service Endpoints (v) - + +### Service Connections (v) + getEndpointUrl
+getEndpointUrlRequired
getEndpointDataParameter
+getEndpointDataParameterRequired
getEndpointAuthorizationScheme
+getEndpointAuthorizationSchemeRequired
getEndpointAuthorizationParameter
+getEndpointAuthorizationParameterRequired
EndpointAuthorization
getEndpointAuthorization
- + +### Secrets (v) + +setSecret
+ ### Secure Files (v) - + getSecureFileName
getSecureFileTicket
- + ### Disk Functions (v) - + which
checkPath
exist
@@ -80,52 +91,68 @@ import tl = require('vsts-task-lib/task') resolve
stats
writeFile
- + ### Globbing (v) - + MatchOptions
match
findMatch
filter
legacyFindFiles
- + ### Localization (v) - + setResourcePath
loc
- + ### Proxy (v) - + getHttpProxyConfiguration
- +
- + ## Input Functions - + --- - + Functions for retrieving inputs for the task
- + ### task.getInput (^) Gets the value of an input. The value is also trimmed. If required is true and the value is not set, it will throw. @returns string ```javascript -getInput(name:string, required?:boolean):string +getInput(name:string, required?:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- name | string | name of the input to get required | boolean | whether input is required. optional, defaults to false - + +
+
+ +### task.getInputRequired (^) +Gets the value of an input. The value is also trimmed. +If the value is not set, it will throw. + +@returns string +```javascript +getInputRequired(name:string):string +``` + +Param | Type | Description +--- | --- | --- +name | string | name of the input to get +
- + ### task.getBoolInput (^) Gets the value of an input and converts to a bool. Convenience. If required is true and the value is not set, it will throw. @@ -134,15 +161,15 @@ If required is true and the value is not set, it will throw. ```javascript getBoolInput(name:string, required?:boolean):boolean ``` - + Param | Type | Description --- | --- | --- name | string | name of the bool input to get required | boolean | whether input is required. optional, defaults to false - +
- + ### task.getPathInput (^) Gets the value of a path input It will be quoted for you if it isn't already and contains spaces @@ -151,18 +178,37 @@ If check is true and the path does not exist, it will throw. @returns string ```javascript -getPathInput(name:string, required?:boolean, check?:boolean):string +getPathInput(name:string, required?:boolean, check?:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- name | string | name of the input to get required | boolean | whether input is required. optional, defaults to false -check | boolean | whether path is checked. optional, defaults to false - +check | boolean | whether path is checked. optional, defaults to false + +
+
+ +### task.getPathInputRequired (^) +Gets the value of a path input +It will be quoted for you if it isn't already and contains spaces +If the value is not set, it will throw. +If check is true and the path does not exist, it will throw. + +@returns string +```javascript +getPathInputRequired(name:string, check?:boolean):string +``` + +Param | Type | Description +--- | --- | --- +name | string | name of the input to get +check | boolean | whether path is checked. optional, defaults to false +
- + ### task.filePathSupplied (^) Checks whether a path inputs value was supplied by the user File paths are relative with a picker, so an empty path is the root of the repo. @@ -172,14 +218,14 @@ Useful if you need to condition work (like append an arg) if a value was supplie ```javascript filePathSupplied(name:string):boolean ``` - + Param | Type | Description --- | --- | --- name | string | name of the path input to check - +
- + ### task.getDelimitedInput (^) Gets the value of an input and splits the value using a delimiter (space, comma, etc). Empty values are removed. This function is useful for splitting an input containing a simple @@ -192,43 +238,43 @@ If required is true and the value is not set, it will throw. ```javascript getDelimitedInput(name:string, delim:string, required?:boolean):string[] ``` - + Param | Type | Description --- | --- | --- name | string | name of the input to get delim | string | delimiter to split on required | boolean | whether input is required. optional, defaults to false - +
- + ### task.getVariable (^) Gets a variable value that is defined on the build/release definition or set at runtime. @returns string ```javascript -getVariable(name:string):string +getVariable(name:string):string | undefined ``` - + Param | Type | Description --- | --- | --- name | string | name of the variable to get - +
- + ### task.VariableInfo (^) -Snapshot of a variable at the time when getVariables was called. - +Snapshot of a variable at the time when getVariables was called. + Property | Type | Description --- | --- | --- -name | string | -value | string | -secret | boolean | - +name | string | +value | string | +secret | boolean | +
- + ### task.getVariables (^) Gets a snapshot of the current state of all job variables available to the task. Requires a 2.104.1 agent or higher for full functionality. @@ -244,10 +290,22 @@ Limitations on an agent prior to 2.104.1: ```javascript getVariables():VariableInfo[] ``` - + +
+
+ +### task.getAgentMode (^) +Gets a agent hosted mode: Unknown, SelfHosted or MsHosted. +Requires a 2.212.0 agent or higher for full functionality. With lower version returns AgentHostedMode.Unknown value. + +@returns AgentHostedMode +```javascript +getAgentMode():AgentHostedMode +``` +
- + ### task.setVariable (^) Sets a variable which will be available to subsequent tasks as well. @@ -255,32 +313,32 @@ Sets a variable which will be available to subsequent tasks as well. ```javascript setVariable(name:string, val:string, secret?:boolean):void ``` - + Param | Type | Description --- | --- | --- name | string | name of the variable to set val | string | value to set -secret | boolean | whether variable is secret. optional, defaults to false - +secret | boolean | whether variable is secret. Multi\-line secrets are not allowed. Optional, defaults to false +
- + ### task.getTaskVariable (^) Gets a variable value that is set by previous step from the same wrapper task. Requires a 2.115.0 agent or higher. @returns string ```javascript -getTaskVariable(name:string):string +getTaskVariable(name:string):string | undefined ``` - + Param | Type | Description --- | --- | --- name | string | name of the variable to get - +
- + ### task.setTaskVariable (^) Sets a task variable which will only be available to subsequent steps belong to the same wrapper task. Requires a 2.115.0 agent or higher. @@ -289,32 +347,32 @@ Requires a 2.115.0 agent or higher. ```javascript setTaskVariable(name:string, val:string, secret?:boolean):void ``` - + Param | Type | Description --- | --- | --- name | string | name of the variable to set val | string | value to set secret | boolean | whether variable is secret. optional, defaults to false - - + +
- + ## Execution - + --- - + Tasks typically execute a series of tools (cli) and set the result of the task based on the outcome of those - + ```javascript -/// -import tl = require('vsts-task-lib/task'); -import tr = require('vsts-task-lib/toolrunner'); +/// +import tl = require('azure-pipelines-task-lib/task'); +import tr = require('azure-pipelines-task-lib/toolrunner'); try { var toolPath = tl.which('atool'); var atool:tr.ToolRunner = tl.tool(toolPath).arg('--afile').line('arguments'); - var code: number = await tr.exec(); + var code: number = await atool.exec(); console.log('rc=' + code); } catch (err) { @@ -323,7 +381,7 @@ catch (err) { ```
- + ### task.tool (^) Convenience factory to create a ToolRunner. @@ -331,31 +389,31 @@ Convenience factory to create a ToolRunner. ```javascript tool(tool:string):ToolRunner ``` - + Param | Type | Description --- | --- | --- tool | string | path to tool to exec - +
- + ### toolrunner.ToolRunner.arg (^) Add argument -Append an argument or an array of arguments +Append an argument or an array of arguments returns ToolRunner for chaining @returns ToolRunner ```javascript arg(val:string | string[]):ToolRunner ``` - + Param | Type | Description --- | --- | --- val | string \| string\[\] | string cmdline or array of strings - +
- + ### toolrunner.ToolRunner.line (^) Parses an argument line into one or more arguments e.g. .line('"arg one" two -z') is equivalent to .arg(['arg one', 'two', '-z']) @@ -365,14 +423,14 @@ returns ToolRunner for chaining ```javascript line(val:string):ToolRunner ``` - + Param | Type | Description --- | --- | --- val | string | string argument line - +
- + ### toolrunner.ToolRunner.argIf (^) Add argument(s) if a condition is met Wraps arg(). See arg for details @@ -382,26 +440,26 @@ returns ToolRunner for chaining ```javascript argIf(condition:any, val:any):this ``` - + Param | Type | Description --- | --- | --- condition | any | boolean condition val | any | string cmdline or array of strings - +
- + ### toolrunner.IExecOptions (^) Interface for exec options - + Property | Type | Description --- | --- | --- -failOnStdErr | boolean | optional. whether to fail if output to stderr. defaults to false -ignoreReturnCode | boolean | optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller - +failOnStdErr | boolean | optional. whether to fail if output to stderr. defaults to false +ignoreReturnCode | boolean | optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller +
- + ### toolrunner.ToolRunner.exec (^) Exec a tool. Output will be streamed to the live console. @@ -411,59 +469,60 @@ Returns promise with return code ```javascript exec(options?:IExecOptions):any ``` - + Param | Type | Description --- | --- | --- options | IExecOptions | optional exec options. See IExecOptions - +
- + ### toolrunner.ToolRunner.execSync (^) -Exec a tool synchronously. +Exec a tool synchronously. Output will be *not* be streamed to the live console. It will be returned after execution is complete. -Appropriate for short running tools +Appropriate for short running tools Returns IExecSyncResult with output and return code @returns IExecSyncResult ```javascript execSync(options?:IExecSyncOptions):IExecSyncResult ``` - + Param | Type | Description --- | --- | --- options | IExecSyncOptions | optional exec options. See IExecSyncOptions - +
- + ### toolrunner.ToolRunner.pipeExecOutputToTool (^) Pipe output of exec() to another tool @returns {ToolRunner} ```javascript -pipeExecOutputToTool(tool:ToolRunner):ToolRunner +pipeExecOutputToTool(tool:ToolRunner, file?:string):ToolRunner ``` - + Param | Type | Description --- | --- | --- -tool | ToolRunner | - +tool | ToolRunner | +file | string | optional filename to additionally stream the output to. +
- + ### toolrunner.IExecSyncResult (^) Interface for exec results returned from synchronous exec functions - + Property | Type | Description --- | --- | --- -stdout | string | standard output -stderr | string | error output -code | number | return code -error | Error | Error on failure - +stdout | string | standard output +stderr | string | error output +code | number | return code +error | Error | Error on failure +
- + ### task.exec (^) Exec a tool. Convenience wrapper over ToolRunner to exec with args in one call. Output will be streamed to the live console. @@ -473,36 +532,36 @@ Returns promise with return code ```javascript exec(tool:string, args:any, options?:IExecOptions):any ``` - + Param | Type | Description --- | --- | --- tool | string | path to tool to exec args | any | an arg string or array of args options | IExecOptions | optional exec options. See IExecOptions - +
- + ### task.execSync (^) Exec a tool synchronously. Convenience wrapper over ToolRunner to execSync with args in one call. Output will be *not* be streamed to the live console. It will be returned after execution is complete. -Appropriate for short running tools +Appropriate for short running tools Returns IExecResult with output and return code @returns IExecSyncResult ```javascript execSync(tool:string, args:string | string[], options?:IExecSyncOptions):IExecSyncResult ``` - + Param | Type | Description --- | --- | --- tool | string | path to tool to exec args | string \| string\[\] | an arg string or array of args options | IExecSyncOptions | optional exec options. See IExecSyncOptions - +
- + ### task.setResult (^) Sets the result of the task. Execution will continue. @@ -513,128 +572,212 @@ If multiple calls are made to setResult the most pessimistic call wins (Failed) ```javascript setResult(result:TaskResult, message:string):void ``` - + Param | Type | Description --- | --- | --- -result | TaskResult | TaskResult enum of Succeeded, SucceededWithIssues or Failed. +result | TaskResult | TaskResult enum of Succeeded, SucceededWithIssues or Failed. message | string | A message which will be logged as an error issue if the result is Failed. - - + +
-
- -## Service Endpoints - +
+ +## Service Connections + --- - -Retrieve service endpoints and authorization details + +Retrieve service connections (previously called "service endpoints") and authorization details
- + ### task.getEndpointUrl (^) Gets the url for a service endpoint If the url was not set and is not optional, it will throw. @returns string ```javascript -getEndpointUrl(id:string, optional:boolean):string +getEndpointUrl(id:string, optional:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- id | string | name of the service endpoint optional | boolean | whether the url is optional - + +
+
+ +### task.getEndpointUrlRequired (^) +Gets the url for a service endpoint +If the url was not set, it will throw. + +@returns string +```javascript +getEndpointUrlRequired(id:string):string +``` + +Param | Type | Description +--- | --- | --- +id | string | name of the service endpoint +
- + ### task.getEndpointDataParameter (^) ```javascript -getEndpointDataParameter(id:string, key:string, optional:boolean):string +getEndpointDataParameter(id:string, key:string, optional:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- -id | string | -key | string | -optional | boolean | - +id | string | +key | string | +optional | boolean | + +
+
+ +### task.getEndpointDataParameterRequired (^) +```javascript +getEndpointDataParameterRequired(id:string, key:string):string +``` + +Param | Type | Description +--- | --- | --- +id | string | +key | string | +
- + ### task.getEndpointAuthorizationScheme (^) Gets the endpoint authorization scheme for a service endpoint If the endpoint authorization scheme is not set and is not optional, it will throw. @returns {string} value of the endpoint authorization scheme ```javascript -getEndpointAuthorizationScheme(id:string, optional:boolean):string +getEndpointAuthorizationScheme(id:string, optional:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- id | string | name of the service endpoint optional | boolean | whether the endpoint authorization scheme is optional - + +
+
+ +### task.getEndpointAuthorizationSchemeRequired (^) +Gets the endpoint authorization scheme for a service endpoint +If the endpoint authorization scheme is not set, it will throw. + +@returns {string} value of the endpoint authorization scheme +```javascript +getEndpointAuthorizationSchemeRequired(id:string):string +``` + +Param | Type | Description +--- | --- | --- +id | string | name of the service endpoint +
- + ### task.getEndpointAuthorizationParameter (^) Gets the endpoint authorization parameter value for a service endpoint with specified key If the endpoint authorization parameter is not set and is not optional, it will throw. @returns {string} value of the endpoint authorization parameter value ```javascript -getEndpointAuthorizationParameter(id:string, key:string, optional:boolean):string +getEndpointAuthorizationParameter(id:string, key:string, optional:boolean):string | undefined ``` - + Param | Type | Description --- | --- | --- id | string | name of the service endpoint key | string | key to find the endpoint authorization parameter optional | boolean | optional whether the endpoint authorization scheme is optional - + +
+
+ +### task.getEndpointAuthorizationParameterRequired (^) +Gets the endpoint authorization parameter value for a service endpoint with specified key +If the endpoint authorization parameter is not set, it will throw. + +@returns {string} value of the endpoint authorization parameter value +```javascript +getEndpointAuthorizationParameterRequired(id:string, key:string):string +``` + +Param | Type | Description +--- | --- | --- +id | string | name of the service endpoint +key | string | key to find the endpoint authorization parameter +
- + ### task.EndpointAuthorization (^) Interface for EndpointAuthorization Contains a schema and a string/string dictionary of auth data - + Property | Type | Description --- | --- | --- -parameters | \{ \[key: string\]: string; \} | dictionary of auth data -scheme | string | auth scheme such as OAuth or username/password etc... - +parameters | \{ \[key: string\]: string; \} | dictionary of auth data +scheme | string | auth scheme such as OAuth or username/password etc... +
- + ### task.getEndpointAuthorization (^) Gets the authorization details for a service endpoint -If the authorization was not set and is not optional, it will throw. +If the authorization was not set and is not optional, it will set the task result to Failed. @returns string ```javascript getEndpointAuthorization(id:string, optional:boolean):EndpointAuthorization ``` - + Param | Type | Description --- | --- | --- id | string | name of the service endpoint optional | boolean | whether the url is optional - - + + +
+
+ +## Secrets + +--- + +Functions for managing pipeline secrets +
+
+ +### task.setSecret (^) +Registers a value with the logger, so the value will be masked from the logs. Multi-line secrets are not allowed. +```javascript +setSecret(val:string):void +``` + +Param | Type | Description +--- | --- | --- +val | string | value to register + +
- + ## Secure Files - + --- - + Retrieve secure files details required to download the file
- + ### task.getSecureFileName (^) Gets the name for a secure file @@ -642,14 +785,14 @@ Gets the name for a secure file ```javascript getSecureFileName(id:string):string ``` - + Param | Type | Description --- | --- | --- id | string | secure file id - +
- + ### task.getSecureFileTicket (^) Gets the secure file ticket that can be used to download the secure file contents @@ -657,23 +800,23 @@ Gets the secure file ticket that can be used to download the secure file content ```javascript getSecureFileTicket(id:string):string ``` - + Param | Type | Description --- | --- | --- id | string | name of the secure file - - + +
- + ## Disk Functions - + --- - + Functions for disk operations
- + ### task.which (^) Returns path of a tool had the tool actually been invoked. Resolves via paths. If you check and the tool does not exist, it will throw. @@ -682,15 +825,15 @@ If you check and the tool does not exist, it will throw. ```javascript which(tool:string, check?:boolean):string ``` - + Param | Type | Description --- | --- | --- tool | string | name of the tool check | boolean | whether to check if tool exists - +
- + ### task.checkPath (^) Checks whether a path exists. If the path does not exist, it will throw. @@ -699,77 +842,78 @@ If the path does not exist, it will throw. ```javascript checkPath(p:string, name:string):void ``` - + Param | Type | Description --- | --- | --- p | string | path to check name | string | name only used in error message to identify the path - +
- + ### task.exist (^) Returns whether a path exists. -@returns boolean +@returns boolean ```javascript exist(path:string):boolean ``` - + Param | Type | Description --- | --- | --- path | string | path to check - +
- + ### task.cd (^) Change working directory. -@returns void +@returns void ```javascript cd(path:string):void ``` - + Param | Type | Description --- | --- | --- path | string | new working directory path - +
- + ### task.cp (^) Copies a file or folder. ```javascript cp(source:string, dest:string, options?:string, continueOnError?:boolean):void ``` - + Param | Type | Description --- | --- | --- source | string | source path dest | string | destination path -options | string | string \-r, \-f or \-rf for recursive and force +options | string | string \-r, \-f or \-rf for recursive and force continueOnError | boolean | optional. whether to continue on error - +retryCount | number | optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. +
- + ### task.mv (^) Moves a path. ```javascript mv(source:string, dest:string, options?:string, continueOnError?:boolean):void ``` - + Param | Type | Description --- | --- | --- source | string | source path dest | string | destination path -options | string | string \-f or \-n for force and no clobber +options | string | string \-f or \-n for force and no clobber continueOnError | boolean | optional. whether to continue on error - +
- + ### task.mkdirP (^) Make a directory. Creates the full path with folders in between Will throw if it fails @@ -778,26 +922,27 @@ Will throw if it fails ```javascript mkdirP(p:string):void ``` - + Param | Type | Description --- | --- | --- p | string | path to create - +
- + ### task.FindOptions (^) Interface for FindOptions Contains properties to control whether to follow symlinks - + Property | Type | Description --- | --- | --- +allowBrokenSymbolicLinks | boolean | When true, broken symbolic link will not cause an error. followSpecifiedSymbolicLink | boolean | Equivalent to the \-H command line option. Indicates whether to traverse descendants if the specified path is a symbolic link directory. Does not cause nested symbolic link directories to be traversed. followSymbolicLinks | boolean | Equivalent to the \-L command line option. Indicates whether to traverse descendants of symbolic link directories. - +
- + ### task.find (^) Recursively finds all paths a given path. Returns an array of paths. @@ -805,15 +950,15 @@ Recursively finds all paths a given path. Returns an array of paths. ```javascript find(findPath:string, options?:FindOptions):string[] ``` - + Param | Type | Description --- | --- | --- findPath | string | path to search options | FindOptions | optional. defaults to \{ followSymbolicLinks: true \}. following soft links is generally appropriate unless deleting files. - +
- + ### task.rmRF (^) Remove a path recursively with force Returns whether it succeeds @@ -822,14 +967,14 @@ Returns whether it succeeds ```javascript rmRF(path:string):void ``` - + Param | Type | Description --- | --- | --- path | string | path to remove - +
- + ### task.pushd (^) Change working directory and push it on the stack @@ -837,14 +982,14 @@ Change working directory and push it on the stack ```javascript pushd(path:string):void ``` - + Param | Type | Description --- | --- | --- path | string | new working directory path - +
- + ### task.popd (^) Change working directory back to previously pushed directory @@ -852,10 +997,10 @@ Change working directory back to previously pushed directory ```javascript popd():void ``` - +
- + ### task.resolve (^) Resolves a sequence of paths or path segments into an absolute path. Calls node.js path.resolve() @@ -864,89 +1009,89 @@ Allows L0 testing with consistent path formats on Mac/Linux and Windows in the m ```javascript resolve(pathSegments:any[]):string ``` - + Param | Type | Description --- | --- | --- -pathSegments | any\[\] | - +pathSegments | any\[\] | +
- + ### task.stats (^) -Get's stat on a path. +Get's stat on a path. Useful for checking whether a file or directory. Also getting created, modified and accessed time. see [fs.stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) -@returns fsStat +@returns fsStat ```javascript stats(path:string):FsStats ``` - + Param | Type | Description --- | --- | --- path | string | path to check - +
- + ### task.writeFile (^) ```javascript writeFile(file:string, data:any, options?:string | FsOptions):void ``` - + Param | Type | Description --- | --- | --- -file | string | -data | any | -options | string \| FsOptions | - - +file | string | +data | any | +options | string \| FsOptions | + +
- + ## Globbing - + --- - + Functions for matching file paths
- + ### task.MatchOptions (^) - + Property | Type | Description --- | --- | --- -debug | boolean | -nobrace | boolean | -noglobstar | boolean | -dot | boolean | -noext | boolean | -nocase | boolean | -nonull | boolean | -matchBase | boolean | -nocomment | boolean | -nonegate | boolean | -flipNegate | boolean | - +debug | boolean | +nobrace | boolean | +noglobstar | boolean | +dot | boolean | +noext | boolean | +nocase | boolean | +nonull | boolean | +matchBase | boolean | +nocomment | boolean | +nonegate | boolean | +flipNegate | boolean | +
- + ### task.match (^) Applies glob patterns to a list of paths. Supports interleaved exclude patterns. ```javascript match(list:string[], patterns:string[] | string, patternRoot?:string, options?:MatchOptions):string[] ``` - + Param | Type | Description --- | --- | --- list | string\[\] | array of paths patterns | string\[\] \| string | patterns to apply. supports interleaved exclude patterns. patternRoot | string | optional. default root to apply to unrooted patterns. not applied to basename\-only patterns when matchBase:true. options | MatchOptions | optional. defaults to \{ dot: true, nobrace: true, nocase: process.platform == 'win32' \}. - +
- + ### task.findMatch (^) Determines the find root from a list of patterns. Performs the find and then applies the glob patterns. Supports interleaved exclude patterns. Unrooted patterns are rooted using defaultRoot, unless @@ -955,31 +1100,31 @@ defaultRoot is used as the find root. ```javascript findMatch(defaultRoot:string, patterns:string[] | string, findOptions?:FindOptions, matchOptions?:MatchOptions):string[] ``` - + Param | Type | Description --- | --- | --- defaultRoot | string | default path to root unrooted patterns. falls back to System.DefaultWorkingDirectory or process.cwd\(\). patterns | string\[\] \| string | pattern or array of patterns to apply findOptions | FindOptions | defaults to \{ followSymbolicLinks: true \}. following soft links is generally appropriate unless deleting files. matchOptions | MatchOptions | defaults to \{ dot: true, nobrace: true, nocase: process.platform == 'win32' \} - +
- + ### task.filter (^) Filter to apply glob patterns ```javascript filter(pattern:string, options?:MatchOptions):(element: string, indexed: number, array: string[]) => boolean ``` - + Param | Type | Description --- | --- | --- pattern | string | pattern to apply options | MatchOptions | optional. defaults to \{ dot: true, nobrace: true, nocase: process.platform == 'win32' \}. - +
- + ### task.legacyFindFiles (^) Prefer tl.find() and tl.match() instead. This function is for backward compatibility when porting tasks to Node from the PowerShell or PowerShell3 execution handler. @@ -988,26 +1133,26 @@ when porting tasks to Node from the PowerShell or PowerShell3 execution handler. ```javascript legacyFindFiles(rootDirectory:string, pattern:string, includeFiles?:boolean, includeDirectories?:boolean):string[] ``` - + Param | Type | Description --- | --- | --- rootDirectory | string | path to root unrooted patterns with pattern | string | include and exclude patterns includeFiles | boolean | whether to include files in the result. defaults to true when includeFiles and includeDirectories are both false includeDirectories | boolean | whether to include directories in the result - - + +
- + ## Localization - + --- - + Localization is optional but is supported using these functions at runtime - + ```javascript -/// +/// tl.setResourcePath(path.join( __dirname, 'task.json')); @@ -1025,7 +1170,7 @@ var errMsg = tl.loc('FailedWithReturnCode', code)); ```
- + ### task.setResourcePath (^) Sets the location of the resources json. This is typically the task.json file. Call once at the beginning of the script before any calls to loc. @@ -1034,14 +1179,14 @@ Call once at the beginning of the script before any calls to loc. ```javascript setResourcePath(path:string):void ``` - + Param | Type | Description --- | --- | --- path | string | Full path to the json. - +
- + ### task.loc (^) Gets the localized string from the json resource file. Optionally formats with additional params. @@ -1049,29 +1194,33 @@ Gets the localized string from the json resource file. Optionally formats with ```javascript loc(key:string, param:any[]):string ``` - + Param | Type | Description --- | --- | --- key | string | key of the resources string in the resource file param | any\[\] | additional params for formatting the string - - + +
- + ## Proxy - + --- - + Funtions for web proxy settings
- + ### task.getHttpProxyConfiguration (^) Gets http proxy configuration used by Build/Release agent @return ProxyConfiguration ```javascript -getHttpProxyConfiguration():ProxyConfiguration +getHttpProxyConfiguration(requestUrl?:string):ProxyConfiguration ``` - + +Param | Type | Description +--- | --- | --- +requestUrl | string | + diff --git a/node/docs/cert.md b/node/docs/cert.md new file mode 100644 index 000000000..7354d7941 --- /dev/null +++ b/node/docs/cert.md @@ -0,0 +1,92 @@ +### Get certificate configuration by using [AZURE-PIPELINES-TASK-LIB](https://github.com/Microsoft/azure-pipelines-task-lib) method (Min Agent Version 2.122.0) + +#### Node.js Lib + +Method for retrieve certificate settings in node.js lib +``` typescript +export function getHttpCertConfiguration(): CertConfiguration { +} +``` +`CertConfiguration` has following fields +```typescript +export interface CertConfiguration { + caFile?: string; + certFile?: string; + keyFile?: string; + certArchiveFile?: string; + passphrase?: string; + } +``` + +In the following example, we will retrieve certificate configuration information and use VSTS-Node-Api to make a Rest Api call back to VSTS/TFS service, the Rest call will use the certificates you configured in agent. +```typescript +// MyCertExampleTask.ts +import tl = require('azure-pipelines-task-lib/task'); +import api = require('vso-node-api'); +import VsoBaseInterfaces = require('vso-node-api/interfaces/common/VsoBaseInterfaces'); + +async function run() { + + // get cert config + let cert = tl.getHttpCertConfiguration(); + + // TFS server url + let serverUrl = "https://mycompanytfs.com/tfs"; + + // Personal access token + let token = "g6zzur6bfypfwuqdxxupv3y3qfcoudlgh26bjz77t3mgylzmvjiq"; + let authHandler = api.getPersonalAccessTokenHandler(token); + + // Options for VSTS-Node-Api, + // this is not required if you want to send http request to the same TFS + // instance the agent currently connect to. + // VSTS-Node-Api will pick up certificate setting from azure-pipelines-task-lib automatically + let option: VsoBaseInterfaces.IRequestOptions = { + cert: { + caFile: "C:\\ca.pem", + certFile: "C:\\client-cert.pem", + keyFile: "C:\\client-cert-key.pem", + passphrase: "test123", + } + }; + + // Make a Rest call to VSTS/TFS + let vsts: api.WebApi = new api.WebApi(serverUrl, authHandler, option); + let connData: lim.ConnectionData = await vsts.connect(); + console.log('Hello ' + connData.authenticatedUser.providerDisplayName); + + // You should only use the retrieved certificate config to call the TFS instance your agent current connect to or any resource within your cooperation that accept those certificates. +} + +run(); +``` + +#### PowerShell Lib + +On Windows the CA certificate needs to be installed into the `Trusted CA Store` of `Windows Certificate manager` first. +So the PowerShell lib will only expose the client certificate information + +Method for retrieve client certificate settings in PowerShell lib +``` powershell +function Get-ClientCertificate { + [CmdletBinding()] + param() + + # Return a new X509Certificate2 object to the client certificate + return New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 +} +``` + +In the following example, we will retrieve client certificate configuration information and print it out first, then we will use PowerShell lib method to get `VssHttpClient` and make a Rest Api call back to TFS service's `Project` endpoint and retrieve all team projects. The Rest call will use the client certificate you configured in agent. + +```powershell +# retrieve cert config +$cert = Get-VstsClientCertificate +Write-Host $cert + +# get project http client (the client will have proxy hook up by default) +$projectHttpClient = Get-VstsVssHttpClient -TypeName Microsoft.TeamFoundation.Core.WebApi.ProjectHttpClient -OMDirectory "" + +# print out all team projects +$projectHttpClient.GetProjects().Result +``` \ No newline at end of file diff --git a/node/docs/docs.json b/node/docs/docs.json index a3f1da4db..3b7fd154d 100644 --- a/node/docs/docs.json +++ b/node/docs/docs.json @@ -12,8 +12,10 @@ "Summary": "Functions for retrieving inputs for the task", "Document": [ "task.getInput", + "task.getInputRequired", "task.getBoolInput", "task.getPathInput", + "task.getPathInputRequired", "task.filePathSupplied", "task.getDelimitedInput", "task.getVariable", @@ -21,7 +23,8 @@ "task.getVariables", "task.setVariable", "task.getTaskVariable", - "task.setTaskVariable" + "task.setTaskVariable", + "task.getAgentMode" ] }, "Execution": { @@ -41,18 +44,28 @@ "task.execSync", "task.setResult" ] - }, - "Service Endpoints": { - "Summary": "Retrieve service endpoints and authorization details", + }, + "Service Connections": { + "Summary": "Retrieve service connections (previously called \"service endpoints\") and authorization details", "Document": [ "task.getEndpointUrl", + "task.getEndpointUrlRequired", "task.getEndpointDataParameter", + "task.getEndpointDataParameterRequired", "task.getEndpointAuthorizationScheme", + "task.getEndpointAuthorizationSchemeRequired", "task.getEndpointAuthorizationParameter", + "task.getEndpointAuthorizationParameterRequired", "task.EndpointAuthorization", "task.getEndpointAuthorization" ] - }, + }, + "Secrets": { + "Summary": "Functions for managing pipeline secrets", + "Document": [ + "task.setSecret" + ] + }, "Secure Files": { "Summary": "Retrieve secure files details required to download the file", "Document": [ @@ -103,6 +116,6 @@ "Document": [ "task.getHttpProxyConfiguration" ] - } + } } } \ No newline at end of file diff --git a/node/docs/findingfiles.md b/node/docs/findingfiles.md index ab5eccf8c..f5748904c 100644 --- a/node/docs/findingfiles.md +++ b/node/docs/findingfiles.md @@ -16,19 +16,19 @@ When designing a task glob input experience, one of the following UI layout patt ## Task Lib Functions -The above UI experiences translate to one of the following consumption patterns of the [task lib API](vsts-task-lib.md): +The above UI experiences translate to one of the following consumption patterns of the [task lib API](azure-pipelines-task-lib.md): 1. `filePath` input only - - Call [findMatch](vsts-task-lib.md#taskfindMatch) and pass the filePath input as the pattern. + - Call [findMatch](azure-pipelines-task-lib.md#taskfindMatch) and pass the filePath input as the pattern. 2. `filePath` input to specify a root directory, followed by a `multiLine` input for match patterns. - - Call [find](vsts-task-lib.md#taskfind) to recursively find all paths under the specified root directory. - - Then call [match](vsts-task-lib.md#taskmatch) to filter the results using the multiLine input as the patterns. + - Call [find](azure-pipelines-task-lib.md#taskfind) to recursively find all paths under the specified root directory. + - Then call [match](azure-pipelines-task-lib.md#taskmatch) to filter the results using the multiLine input as the patterns. 3. `filePath` input to specify a *default* root directory to root any unrooted patterns, followed by a `multiLine` input for match patterns. - - Call [findMatch](vsts-task-lib.md#taskfindMatch) and pass the filePath input as the defaultRoot and the multiLine input as the patterns. + - Call [findMatch](azure-pipelines-task-lib.md#taskfindMatch) and pass the filePath input as the defaultRoot and the multiLine input as the patterns. -Note, use [getDelimitedInput](vsts-task-lib.md#taskgetDelimitedInput) to split a multiLine input using the delimiter `'\n'`. +Note, use [getDelimitedInput](azure-pipelines-task-lib.md#taskgetDelimitedInput) to split a multiLine input using the delimiter `'\n'`. ## Recommended FindOptions and MatchOptions diff --git a/node/docs/gendocs.ts b/node/docs/gendocs.ts index 5192b7b18..86cf09a4f 100644 --- a/node/docs/gendocs.ts +++ b/node/docs/gendocs.ts @@ -20,8 +20,8 @@ let options: ts.CompilerOptions = { target: srcOptions['target'] } -const jsonDocName: string = "vsts-task-lib.json"; -const mdDocName: string = "vsts-task-lib.md"; +const jsonDocName: string = "azure-pipelines-task-lib.json"; +const mdDocName: string = "azure-pipelines-task-lib.md"; header('Generating ' + jsonDocName); let doc: ts2json.DocEntry = ts2json.generate(docs.files, options); @@ -155,20 +155,20 @@ var writeInterface = function(name: string, item: ts2json.DocEntry) { writeLine(); } -writeLine('# VSTS-TASK-LIB TYPESCRIPT API'); +writeLine('# AZURE-DEVOPS-TASK-LIB TYPESCRIPT API'); writeLine(); writeLine('## Dependencies'); writeLine('A [cross platform agent](https://github.com/Microsoft/vso-agent) OR a TFS 2015 Update 2 Windows agent (or higher) is required to run a Node task end-to-end. However, an agent is not required for interactively testing the task.'); writeLine(); writeLine('## Importing'); -writeLine('For now, the built vsts-task-lib (in _build) should be packaged with your task in a node_modules folder'); +writeLine('For now, the built azure-pipelines-task-lib (in _build) should be packaged with your task in a node_modules folder'); writeLine(); -writeLine('The build generates a vsts-task-lib.d.ts file for use when compiling tasks'); +writeLine('The build generates a azure-pipelines-task-lib.d.ts file for use when compiling tasks'); writeLine('In the example below, it is in a folder named definitions above the tasks lib'); writeLine(); writeLine('```'); -writeLine('/// '); -writeLine("import tl = require('vsts-task-lib/task')"); +writeLine('/// '); +writeLine("import tl = require('azure-pipelines-task-lib/task')"); writeLine('```'); writeLine(); writeLine('## [Release notes](releases.md)'); diff --git a/node/docs/minagent.md b/node/docs/minagent.md index ae120607b..2d30abfc0 100644 --- a/node/docs/minagent.md +++ b/node/docs/minagent.md @@ -25,8 +25,11 @@ Use the details below to determine when specific agent features were added: - `prejobexecution` and `postjobexecution` handler sections were added in 2.115.0 * `node` handler - Added in 1.95.1. Used node v5.10.1. - - Updated in 2.117.0 to use node v6.10.3 - - Updated in 2.117.0. Previously used v5.10.1 + - Updated in 2.117.0 to use node v6.10.3. +* `node10` handler + - Added in 2.144.0. Used node v10.x +* `node16` handler + - Added in 2.206.1. Used node v16.x * `powershell3` handler - Added in 1.95.1 - Updated in 1.97 to propagate `Data` property for endpoints @@ -48,6 +51,7 @@ The following chart details the agent versions that shipped with each on-premise | Agent | TFS | |---------|-------------------| +| 2.122.1 | TFS 2017 Update 3 | | 2.117.x | TFS 2017 Update 2 | | 2.112.0 | TFS 2017 Update 1 | | 2.105.7 | TFS 2017 RTM | diff --git a/node/docs/nodeEnvironment.md b/node/docs/nodeEnvironment.md new file mode 100644 index 000000000..b729283cf --- /dev/null +++ b/node/docs/nodeEnvironment.md @@ -0,0 +1,29 @@ +# Node Handler Versioning + +As of agent version 2.144.0, Azure Pipelines supports running tasks in a Node 10 environment in addition to the previously supported Node 6 environment. + +To leverage this capability, simply add `Node10` as an execution target: + +``` +"execution": { + "Node10": { + "target": "path/to/entry" + } +}, +``` + +With agent version 2.206.1 Node 16 can be used. + +``` +"execution": { + "Node16": { + "target": "path/to/entry" + } +}, +``` + +Existing `Node` execution targets will still resolve to a Node 6 environment for now to maintain back-compat. + +### Testing your task + +If you use the task-lib for testing, it will automatically use the appropriate version of Node to test your tasks, downloading it if necessary. \ No newline at end of file diff --git a/node/docs/nodeVersioning.md b/node/docs/nodeVersioning.md new file mode 100644 index 000000000..91a805ce1 --- /dev/null +++ b/node/docs/nodeVersioning.md @@ -0,0 +1,20 @@ +# Node Handler versioning + +## Agent Node Handler + +The agent currently has 3 different node handlers that it can use to execute node tasks: Node 6, Node 10, Node 16. +The handler used depends on the `execution` property specified in the tasks `task.json`. +If the `execution` property is specified to be `Node`, the task will run on the Node 6 handler, for `Node10` it will run on the Node 10 handler, for `Node16` it will run on the Node 16 handler. + +## Mock-test Node Handler + +[Unit testing](https://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=azure-devops#step-2-unit-testing-your-task-scripts) of tasks can be done using the task-lib's built in mock-task functionality. +To ensure tests are run in the same environment as the agent, this library looks for a `task.json` file in the same directory as the supplied task entry point. +If no `task.json` is found it searches all ancestor directories as well. +If the `task.json` is still not found, the library defaults to Node 16, otherwise it uses the appropriate handler based on the `execution` property. +If this version of node is not found on the path, the library downloads the appropriate version. + +### Behavior overrides + +To specify a specific version of node to use, set the `nodeVersion` optional parameter in the `run` function of the `MockTestRunner` to the integer major version (e.g. `mtr.run(5)`). +To specify the location of a `task.json` file, set the `taskJsonPath` optional parameter in the `MockTestRunner` constructor to the path of the file (e.g. `let mtr = new mt.MockTaskRunner('', ''`). \ No newline at end of file diff --git a/node/docs/proxy.md b/node/docs/proxy.md index 46b56cc97..7c6eb437e 100644 --- a/node/docs/proxy.md +++ b/node/docs/proxy.md @@ -1,4 +1,4 @@ -### Get proxy configuration by using [VSTS-Task-Lib](https://github.com/Microsoft/vsts-task-lib) method +### Get proxy configuration by using [AZURE-DEVOPS-TASK-LIB](https://github.com/Microsoft/azure-pipelines-task-lib) method #### Node.js Lib @@ -14,13 +14,14 @@ export interface ProxyConfiguration { proxyUsername?: string; proxyPassword?: string; proxyBypassHosts?: string[]; + proxyFormattedUrl: string; } ``` In the following example, we will retrieve proxy configuration information and use VSTS-Node-Api to make a Rest Api call back to VSTS/TFS service, the Rest call will go through the web proxy you configured in `.proxy` file. ```typescript // MyProxyExampleTask.ts -import tl = require('vsts-task-lib/task'); +import tl = require('azure-pipelines-task-lib/task'); import api = require('vso-node-api'); import VsoBaseInterfaces = require('vso-node-api/interfaces/common/VsoBaseInterfaces'); @@ -36,7 +37,10 @@ async function run() { let token = "g6zzur6bfypfwuqdxxupv3y3qfcoudlgh26bjz77t3mgylzmvjiq"; let authHandler = api.getPersonalAccessTokenHandler(token); - // Options for VSTS-Node-Api + // Options for VSTS-Node-Api, + // this is not required if you want to send http request to the same VSTS/TFS + // instance the agent currently connect to. + // VSTS-Node-Api will pick up proxy setting from azure-pipelines-task-lib automatically let option: VsoBaseInterfaces.IRequestOptions = { proxy: { proxyUrl: proxy.proxyUrl, @@ -58,6 +62,24 @@ async function run() { run(); ``` +For some external applications executed from the shell, you might need to set an environment variable that contains a formatted URL +in the following format: protocol://user:password@hostname:port +You can retrieve such configuration directly from task-lib: +```typescript +import tl = require('azure-pipelines-task-lib/task'); + +async function run() { + let proxy = tl.getProxyConfiguration() + + process.env['http_proxy'] = proxy.proxyFormattedUrl; + process.env['https_proxy'] = proxy.proxyFormattedUrl; + const gitPath: string = tl.which('git'); + const gitPull = tl.tool(gitPath); + await gitPull.exec() +} + +run(); +``` #### PowerShell Lib Method for retrieve proxy settings in PowerShell lib diff --git a/node/docs/releases.md b/node/docs/releases.md index cec48e629..230012fa7 100644 --- a/node/docs/releases.md +++ b/node/docs/releases.md @@ -1,4 +1,25 @@ -# VSTS-TASK-LIB RELEASES +# AZURE-PIPELINES-TASK-LIB RELEASES + +## 2.8.0 + * Updated to TypeScript 3.0. + * Fixed `which` so that it finds executables that can be executed by the current process, not just those that are executable by everyone. + * Added `setVariable` method to `mock-run` for mocking variables during test. + * Update `MockToolRunner` to emit stdout. + * Added support for UNC args in `ToolRunner`. + * Added additional logging commands to the Node library to obtain parity with PowerShell. + * Added `getPlatform` convenience function. + * Updated `vault` to use safer cipheriv encoding (stops npm from warning about encryption). + +## 2.7.0 + * Updated `setResult` to expose optional done parameter + * Updated `ToolRunner` to distinguish between events for process exit and STDIO streams closed + +## 2.5.0 + * Updated `FindOptions` to expose `allowBrokenSymbolicLinks`. + +## 2.3.0 + * Updated `setVariable` to fail when a secret contains multiple lines. + * Added `setSecret` to register a secret with the log scrubber, without registering a variable. Multi-line secrets are not supported. ## 2.0.4-preview * Updated `ToolRunner` to validate the specified tool can be found and is executable. diff --git a/node/docs/run.sh b/node/docs/run.sh index 7676aeae6..4eca166db 100755 --- a/node/docs/run.sh +++ b/node/docs/run.sh @@ -6,4 +6,4 @@ function failed() } ../node_modules/.bin/tsc || failed 'Compilation failed.' -../_download/archive/https___nodejs.org_dist_v5.10.1_node-v5.10.1-darwin-x64.tar.gz/node-v5.10.1-darwin-x64/bin/node gendocs.js \ No newline at end of file +../_download/archive/*/*/bin/node gendocs.js \ No newline at end of file diff --git a/node/docs/samples/loc.src b/node/docs/samples/loc.src index c284c05b6..1d88b20ce 100644 --- a/node/docs/samples/loc.src +++ b/node/docs/samples/loc.src @@ -1,4 +1,4 @@ -/// +/// tl.setResourcePath(path.join( __dirname, 'task.json')); diff --git a/node/docs/samples/toolrunner.src b/node/docs/samples/toolrunner.src index ddc0935e5..b9a325b31 100644 --- a/node/docs/samples/toolrunner.src +++ b/node/docs/samples/toolrunner.src @@ -1,13 +1,13 @@ -/// -import tl = require('vsts-task-lib/task'); -import tr = require('vsts-task-lib/toolrunner'); +/// +import tl = require('azure-pipelines-task-lib/task'); +import tr = require('azure-pipelines-task-lib/toolrunner'); try { var toolPath = tl.which('atool'); var atool:tr.ToolRunner = tl.tool(toolPath).arg('--afile').line('arguments'); - var code: number = await tr.exec(); + var code: number = await atool.exec(); console.log('rc=' + code); } catch (err) { tl.setResult(tl.TaskResult.Failed, err.message); -} \ No newline at end of file +} diff --git a/node/docs/stepbystep.md b/node/docs/stepbystep.md index ed1957cd3..9bc217ff8 100644 --- a/node/docs/stepbystep.md +++ b/node/docs/stepbystep.md @@ -1,466 +1,3 @@ # Step by Step: Node Task with Typescript API -This step by step will show how to manually create, debug and test a cross platform task with no service, server or agent! - -Tasks can be created using tfx as a convenience, but it's good to understand the parts of a task. -This tutorial walks through each manual step of creating a task. - -Note: The tutorial was done on a mac. Attempted to make generic for all platforms. Create issues in the repo. - -## Video - -TODO: posting soon - -## Tools - -[Typescript Compiler 2.2.0 or greater](https://www.npmjs.com/package/typescript) - -[Node 4.4.7 (LTS) or greater](https://nodejs.org/en/) - -[Npm 3.0 or greater recommended](https://www.npmjs.com/package/npm3) (comes with node >=5. creates flat dependencies in tasks) - -This tutorial uses [VS Code](https://code.visualstudio.com) for great intellisense and debugging support - -## Sample Files - -Files used for this walk through are [located here in this gist](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e) - -## Create Task Scaffolding - -Create a directory and package.json. Accept defaults of npm init for sample. -```bash -$ mkdir sampletask && cd sampletask -$ npm init -``` - -### Add vsts-task-lib - -Add vsts-task-lib to your task. Remember your task must carry the lib. Ensure it's at least 0.9.5 which now carries typings. - -The package.json should have dependency with ^. Ex: ^0.9.5. This means you are locked to 0.9.x and will pick up patches on npm install. - -The npm module carries the .d.ts typecript definition files so compile and intellisense support will just work. - -``` -$ npm install vsts-task-lib --save -... -└─┬ vsts-task-lib@2.0.5 -... -``` - -### Add Typings for Externals - -Ensure typings are installed for external dependencies - -```bash -$ npm install @types/node --save-dev -$ npm install @types/q --save-dev -``` - -Create a .gitignore and add node_modules to it. Your build process should do `npm install` and `typings install`. No need to checkin dependencies. - -```bash -$ cat .gitignore -node_modules -``` - -### Create tsconfig.json Compiler Options - -```bash -$ tsc --init -``` - -Change `tsconfig.json` file to ES6 to match the sample gist. ES6 is for async await support. - -## Task Implementation - -Now that the scaffolding is out of the way, let's create the task! - -Create a `task.json` file using `sample_task.json` as a starting point. - -Replace the `{{placeholders}}`. The most important being a [unique guid](http://www.guidgen.com/). -Note: copy from web view since file needs property names in quotes (browser might strip in raw view) - -Create a `index.ts` file using index.ts from the gist as a starting point. -Create a `taskmod.ts` file using taskmod.ts from the gist as a starting point. - -Instellisense should just work in [VS Code](https://code.visualstudio.com) - -The code is straight forward. As a reference: - -```javascript -import tl = require('vsts-task-lib/task'); -import trm = require('vsts-task-lib/toolrunner'); -import mod = require('./taskmod'); - -async function run() { - try { - console.log(process.env["INPUT_SAMPLESTRING"]); - let tool: trm.ToolRunner; - if (process.platform == 'win32') { - let cmdPath = tl.which('cmd'); - tool = tl.tool(cmdPath).arg('/c').arg('echo ' + tl.getInput('samplestring', true)); - } - else { - let echoPath = tl.which('echo'); - tool = tl.tool(echoPath).arg(tl.getInput('samplestring', true)); - } - - let rc1: number = await tool.exec(); - - // call some module which does external work - if (rc1 == 0) { - mod.sayHello(); - } - - console.log('Task done! ' + rc1); - } - catch (err) { - tl.setResult(tl.TaskResult.Failed, err.message); - } -} - -run(); -``` - -taskmod.ts: - -```javascript -export function sayHello() { - console.log('Hello World!'); -} -``` - -Key Points: - - - Async code is linear. Note the two executions written one after each other in linear fashion. - - Must be wrapped in async function. - - Greatly simplifies error handling. - - Never process.exit your task. You can sometimes lose output and often the last bit of output is critical - -If we did our job well, the code should be pretty self explanatory. -But, see the [API Reference](vsts-task-lib.md) for specifics. - -## Compile - -Just type `tsc` from the root of the task. That should have compiled an index.js - -## Run the Task - -The task can be run by simply running `node index.js`. Note that is exactly what our agent will do. - -```bash -$ node index.js -##vso[task.debug]agent.workFolder=undefined -##vso[task.debug]loading inputs and endpoints -##vso[task.debug]loaded 0 -##vso[task.debug]task result: Failed -##vso[task.issue type=error;]Input required: samplestring -##vso[task.complete result=Failed;]Input required: samplestring -``` - -The task failed! That's exactly what would happen if the task run and inputs were not supplied. - -The agent runs the task and reads key information from command output output over stdout and stderr. This allows for consistency in node, powershell or even ad-hoc scripts. The lib is mostly a thin wrapper generating those commands. - -Let's supply one of the inputs and try again. - -```bash -$ export INPUT_SAMPLESTRING="Hello World" -$ node index.js -##vso[task.debug]agent.workFolder=undefined -##vso[task.debug]loading inputs and endpoints -##vso[task.debug]loading INPUT_SAMPLESTRING -##vso[task.debug]loaded 1 -##vso[task.debug]samplestring=Hello World -##vso[task.debug]echo arg: Hello World -##vso[task.debug]exec tool: echo -##vso[task.debug]Arguments: -##vso[task.debug] Hello World -[command]echo Hello World -Hello World -##vso[task.debug]rc:0 -##vso[task.debug]success:true -##vso[task.debug]samplebool=null -Task done! 0,-1 -$ -``` - -> TIP: be careful with chars like ! in env vars. [Example here](http://superuser.com/questions/133780/in-bash-how-do-i-escape-an-exclamation-mark) - -Now let's set the sample bool. This should fail since if sample bool is true, it should need the other input. See the code. - -```bash -$ export INPUT_SAMPLEBOOL=true -$ node index.js -##vso[task.debug]agent.workFolder=undefined -##vso[task.debug]loading inputs and endpoints -##vso[task.debug]loading INPUT_SAMPLEBOOL -##vso[task.debug]loading INPUT_SAMPLESTRING -##vso[task.debug]loaded 2 -##vso[task.debug]samplestring=Hello World -##vso[task.debug]echo arg: Hello World -##vso[task.debug]exec tool: echo -##vso[task.debug]Arguments: -##vso[task.debug] Hello World -[command]echo Hello World -Hello World -##vso[task.debug]rc:0 -##vso[task.debug]success:true -##vso[task.debug]samplebool=true -##vso[task.debug]task result: Failed -##vso[task.issue type=error;]Input required: samplepathinput -##vso[task.complete result=Failed;]Input required: samplepathinput -$ -``` - -So, as you can see, this offers powerful testing automation options to test all arcs with no agent or server. - -## Interactive Use from Command Line (Advanced) - -Node offers an interactive console and since the task lib is in your node_modules folder, you can interactively poke around. - -```bash -$ node -> var tl = require('vsts-task-lib/task'); -##vso[task.debug]agent.workFolder=undefined -##vso[task.debug]loading inputs and endpoints -##vso[task.debug]loaded 0 -undefined -> tl.which('echo'); -##vso[task.debug]echo=/bin/echo -'/bin/echo' -> tl.tool('echo').arg('Hello World!').args -##vso[task.debug]echo arg: Hello World! -[ 'Hello World!' ] -> .exit - -``` -## Debugging - -Coming soon - -## Unit testing your task scripts - -This requires vsts-task-lib 0.9.15 or greater. - - -### Goals: - -- Unit test the script, not the external tools it's calling. -- Run subsecond (often < 200 ms) -- Validate all arcs of the script, including failure paths. -- Run your task script unaltered the exact way the agent does (envvar as contract, run node against your script) -- Assert all aspects of the execution (succeeded, issues, std/errout etc...) by intercepting command output - -### Install test tools - -We will use mocha as the test driver in this examples. Others exist. -```bash -npm install mocha --save-dev -g -npm install @types/mocha --save-dev -``` - -### Create test suite - -Creates tests folder and _suite.ts. [Example here](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e#file-_suite-ts) - -### Success test - -The success test will validate that the task will succeed if the tool returns 0. It also confirms that no error or warning issues are added to the build summary. Finally it validates that the task module is called if the tool succeeds. - -The success test [from _suite.ts](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e#file-_suite-ts) looks like: - -```javascript - it('should succeed with simple inputs', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'success.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - assert(tr.succeeded, 'should have succeeded'); - assert.equal(tr.invokedToolCount, 1); - assert.equal(tr.warningIssues.length, 0, "should have no warnings"); - assert.equal(tr.errorIssues.length, 0, "should have no errors"); - assert(tr.stdout.indexOf('atool output here') >= 0, "tool stdout"); - assert(tr.stdout.indexOf('Hello Mock!') >= 0, "task module is called"); - - done(); - }); -``` - -key code from [success.ts test file](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e#file-success-ts) - -```javascript - -let taskPath = path.join(__dirname, '..', 'index.js'); -let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); - -tmr.setInput('samplestring', "Hello, from task!"); -tmr.setInput('samplebool', 'true'); - -// provide answers for task mock -let a: ma.TaskLibAnswers = { - "which": { - "echo": "/mocked/tools/echo", - "cmd": "/mocked/tools/cmd" - }, - "exec": { - "/mocked/tools/echo Hello, from task!": { - "code": 0, - "stdout": "atool output here", - "stderr": "atool with this stderr output" - }, - "/mocked/tools/cmd /c echo Hello, from task!": { - "code": 0, - "stdout": "atool output here", - "stderr": "atool with this stderr output" - } - } -}; -tmr.setAnswers(a); - -// mock a specific module function called in task -tmr.registerMock('./taskmod', { - sayHello: function() { - console.log('Hello Mock!'); - } -}); - -tmr.run(); - -``` - -### Fail test - -This test validates that the task will fail if the tool returns 1. It also validates that an error issue will be added to the build summary. Finally, it validated that the task module is not called if the tool fails. - -The fail test [from _suite.ts](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e#file-_suite-ts) looks like: - -```javascript - it('it should fail if tool returns 1', (done: MochaDone) => { - this.timeout(1000); - - let tp = path.join(__dirname, 'failrc.js'); - let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - - tr.run(); - assert(!tr.succeeded, 'should have failed'); - assert.equal(tr.invokedToolCount, 1); - assert.equal(tr.warningIssues, 0, "should have no warnings"); - assert.equal(tr.errorIssues.length, 1, "should have 1 error issue"); - if (process.platform == 'win32') { - assert.equal(tr.errorIssues[0], '/mocked/tools/cmd failed with return code: 1', 'error issue output'); - } - else { - assert.equal(tr.errorIssues[0], '/mocked/tools/echo failed with return code: 1', 'error issue output'); - } - assert(tr.stdout.indexOf('atool output here') >= 0, "tool stdout"); - assert.equal(tr.stdout.indexOf('Hello Mock!'), -1, "task module should have never been called"); - - done(); - }); -``` - -The [failrc.ts test file is here](https://gist.github.com/bryanmacfarlane/154f14dd8cb11a71ef04b0c836e5be6e#file-failrc-ts) - -### Fail on required inputs - -We also typically write tests to confirm the task fails with proper actionable guidance if required inputs are not supplied. Give it a try. - -### Running the tests - -```bash -mocha tests/_suite.js -``` - -```bash -$ mocha tests/_suite.js - - - Sample task tests - ✓ should succeed with simple inputs (127ms) - ✓ it should fail if tool returns 1 (121ms) - - - 2 passing (257ms) -``` - -If you want to run tests with verbose task output (what you would see in the build console) set envvar TASK_TEST_TRACE=1 - -```bash -export TASK_TEST_TRACE=1 -``` - -## Add Task to an Extension - -### Create your publisher - -All extensions are identified as being provided by a publisher. If you aren't already a member of an existing publisher, you'll create one. Sign in to the [Visual Studio Marketplace Publishing Portal](http://aka.ms/vsmarketplace-manage). If you're not prompted to create a publisher, scroll down to the bottom of the page and select *Publish Extensions* underneath **Related Sites**. - -### Create an extension manifest file - -You will need to create the extension manifest file in the directory above `sampletask`. - -Create a file `vss-extension.json`: -```json -{ - "manifestVersion": 1, - "id": "sample-task", - "name": "Sample Build Tools", - "version": "0.0.1", - "publisher": "samplepublisher", - "targets": [ - { - "id": "Microsoft.VisualStudio.Services" - } - ], - "description": "Sample tools for building. Includes one build task.", - "categories": [ - "Build and release" - ], - "//uncomment 'icons' below to include a custom icon": "", - "//icons": { - "default": "images/extension-icon.png" - }, - "files": [ - { - "//Relative path of the task directory": "", - "path": "sampletask" - } - ], - "contributions": [ - { - "//Identifier of the contribution. Must be unique within the extension. Does not need to match the name of the build task, but typically the build task name is included in the ID of the contribution.": "", - "id": "sample-build-task", - "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], - "properties": { - "//Name of the task. This must match the folder name of the corresponding self-contained build task definition.": "", - "name": "sampletask" - } - } - ] -} -``` - -### Publish, Install, Publish - -Publish the extension to the Marketplace and grant your account the ability to see it. Sharing the extension with your account allows you to install and test it. - -You will need a personal access token scoped to `All accessible` accounts. - -``` -tfx extension publish --manifest-globs your-manifest.json --share-with youraccountname -``` - -Install the extension into your account. - -``` -tfx extension install --vsix .\samplepublisher.sample-task-0.0.1.vsix --accounts youraccountname -``` - -Future publishes will automatically update the extension installed into your account. Add `--rev-version` when publishing updates to the extension, and also rev the task version (in the `task.json`). +This documentation has been moved and can now be found [here](https://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=vsts)! \ No newline at end of file diff --git a/node/generate-third-party-notice.js b/node/generate-third-party-notice.js new file mode 100644 index 000000000..a076e4d64 --- /dev/null +++ b/node/generate-third-party-notice.js @@ -0,0 +1,169 @@ +/** + * Run from the root of the vsts-tasks repo. + * Usage: `node generate-third-party-notice.js ` + */ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const log = { + info(message) { + console.log(`[INFO] ${message}`); + }, + warning(message) { + console.log(`[WARNING] ${message}`); + }, + error(message) { + console.error(`[ERROR] ${message}`) + } +}; + +/** Log `label: ${value}` and pass the value through. */ +function trace(label, value) { + log.info(`${label}: ${value}`); + return value; +} + +/** + * Read `packagePath`'s package.json and deserialize it. + * @param packagePath Absolute path to the NPM package + * @returns Package manifest information parsed from the package's package.json + */ +function readPackageJson(packagePath) { + log.info(`Reading the package.json for ${packagePath} ...`); + const contents = fs.readFileSync(path.join(packagePath, 'package.json'), { encoding: 'utf-8' }); + return JSON.parse(contents); +} + +/** + * Get the name of the file containing the license for `packagePath`. + * @param packagePath Absolute path to the NPM package + * @returns Absolute path to the license file, or `null` if the license file can't be found + */ +function findLicense(packagePath) { + log.info(`Finding the license for '${packagePath}'`); + const children = fs.readdirSync(packagePath); + const licenseNames = [ + 'LICENSE', + 'LICENSE.md', + 'LICENSE.txt', + 'LICENSE-MIT.txt' + ].map(x => x.toLowerCase()); + const candidates = children.filter(x => licenseNames.includes(x.toLowerCase())); + if (!candidates || candidates.length === 0) { + log.warning(`Could not find a license for ${packagePath}`); + return null; + } else { + //console.log(JSON.stringify(candidates)); + if (candidates.length > 1) { + log.warning(`Found multiple license files for ${packagePath}: ${candidates.join(', ')}`); + } + return trace('Found license', path.join(packagePath, candidates[0])); + } +} + +/** + * Scan the contents of the 'node_modules' directory for license information. + * @param modulesRoot NPM package installation directory to scan + * @returns Iterable of objects: `name` x `url` x `licenseText` + */ +function* collectLicenseInfo(modulesRoot) { + const packagePaths = fs.readdirSync(modulesRoot).map(x => path.join(modulesRoot, x)); + for (let absolutePath of packagePaths) { + log.info(`Collecting license information from ${absolutePath} ...`); + const name = (() => { + const basename = path.basename(absolutePath); + if (path.dirname(absolutePath).endsWith('@types')) { + return `@types/${basename}`; + } else { + return basename; + } + })(); + + if (name === '.bin') { + continue; + } + + if (name === '@types') { + yield* collectLicenseInfo(absolutePath); + continue; + } + + const manifest = readPackageJson(absolutePath); + const license = findLicense(absolutePath); + + let licenseText; + if (license) { + licenseText = fs.readFileSync(license, { encoding: 'utf-8' }); + } else { + licenseText = 'No license text available.'; + } + + yield { + name: name, + url: manifest.repository.url, + licenseText: licenseText + }; + } +} + +/** Generate the third party notice line-by-line. */ +function* thirdPartyNotice(libName, licenseInfo) { + // Preamble + yield ''; + yield 'THIRD-PARTY SOFTWARE NOTICES AND INFORMATION'; + yield 'Do Not Translate or Localize'; + yield ''; + yield `This Visual Studio Team Services extension (${libName}) is based on or incorporates material from the projects listed below (Third Party IP). The original copyright notice and the license under which Microsoft received such Third Party IP, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party IP to you under the licensing terms for the Visual Studio Team Services extension. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise.`; + yield ''; + + // Enumerated modules + let num = 1; + for (let item of licenseInfo) { + if (item.url) { + yield `${num}.\t${item.name} (${item.url})`; + } else { + yield `${num}.\t${item.name}`; + } + num += 1; + } + + yield ''; + yield ''; + + // Module licenses + for (let item of licenseInfo) { + yield `%% ${item.name} NOTICES, INFORMATION, AND LICENSE BEGIN HERE`; + yield '========================================='; + yield item.licenseText.trim(); + yield '========================================='; + yield `END OF ${item.name} NOTICES, INFORMATION, AND LICENSE`; + yield ''; + } +} + +function writeLines(writeStream, lines) { + const writeLine = (line) => { + writeStream.write(line); + writeStream.write(os.EOL); + }; + + for (let line of lines) { + writeLine(line); + } +} + +function main(args) { + try { + const nodeModuleDir = path.join(__dirname, 'node_modules'); + const licenseInfo = Array.from(collectLicenseInfo(nodeModuleDir)); + + const writeStream = fs.createWriteStream(path.join(__dirname, 'ThirdPartyNotice.txt')); + writeLines(writeStream, thirdPartyNotice('azure-pipelines-task-lib', licenseInfo)); + writeStream.end(); + } catch (e) { + log.error(e.message); + } +} + +main(process.argv); \ No newline at end of file diff --git a/node/index.ts b/node/index.ts index d3d76a1ea..095d090b4 100644 --- a/node/index.ts +++ b/node/index.ts @@ -1,3 +1 @@ -/// - import trm = require('./task'); diff --git a/node/internal.ts b/node/internal.ts index f13fc7e91..d017a605f 100644 --- a/node/internal.ts +++ b/node/internal.ts @@ -6,6 +6,7 @@ import util = require('util'); import tcm = require('./taskcommand'); import vm = require('./vault'); import semver = require('semver'); +import crypto = require('crypto'); /** * Hash table of known variable info. The formatted env var name is the lookup key. @@ -26,7 +27,7 @@ export var _vault: vm.Vault; // async await needs generators in node 4.x+ if (semver.lt(process.versions.node, '4.2.0')) { - this.warning('Tasks require a new agent. Upgrade your agent or node to 4.2.0 or later'); + _warning('Tasks require a new agent. Upgrade your agent or node to 4.2.0 or later'); } //----------------------------------------------------- @@ -52,11 +53,11 @@ export function _writeLine(str: string): void { _outStream.write(str + os.EOL); } -export function _setStdStream(stdStream): void { +export function _setStdStream(stdStream: NodeJS.WriteStream): void { _outStream = stdStream; } -export function _setErrStream(errStream): void { +export function _setErrStream(errStream: NodeJS.WriteStream): void { _errStream = errStream; } @@ -65,12 +66,12 @@ export function _setErrStream(errStream): void { //----------------------------------------------------- let _locStringCache: { [key: string]: string } = {}; -let _resourceFile: string; +let _resourceFiles: { [key: string]: string } = {}; let _libResourceFileLoaded: boolean = false; let _resourceCulture: string = 'en-US'; function _loadResJson(resjsonFile: string): any { - var resJson: {} = null; + var resJson: any; if (_exist(resjsonFile)) { var resjsonContent = fs.readFileSync(resjsonFile, 'utf8').toString(); // remove BOM @@ -83,7 +84,6 @@ function _loadResJson(resjsonFile: string): any { } catch (err) { _debug('unable to parse resjson with err: ' + err.message); - resJson = null; } } else { @@ -101,12 +101,12 @@ function _loadLocStrings(resourceFile: string, culture: string): { [key: string] if (_exist(resourceFile)) { var resourceJson = require(resourceFile); if (resourceJson && resourceJson.hasOwnProperty('messages')) { - var locResourceJson = null; + var locResourceJson: any; // load up resource resjson for different culture var localizedResourceFile = path.join(path.dirname(resourceFile), 'Strings', 'resources.resjson'); var upperCulture = culture.toUpperCase(); - var cultures = []; + var cultures: string[] = []; try { cultures = fs.readdirSync(localizedResourceFile); } catch (ex) { } for (var i = 0; i < cultures.length; i++) { @@ -131,7 +131,7 @@ function _loadLocStrings(resourceFile: string, culture: string): { [key: string] } } else { - _warning(_loc('LIB_ResourceFileNotExist', resourceFile)); + _warning('LIB_ResourceFile does not exist'); } return locStrings; @@ -140,25 +140,25 @@ function _loadLocStrings(resourceFile: string, culture: string): { [key: string] /** * Sets the location of the resources json. This is typically the task.json file. * Call once at the beginning of the script before any calls to loc. - * * @param path Full path to the json. + * @param ignoreWarnings Won't throw warnings if path already set. * @returns void */ -export function _setResourcePath(path: string): void { +export function _setResourcePath(path: string, ignoreWarnings: boolean = false): void { if (process.env['TASKLIB_INPROC_UNITS']) { - _resourceFile = null; + _resourceFiles = {}; _libResourceFileLoaded = false; _locStringCache = {}; _resourceCulture = 'en-US'; } - if (!_resourceFile) { + if (!_resourceFiles[path]) { _checkPath(path, 'resource file path'); - _resourceFile = path; - _debug('set resource file to: ' + _resourceFile); + _resourceFiles[path] = path; + _debug('adding resource file: ' + path); _resourceCulture = _getVariable('system.culture') || _resourceCulture; - var locStrs = _loadLocStrings(_resourceFile, _resourceCulture); + var locStrs: { [key: string]: string; } = _loadLocStrings(path, _resourceCulture); for (var key in locStrs) { //cache loc string _locStringCache[key] = locStrs[key]; @@ -166,24 +166,28 @@ export function _setResourcePath(path: string): void { } else { - _warning(_loc('LIB_ResourceFileAlreadySet', _resourceFile)); + if (ignoreWarnings) { + _debug(_loc('LIB_ResourceFileAlreadySet', path)); + } else { + _warning(_loc('LIB_ResourceFileAlreadySet', path)); + } } } /** * Gets the localized string from the json resource file. Optionally formats with additional params. - * + * * @param key key of the resources string in the resource file * @param param additional params for formatting the string * @returns string */ export function _loc(key: string, ...param: any[]): string { if (!_libResourceFileLoaded) { - // merge loc strings from vsts-task-lib. + // merge loc strings from azure-pipelines-task-lib. var libResourceFile = path.join(__dirname, 'lib.json'); var libLocStrs = _loadLocStrings(libResourceFile, _resourceCulture); for (var libKey in libLocStrs) { - //cache vsts-task-lib loc string + //cache azure-pipelines-task-lib loc string _locStringCache[libKey] = libLocStrs[libKey]; } @@ -195,11 +199,11 @@ export function _loc(key: string, ...param: any[]): string { locString = _locStringCache[key]; } else { - if (!_resourceFile) { - _warning(_loc('LIB_ResourceFileNotSet', key)); + if (Object.keys(_resourceFiles).length <= 0) { + _warning(`Resource file haven't been set, can't find loc string for key: ${key}`); } else { - _warning(_loc('LIB_LocStringNotFound', key)); + _warning(`Can't find loc string for key: ${key}`); } locString = key; @@ -219,15 +223,15 @@ export function _loc(key: string, ...param: any[]): string { /** * Gets a variable value that is defined on the build/release definition or set at runtime. - * + * * @param name name of the variable to get * @returns string */ -export function _getVariable(name: string): string { - let varval: string; +export function _getVariable(name: string): string | undefined { + let varval: string | undefined; // get the metadata - let info: _KnownVariableInfo; + let info: _KnownVariableInfo | undefined; let key: string = _getVariableKey(name); if (_knownVariableMap.hasOwnProperty(key)) { info = _knownVariableMap[key]; @@ -273,7 +277,7 @@ export interface _KnownVariableInfo { // Cmd Helpers //----------------------------------------------------- -export function _command(command: string, properties, message: string) { +export function _command(command: string, properties: any, message: string) { var taskCmd = new tcm.TaskCommand(command, properties, message); _writeLine(taskCmd.toString()); } @@ -296,14 +300,14 @@ export function _debug(message: string): void { /** * Returns whether a path exists. - * + * * @param path path to check - * @returns boolean + * @returns boolean */ export function _exist(path: string): boolean { var exist = false; try { - exist = path && fs.statSync(path) != null; + exist = !!(path && fs.statSync(path) != null); } catch (err) { if (err && err.code === 'ENOENT') { exist = false; @@ -317,7 +321,7 @@ export function _exist(path: string): boolean { /** * Checks whether a path exists. * If the path does not exist, it will throw. - * + * * @param p path to check * @param name name only used in error message to identify the path * @returns void @@ -332,7 +336,7 @@ export function _checkPath(p: string, name: string): void { /** * Returns path of a tool had the tool actually been invoked. Resolves via paths. * If you check and the tool does not exist, it will throw. - * + * * @param tool name of the tool * @param check whether to check if tool exists * @returns string @@ -345,7 +349,10 @@ export function _which(tool: string, check?: boolean): string { // recursive when check=true if (check) { let result: string = _which(tool, false); - if (!result) { + if (result) { + return result; + } + else { if (process.platform == 'win32') { throw new Error(_loc('LIB_WhichNotFound_Win', tool)); } @@ -391,7 +398,7 @@ export function _which(tool: string, check?: boolean): string { // it feels like we should not do this. Checking the current directory seems like more of a use // case of a shell, and the which() function exposed by the task lib should strive for consistency // across platforms. - let directories: string[] = [ ]; + let directories: string[] = []; if (process.env['PATH']) { for (let p of process.env['PATH'].split(path.delimiter)) { if (p) { @@ -441,10 +448,7 @@ function _tryGetExecutablePath(filePath: string, extensions: string[]): string { } } else { - // on Mac/Linux, test the execute bit - // R W X R W X R W X - // 256 128 64 32 16 8 4 2 1 - if ((stats.mode & 1) == 1) { + if (isUnixExecutable(stats)) { return filePath; } } @@ -483,10 +487,7 @@ function _tryGetExecutablePath(filePath: string, extensions: string[]): string { return filePath; } else { - // on Mac/Linux, test the execute bit - // R W X R W X R W X - // 256 128 64 32 16 8 4 2 1 - if ((stats.mode & 1) == 1) { + if (isUnixExecutable(stats)) { return filePath; } } @@ -502,6 +503,13 @@ function _tryGetExecutablePath(filePath: string, extensions: string[]): string { return ''; } +// on Mac/Linux, test the execute bit +// R W X R W X R W X +// 256 128 64 32 16 8 4 2 1 +function isUnixExecutable(stats: fs.Stats) { + return (stats.mode & 1) > 0 || ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || ((stats.mode & 64) > 0 && stats.uid === process.getuid()); +} + export function _legacyFindFiles_convertPatternToRegExp(pattern: string): RegExp { pattern = (process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern) // normalize separator on Windows .replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') // regex escape - from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript @@ -686,9 +694,9 @@ export function _ensurePatternRooted(root: string, p: string) { //------------------------------------------------------------------- export function _loadData(): void { - // in agent, workFolder is set. + // in agent, prefer TempDirectory then workFolder. // In interactive dev mode, it won't be - let keyPath: string = _getVariable("agent.workFolder") || process.cwd(); + let keyPath: string = _getVariable("agent.TempDirectory") || _getVariable("agent.workFolder") || process.cwd(); _vault = new vm.Vault(keyPath); _knownVariableMap = {}; _debug('loading inputs and endpoints'); @@ -714,10 +722,11 @@ export function _loadData(): void { } // store the secret - if (process.env[envvar]) { + var value = process.env[envvar]; + if (value) { ++loaded; _debug('loading ' + envvar); - _vault.storeSecret(envvar, process.env[envvar]); + _vault.storeSecret(envvar, value); delete process.env[envvar]; } } @@ -759,6 +768,16 @@ export function _loadData(): void { // Internal path helpers. //-------------------------------------------------------------------------------- +/** + * Defines if path is unc-path. + * + * @param path a path to a file. + * @returns true if path starts with double backslash, otherwise returns false. + */ + export function _isUncPath(path: string) { + return /^\\\\[^\\]/.test(path); +} + export function _ensureRooted(root: string, p: string) { if (!root) { throw new Error('ensureRooted() parameter "root" cannot be empty'); @@ -914,3 +933,72 @@ export function _normalizeSeparators(p: string): string { // remove redundant slashes return p.replace(/\/\/+/g, '/'); } + +//----------------------------------------------------- +// Expose proxy information to vsts-node-api +//----------------------------------------------------- +export function _exposeProxySettings(): void { + let proxyUrl: string | undefined = _getVariable('Agent.ProxyUrl'); + if (proxyUrl && proxyUrl.length > 0) { + let proxyUsername: string | undefined = _getVariable('Agent.ProxyUsername'); + let proxyPassword: string | undefined = _getVariable('Agent.ProxyPassword'); + let proxyBypassHostsJson: string | undefined = _getVariable('Agent.ProxyBypassList'); + + global['_vsts_task_lib_proxy_url'] = proxyUrl; + global['_vsts_task_lib_proxy_username'] = proxyUsername; + global['_vsts_task_lib_proxy_bypass'] = proxyBypassHostsJson; + global['_vsts_task_lib_proxy_password'] = _exposeTaskLibSecret('proxy', proxyPassword || ''); + + _debug('expose agent proxy configuration.') + global['_vsts_task_lib_proxy'] = true; + } +} + +//----------------------------------------------------- +// Expose certificate information to vsts-node-api +//----------------------------------------------------- +export function _exposeCertSettings(): void { + let ca: string | undefined = _getVariable('Agent.CAInfo'); + if (ca) { + global['_vsts_task_lib_cert_ca'] = ca; + } + + let clientCert = _getVariable('Agent.ClientCert'); + if (clientCert) { + let clientCertKey: string | undefined = _getVariable('Agent.ClientCertKey'); + let clientCertArchive: string | undefined = _getVariable('Agent.ClientCertArchive'); + let clientCertPassword: string | undefined = _getVariable('Agent.ClientCertPassword'); + + global['_vsts_task_lib_cert_clientcert'] = clientCert; + global['_vsts_task_lib_cert_key'] = clientCertKey; + global['_vsts_task_lib_cert_archive'] = clientCertArchive; + global['_vsts_task_lib_cert_passphrase'] = _exposeTaskLibSecret('cert', clientCertPassword || ''); + } + + if (ca || clientCert) { + _debug('expose agent certificate configuration.') + global['_vsts_task_lib_cert'] = true; + } + + let skipCertValidation: string = _getVariable('Agent.SkipCertValidation') || 'false'; + if (skipCertValidation) { + global['_vsts_task_lib_skip_cert_validation'] = skipCertValidation.toUpperCase() === 'TRUE'; + } +} + +// We store the encryption key on disk and hold the encrypted content and key file in memory +// return base64encoded:base64encoded +// downstream vsts-node-api will retrieve the secret later +function _exposeTaskLibSecret(keyFile: string, secret: string): string | undefined { + if (secret) { + let encryptKey = crypto.randomBytes(256); + let cipher = crypto.createCipher("aes-256-ctr", encryptKey); + let encryptedContent = cipher.update(secret, "utf8", "hex"); + encryptedContent += cipher.final("hex"); + + let storageFile = path.join(_getVariable('Agent.TempDirectory') || _getVariable("agent.workFolder") || process.cwd(), keyFile); + fs.writeFileSync(storageFile, encryptKey.toString('base64'), { encoding: 'utf8' }); + + return new Buffer(storageFile).toString('base64') + ':' + new Buffer(encryptedContent).toString('base64'); + } +} diff --git a/node/lib.json b/node/lib.json index 5287d0990..333fa1477 100644 --- a/node/lib.json +++ b/node/lib.json @@ -6,10 +6,15 @@ "LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", "LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", "LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", + "LIB_MultilineSecret": "Secrets cannot contain multiple lines", + "LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", + "LIB_ProcessExitCode": "The process '%s' failed with exit code %s", + "LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", "LIB_ReturnCode": "Return code: %d", "LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", "LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", "LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", + "LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", "LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", "LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", "LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", @@ -25,6 +30,8 @@ "LIB_PathHasNullByte": "Path cannot contain null bytes", "LIB_OperationFailed": "Failed %s: %s", "LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", - "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS." + "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", + "LIB_PlatformNotSupported": "Platform not supported: %s", + "LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s" } } diff --git a/node/make.js b/node/make.js index fbfc97a5c..7782f4240 100644 --- a/node/make.js +++ b/node/make.js @@ -1,4 +1,3 @@ - require('shelljs/make'); var fs = require('fs'); var path = require('path'); @@ -28,11 +27,12 @@ target.build = function() { target.loc(); run('tsc --outDir ' + buildPath); - cp(rp('dependencies/typings.json'), buildPath); cp(rp('package.json'), buildPath); + cp(rp('package-lock.json'), buildPath); cp(rp('README.md'), buildPath); cp(rp('../LICENSE'), buildPath); cp(rp('lib.json'), buildPath); + cp(rp('ThirdPartyNotice.txt'), buildPath); cp('-Rf', rp('Strings'), buildPath); // just a bootstrap file to avoid /// in final js and .d.ts file rm(path.join(buildPath, 'index.*')); @@ -44,8 +44,10 @@ target.test = function() { buildutils.getExternals(); run('tsc -p ./test'); cp('-Rf', rp('test/scripts'), testPath); + cp('-Rf', rp('test/fakeTasks'), testPath); process.env['TASKLIB_INPROC_UNITS'] = '1'; // export task-lib internals for internal unit testing - run('mocha ' + testPath + ' --recursive'); + set('+e'); // Don't throw an exception when tests fail + run('mocha ' + testPath); } target.loc = function() { diff --git a/node/mock-answer.ts b/node/mock-answer.ts index 250a2e002..ec7d0b7f9 100644 --- a/node/mock-answer.ts +++ b/node/mock-answer.ts @@ -1,5 +1,4 @@ -import path = require('path'); -import fs = require('fs'); +import * as task from './task'; export interface TaskLibAnswerExecResult { code: number, @@ -8,17 +7,26 @@ export interface TaskLibAnswerExecResult { } export interface TaskLibAnswers { - which?: { [key: string]: string; }, - exec?: { [ key: string]: TaskLibAnswerExecResult }, checkPath?: { [key: string]: boolean }, + cwd?: { [key: string]: string }, + exec?: { [ key: string]: TaskLibAnswerExecResult }, exist?: { [key: string]: boolean }, find?: { [key: string]: string[] }, findMatch?: { [key: string]: string[] }, + getPlatform?: { [key: string]: task.Platform }, + getAgentMode?: { [key: string]: task.AgentHostedMode }, + legacyFindFiles?: { [key: string]: string[] }, + ls?: { [key: string]: string }, + osType?: { [key: string]: string }, rmRF?: { [key: string]: { success: boolean } }, + stats?: { [key: string]: any }, // Can't use `fs.Stats` as most existing uses don't mock all required properties + which?: { [key: string]: string }, } +export type MockedCommand = keyof TaskLibAnswers; + export class MockAnswers { - private _answers: TaskLibAnswers; + private _answers: TaskLibAnswers | undefined; public initialize(answers: TaskLibAnswers) { if (!answers) { @@ -27,28 +35,31 @@ export class MockAnswers { this._answers = answers; } - public getResponse(cmd: string, key: string, debug: (message: string) => void): any { + public getResponse(cmd: MockedCommand, key: string, debug: (message: string) => void): any { debug(`looking up mock answers for ${JSON.stringify(cmd)}, key '${JSON.stringify(key)}'`); if (!this._answers) { throw new Error('Must initialize'); } if (!this._answers[cmd]) { - debug(`no mock responses registered for given cmd`); + debug(`no mock responses registered for ${JSON.stringify(cmd)}`); return null; } - if (this._answers[cmd][key]) { + const cmd_answer = this._answers[cmd]!; + + //use this construction to avoid falsy zero + if (cmd_answer[key] != null) { debug('found mock response'); - return this._answers[cmd][key]; + return cmd_answer[key]; } if (key && process.env['MOCK_NORMALIZE_SLASHES'] === 'true') { // try normalizing the slashes var key2 = key.replace(/\\/g, "/"); - if (this._answers[cmd][key2]) { + if (cmd_answer[key2]) { debug('found mock response for normalized key'); - return this._answers[cmd][key2]; + return cmd_answer[key2]; } } diff --git a/node/mock-run.ts b/node/mock-run.ts index 8cb3245fb..9989e40e5 100644 --- a/node/mock-run.ts +++ b/node/mock-run.ts @@ -1,5 +1,6 @@ import ma = require('./mock-answer'); import mockery = require('mockery'); +import im = require('./internal'); export class TaskMockRunner { constructor(taskPath: string) { @@ -7,16 +8,27 @@ export class TaskMockRunner { } _taskPath: string; - _answers: ma.TaskLibAnswers; + _answers: ma.TaskLibAnswers | undefined; _exports: {[key: string]: any} = { }; _moduleCount: number = 0; public setInput(name: string, val: string) { - process.env['INPUT_' + name.replace(' ', '_').toUpperCase()] = val; + let key: string = im._getVariableKey(name); + process.env['INPUT_' + key] = val; + } + + public setVariableName(name: string, val: string, isSecret?: boolean) { + let key: string = im._getVariableKey(name); + if (isSecret) { + process.env['SECRET_' + key] = val; + } + else { + process.env['VSTS_TASKVARIABLE_' + key] = val; + } } /** - * Register answers for the mock "vsts-task-lib/task" instance. + * Register answers for the mock "azure-pipelines-task-lib/task" instance. * * @param answers Answers to be returned when the task lib functions are called. */ @@ -38,7 +50,7 @@ export class TaskMockRunner { } /** - * Registers an override for a specific function on the mock "vsts-task-lib/task" instance. + * Registers an override for a specific function on the mock "azure-pipelines-task-lib/task" instance. * This can be used in conjunction with setAnswers(), for cases where additional runtime * control is needed for a specific function. * @@ -53,7 +65,7 @@ export class TaskMockRunner { /** * Runs a task script. * - * @param noMockTask Indicates whether to mock "vsts-task-lib/task". Default is to mock. + * @param noMockTask Indicates whether to mock "azure-pipelines-task-lib/task". Default is to mock. * @returns void */ public run(noMockTask?: boolean): void { @@ -70,7 +82,7 @@ export class TaskMockRunner { } // register mock task lib else { - var tlm = require('vsts-task-lib/mock-task'); + var tlm = require('azure-pipelines-task-lib/mock-task'); if (this._answers) { tlm.setAnswers(this._answers); } @@ -80,10 +92,10 @@ export class TaskMockRunner { tlm[key] = this._exports[key]; }); - mockery.registerMock('vsts-task-lib/task', tlm); + mockery.registerMock('azure-pipelines-task-lib/task', tlm); } // run it require(this._taskPath); } -} \ No newline at end of file +} diff --git a/node/mock-task.ts b/node/mock-task.ts index 731d2cb88..91d4b4c95 100644 --- a/node/mock-task.ts +++ b/node/mock-task.ts @@ -1,9 +1,6 @@ - import Q = require('q'); import path = require('path'); import fs = require('fs'); -import os = require('os'); -import util = require('util'); import task = require('./task'); import tcm = require('./taskcommand'); import trm = require('./mock-toolrunner'); @@ -16,29 +13,23 @@ export function setAnswers(answers: ma.TaskLibAnswers) { trm.setAnswers(answers); } -module.exports.TaskResult = task.TaskResult; - //----------------------------------------------------- -// General Helpers +// Enums //----------------------------------------------------- -let _outStream = process.stdout; -let _errStream = process.stderr; - -function _writeError(str: string): void { - _errStream.write(str + os.EOL); -} - -function _writeLine(str: string): void { - _outStream.write(str + os.EOL); -} -module.exports.setStdStream = task.setStdStream; -module.exports.setErrStream = task.setErrStream; +module.exports.TaskResult = task.TaskResult; +module.exports.TaskState = task.TaskState; +module.exports.IssueType = task.IssueType; +module.exports.ArtifactType = task.ArtifactType; +module.exports.FieldType = task.FieldType; +module.exports.Platform = task.Platform; //----------------------------------------------------- // Results and Exiting //----------------------------------------------------- +module.exports.setStdStream = task.setStdStream; +module.exports.setErrStream = task.setErrStream; module.exports.setResult = task.setResult; //----------------------------------------------------- @@ -60,16 +51,21 @@ export function loc(key: string, ...args: any[]): string { //----------------------------------------------------- // Input Helpers //----------------------------------------------------- +module.exports.assertAgent = task.assertAgent; module.exports.getVariable = task.getVariable; +module.exports.getVariables = task.getVariables; module.exports.setVariable = task.setVariable; +module.exports.setSecret = task.setSecret; module.exports.getTaskVariable = task.getTaskVariable; module.exports.setTaskVariable = task.setTaskVariable; module.exports.getInput = task.getInput; +module.exports.getInputRequired = task.getInputRequired; module.exports.getBoolInput = task.getBoolInput; +module.exports.getBoolFeatureFlag = task.getBoolFeatureFlag; module.exports.getDelimitedInput = task.getDelimitedInput; module.exports.filePathSupplied = task.filePathSupplied; -function getPathInput(name, required, check) { +function getPathInput(name: string, required?: boolean, check?: boolean): string { var inval = module.exports.getInput(name, required); if (inval) { if (check) { @@ -80,16 +76,25 @@ function getPathInput(name, required, check) { } module.exports.getPathInput = getPathInput; +function getPathInputRequired(name: string, check?: boolean): string { + return getPathInput(name, true, check)!; +} +module.exports.getPathInputRequired = getPathInputRequired; + //----------------------------------------------------- // Endpoint Helpers //----------------------------------------------------- module.exports.getEndpointUrl = task.getEndpointUrl; +module.exports.getEndpointUrlRequired = task.getEndpointUrlRequired; module.exports.getEndpointDataParameter = task.getEndpointDataParameter; +module.exports.getEndpointDataParameterRequired = task.getEndpointDataParameterRequired; module.exports.getEndpointAuthorizationScheme = task.getEndpointAuthorizationScheme; +module.exports.getEndpointAuthorizationSchemeRequired = task.getEndpointAuthorizationSchemeRequired; module.exports.getEndpointAuthorizationParameter = task.getEndpointAuthorizationParameter; +module.exports.getEndpointAuthorizationParameterRequired = task.getEndpointAuthorizationParameterRequired; module.exports.getEndpointAuthorization = task.getEndpointAuthorization; -// TODO: should go away when task lib +// TODO: should go away when task lib export interface EndpointAuthorization { parameters: { [key: string]: string; @@ -108,52 +113,56 @@ module.exports.getSecureFileTicket = task.getSecureFileTicket; //----------------------------------------------------- export class FsStats implements fs.Stats { - private m_isFile: boolean; - private m_isDirectory: boolean; - private m_isBlockDevice: boolean; - private m_isCharacterDevice: boolean; - private m_isSymbolicLink: boolean; - private m_isFIFO: boolean; - private m_isSocket: boolean; - - dev: number; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - rdev: number; - size: number; - blksize: number; - blocks: number; - atime: Date; - mtime: Date; - ctime: Date; - birthtime: Date; - - setAnswers(mockResponses) { - this.m_isFile = mockResponses['isFile'] || false; - this.m_isDirectory = mockResponses['isDirectory'] || false; - this.m_isBlockDevice = mockResponses['isBlockDevice'] || false; - this.m_isCharacterDevice = mockResponses['isCharacterDevice'] || false; - this.m_isSymbolicLink = mockResponses['isSymbolicLink'] || false; - this.m_isFIFO = mockResponses['isFIFO'] || false; - this.m_isSocket = mockResponses['isSocket'] || false; - - this.dev = mockResponses['dev']; - this.ino = mockResponses['ino']; - this.mode = mockResponses['mode']; - this.nlink = mockResponses['nlink']; - this.uid = mockResponses['uid']; - this.gid = mockResponses['gid']; - this.rdev = mockResponses['rdev']; - this.size = mockResponses['size']; - this.blksize = mockResponses['blksize']; - this.blocks = mockResponses['blocks']; - this.atime = mockResponses['atime']; - this.mtime = mockResponses['mtime']; - this.ctime = mockResponses['ctime']; - this.m_isSocket = mockResponses['isSocket']; + private m_isFile: boolean = false; + private m_isDirectory: boolean = false; + private m_isBlockDevice: boolean = false; + private m_isCharacterDevice: boolean = false; + private m_isSymbolicLink: boolean = false; + private m_isFIFO: boolean = false; + private m_isSocket: boolean = false; + + dev: number = 0; + ino: number = 0; + mode: number = 0; + nlink: number = 0; + uid: number = 0; + gid: number = 0; + rdev: number = 0; + size: number = 0; + blksize: number = 0; + blocks: number = 0; + atime: Date = new Date(); + mtime: Date = new Date(); + ctime: Date = new Date(); + birthtime: Date = new Date(); + atimeMs: number; + mtimeMs: number; + ctimeMs: number; + birthtimeMs: number; + + setAnswers(mockResponses: any): void { + this.m_isFile = mockResponses['isFile'] || this.m_isFile; + this.m_isDirectory = mockResponses['isDirectory'] || this.m_isDirectory; + this.m_isBlockDevice = mockResponses['isBlockDevice'] || this.m_isBlockDevice; + this.m_isCharacterDevice = mockResponses['isCharacterDevice'] || this.m_isCharacterDevice; + this.m_isSymbolicLink = mockResponses['isSymbolicLink'] || this.m_isSymbolicLink; + this.m_isFIFO = mockResponses['isFIFO'] || this.m_isFIFO; + this.m_isSocket = mockResponses['isSocket'] || this.m_isSocket; + + this.dev = mockResponses['dev'] || this.dev; + this.ino = mockResponses['ino'] || this.ino; + this.mode = mockResponses['mode'] || this.mode; + this.nlink = mockResponses['nlink'] || this.nlink; + this.uid = mockResponses['uid'] || this.uid; + this.gid = mockResponses['gid'] || this.gid; + this.rdev = mockResponses['rdev'] || this.rdev; + this.size = mockResponses['size'] || this.size; + this.blksize = mockResponses['blksize'] || this.blksize; + this.blocks = mockResponses['blocks'] || this.blocks; + this.atime = mockResponses['atime'] || this.atime; + this.mtime = mockResponses['mtime'] || this.mtime; + this.ctime = mockResponses['ctime'] || this.ctime; + this.m_isSocket = mockResponses['isSocket'] || this.m_isSocket; } isFile(): boolean { @@ -209,6 +218,14 @@ export function osType(): string { return mock.getResponse('osType', 'osType', module.exports.debug); } +export function getPlatform(): task.Platform { + return mock.getResponse('getPlatform', 'getPlatform', module.exports.debug); +} + +export function getAgentMode(): task.AgentHostedMode { + return mock.getResponse('getAgentMode', 'getAgentMode', module.exports.debug); +} + export function cwd(): string { return mock.getResponse('cwd', 'cwd', module.exports.debug); } @@ -251,7 +268,7 @@ export function checkPath(p: string, name: string): void { // - inject system.debug info // - have option to switch internal impl (shelljs now) //----------------------------------------------------- -export function mkdirP(p): void { +export function mkdirP(p: string): void { module.exports.debug('creating path: ' + p); } @@ -284,6 +301,10 @@ export function cp(source: string, dest: string): void { module.exports.debug('copying ' + source + ' to ' + dest); } +export function retry(func: Function, args: any[], retryOptions: task.RetryOptions): any { + module.exports.debug(`trying to execute ${func?.name}(${args.toString()}) with ${retryOptions.retryCount} retries`); +} + export function find(findPath: string): string[] { return mock.getResponse('find', findPath, module.exports.debug); } @@ -313,6 +334,18 @@ export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Pro return tr.exec(options); } +//----------------------------------------------------- +// Exec convenience wrapper +//----------------------------------------------------- +export function execAsync(tool: string, args: any, options?: trm.IExecOptions): Promise { + var toolPath = which(tool, true); + var tr: trm.ToolRunner = this.tool(toolPath); + if (args) { + tr.arg(args); + } + return tr.execAsync(options); +} + export function execSync(tool: string, args: any, options?: trm.IExecSyncOptions): trm.IExecSyncResult { var toolPath = which(tool, true); var tr: trm.ToolRunner = this.tool(toolPath); @@ -358,17 +391,18 @@ export function findMatch(defaultRoot: string, patterns: string[] | string) : st return mock.getResponse('findMatch', responseKey, module.exports.debug); } +export function legacyFindFiles(rootDirectory: string, pattern: string, includeFiles?: boolean, includeDirectories?: boolean) : string[] { + return mock.getResponse('legacyFindFiles', pattern, module.exports.debug); +} + //----------------------------------------------------- // Test Publisher //----------------------------------------------------- export class TestPublisher { - constructor(testRunner) { - this.testRunner = testRunner; + constructor(public testRunner: string) { } - public testRunner: string; - - public publish(resultFiles, mergeResults, platform, config, runTitle, publishRunAttachments) { + public publish(resultFiles?: string, mergeResults?: string, platform?: string, config?: string, runTitle?: string, publishRunAttachments?: string) { var properties = <{ [key: string]: string }>{}; properties['type'] = this.testRunner; @@ -407,7 +441,7 @@ export class TestPublisher { export class CodeCoveragePublisher { constructor() { } - public publish(codeCoverageTool, summaryFileLocation, reportDirectory, additionalCodeCoverageFiles) { + public publish(codeCoverageTool?: string, summaryFileLocation?: string, reportDirectory?: string, additionalCodeCoverageFiles?: string) { var properties = <{ [key: string]: string }>{}; @@ -427,7 +461,7 @@ export class CodeCoveragePublisher { properties['additionalcodecoveragefiles'] = additionalCodeCoverageFiles; } - module.exports.command('codecoverage.publish', properties, ""); + module.exports.command('codecoverage.publish', properties, ""); } } @@ -450,9 +484,53 @@ export class CodeCoverageEnabler { } } +//----------------------------------------------------- +// Task Logging Commands +//----------------------------------------------------- +exports.uploadFile = task.uploadFile; +exports.prependPath = task.prependPath; +exports.uploadSummary = task.uploadSummary; +exports.addAttachment = task.addAttachment; +exports.setEndpoint = task.setEndpoint; +exports.setProgress = task.setProgress; +exports.logDetail = task.logDetail; +exports.logIssue = task.logIssue; + +//----------------------------------------------------- +// Artifact Logging Commands +//----------------------------------------------------- +exports.uploadArtifact = task.uploadArtifact; +exports.associateArtifact = task.associateArtifact; + +//----------------------------------------------------- +// Build Logging Commands +//----------------------------------------------------- +exports.uploadBuildLog = task.uploadBuildLog; +exports.updateBuildNumber = task.updateBuildNumber; +exports.addBuildTag = task.addBuildTag; + +//----------------------------------------------------- +// Release Logging Commands +//----------------------------------------------------- +exports.updateReleaseName = task.updateReleaseName; + //----------------------------------------------------- // Tools //----------------------------------------------------- exports.TaskCommand = tcm.TaskCommand; exports.commandFromString = tcm.commandFromString; exports.ToolRunner = trm.ToolRunner; + +//----------------------------------------------------- +// Http Proxy Helper +//----------------------------------------------------- +export function getHttpProxyConfiguration(requestUrl?: string): task.ProxyConfiguration | null { + return null; +} + +//----------------------------------------------------- +// Http Certificate Helper +//----------------------------------------------------- +export function getHttpCertConfiguration(): task.CertConfiguration | null { + return null +} \ No newline at end of file diff --git a/node/mock-test.ts b/node/mock-test.ts index 4dce93786..19d44f52e 100644 --- a/node/mock-test.ts +++ b/node/mock-test.ts @@ -1,31 +1,38 @@ import cp = require('child_process'); import fs = require('fs'); -import path = require('path'); +import ncp = require('child_process'); import os = require('os'); +import path = require('path'); import cmdm = require('./taskcommand'); import shelljs = require('shelljs'); +import syncRequest from 'sync-request'; const COMMAND_TAG = '[command]'; const COMMAND_LENGTH = COMMAND_TAG.length; +const downloadDirectory = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE as string, 'azure-pipelines-task-lib', '_download'); export class MockTestRunner { - constructor(testPath: string) { + constructor(testPath: string, taskJsonPath?: string) { + this._taskJsonPath = taskJsonPath || ''; this._testPath = testPath; + this.nodePath = this.getNodePath(); } - private _testPath: string; - public stdout: string; - public stderr: string; - public cmdlines: any; - public invokedToolCount: number; - public succeeded: boolean; - public errorIssues: string[]; - public warningIssues: string[]; + private _testPath = ''; + private _taskJsonPath = ''; + public nodePath = ''; + public stdout = ''; + public stderr = ''; + public cmdlines: {[key:string]: boolean} = {}; + public invokedToolCount = 0; + public succeeded = false; + public errorIssues: string[] = []; + public warningIssues: string[] = []; get failed(): boolean { return !this.succeeded; } - + public ran(cmdline: string): boolean { return this.cmdlines.hasOwnProperty(cmdline.trim()); } @@ -36,17 +43,17 @@ export class MockTestRunner { public createdWarningIssue(message: string): boolean { return this.warningIssues.indexOf(message.trim()) >= 0; - } + } public stdOutContained(message: string): boolean { - return this.stdout && this.stdout.indexOf(message) > 0; + return this.stdout.indexOf(message) > 0; } public stdErrContained(message: string): boolean { - return this.stderr && this.stderr.indexOf(message) > 0; + return this.stderr.indexOf(message) > 0; } - public run(): void { + public run(nodeVersion?: number): void { this.cmdlines = {}; this.invokedToolCount = 0; this.succeeded = true; @@ -54,15 +61,19 @@ export class MockTestRunner { this.errorIssues = []; this.warningIssues = []; - // we use node in the path. - // if you want to test with a specific node, ensure it's in the path - let nodePath = shelljs.which('node'); - if (!nodePath) { - console.error('Could not find node in path'); - return; + let nodePath = this.nodePath; + if (nodeVersion) { + nodePath = this.getNodePath(nodeVersion); } - let spawn = cp.spawnSync(nodePath, [this._testPath]); + + // Clean environment + Object.keys(process.env) + .filter(key => (key.substr(0, 'INPUT_'.length) === 'INPUT_' || + key.substr(0, 'SECRET_'.length) === 'SECRET_' || + key.substr(0, 'VSTS_TASKVARIABLE_'.length) === 'VSTS_TASKVARIABLE_')) + .forEach(key => delete process.env[key]); + if (spawn.error) { console.error('Running test failed'); console.error(spawn.error.message); @@ -80,7 +91,7 @@ export class MockTestRunner { let traceFile: string = this._testPath + '.log'; lines.forEach((line: string) => { let ci = line.indexOf('##vso['); - let cmd: cmdm.TaskCommand; + let cmd: cmdm.TaskCommand | undefined; let cmi = line.indexOf(COMMAND_TAG); if (ci >= 0) { cmd = cmdm.commandFromString(line.substring(ci)); @@ -125,4 +136,191 @@ export class MockTestRunner { console.log('TRACE FILE: ' + traceFile); } } + + // Returns a path to node.exe with the correct version for this task (based on if its node10 or node) + private getNodePath(nodeVersion?: number): string { + const version: number = nodeVersion || this.getNodeVersion(); + + let downloadVersion: string; + switch (version) { + case 6: + downloadVersion = 'v6.17.1'; + break; + case 10: + downloadVersion = 'v10.21.0'; + break; + case 16: + downloadVersion = 'v16.13.0'; + break; + default: + throw new Error('Invalid node version, must be 6, 10, or 16 (received ' + version + ')'); + } + + // Install node in home directory if it isn't already there. + const downloadDestination: string = path.join(downloadDirectory, 'node' + version); + const pathToExe: string = this.getPathToNodeExe(downloadVersion, downloadDestination); + if (pathToExe) { + return pathToExe; + } + else { + return this.downloadNode(downloadVersion, downloadDestination); + } + } + + // Determines the correct version of node to use based on the contents of the task's task.json. Defaults to Node 16. + private getNodeVersion(): number { + const taskJsonPath: string = this.getTaskJsonPath(); + if (!taskJsonPath) { + console.warn('Unable to find task.json, defaulting to use Node 16'); + return 16; + } + const taskJsonContents = fs.readFileSync(taskJsonPath, { encoding: 'utf-8' }); + const taskJson: {[key:string]: string} = JSON.parse(taskJsonContents); + + let nodeVersionFound = false; + const execution = ( + taskJson['execution'] + || taskJson['prejobexecution'] + || taskJson['postjobexecution'] + ); + const keys = Object.keys(execution); + for (let i = 0; i < keys.length; i++) { + if (keys[i].toLowerCase() == 'node16') { + // Prefer node 16 and return immediately. + return 16; + } else if (keys[i].toLowerCase() == 'node10') { + // Prefer node 10 and return immediately. + return 10; + } else if (keys[i].toLowerCase() == 'node') { + nodeVersionFound = true; + } + } + + if (!nodeVersionFound) { + console.warn('Unable to determine execution type from task.json, defaulting to use Node 16'); + return 16; + } + + return 6; + } + + // Returns the path to the task.json for the task being tested. Returns null if unable to find it. + // Searches by moving up the directory structure from the initial starting point and checking at each level. + private getTaskJsonPath(): string { + if (this._taskJsonPath) { + return this._taskJsonPath; + } + let curPath: string = this._testPath; + let newPath: string = path.join(this._testPath, '..'); + while (curPath != newPath) { + curPath = newPath; + let taskJsonPath: string = path.join(curPath, 'task.json'); + if (fs.existsSync(taskJsonPath)) { + return taskJsonPath; + } + newPath = path.join(curPath, '..'); + } + return ''; + } + + // Downloads the specified node version to the download destination. Returns a path to node.exe + private downloadNode(nodeVersion: string, downloadDestination: string): string { + shelljs.rm('-rf', downloadDestination); + let nodeUrl: string = process.env['TASK_NODE_URL'] || 'https://nodejs.org/dist'; + nodeUrl = nodeUrl.replace(/\/$/, ''); // ensure there is no trailing slash on the base URL + let downloadPath = ''; + switch (this.getPlatform()) { + case 'darwin': + this.downloadTarGz(nodeUrl + '/' + nodeVersion + '/node-' + nodeVersion + '-darwin-x64.tar.gz', downloadDestination); + downloadPath = path.join(downloadDestination, 'node-' + nodeVersion + '-darwin-x64', 'bin', 'node'); + break; + case 'linux': + this.downloadTarGz(nodeUrl + '/' + nodeVersion + '/node-' + nodeVersion + '-linux-x64.tar.gz', downloadDestination); + downloadPath = path.join(downloadDestination, 'node-' + nodeVersion + '-linux-x64', 'bin', 'node'); + break; + case 'win32': + this.downloadFile(nodeUrl + '/' + nodeVersion + '/win-x64/node.exe', downloadDestination, 'node.exe'); + this.downloadFile(nodeUrl + '/' + nodeVersion + '/win-x64/node.lib', downloadDestination, 'node.lib'); + downloadPath = path.join(downloadDestination, 'node.exe') + } + + // Write marker to indicate download completed. + const marker = downloadDestination + '.completed'; + fs.writeFileSync(marker, ''); + + return downloadPath; + } + + // Downloads file to the downloadDestination, making any necessary folders along the way. + private downloadFile(url: string, downloadDestination: string, fileName: string): void { + const filePath: string = path.join(downloadDestination, fileName); + if (!url) { + throw new Error('Parameter "url" must be set.'); + } + if (!downloadDestination) { + throw new Error('Parameter "downloadDestination" must be set.'); + } + console.log('Downloading file:', url); + shelljs.mkdir('-p', downloadDestination); + const result: any = syncRequest('GET', url); + fs.writeFileSync(filePath, result.getBody()); + } + + // Downloads tarGz to the download destination, making any necessary folders along the way. + private downloadTarGz(url: string, downloadDestination: string): void { + if (!url) { + throw new Error('Parameter "url" must be set.'); + } + if (!downloadDestination) { + throw new Error('Parameter "downloadDestination" must be set.'); + } + const tarGzName: string = 'node.tar.gz'; + this.downloadFile(url, downloadDestination, tarGzName); + + // Extract file + const originalCwd: string = process.cwd(); + process.chdir(downloadDestination); + try { + ncp.execSync(`tar -xzf "${path.join(downloadDestination, tarGzName)}"`); + } + catch { + throw new Error('Failed to unzip node tar.gz from ' + url); + } + finally { + process.chdir(originalCwd); + } + } + + // Checks if node is installed at downloadDestination. If it is, returns a path to node.exe, otherwise returns null. + private getPathToNodeExe(nodeVersion: string, downloadDestination: string): string { + let exePath = ''; + switch (this.getPlatform()) { + case 'darwin': + exePath = path.join(downloadDestination, 'node-' + nodeVersion + '-darwin-x64', 'bin', 'node'); + break; + case 'linux': + exePath = path.join(downloadDestination, 'node-' + nodeVersion + '-linux-x64', 'bin', 'node'); + break; + case 'win32': + exePath = path.join(downloadDestination, 'node.exe'); + } + + // Only use path if marker is found indicating download completed successfully (and not partially) + const marker = downloadDestination + '.completed'; + + if (fs.existsSync(exePath) && fs.existsSync(marker)) { + return exePath; + } + else { + return ''; + } + } + + private getPlatform(): string { + let platform: string = os.platform(); + if (platform != 'darwin' && platform != 'linux' && platform != 'win32') { + throw new Error('Unexpected platform: ' + platform); + } + return platform; + } } diff --git a/node/mock-toolrunner.ts b/node/mock-toolrunner.ts index 45d7f8cf8..f2e622717 100644 --- a/node/mock-toolrunner.ts +++ b/node/mock-toolrunner.ts @@ -1,7 +1,5 @@ - import Q = require('q'); import os = require('os'); -import path = require('path'); import events = require('events'); import ma = require('./mock-answer'); @@ -11,30 +9,18 @@ export function setAnswers(answers: ma.TaskLibAnswers) { mock.initialize(answers); } -var run = function(cmd, callback) { - console.log('running: ' + cmd); - var output = ''; - try { - - } - catch (err) { - console.log(err.message); - } - -} - export interface IExecOptions extends IExecSyncOptions { - failOnStdErr: boolean; - ignoreReturnCode: boolean; + failOnStdErr?: boolean; + ignoreReturnCode?: boolean; }; export interface IExecSyncOptions { - cwd: string; - env: { [key: string]: string }; - silent: boolean; + cwd?: string; + env?: { [key: string]: string | undefined }; + silent?: boolean; outStream: NodeJS.WritableStream; errStream: NodeJS.WritableStream; - windowsVerbatimArguments: boolean; + windowsVerbatimArguments?: boolean; }; export interface IExecSyncResult { @@ -44,37 +30,37 @@ export interface IExecSyncResult { error: Error; } -export function debug(message) { +export function debug(message: string) { // do nothing, overridden -}; +} export class ToolRunner extends events.EventEmitter { - constructor(toolPath) { + constructor(toolPath: string) { debug('toolRunner toolPath: ' + toolPath); super(); - + this.toolPath = toolPath; this.args = []; } private toolPath: string; private args: string[]; - private pipeOutputToTool: ToolRunner; + private pipeOutputToTool: ToolRunner | undefined; - private _debug(message) { + private _debug(message: string) { debug(message); this.emit('debug', message); } private _argStringToArray(argString: string): string[] { - var args = []; + var args: string[] = []; var inQuotes = false; var escaped =false; var arg = ''; - var append = function(c) { + var append = function(c: string) { // we only escape double quotes. if (escaped && c !== '"') { arg += '\\'; @@ -96,7 +82,7 @@ export class ToolRunner extends events.EventEmitter { } continue; } - + if (c === "\\" && inQuotes) { escaped = true; continue; @@ -122,7 +108,7 @@ export class ToolRunner extends events.EventEmitter { public arg(val: any): ToolRunner { if (!val) { - return; + return this; } if (val instanceof Array) { @@ -136,7 +122,7 @@ export class ToolRunner extends events.EventEmitter { return this; } - + public argIf(condition: any, val: any): ToolRunner { if (condition) { this.arg(val); @@ -147,12 +133,12 @@ export class ToolRunner extends events.EventEmitter { public line(val: string): ToolRunner { if (!val) { - return; + return this; } this._debug(this.toolPath + ' arg: ' + val); this.args = this.args.concat(this._argStringToArray(val)); - return this; + return this; } public pipeExecOutputToTool(tool: ToolRunner) : ToolRunner { @@ -165,17 +151,124 @@ export class ToolRunner extends events.EventEmitter { this._debug('tempPath=' + process.env['MOCK_TEMP_PATH']); if (process.env['MOCK_IGNORE_TEMP_PATH'] === 'true') { // Using split/join to replace the temp path - cmdString = cmdString.split(process.env['MOCK_TEMP_PATH']).join(''); + cmdString = cmdString.split(process.env['MOCK_TEMP_PATH'] || "").join(''); } return cmdString; - } + } // // Exec - use for long running tools where you need to stream live output as it runs // returns a promise with return code. // - public exec(options: IExecOptions): Q.Promise { + public execAsync(options?: IExecOptions): Promise { + this._debug('exec tool: ' + this.toolPath); + this._debug('Arguments:'); + this.args.forEach((arg) => { + this._debug(' ' + arg); + }); + + var success = true; + options = options || {}; + + var ops: IExecOptions = { + cwd: options.cwd || process.cwd(), + env: options.env || process.env, + silent: options.silent || false, + outStream: options.outStream || process.stdout, + errStream: options.errStream || process.stderr, + failOnStdErr: options.failOnStdErr || false, + ignoreReturnCode: options.ignoreReturnCode || false, + windowsVerbatimArguments: options.windowsVerbatimArguments + }; + + var argString = this.args.join(' ') || ''; + var cmdString = this.toolPath; + if (argString) { + cmdString += (' ' + argString); + } + + // Using split/join to replace the temp path + cmdString = this.ignoreTempPath(cmdString); + + if (!ops.silent) { + if(this.pipeOutputToTool) { + var pipeToolArgString = this.pipeOutputToTool.args.join(' ') || ''; + var pipeToolCmdString = this.ignoreTempPath(this.pipeOutputToTool.toolPath); + if(pipeToolArgString) { + pipeToolCmdString += (' ' + pipeToolArgString); + } + + cmdString += ' | ' + pipeToolCmdString; + } + + ops.outStream.write('[command]' + cmdString + os.EOL); + } + + // TODO: filter process.env + var res = mock.getResponse('exec', cmdString, debug); + if (res.stdout) { + this.emit('stdout', res.stdout); + if (!ops.silent) { + ops.outStream.write(res.stdout + os.EOL); + } + const stdLineArray = res.stdout.split(os.EOL); + for (const line of stdLineArray.slice(0, -1)) { + this.emit('stdline', line); + } + if(stdLineArray.length > 0 && stdLineArray[stdLineArray.length - 1].length > 0) { + this.emit('stdline', stdLineArray[stdLineArray.length - 1]); + } + } + + if (res.stderr) { + this.emit('stderr', res.stderr); + + success = !ops.failOnStdErr; + if (!ops.silent) { + var s = ops.failOnStdErr ? ops.errStream : ops.outStream; + s.write(res.stderr + os.EOL); + } + const stdErrArray = res.stderr.split(os.EOL); + for (const line of stdErrArray.slice(0, -1)) { + this.emit('errline', line); + } + if (stdErrArray.length > 0 && stdErrArray[stdErrArray.length - 1].length > 0) { + this.emit('errline', stdErrArray[stdErrArray.length - 1]); + } + } + + + var code = res.code; + + if (!ops.silent) { + ops.outStream.write('rc:' + res.code + os.EOL); + } + + if (code != 0 && !ops.ignoreReturnCode) { + success = false; + } + + if (!ops.silent) { + ops.outStream.write('success:' + success + os.EOL); + } + + return new Promise((resolve, reject) => { + if (success) { + resolve(code); + } + else { + reject(new Error(this.toolPath + ' failed with return code: ' + code)); + } + }); + } + + /** + * Exec - use for long running tools where you need to stream live output as it runs + * @deprecated use `execAsync` instead + * @returns a promise with return code. + */ + public exec(options?: IExecOptions): Q.Promise { var defer = Q.defer(); this._debug('exec tool: ' + this.toolPath); @@ -228,6 +321,13 @@ export class ToolRunner extends events.EventEmitter { if (!ops.silent) { ops.outStream.write(res.stdout + os.EOL); } + const stdLineArray = res.stdout.split(os.EOL); + for (const line of stdLineArray.slice(0, -1)) { + this.emit('stdline', line); + } + if(stdLineArray.length > 0 && stdLineArray[stdLineArray.length - 1].length > 0) { + this.emit('stdline', stdLineArray[stdLineArray.length - 1]); + } } if (res.stderr) { @@ -238,18 +338,29 @@ export class ToolRunner extends events.EventEmitter { var s = ops.failOnStdErr ? ops.errStream : ops.outStream; s.write(res.stderr + os.EOL); } + const stdErrArray = res.stderr.split(os.EOL); + for (const line of stdErrArray.slice(0, -1)) { + this.emit('errline', line); + } + if (stdErrArray.length > 0 && stdErrArray[stdErrArray.length - 1].length > 0) { + this.emit('errline', stdErrArray[stdErrArray.length - 1]); + } } var code = res.code; - ops.outStream.write('rc:' + res.code + os.EOL); + if (!ops.silent) { + ops.outStream.write('rc:' + res.code + os.EOL); + } if (code != 0 && !ops.ignoreReturnCode) { success = false; } - ops.outStream.write('success:' + success + os.EOL); + if (!ops.silent) { + ops.outStream.write('success:' + success + os.EOL); + } if (success) { defer.resolve(code); } @@ -264,9 +375,7 @@ export class ToolRunner extends events.EventEmitter { // ExecSync - use for short running simple commands. Simple and convenient (synchronous) // but also has limits. For example, no live output and limited to max buffer // - public execSync(options: IExecSyncOptions): IExecSyncResult { - var defer = Q.defer(); - + public execSync(options?: IExecSyncOptions): IExecSyncResult { this._debug('exec tool: ' + this.toolPath); this._debug('Arguments:'); this.args.forEach((arg) => { diff --git a/node/package-lock.json b/node/package-lock.json new file mode 100644 index 000000000..21cad67f2 --- /dev/null +++ b/node/package-lock.json @@ -0,0 +1,1070 @@ +{ + "name": "azure-pipelines-task-lib", + "version": "4.5.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", + "requires": { + "@types/node": "*" + } + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true + }, + "@types/mockery": { + "version": "1.4.29", + "resolved": "https://registry.npmjs.org/@types/mockery/-/mockery-1.4.29.tgz", + "integrity": "sha1-m6It838H43gP/4Ux0aOOYz+UV6U=", + "dev": true + }, + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==" + }, + "@types/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==", + "dev": true + }, + "@types/shelljs": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.8.tgz", + "integrity": "sha512-lD3LWdg6j8r0VRBFahJVaxoW0SIcswxKaFUrmKl33RJVeeoNYQAz4uqCJ5Z6v4oIBOsC5GozX+I5SorIKiTcQA==", + "dev": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.35.tgz", + "integrity": "sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA==" + } + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "mockery": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz", + "integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "requires": { + "asap": "~2.0.6" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + }, + "qs": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", + "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "requires": { + "get-port": "^3.1.0" + } + }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.64", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.64.tgz", + "integrity": "sha512-/EwBIb+imu8Qi/A3NF9sJ9iuKo7yV+pryqjmeRqaU0C4wBAOhas5mdvoYeJ5PCKrh6thRSJHdoasFqh3BQGILA==" + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/node/package.json b/node/package.json index b1cc397d1..cf32c9e09 100644 --- a/node/package.json +++ b/node/package.json @@ -1,7 +1,7 @@ { - "name": "vsts-task-lib", - "version": "2.0.7", - "description": "VSTS Task SDK", + "name": "azure-pipelines-task-lib", + "version": "4.5.0", + "description": "Azure Pipelines Task SDK", "main": "./task.js", "typings": "./task.d.ts", "scripts": { @@ -10,31 +10,40 @@ }, "repository": { "type": "git", - "url": "https://github.com/Microsoft/vsts-task-lib" + "url": "https://github.com/Microsoft/azure-pipelines-task-lib" }, "keywords": [ - "vsts", - "xplat", + "azure-pipelines", "agent", - "build" + "build", + "release", + "ci-cd", + "task" ], "author": "Microsoft", "license": "MIT", "bugs": { - "url": "https://github.com/Microsoft/vsts-task-lib/issues" + "url": "https://github.com/Microsoft/azure-pipelines-task-lib/issues" }, - "homepage": "https://github.com/Microsoft/vsts-task-lib", + "homepage": "https://github.com/Microsoft/azure-pipelines-task-lib", "dependencies": { - "minimatch": "^3.0.0", - "mockery": "^1.7.0", - "uuid": "^3.0.1", - "q": "^1.1.2", + "minimatch": "3.0.5", + "mockery": "^2.1.0", + "q": "^1.5.1", "semver": "^5.1.0", - "shelljs": "^0.3.0" + "shelljs": "^0.8.5", + "sync-request": "6.1.0", + "uuid": "^3.0.1" }, "devDependencies": { - "mocha": "^2.2.5", - "sync-request": "3.0.1", - "typescript": "1.8.7" + "@types/minimatch": "3.0.3", + "@types/mocha": "^9.1.1", + "@types/mockery": "^1.4.29", + "@types/node": "^10.17.0", + "@types/q": "^1.5.4", + "@types/semver": "^7.3.4", + "@types/shelljs": "^0.8.8", + "mocha": "^9.2.2", + "typescript": "^4.0.0" } } diff --git a/node/task.ts b/node/task.ts index a9a580c86..42c833330 100644 --- a/node/task.ts +++ b/node/task.ts @@ -1,20 +1,60 @@ import Q = require('q'); import shell = require('shelljs'); +import childProcess = require('child_process'); import fs = require('fs'); import path = require('path'); import os = require('os'); import minimatch = require('minimatch'); -import util = require('util'); import im = require('./internal'); import tcm = require('./taskcommand'); import trm = require('./toolrunner'); -import vm = require('./vault'); import semver = require('semver'); export enum TaskResult { Succeeded = 0, SucceededWithIssues = 1, - Failed = 2 + Failed = 2, + Cancelled = 3, + Skipped = 4 +} + +export enum TaskState { + Unknown = 0, + Initialized = 1, + InProgress = 2, + Completed = 3 +} + +export enum IssueType { + Error, + Warning +} + +export enum ArtifactType { + Container, + FilePath, + VersionControl, + GitRef, + TfvcLabel +} + +export enum FieldType { + AuthParameter, + DataParameter, + Url +} + +/** Platforms supported by our build agent */ +export enum Platform { + Windows, + MacOS, + Linux +} + +export enum AgentHostedMode { + Unknown, + SelfHosted, + MsHosted } //----------------------------------------------------- @@ -32,12 +72,17 @@ export const setErrStream = im._setErrStream; * Execution will continue. * If not set, task will be Succeeded. * If multiple calls are made to setResult the most pessimistic call wins (Failed) regardless of the order of calls. - * - * @param result TaskResult enum of Succeeded, SucceededWithIssues or Failed. + * + * @param result TaskResult enum of Succeeded, SucceededWithIssues, Failed, Cancelled or Skipped. * @param message A message which will be logged as an error issue if the result is Failed. + * @param done Optional. Instructs the agent the task is done. This is helpful when child processes + * may still be running and prevent node from fully exiting. This argument is supported + * from agent version 2.142.0 or higher (otherwise will no-op). * @returns void */ -export function setResult(result: TaskResult, message: string): void { +export function setResult(result: TaskResult.Succeeded, message?: string, done?: boolean): void; +export function setResult(result: Exclude, message: string, done?: boolean): void; +export function setResult(result: TaskResult, message: string, done?: boolean): void { debug('task result: ' + TaskResult[result]); // add an error issue @@ -48,15 +93,35 @@ export function setResult(result: TaskResult, message: string): void { warning(message); } - // set the task result - command('task.complete', { 'result': TaskResult[result] }, message); + // task.complete + var properties: {[key:string]: string} = { 'result': TaskResult[result] }; + if (done) { + properties['done'] = 'true'; + } + + command('task.complete', properties, message); } // // Catching all exceptions // -process.on('uncaughtException', (err) => { +process.on('uncaughtException', (err: Error) => { setResult(TaskResult.Failed, loc('LIB_UnhandledEx', err.message)); + error(String(err.stack)); +}); + +// +// Catching unhandled rejections from promises and rethrowing them as exceptions +// For example, a promise that is rejected but not handled by a .catch() handler in node 10 +// doesn't cause an uncaughtException but causes in Node 16. +// For types definitions(Error | Any) see https://nodejs.org/docs/latest-v16.x/api/process.html#event-unhandledrejection +// +process.on('unhandledRejection', (reason: Error | any) => { + if (reason instanceof Error) { + throw reason; + } else { + throw new Error(reason); + } }); //----------------------------------------------------- @@ -111,13 +176,14 @@ export function getVariables(): VariableInfo[] { /** * Sets a variable which will be available to subsequent tasks as well. - * - * @param name name of the variable to set - * @param val value to set - * @param secret whether variable is secret. optional, defaults to false + * + * @param name name of the variable to set + * @param val value to set + * @param secret whether variable is secret. Multi-line secrets are not allowed. Optional, defaults to false + * @param isOutput whether variable is an output variable. Optional, defaults to false * @returns void */ -export function setVariable(name: string, val: string, secret: boolean = false): void { +export function setVariable(name: string, val: string, secret: boolean = false, isOutput: boolean = false): void { // once a secret always a secret let key: string = im._getVariableKey(name); if (im._knownVariableMap.hasOwnProperty(key)) { @@ -128,6 +194,10 @@ export function setVariable(name: string, val: string, secret: boolean = false): let varValue = val || ''; debug('set ' + name + '=' + (secret && varValue ? '********' : varValue)); if (secret) { + if (varValue && varValue.match(/\r|\n/) && `${process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']}`.toUpperCase() != 'TRUE') { + throw new Error(loc('LIB_MultilineSecret')); + } + im._vault.storeSecret('SECRET_' + key, varValue); delete process.env[key]; } else { @@ -137,8 +207,22 @@ export function setVariable(name: string, val: string, secret: boolean = false): // store the metadata im._knownVariableMap[key] = { name: name, secret: secret }; - // write the command - command('task.setvariable', { 'variable': name || '', 'issecret': (secret || false).toString() }, varValue); + // write the setvariable command + command('task.setvariable', { 'variable': name || '', isOutput: (isOutput || false).toString(), 'issecret': (secret || false).toString() }, varValue); +} + +/** + * Registers a value with the logger, so the value will be masked from the logs. Multi-line secrets are not allowed. + * + * @param val value to register + */ +export function setSecret(val: string): void { + if (val) { + if (val.match(/\r|\n/) && `${process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']}`.toUpperCase() !== 'TRUE') { + throw new Error(loc('LIB_MultilineSecret')); + } + command('task.setsecret', {}, val); + } } /** Snapshot of a variable at the time when getVariables was called. */ @@ -149,18 +233,15 @@ export interface VariableInfo { } /** - * Gets the value of an input. The value is also trimmed. + * Gets the value of an input. * If required is true and the value is not set, it will throw. - * + * * @param name name of the input to get * @param required whether input is required. optional, defaults to false * @returns string */ -export function getInput(name: string, required?: boolean): string { - var inval = im._vault.retrieveSecret('INPUT_' + name.replace(' ', '_').toUpperCase()); - if (inval) { - inval = inval.trim(); - } +export function getInput(name: string, required?: boolean): string | undefined { + var inval = im._vault.retrieveSecret('INPUT_' + im._getVariableKey(name)); if (required && !inval) { throw new Error(loc('LIB_InputRequired', name)); @@ -170,18 +251,50 @@ export function getInput(name: string, required?: boolean): string { return inval; } +/** + * Gets the value of an input. + * If the value is not set, it will throw. + * + * @param name name of the input to get + * @returns string + */ +export function getInputRequired(name: string): string { + return getInput(name, true)!; +} + /** * Gets the value of an input and converts to a bool. Convenience. * If required is true and the value is not set, it will throw. - * + * If required is false and the value is not set, returns false. + * * @param name name of the bool input to get * @param required whether input is required. optional, defaults to false - * @returns string + * @returns boolean */ export function getBoolInput(name: string, required?: boolean): boolean { return (getInput(name, required) || '').toUpperCase() == "TRUE"; } +/** + * Gets the value of an feature flag and converts to a bool. + * + * @param name name of the feature flag to get. + * @param defaultValue default value of the feature flag in case it's not found in env. (optional. Default value = false) + * @returns boolean + */ +export function getBoolFeatureFlag(ffName: string, defaultValue: boolean = false): boolean { + const ffValue = process.env[ffName]; + + if (!ffValue) { + debug(`Feature flag ${ffName} not found. Returning ${defaultValue} as default.`); + return defaultValue; + } + + debug(`Feature flag ${ffName} = ${ffValue}`); + + return ffValue.toLowerCase() === "true"; +} + /** * Gets the value of an input and splits the value using a delimiter (space, comma, etc). * Empty values are removed. This function is useful for splitting an input containing a simple @@ -189,13 +302,13 @@ export function getBoolInput(name: string, required?: boolean): boolean { * IMPORTANT: Do not use this function for splitting additional args! Instead use argString(), which * follows normal argument splitting rules and handles values encapsulated by quotes. * If required is true and the value is not set, it will throw. - * + * * @param name name of the input to get * @param delim delimiter to split on * @param required whether input is required. optional, defaults to false * @returns string[] */ -export function getDelimitedInput(name: string, delim: string, required?: boolean): string[] { +export function getDelimitedInput(name: string, delim: string | RegExp, required?: boolean): string[] { let inputVal = getInput(name, required); if (!inputVal) { return []; @@ -215,7 +328,7 @@ export function getDelimitedInput(name: string, delim: string, required?: boolea * Checks whether a path inputs value was supplied by the user * File paths are relative with a picker, so an empty path is the root of the repo. * Useful if you need to condition work (like append an arg) if a value was supplied - * + * * @param name name of the path input to check * @returns boolean */ @@ -234,13 +347,13 @@ export function filePathSupplied(name: string): boolean { * It will be quoted for you if it isn't already and contains spaces * If required is true and the value is not set, it will throw. * If check is true and the path does not exist, it will throw. - * + * * @param name name of the input to get * @param required whether input is required. optional, defaults to false - * @param check whether path is checked. optional, defaults to false + * @param check whether path is checked. optional, defaults to false * @returns string */ -export function getPathInput(name: string, required?: boolean, check?: boolean): string { +export function getPathInput(name: string, required?: boolean, check?: boolean): string | undefined { var inval = getInput(name, required); if (inval) { if (check) { @@ -251,6 +364,20 @@ export function getPathInput(name: string, required?: boolean, check?: boolean): return inval; } +/** + * Gets the value of a path input + * It will be quoted for you if it isn't already and contains spaces + * If the value is not set, it will throw. + * If check is true and the path does not exist, it will throw. + * + * @param name name of the input to get + * @param check whether path is checked. optional, defaults to false + * @returns string + */ +export function getPathInputRequired(name: string, check?: boolean): string { + return getPathInput(name, true, check)!; +} + //----------------------------------------------------- // Endpoint Helpers //----------------------------------------------------- @@ -258,12 +385,12 @@ export function getPathInput(name: string, required?: boolean, check?: boolean): /** * Gets the url for a service endpoint * If the url was not set and is not optional, it will throw. - * + * * @param id name of the service endpoint * @param optional whether the url is optional * @returns string */ -export function getEndpointUrl(id: string, optional: boolean): string { +export function getEndpointUrl(id: string, optional: boolean): string | undefined { var urlval = process.env['ENDPOINT_URL_' + id]; if (!optional && !urlval) { @@ -274,6 +401,17 @@ export function getEndpointUrl(id: string, optional: boolean): string { return urlval; } +/** + * Gets the url for a service endpoint + * If the url was not set, it will throw. + * + * @param id name of the service endpoint + * @returns string + */ +export function getEndpointUrlRequired(id: string): string { + return getEndpointUrl(id, false)!; +} + /* * Gets the endpoint data parameter value with specified key for a service endpoint * If the endpoint data parameter was not set and is not optional, it will throw. @@ -283,7 +421,7 @@ export function getEndpointUrl(id: string, optional: boolean): string { * @param optional whether the endpoint data is optional * @returns {string} value of the endpoint data parameter */ -export function getEndpointDataParameter(id: string, key: string, optional: boolean): string { +export function getEndpointDataParameter(id: string, key: string, optional: boolean): string | undefined { var dataParamVal = process.env['ENDPOINT_DATA_' + id + '_' + key.toUpperCase()]; if (!optional && !dataParamVal) { @@ -294,6 +432,18 @@ export function getEndpointDataParameter(id: string, key: string, optional: bool return dataParamVal; } +/* + * Gets the endpoint data parameter value with specified key for a service endpoint + * If the endpoint data parameter was not set, it will throw. + * + * @param id name of the service endpoint + * @param key of the parameter + * @returns {string} value of the endpoint data parameter + */ +export function getEndpointDataParameterRequired(id: string, key: string): string { + return getEndpointDataParameter(id, key, false)!; +} + /** * Gets the endpoint authorization scheme for a service endpoint * If the endpoint authorization scheme is not set and is not optional, it will throw. @@ -302,7 +452,7 @@ export function getEndpointDataParameter(id: string, key: string, optional: bool * @param optional whether the endpoint authorization scheme is optional * @returns {string} value of the endpoint authorization scheme */ -export function getEndpointAuthorizationScheme(id: string, optional: boolean): string { +export function getEndpointAuthorizationScheme(id: string, optional: boolean): string | undefined { var authScheme = im._vault.retrieveSecret('ENDPOINT_AUTH_SCHEME_' + id); if (!optional && !authScheme) { @@ -313,6 +463,17 @@ export function getEndpointAuthorizationScheme(id: string, optional: boolean): s return authScheme; } +/** + * Gets the endpoint authorization scheme for a service endpoint + * If the endpoint authorization scheme is not set, it will throw. + * + * @param id name of the service endpoint + * @returns {string} value of the endpoint authorization scheme + */ +export function getEndpointAuthorizationSchemeRequired(id: string): string { + return getEndpointAuthorizationScheme(id, false)!; +} + /** * Gets the endpoint authorization parameter value for a service endpoint with specified key * If the endpoint authorization parameter is not set and is not optional, it will throw. @@ -322,7 +483,7 @@ export function getEndpointAuthorizationScheme(id: string, optional: boolean): s * @param optional optional whether the endpoint authorization scheme is optional * @returns {string} value of the endpoint authorization parameter value */ -export function getEndpointAuthorizationParameter(id: string, key: string, optional: boolean): string { +export function getEndpointAuthorizationParameter(id: string, key: string, optional: boolean): string | undefined { var authParam = im._vault.retrieveSecret('ENDPOINT_AUTH_PARAMETER_' + id + '_' + key.toUpperCase()); if (!optional && !authParam) { @@ -332,6 +493,19 @@ export function getEndpointAuthorizationParameter(id: string, key: string, optio debug(id + ' auth param ' + key + ' = ' + authParam); return authParam; } + +/** + * Gets the endpoint authorization parameter value for a service endpoint with specified key + * If the endpoint authorization parameter is not set, it will throw. + * + * @param id name of the service endpoint + * @param key key to find the endpoint authorization parameter + * @returns {string} value of the endpoint authorization parameter value + */ +export function getEndpointAuthorizationParameterRequired(id: string, key: string): string { + return getEndpointAuthorizationParameter(id, key, false)!; +} + /** * Interface for EndpointAuthorization * Contains a schema and a string/string dictionary of auth data @@ -348,25 +522,26 @@ export interface EndpointAuthorization { /** * Gets the authorization details for a service endpoint - * If the authorization was not set and is not optional, it will throw. - * + * If the authorization was not set and is not optional, it will set the task result to Failed. + * * @param id name of the service endpoint * @param optional whether the url is optional * @returns string */ -export function getEndpointAuthorization(id: string, optional: boolean): EndpointAuthorization { +export function getEndpointAuthorization(id: string, optional: boolean): EndpointAuthorization | undefined { var aval = im._vault.retrieveSecret('ENDPOINT_AUTH_' + id); if (!optional && !aval) { setResult(TaskResult.Failed, loc('LIB_EndpointAuthNotExist', id)); } - console.log(id + ' exists ' + (aval !== null)); - debug(id + ' exists ' + (aval !== null)); + debug(id + ' exists ' + (!!aval)); - var auth: EndpointAuthorization; + var auth: EndpointAuthorization | undefined; try { - auth = JSON.parse(aval); + if (aval) { + auth = JSON.parse(aval); + } } catch (err) { throw new Error(loc('LIB_InvalidEndpointAuth', aval)); @@ -381,24 +556,24 @@ export function getEndpointAuthorization(id: string, optional: boolean): Endpoin /** * Gets the name for a secure file - * + * * @param id secure file id * @returns string */ -export function getSecureFileName(id: string): string { +export function getSecureFileName(id: string): string | undefined { var name = process.env['SECUREFILE_NAME_' + id]; debug('secure file name for id ' + id + ' = ' + name); return name; } -/** +/** * Gets the secure file ticket that can be used to download the secure file contents * * @param id name of the secure file * @returns {string} secure file ticket */ -export function getSecureFileTicket(id: string): string { +export function getSecureFileTicket(id: string): string | undefined { var ticket = im._vault.retrieveSecret('SECUREFILE_TICKET_' + id); debug('secure file ticket for id ' + id + ' = ' + ticket); @@ -411,13 +586,13 @@ export function getSecureFileTicket(id: string): string { /** * Gets a variable value that is set by previous step from the same wrapper task. * Requires a 2.115.0 agent or higher. - * + * * @param name name of the variable to get * @returns string */ -export function getTaskVariable(name: string): string { +export function getTaskVariable(name: string): string | undefined { assertAgent('2.115.0'); - var inval = im._vault.retrieveSecret('VSTS_TASKVARIABLE_' + name.replace(' ', '_').toUpperCase()); + var inval = im._vault.retrieveSecret('VSTS_TASKVARIABLE_' + im._getVariableKey(name)); if (inval) { inval = inval.trim(); } @@ -429,7 +604,7 @@ export function getTaskVariable(name: string): string { /** * Sets a task variable which will only be available to subsequent steps belong to the same wrapper task. * Requires a 2.115.0 agent or higher. - * + * * @param name name of the variable to set * @param val value to set * @param secret whether variable is secret. optional, defaults to false @@ -480,12 +655,12 @@ export interface FsStats extends fs.Stats { } /** - * Get's stat on a path. + * Get's stat on a path. * Useful for checking whether a file or directory. Also getting created, modified and accessed time. * see [fs.stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) - * + * * @param path path to check - * @returns fsStat + * @returns fsStat */ export function stats(path: string): FsStats { return fs.statSync(path); @@ -493,30 +668,60 @@ export function stats(path: string): FsStats { export const exist = im._exist; -export interface FsOptions { - encoding?: string; - mode?: number; - flag?: string; -} - -export function writeFile(file: string, data: string | Buffer, options?: string | FsOptions) { - fs.writeFileSync(file, data, options); +export function writeFile(file: string, data: string | Buffer, options?: BufferEncoding | fs.WriteFileOptions) { + if (typeof (options) === 'string') { + fs.writeFileSync(file, data, { encoding: options as BufferEncoding }); + } + else { + fs.writeFileSync(file, data, options); + } } /** + * @deprecated Use `getPlatform` * Useful for determining the host operating system. * see [os.type](https://nodejs.org/api/os.html#os_os_type) - * + * * @return the name of the operating system */ export function osType(): string { return os.type(); } +/** + * Determine the operating system the build agent is running on. + * @returns {Platform} + * @throws {Error} Platform is not supported by our agent + */ +export function getPlatform(): Platform { + switch (process.platform) { + case 'win32': return Platform.Windows; + case 'darwin': return Platform.MacOS; + case 'linux': return Platform.Linux; + default: throw Error(loc('LIB_PlatformNotSupported', process.platform)); + } +} + +/** + * Return hosted type of Agent + * @returns {AgentHostedMode} + */ +export function getAgentMode(): AgentHostedMode { + let agentCloudId = getVariable('Agent.CloudId'); + + if (agentCloudId === undefined) + return AgentHostedMode.Unknown; + + if (agentCloudId) + return AgentHostedMode.MsHosted; + + return AgentHostedMode.SelfHosted; +} + /** * Returns the process's current working directory. * see [process.cwd](https://nodejs.org/api/process.html#process_process_cwd) - * + * * @return the path to the current working directory of the process */ export function cwd(): string { @@ -527,9 +732,9 @@ export const checkPath = im._checkPath; /** * Change working directory. - * + * * @param path new working directory path - * @returns void + * @returns void */ export function cd(path: string): void { if (path) { @@ -540,7 +745,7 @@ export function cd(path: string): void { /** * Change working directory and push it on the stack - * + * * @param path new working directory path * @returns void */ @@ -551,7 +756,7 @@ export function pushd(path: string): void { /** * Change working directory back to previously pushed directory - * + * * @returns void */ export function popd(): void { @@ -562,7 +767,7 @@ export function popd(): void { /** * Make a directory. Creates the full path with folders in between * Will throw if it fails - * + * * @param p path to create * @returns void */ @@ -618,7 +823,7 @@ export function mkdirP(p: string): void { // create each directory while (stack.length) { - let dir = stack.pop(); + let dir = stack.pop()!; // non-null because `stack.length` was truthy debug(`mkdir '${dir}'`); try { fs.mkdirSync(dir); @@ -659,29 +864,47 @@ export function ls(options: string, paths: string[]): string[] { /** * Copies a file or folder. - * + * * @param source source path * @param dest destination path - * @param options string -r, -f or -rf for recursive and force + * @param options string -r, -f or -rf for recursive and force * @param continueOnError optional. whether to continue on error + * @param retryCount optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. */ -export function cp(source: string, dest: string, options?: string, continueOnError?: boolean): void { - if (options) { - shell.cp(options, source, dest); - } - else { - shell.cp(source, dest); - } +export function cp(source: string, dest: string, options?: string, continueOnError?: boolean, retryCount: number = 0): void { + while (retryCount >= 0) { + try { + if (options) { + shell.cp(options, source, dest); + } + else { + shell.cp(source, dest); + } - _checkShell('cp', continueOnError); + _checkShell('cp', false); + break; + } catch (e) { + if (retryCount <= 0) { + if (continueOnError) { + warning(e); + break; + } else { + throw e; + } + } else { + console.log(loc('LIB_CopyFileFailed', retryCount)); + retryCount--; + } + } + } } /** * Moves a path. - * + * * @param source source path * @param dest destination path - * @param options string -f or -n for force and no clobber + * @param options string -f or -n for force and no clobber * @param continueOnError optional. whether to continue on error */ export function mv(source: string, dest: string, options?: string, continueOnError?: boolean): void { @@ -700,6 +923,12 @@ export function mv(source: string, dest: string, options?: string, continueOnErr * Contains properties to control whether to follow symlinks */ export interface FindOptions { + + /** + * When true, broken symbolic link will not cause an error. + */ + allowBrokenSymbolicLinks: boolean, + /** * Equivalent to the -H command line option. Indicates whether to traverse descendants if * the specified path is a symbolic link directory. Does not cause nested symbolic link @@ -712,6 +941,91 @@ export interface FindOptions { * symbolic link directories. */ followSymbolicLinks: boolean; + + /** + * When true, missing files will not cause an error and will be skipped. + */ + skipMissingFiles?: boolean; +} + +/** + * Interface for RetryOptions + * + * Contains "continueOnError" and "retryCount" options. + */ +export interface RetryOptions { + + /** + * If true, code still continues to execute when all retries failed. + */ + continueOnError: boolean, + + /** + * Number of retries. + */ + retryCount: number +} + +/** + * Tries to execute a function a specified number of times. + * + * @param func a function to be executed. + * @param args executed function arguments array. + * @param retryOptions optional. Defaults to { continueOnError: false, retryCount: 0 }. + * @returns the same as the usual function. + */ +export function retry(func: Function, args: any[], retryOptions: RetryOptions = { continueOnError: false, retryCount: 0 }): any { + while (retryOptions.retryCount >= 0) { + try { + return func(...args); + } catch (e) { + if (retryOptions.retryCount <= 0) { + if (retryOptions.continueOnError) { + warning(e); + break; + } else { + throw e; + } + } else { + debug(`Attempt to execute function "${func?.name}" failed, retries left: ${retryOptions.retryCount}`); + retryOptions.retryCount--; + } + } + } +} + +/** + * Gets info about item stats. + * + * @param path a path to the item to be processed. + * @param followSymbolicLink indicates whether to traverse descendants of symbolic link directories. + * @param allowBrokenSymbolicLinks when true, broken symbolic link will not cause an error. + * @returns fs.Stats + */ +function _getStats(path: string, followSymbolicLink: boolean, allowBrokenSymbolicLinks: boolean): fs.Stats { + // stat returns info about the target of a symlink (or symlink chain), + // lstat returns info about a symlink itself + let stats: fs.Stats; + + if (followSymbolicLink) { + try { + // use stat (following symlinks) + stats = fs.statSync(path); + } catch (err) { + if (err.code == 'ENOENT' && allowBrokenSymbolicLinks) { + // fallback to lstat (broken symlinks allowed) + stats = fs.lstatSync(path); + debug(` ${path} (broken symlink)`); + } else { + throw err; + } + } + } else { + // use lstat (not following symlinks) + stats = fs.lstatSync(path); + } + + return stats; } /** @@ -758,26 +1072,29 @@ export function find(findPath: string, options?: FindOptions): string[] { while (stack.length) { // pop the next item and push to the result array - let item: _FindItem = stack.pop(); - result.push(item.path); + let item = stack.pop()!; // non-null because `stack.length` was truthy - // stat the item. the stat info is used further below to determine whether to traverse deeper - // - // stat returns info about the target of a symlink (or symlink chain), - // lstat returns info about a symlink itself let stats: fs.Stats; - if (options.followSymbolicLinks) { - // use stat (following all symlinks) - stats = fs.statSync(item.path); - } - else if (options.followSpecifiedSymbolicLink && result.length == 1) { - // use stat (following symlinks for the specified path and this is the specified path) - stats = fs.statSync(item.path); - } - else { - // use lstat (not following symlinks) - stats = fs.lstatSync(item.path); + try { + // `item.path` equals `findPath` for the first item to be processed, when the `result` array is empty + const isPathToSearch: boolean = !result.length; + + // following specified symlinks only if current path equals specified path + const followSpecifiedSymbolicLink: boolean = options.followSpecifiedSymbolicLink && isPathToSearch; + + // following all symlinks or following symlink for the specified path + const followSymbolicLink: boolean = options.followSymbolicLinks || followSpecifiedSymbolicLink; + + // stat the item. The stat info is used further below to determine whether to traverse deeper + stats = _getStats(item.path, followSymbolicLink, options.allowBrokenSymbolicLinks); + } catch (err) { + if (err.code == 'ENOENT' && options.skipMissingFiles) { + warning(`No such file or directory: "${item.path}" - skipping.`); + continue; + } + throw err; } + result.push(item.path); // note, isDirectory() returns false for the lstat of a symlink if (stats.isDirectory()) { @@ -785,7 +1102,13 @@ export function find(findPath: string, options?: FindOptions): string[] { if (options.followSymbolicLinks) { // get the realpath - let realPath: string = fs.realpathSync(item.path); + let realPath: string; + if (im._isUncPath(item.path)) { + // Sometimes there are spontaneous issues when working with unc-paths, so retries have been added for them. + realPath = retry(fs.realpathSync, [item.path], { continueOnError: false, retryCount: 5 }); + } else { + realPath = fs.realpathSync(item.path); + } // fixup the traversal chain to match the item level while (traversalChain.length >= item.level) { @@ -807,7 +1130,9 @@ export function find(findPath: string, options?: FindOptions): string[] { let childItems: _FindItem[] = fs.readdirSync(item.path) .map((childName: string) => new _FindItem(path.join(item.path, childName), childLevel)); - stack.push(...childItems.reverse()); + for (var i = childItems.length - 1; i >= 0; i--) { + stack.push(childItems[i]); + } } else { debug(` ${item.path} (file)`); @@ -833,14 +1158,18 @@ class _FindItem { } function _debugFindOptions(options: FindOptions): void { + debug(`findOptions.allowBrokenSymbolicLinks: '${options.allowBrokenSymbolicLinks}'`); debug(`findOptions.followSpecifiedSymbolicLink: '${options.followSpecifiedSymbolicLink}'`); debug(`findOptions.followSymbolicLinks: '${options.followSymbolicLinks}'`); + debug(`findOptions.skipMissingFiles: '${options.skipMissingFiles}'`); } function _getDefaultFindOptions(): FindOptions { return { + allowBrokenSymbolicLinks: false, followSpecifiedSymbolicLink: true, - followSymbolicLinks: true + followSymbolicLinks: true, + skipMissingFiles: false }; } @@ -1030,55 +1359,117 @@ function _legacyFindFiles_getMatchingItems( /** * Remove a path recursively with force - * Returns whether it succeeds - * - * @param path path to remove - * @returns void + * + * @param inputPath path to remove + * @throws when the file or directory exists but could not be deleted. */ -export function rmRF(path: string): void { - debug('rm -rf ' + path); +export function rmRF(inputPath: string): void { + debug('rm -rf ' + inputPath); - // get the lstats in order to workaround a bug in shelljs@0.3.0 where symlinks - // with missing targets are not handled correctly by "rm('-rf', path)" - let lstats: fs.Stats; - try { - lstats = fs.lstatSync(path); - } - catch (err) { - // if you try to delete a file that doesn't exist, desired result is achieved - // other errors are valid - if (err.code == 'ENOENT') { - return; + if (getPlatform() == Platform.Windows) { + // Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another + // program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del. + try { + if (fs.statSync(inputPath).isDirectory()) { + debug('removing directory ' + inputPath); + childProcess.execSync(`rd /s /q "${inputPath}"`); + } + else { + debug('removing file ' + inputPath); + childProcess.execSync(`del /f /a "${inputPath}"`); + } + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code != 'ENOENT') { + throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); + } } - throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); + // Shelling out fails to remove a symlink folder with missing source, this unlink catches that + try { + fs.unlinkSync(inputPath); + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code != 'ENOENT') { + throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); + } + } } + else { + // get the lstats in order to workaround a bug in shelljs@0.3.0 where symlinks + // with missing targets are not handled correctly by "rm('-rf', path)" + let lstats: fs.Stats; + try { + lstats = fs.lstatSync(inputPath); + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code == 'ENOENT') { + return; + } - if (lstats.isDirectory()) { - debug('removing directory'); - shell.rm('-rf', path); - let errMsg: string = shell.error(); - if (errMsg) { - throw new Error(loc('LIB_OperationFailed', 'rmRF', errMsg)); + throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); } - return; - } + if (lstats.isDirectory()) { + debug('removing directory'); + shell.rm('-rf', inputPath); + let errMsg: string = shell.error(); + if (errMsg) { + throw new Error(loc('LIB_OperationFailed', 'rmRF', errMsg)); + } - debug('removing file'); - try { - fs.unlinkSync(path); + return; + } + + debug('removing file'); + try { + fs.unlinkSync(inputPath); + } + catch (err) { + throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); + } } - catch (err) { - throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); +} + +/** + * Exec a tool. Convenience wrapper over ToolRunner to exec with args in one call. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param tool path to tool to exec + * @param args an arg string or array of args + * @param options optional exec options. See IExecOptions + * @returns number + */ +export function execAsync(tool: string, args: any, options?: trm.IExecOptions): Promise { + let tr: trm.ToolRunner = this.tool(tool); + tr.on('debug', (data: string) => { + debug(data); + }); + + if (args) { + if (args instanceof Array) { + tr.arg(args); + } + else if (typeof (args) === 'string') { + tr.line(args) + } } + return tr.execAsync(options); } /** * Exec a tool. Convenience wrapper over ToolRunner to exec with args in one call. * Output will be streamed to the live console. * Returns promise with return code - * + * + * @deprecated Use the {@link execAsync} method that returns a native Javascript Promise instead * @param tool path to tool to exec * @param args an arg string or array of args * @param options optional exec options. See IExecOptions @@ -1086,7 +1477,7 @@ export function rmRF(path: string): void { */ export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Promise { let tr: trm.ToolRunner = this.tool(tool); - tr.on('debug', (data) => { + tr.on('debug', (data: string) => { debug(data); }); @@ -1104,9 +1495,9 @@ export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Pro /** * Exec a tool synchronously. Convenience wrapper over ToolRunner to execSync with args in one call. * Output will be *not* be streamed to the live console. It will be returned after execution is complete. - * Appropriate for short running tools + * Appropriate for short running tools * Returns IExecResult with output and return code - * + * * @param tool path to tool to exec * @param args an arg string or array of args * @param options optional exec options. See IExecSyncOptions @@ -1114,7 +1505,7 @@ export function exec(tool: string, args: any, options?: trm.IExecOptions): Q.Pro */ export function execSync(tool: string, args: string | string[], options?: trm.IExecSyncOptions): trm.IExecSyncResult { let tr: trm.ToolRunner = this.tool(tool); - tr.on('debug', (data) => { + tr.on('debug', (data: string) => { debug(data); }); @@ -1132,7 +1523,7 @@ export function execSync(tool: string, args: string | string[], options?: trm.IE /** * Convenience factory to create a ToolRunner. - * + * * @param tool path to tool to exec * @returns ToolRunner */ @@ -1535,29 +1926,110 @@ export function findMatch(defaultRoot: string, patterns: string[] | string, find export interface ProxyConfiguration { proxyUrl: string; + /** + * Proxy URI formated as: protocol://username:password@hostname:port + * + * For tools that require setting proxy configuration in the single environment variable + */ + proxyFormattedUrl: string; proxyUsername?: string; proxyPassword?: string; proxyBypassHosts?: string[]; } +/** + * Build Proxy URL in the following format: protocol://username:password@hostname:port + * @param proxyUrl Url address of the proxy server (eg: http://example.com) + * @param proxyUsername Proxy username (optional) + * @param proxyPassword Proxy password (optional) + * @returns string + */ +function getProxyFormattedUrl(proxyUrl: string, proxyUsername: string | undefined, proxyPassword: string | undefined): string { + const parsedUrl: URL = new URL(proxyUrl); + let proxyAddress: string = `${parsedUrl.protocol}//${parsedUrl.host}`; + if (proxyUsername) { + proxyAddress = `${parsedUrl.protocol}//${proxyUsername}:${proxyPassword}@${parsedUrl.host}`; + } + return proxyAddress; +} + /** * Gets http proxy configuration used by Build/Release agent * * @return ProxyConfiguration */ -export function getHttpProxyConfiguration(): ProxyConfiguration { - let proxyUrl: string = getVariable('Agent.ProxyUrl'); +export function getHttpProxyConfiguration(requestUrl?: string): ProxyConfiguration | null { + let proxyUrl = getVariable('Agent.ProxyUrl'); if (proxyUrl && proxyUrl.length > 0) { - let proxyUsername: string = getVariable('Agent.ProxyUsername'); - let proxyPassword: string = getVariable('Agent.ProxyPassword'); - let proxyBypassHosts: string[] = JSON.parse(getVariable('Agent.ProxyBypassList') || '[]'); + let proxyUsername = getVariable('Agent.ProxyUsername'); + let proxyPassword = getVariable('Agent.ProxyPassword'); + let proxyBypassHosts = JSON.parse(getVariable('Agent.ProxyBypassList') || '[]'); + + let bypass: boolean = false; + if (requestUrl) { + proxyBypassHosts.forEach( (bypassHost: string) => { + if (new RegExp(bypassHost, 'i').test(requestUrl)) { + bypass = true; + } + }); + } + + if (bypass) { + return null; + } + else { + const proxyAddress = getProxyFormattedUrl(proxyUrl, proxyUsername, proxyPassword) + return { + proxyUrl: proxyUrl, + proxyUsername: proxyUsername, + proxyPassword: proxyPassword, + proxyBypassHosts: proxyBypassHosts, + proxyFormattedUrl: proxyAddress + }; + } + } + else { + return null; + } +} + +//----------------------------------------------------- +// Http Certificate Helper +//----------------------------------------------------- + +export interface CertConfiguration { + caFile?: string; + certFile?: string; + keyFile?: string; + certArchiveFile?: string; + passphrase?: string; +} - return { - proxyUrl: proxyUrl, - proxyUsername: proxyUsername, - proxyPassword: proxyPassword, - proxyBypassHosts: proxyBypassHosts - }; +/** + * Gets http certificate configuration used by Build/Release agent + * + * @return CertConfiguration + */ +export function getHttpCertConfiguration(): CertConfiguration | null { + let ca = getVariable('Agent.CAInfo'); + let clientCert = getVariable('Agent.ClientCert'); + + if (ca || clientCert) { + let certConfig: CertConfiguration = {}; + certConfig.caFile = ca; + certConfig.certFile = clientCert; + + if (clientCert) { + let clientCertKey = getVariable('Agent.ClientCertKey'); + let clientCertArchive = getVariable('Agent.ClientCertArchive'); + let clientCertPassword = getVariable('Agent.ClientCertPassword'); + + certConfig.keyFile = clientCertKey; + certConfig.certArchiveFile = clientCertArchive; + certConfig.passphrase = clientCertPassword; + } + + return certConfig; } else { return null; @@ -1568,13 +2040,13 @@ export function getHttpProxyConfiguration(): ProxyConfiguration { // Test Publisher //----------------------------------------------------- export class TestPublisher { - constructor(testRunner) { - this.testRunner = testRunner; + constructor(public testRunner: string) { } - public testRunner: string; - - public publish(resultFiles, mergeResults, platform, config, runTitle, publishRunAttachments) { + public publish(resultFiles?: string | string[], mergeResults?: string, platform?: string, config?: string, runTitle?: string, publishRunAttachments?: string, testRunSystem?: string) { + // Could have used an initializer, but wanted to avoid reordering parameters when converting to strict null checks + // (A parameter cannot both be optional and have an initializer) + testRunSystem = testRunSystem || "VSTSTask"; var properties = <{ [key: string]: string }>{}; properties['type'] = this.testRunner; @@ -1600,9 +2072,11 @@ export class TestPublisher { } if (resultFiles) { - properties['resultFiles'] = resultFiles; + properties['resultFiles'] = Array.isArray(resultFiles) ? resultFiles.join() : resultFiles; } + properties['testRunSystem'] = testRunSystem; + command('results.publish', properties, ''); } } @@ -1613,7 +2087,7 @@ export class TestPublisher { export class CodeCoveragePublisher { constructor() { } - public publish(codeCoverageTool, summaryFileLocation, reportDirectory, additionalCodeCoverageFiles) { + public publish(codeCoverageTool?: string, summaryFileLocation?: string, reportDirectory?: string, additionalCodeCoverageFiles?: string | string[]) { var properties = <{ [key: string]: string }>{}; @@ -1630,7 +2104,7 @@ export class CodeCoveragePublisher { } if (additionalCodeCoverageFiles) { - properties['additionalcodecoveragefiles'] = additionalCodeCoverageFiles; + properties['additionalcodecoveragefiles'] = Array.isArray(additionalCodeCoverageFiles) ? additionalCodeCoverageFiles.join() : additionalCodeCoverageFiles; } command('codecoverage.publish', properties, ""); @@ -1656,6 +2130,230 @@ export class CodeCoverageEnabler { } } +//----------------------------------------------------- +// Task Logging Commands +//----------------------------------------------------- + +/** + * Upload user interested file as additional log information + * to the current timeline record. + * + * The file shall be available for download along with task logs. + * + * @param path Path to the file that should be uploaded. + * @returns void + */ +export function uploadFile(path: string) { + command("task.uploadfile", null, path); +} + +/** + * Instruction for the agent to update the PATH environment variable. + * The specified directory is prepended to the PATH. + * The updated environment variable will be reflected in subsequent tasks. + * + * @param path Local directory path. + * @returns void + */ +export function prependPath(path: string) { + assertAgent("2.115.0"); + command("task.prependpath", null, path); +} + +/** + * Upload and attach summary markdown to current timeline record. + * This summary shall be added to the build/release summary and + * not available for download with logs. + * + * @param path Local directory path. + * @returns void + */ +export function uploadSummary(path: string) { + command("task.uploadsummary", null, path); +} + +/** + * Upload and attach attachment to current timeline record. + * These files are not available for download with logs. + * These can only be referred to by extensions using the type or name values. + * + * @param type Attachment type. + * @param name Attachment name. + * @param path Attachment path. + * @returns void + */ +export function addAttachment(type: string, name: string, path: string) { + command("task.addattachment", { "type": type, "name": name }, path); +} + +/** + * Set an endpoint field with given value. + * Value updated will be retained in the endpoint for + * the subsequent tasks that execute within the same job. + * + * @param id Endpoint id. + * @param field FieldType enum of AuthParameter, DataParameter or Url. + * @param key Key. + * @param value Value for key or url. + * @returns void + */ +export function setEndpoint(id: string, field: FieldType, key: string, value: string) { + command("task.setendpoint", { "id": id, "field": FieldType[field].toLowerCase(), "key": key }, value); +} + +/** + * Set progress and current operation for current task. + * + * @param percent Percentage of completion. + * @param currentOperation Current pperation. + * @returns void + */ +export function setProgress(percent: number, currentOperation: string) { + command("task.setprogress", { "value": `${percent}` }, currentOperation); +} + +/** + * Indicates whether to write the logging command directly to the host or to the output pipeline. + * + * @param id Timeline record Guid. + * @param parentId Parent timeline record Guid. + * @param recordType Record type. + * @param recordName Record name. + * @param order Order of timeline record. + * @param startTime Start time. + * @param finishTime End time. + * @param progress Percentage of completion. + * @param state TaskState enum of Unknown, Initialized, InProgress or Completed. + * @param result TaskResult enum of Succeeded, SucceededWithIssues, Failed, Cancelled or Skipped. + * @param message current operation + * @returns void + */ +export function logDetail(id: string, message: string, parentId?: string, recordType?: string, + recordName?: string, order?: number, startTime?: string, finishTime?: string, + progress?: number, state?: TaskState, result?: TaskResult) { + const properties = { + "id": id, + "parentid": parentId, + "type": recordType, + "name": recordName, + "order": order ? order.toString() : undefined, + "starttime": startTime, + "finishtime": finishTime, + "progress": progress ? progress.toString() : undefined, + "state": state ? TaskState[state] : undefined, + "result": result ? TaskResult[result] : undefined + }; + + command("task.logdetail", properties, message); +} + +/** + * Log error or warning issue to timeline record of current task. + * + * @param type IssueType enum of Error or Warning. + * @param sourcePath Source file location. + * @param lineNumber Line number. + * @param columnNumber Column number. + * @param code Error or warning code. + * @param message Error or warning message. + * @returns void + */ +export function logIssue(type: IssueType, message: string, sourcePath?: string, lineNumber?: number, + columnNumber?: number, errorCode?: string) { + const properties = { + "type": IssueType[type].toLowerCase(), + "code": errorCode, + "sourcepath": sourcePath, + "linenumber": lineNumber ? lineNumber.toString() : undefined, + "columnnumber": columnNumber ? columnNumber.toString() : undefined, + }; + + command("task.logissue", properties, message); +} + +//----------------------------------------------------- +// Artifact Logging Commands +//----------------------------------------------------- + +/** + * Upload user interested file as additional log information + * to the current timeline record. + * + * The file shall be available for download along with task logs. + * + * @param containerFolder Folder that the file will upload to, folder will be created if needed. + * @param path Path to the file that should be uploaded. + * @param name Artifact name. + * @returns void + */ +export function uploadArtifact(containerFolder: string, path: string, name?: string) { + command("artifact.upload", { "containerfolder": containerFolder, "artifactname": name }, path); +} + +/** + * Create an artifact link, artifact location is required to be + * a file container path, VC path or UNC share path. + * + * The file shall be available for download along with task logs. + * + * @param name Artifact name. + * @param path Path to the file that should be associated. + * @param artifactType ArtifactType enum of Container, FilePath, VersionControl, GitRef or TfvcLabel. + * @returns void + */ +export function associateArtifact(name: string, path: string, artifactType: ArtifactType) { + command("artifact.associate", { "type": ArtifactType[artifactType].toLowerCase(), "artifactname": name }, path); +} + +//----------------------------------------------------- +// Build Logging Commands +//----------------------------------------------------- + +/** + * Upload user interested log to build’s container “logs\tool” folder. + * + * @param path Path to the file that should be uploaded. + * @returns void + */ +export function uploadBuildLog(path: string) { + command("build.uploadlog", null, path); +} + +/** + * Update build number for current build. + * + * @param value Value to be assigned as the build number. + * @returns void + */ +export function updateBuildNumber(value: string) { + command("build.updatebuildnumber", null, value); +} + +/** + * Add a tag for current build. + * + * @param value Tag value. + * @returns void + */ +export function addBuildTag(value: string) { + command("build.addbuildtag", null, value); +} + +//----------------------------------------------------- +// Release Logging Commands +//----------------------------------------------------- + +/** + * Update release name for current release. + * + * @param value Value to be assigned as the release name. + * @returns void + */ +export function updateReleaseName(name: string) { + assertAgent("2.132.0"); + command("release.updatereleasename", null, name); +} + //----------------------------------------------------- // Tools //----------------------------------------------------- @@ -1669,7 +2367,7 @@ exports.ToolRunner = trm.ToolRunner; // async await needs generators in node 4.x+ if (semver.lt(process.versions.node, '4.2.0')) { - this.warning('Tasks require a new agent. Upgrade your agent or node to 4.2.0 or later'); + warning('Tasks require a new agent. Upgrade your agent or node to 4.2.0 or later'); } //------------------------------------------------------------------- @@ -1679,4 +2377,6 @@ if (semver.lt(process.versions.node, '4.2.0')) { // avoid loading twice (overwrites .taskkey) if (!global['_vsts_task_lib_loaded']) { im._loadData(); + im._exposeProxySettings(); + im._exposeCertSettings(); } diff --git a/node/taskcommand.ts b/node/taskcommand.ts index f3d96b8dc..4fe5d67d8 100644 --- a/node/taskcommand.ts +++ b/node/taskcommand.ts @@ -9,7 +9,7 @@ let CMD_PREFIX = '##vso['; export class TaskCommand { - constructor(command, properties, message) { + constructor(command: string, properties: {[key: string]: string}, message: string) { if (!command) { command = 'missing.command'; } @@ -32,7 +32,9 @@ export class TaskCommand { if (this.properties.hasOwnProperty(key)) { var val = this.properties[key]; if (val) { - cmdStr += key + '=' + val + ';'; + // safely append the val - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + cmdStr += key + '=' + escape('' + (val || '')) + ';'; } } } @@ -43,13 +45,13 @@ export class TaskCommand { // safely append the message - avoid blowing up when attempting to // call .replace() if message is not a string for some reason let message: string = '' + (this.message || ''); - cmdStr += message.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + cmdStr += escapedata(message); return cmdStr; } } -export function commandFromString(commandLine) { +export function commandFromString(commandLine: string) { var preLen = CMD_PREFIX.length; var lbPos = commandLine.indexOf('['); var rbPos = commandLine.indexOf(']'); @@ -60,28 +62,58 @@ export function commandFromString(commandLine) { var spaceIdx = cmdInfo.indexOf(' '); var command = cmdInfo; - var properties = {}; + var properties: {[key: string]: string} = {}; if (spaceIdx > 0) { command = cmdInfo.trim().substring(0, spaceIdx); var propSection = cmdInfo.trim().substring(spaceIdx+1); - var propLines = propSection.split(';'); - propLines.forEach(function (propLine) { + var propLines: string[] = propSection.split(';'); + propLines.forEach(function (propLine: string) { propLine = propLine.trim(); if (propLine.length > 0) { - var propParts = propLine.split('='); - if (propParts.length != 2) { + var eqIndex = propLine.indexOf('='); + if (eqIndex == -1){ throw new Error('Invalid property: ' + propLine); } - properties[propParts[0]] = propParts[1]; + + var key: string = propLine.substring(0, eqIndex); + var val: string = propLine.substring(eqIndex+1); + + properties[key] = unescape(val); } }); } - var msg = commandLine.substring(rbPos + 1) - .replace(/%0D/g, '\r') - .replace(/%0A/g, '\n'); + let msg: string = unescapedata(commandLine.substring(rbPos + 1)); var cmd = new TaskCommand(command, properties, msg); return cmd; } + +function escapedata(s: string) : string { + return s.replace(/%/g, '%AZP25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} + +function unescapedata(s: string) : string { + return s.replace(/%0D/g, '\r') + .replace(/%0A/g, '\n') + .replace(/%AZP25/g, '%'); +} + +function escape(s: string) : string { + return s.replace(/%/g, '%AZP25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/]/g, '%5D') + .replace(/;/g, '%3B'); +} + +function unescape(s: string) : string { + return s.replace(/%0D/g, '\r') + .replace(/%0A/g, '\n') + .replace(/%5D/g, ']') + .replace(/%3B/g, ';') + .replace(/%AZP25/g, '%'); +} diff --git a/node/test/cctests.ts b/node/test/cctests.ts index 8519c50a1..f2f6e94c4 100644 --- a/node/test/cctests.ts +++ b/node/test/cctests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tl from '../_build/task'; import testutil = require('./testutil'); diff --git a/node/test/commandtests.ts b/node/test/commandtests.ts index 9aa1cdfc9..8d0bb0e59 100644 --- a/node/test/commandtests.ts +++ b/node/test/commandtests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tcm from '../_build/taskcommand'; @@ -34,6 +31,7 @@ describe('Command Tests', function () { done(); }) + it('toStrings', function (done) { this.timeout(1000); @@ -43,15 +41,47 @@ describe('Command Tests', function () { assert.equal(cmdStr, '##vso[some.cmd foo=bar;]a message'); done(); }) + it('toString escapes message', function (done) { this.timeout(1000); - var tc = new tcm.TaskCommand('some.cmd', { foo: 'bar' }, 'cr \r lf \n crlf \r\n eom'); + var tc = new tcm.TaskCommand('some.cmd', { foo: 'bar' }, 'cr \r lf \n crlf \r\n eom ] ;'); + assert(tc, 'TaskCommand constructor works'); + var cmdStr = tc.toString(); + assert.equal(cmdStr, '##vso[some.cmd foo=bar;]cr %0D lf %0A crlf %0D%0A eom ] ;'); + done(); + }) + + it('toString handles non string value in properties', function (done) { + this.timeout(1000); + + var tc = new tcm.TaskCommand('some.cmd', { foo: ['bar', 'baz'] }, 'cr \r lf \n crlf \r\n eom ] ;'); assert(tc, 'TaskCommand constructor works'); var cmdStr = tc.toString(); - assert.equal(cmdStr, '##vso[some.cmd foo=bar;]cr %0D lf %0A crlf %0D%0A eom'); + assert.equal(cmdStr, '##vso[some.cmd foo=bar,baz;]cr %0D lf %0A crlf %0D%0A eom ] ;'); done(); }) + + it ('toString escapes properties', function (done) { + this.timeout(1000); + + var tc = new tcm.TaskCommand('some.cmd', { foo: ';=\r=\n%3B' }, 'dog'); + assert(tc, 'TaskCommand constructor works'); + var cmdStr = tc.toString(); + assert.equal(cmdStr, '##vso[some.cmd foo=%3B=%0D=%0A%AZP253B;]dog'); + done(); + }) + + it ('toString writes isOutput', function (done) { + this.timeout(1000); + + var tc = new tcm.TaskCommand('task.setvariable', { variable: 'bar', isOutput: 'true' }, 'dog'); + assert(tc, 'TaskCommand constructor works'); + var cmdStr = tc.toString(); + assert.equal(cmdStr, '##vso[task.setvariable variable=bar;isOutput=true;]dog'); + done(); + }) + it('handles null properties', function (done) { this.timeout(1000); @@ -59,6 +89,7 @@ describe('Command Tests', function () { assert.equal(tc.toString(), '##vso[some.cmd]a message'); done(); }) + it('parses cmd with no properties', function (done) { var cmdStr = '##vso[basic.command]messageVal'; @@ -69,6 +100,7 @@ describe('Command Tests', function () { assert.equal(tc.message, 'messageVal', 'message is correct'); done(); }) + it('parses basic cmd with values', function (done) { var cmdStr = '##vso[basic.command prop1=val1;]messageVal'; @@ -81,6 +113,7 @@ describe('Command Tests', function () { assert.equal(tc.message, 'messageVal', 'message is correct'); done(); }) + it('parses basic cmd with multiple properties no trailing semi', function (done) { var cmdStr = '##vso[basic.command prop1=val1;prop2=val2]messageVal'; @@ -94,6 +127,7 @@ describe('Command Tests', function () { assert.equal(tc.message, 'messageVal', 'message is correct'); done(); }) + it('parses values with spaces in them', function (done) { var cmdStr = '##vso[task.setvariable variable=task variable;]task variable set value'; @@ -104,14 +138,26 @@ describe('Command Tests', function () { assert.equal(tc.message, 'task variable set value'); done(); }) + it('parses and unescapes message', function (done) { - var cmdStr = '##vso[basic.command]cr %0D lf %0A crlf %0D%0A eom'; + var cmdStr = '##vso[basic.command]cr %0D lf %0A crlf %0D%0A eom ] ;'; var tc = tcm.commandFromString(cmdStr); assert.equal(tc.command, 'basic.command', 'cmd should be basic.command'); - assert.equal(tc.message, 'cr \r lf \n crlf \r\n eom'); + assert.equal(tc.message, 'cr \r lf \n crlf \r\n eom ] ;'); done(); }) + + it ('parses and unescapes properties', function (done) { + var cmdStr = '##vso[basic.command foo=%3B=%0D=%0A%AZP253B;]dog'; + + var tc = tcm.commandFromString(cmdStr); + assert.equal(tc.command, 'basic.command', 'cmd should be basic.command'); + assert.equal(tc.properties['foo'], ';=\r=\n%3B', 'property should be unescaped') + assert.equal(tc.message, 'dog'); + done(); + }) + it('handles empty properties', function (done) { this.timeout(1000); diff --git a/node/test/dirtests.ts b/node/test/dirtests.ts index 40544c547..cfea46310 100644 --- a/node/test/dirtests.ts +++ b/node/test/dirtests.ts @@ -1,5 +1,5 @@ -/// -/// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. import assert = require('assert'); import path = require('path'); @@ -27,12 +27,13 @@ describe('Dir Operation Tests', function () { // this test verifies the expected version of node is being used to run the tests. // 5.10.1 is what ships in the 1.x and 2.x agent. - it('is expected version', (done: MochaDone) => { + it('is expected version', (done) => { this.timeout(1000); console.log('node version: ' + process.version); - if (process.version != 'v5.10.1' && process.version != 'v6.10.3') { - assert.fail('expected node v5.10.1 or v6.10.3. actual: ' + process.version); + const supportedNodeVersions = ['v16.13.0']; + if (supportedNodeVersions.indexOf(process.version) === -1) { + assert.fail(`expected node node version to be one of ${supportedNodeVersions.map(o => o).join(', ')}. actual: ` + process.version); } done(); @@ -40,7 +41,7 @@ describe('Dir Operation Tests', function () { // which tests it('which() finds file name', function (done) { - this.timeout(1000); + this.timeout(3000); // create a executable file let testPath = path.join(testutil.getTestTemp(), 'which-finds-file-name'); @@ -95,6 +96,7 @@ describe('Dir Operation Tests', function () { done(); }); + it('which() not found', function (done) { this.timeout(1000); @@ -183,6 +185,28 @@ describe('Dir Operation Tests', function () { done(); }); + + // which permissions tests + it('which() finds executable with owner permissions', function (done) { + this.timeout(1000); + findsExecutableWithScopedPermissions('u=rwx,g=r,o=r'); + done(); + }); + + // which permissions tests + it('which() finds executable with group permissions', function (done) { + this.timeout(1000); + findsExecutableWithScopedPermissions('u=rw,g=rx,o=r'); + done(); + }); + + // which permissions tests + it('which() finds executable with everyone permissions', function (done) { + this.timeout(1000); + findsExecutableWithScopedPermissions('u=rw,g=r,o=rx'); + done(); + }); + it('which() ignores directory match', function (done) { this.timeout(1000); @@ -268,7 +292,7 @@ describe('Dir Operation Tests', function () { done(); }); - + it('which() requires rooted path to be a file', function (done) { this.timeout(1000); @@ -538,8 +562,8 @@ describe('Dir Operation Tests', function () { } // find tests - it('returns hidden files with find', (done: MochaDone) => { - this.timeout(1000); + it('returns hidden files with find', (done) => { + this.timeout(3000); // create the following layout: // find_hidden_files @@ -564,7 +588,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('returns depth first find', (done: MochaDone) => { + it('returns depth first find', (done) => { this.timeout(1000); // create the following layout: @@ -597,7 +621,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('returns empty when not exists', (done: MochaDone) => { + it('returns empty when not exists', (done) => { this.timeout(1000); let itemPaths: string[] = tl.find(path.join(testutil.getTestTemp(), 'nosuch')); @@ -606,7 +630,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('does not follow specified symlink', (done: MochaDone) => { + it('does not follow specified symlink', (done) => { this.timeout(1000); // create the following layout: @@ -625,7 +649,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('follows specified symlink when -H', (done: MochaDone) => { + it('follows specified symlink when -H', (done) => { this.timeout(1000); // create the following layout: @@ -647,7 +671,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('follows specified symlink when -L', (done: MochaDone) => { + it('follows specified symlink when -L', (done) => { this.timeout(1000); // create the following layout: @@ -669,7 +693,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('does not follow symlink', (done: MochaDone) => { + it('does not follow symlink', (done) => { this.timeout(1000); // create the following layout: @@ -692,7 +716,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('does not follow symlink when -H', (done: MochaDone) => { + it('does not follow symlink when -H', (done) => { this.timeout(1000); // create the following layout: @@ -717,7 +741,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('follows symlink when -L', (done: MochaDone) => { + it('follows symlink when -L', (done) => { this.timeout(1000); // create the following layout: @@ -743,7 +767,228 @@ describe('Dir Operation Tests', function () { done(); }); - it('detects cycle', (done: MochaDone) => { + it('allows broken symlink', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + // /realDir + // /realDir/file + // /symDir -> /realDir + let root: string = path.join(testutil.getTestTemp(), 'find_no_follow_symlink_allows_broken_symlink'); + tl.mkdirP(root); + testutil.createSymlinkDir(path.join(root, 'noSuch'), path.join(root, 'brokenSym')); + tl.mkdirP(path.join(root, 'realDir')); + fs.writeFileSync(path.join(root, 'realDir', 'file'), 'test file content'); + testutil.createSymlinkDir(path.join(root, 'realDir'), path.join(root, 'symDir')); + + let itemPaths: string[] = tl.find(root, { }); + assert.equal(itemPaths.length, 5); + assert.equal(itemPaths[0], root); + assert.equal(itemPaths[1], path.join(root, 'brokenSym')); + assert.equal(itemPaths[2], path.join(root, 'realDir')); + assert.equal(itemPaths[3], path.join(root, 'realDir', 'file')); + assert.equal(itemPaths[4], path.join(root, 'symDir')); + + done(); + }); + + it('allows specified broken symlink', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_no_follow_symlink_allows_specified_broken_symlink'); + tl.mkdirP(root); + let brokenSymPath = path.join(root, 'brokenSym'); + testutil.createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath); + + let itemPaths: string[] = tl.find(brokenSymPath, { }); + assert.equal(itemPaths.length, 1); + assert.equal(itemPaths[0], brokenSymPath); + + done(); + }); + + it('allows nested broken symlink when -H', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + // /realDir + // /realDir/file + // /symDir -> /realDir + let root: string = path.join(testutil.getTestTemp(), 'find_allows_nested_broken_symlink_when_-H'); + tl.mkdirP(root); + testutil.createSymlinkDir(path.join(root, 'noSuch'), path.join(root, 'brokenSym')); + tl.mkdirP(path.join(root, 'realDir')); + fs.writeFileSync(path.join(root, 'realDir', 'file'), 'test file content'); + testutil.createSymlinkDir(path.join(root, 'realDir'), path.join(root, 'symDir')); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.followSpecifiedSymbolicLink = true; + let itemPaths: string[] = tl.find(root, options); + assert.equal(itemPaths.length, 5); + assert.equal(itemPaths[0], root); + assert.equal(itemPaths[1], path.join(root, 'brokenSym')); + assert.equal(itemPaths[2], path.join(root, 'realDir')); + assert.equal(itemPaths[3], path.join(root, 'realDir', 'file')); + assert.equal(itemPaths[4], path.join(root, 'symDir')); + + done(); + }); + + it('allows specified broken symlink with -H', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_allows_specified_broken_symlink_with_-H'); + tl.mkdirP(root); + let brokenSymPath = path.join(root, 'brokenSym'); + testutil.createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.allowBrokenSymbolicLinks = true; + options.followSpecifiedSymbolicLink = true; + let itemPaths: string[] = tl.find(brokenSymPath, options); + assert.equal(itemPaths.length, 1); + assert.equal(itemPaths[0], brokenSymPath); + + done(); + }); + + it('does not allow specified broken symlink when only -H', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_not_allow_specified_broken_sym_when_only_-H'); + tl.mkdirP(root); + let brokenSymPath = path.join(root, 'brokenSym'); + testutil.createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath); + fs.lstatSync(brokenSymPath); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.followSpecifiedSymbolicLink = true; + try { + tl.find(brokenSymPath, options); + throw new Error('Expected tl.find to throw'); + } + catch (err) { + assert(err.message.match(/ENOENT.*brokenSym/), `Expected broken symlink error message, actual: '${err.message}'`); + } + + done(); + }); + + it('does not allow broken symlink when only -L', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_not_allow_broken_sym_when_only_-L'); + tl.mkdirP(root); + testutil.createSymlinkDir(path.join(root, 'noSuch'), path.join(root, 'brokenSym')); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.followSymbolicLinks = true; + try { + tl.find(root, options); + throw new Error('Expected tl.find to throw'); + } + catch (err) { + assert(err.message.match(/ENOENT.*brokenSym/), `Expected broken symlink error message, actual: '${err.message}'`); + } + + done(); + }); + + it('does not allow specied broken symlink when only -L', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_not_allow_specified_broken_sym_when_only_-L'); + tl.mkdirP(root); + let brokenSymPath = path.join(root, 'brokenSym'); + testutil.createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath); + fs.lstatSync(brokenSymPath); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.followSymbolicLinks = true; + try { + tl.find(brokenSymPath, options); + throw new Error('Expected tl.find to throw'); + } + catch (err) { + assert(err.message.match(/ENOENT.*brokenSym/), `Expected broken symlink error message, actual: '${err.message}'`); + } + + done(); + }); + + it('allow broken symlink with -L', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + // /realDir + // /realDir/file + // /symDir -> /realDir + let root: string = path.join(testutil.getTestTemp(), 'find_allow_broken_sym_with_-L'); + tl.mkdirP(root); + testutil.createSymlinkDir(path.join(root, 'noSuch'), path.join(root, 'brokenSym')); + tl.mkdirP(path.join(root, 'realDir')); + fs.writeFileSync(path.join(root, 'realDir', 'file'), 'test file content'); + testutil.createSymlinkDir(path.join(root, 'realDir'), path.join(root, 'symDir')); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.allowBrokenSymbolicLinks = true; + options.followSymbolicLinks = true; + let itemPaths: string[] = tl.find(root, options); + assert.equal(itemPaths.length, 6); + assert.equal(itemPaths[0], root); + assert.equal(itemPaths[1], path.join(root, 'brokenSym')); + assert.equal(itemPaths[2], path.join(root, 'realDir')); + assert.equal(itemPaths[3], path.join(root, 'realDir', 'file')); + assert.equal(itemPaths[4], path.join(root, 'symDir')); + assert.equal(itemPaths[5], path.join(root, 'symDir', 'file')); + + done(); + }); + + it('allow specified broken symlink with -L', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /brokenSym -> /noSuch + let root: string = path.join(testutil.getTestTemp(), 'find_allow_specified_broken_sym_with_-L'); + tl.mkdirP(root); + let brokenSymPath = path.join(root, 'brokenSym'); + testutil.createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath); + fs.lstatSync(brokenSymPath); + + let options: tl.FindOptions = {} as tl.FindOptions; + options.allowBrokenSymbolicLinks = true; + options.followSymbolicLinks = true; + let itemPaths: string[] = tl.find(brokenSymPath, options); + assert.equal(itemPaths.length, 1); + assert.equal(itemPaths[0], brokenSymPath); + + done(); + }); + + it('detects cycle', (done) => { this.timeout(1000); // create the following layout: @@ -764,7 +1009,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('detects cycle starting from symlink', (done: MochaDone) => { + it('detects cycle starting from symlink', (done) => { this.timeout(1000); // create the following layout: @@ -785,7 +1030,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('detects deep cycle starting from middle', (done: MochaDone) => { + it('detects deep cycle starting from middle', (done) => { this.timeout(1000); // create the following layout: @@ -824,7 +1069,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('applies default options', (done: MochaDone) => { + it('default options', (done) => { this.timeout(1000); // create the following layout: @@ -832,7 +1077,7 @@ describe('Dir Operation Tests', function () { // /real_folder // /real_folder/file_under_real_folder // /sym_folder -> real_folder - let root: string = path.join(testutil.getTestTemp(), 'find_applies_default_options'); + let root: string = path.join(testutil.getTestTemp(), 'find_default_options'); tl.mkdirP(path.join(root, 'real_folder')); fs.writeFileSync(path.join(root, 'real_folder', 'file_under_real_folder'), 'test file under real folder'); testutil.createSymlinkDir(path.join(root, 'real_folder'), path.join(root, 'sym_folder')); @@ -840,6 +1085,7 @@ describe('Dir Operation Tests', function () { () => fs.statSync(path.join(root, 'sym_folder', 'file_under_real_folder')), 'sym_folder should be created properly'); + // assert the expected files are returned let actual: string[] = tl.find(root); let expected: string[] = [ root, @@ -853,7 +1099,29 @@ describe('Dir Operation Tests', function () { done(); }); - it('empty find path returns empty array', (done: MochaDone) => { + it('default options do not allow broken symlinks', (done) => { + this.timeout(1000); + + // create the following layout: + // + // /broken_symlink -> no_such_file + let root: string = path.join(testutil.getTestTemp(), 'find_default_options_broken_symlink'); + tl.mkdirP(root); + testutil.createSymlinkDir(path.join(root, 'no_such_file'), path.join(root, 'broken_symlink')); + + // assert the broken symlink is a problem + try { + tl.find(root); + throw new Error('Expected tl.find to throw'); + } + catch (err) { + assert(err.message.match(/ENOENT.*broken_symlink/), `Expected broken symlink error message, actual: '${err.message}'`); + } + + done(); + }); + + it('empty find path returns empty array', (done) => { this.timeout(1000); let actual: string[] = tl.find(''); @@ -863,7 +1131,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('normalizes find path', (done: MochaDone) => { + it('normalizes find path', (done) => { this.timeout(1000); // create the following layout: @@ -952,7 +1220,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('fails if mkdirP with conflicting file path', (done: MochaDone) => { + it('fails if mkdirP with conflicting file path', (done) => { this.timeout(1000); let testPath = path.join(testutil.getTestTemp(), 'mkdirP_conflicting_file_path'); @@ -970,7 +1238,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('fails if mkdirP with conflicting parent file path', (done: MochaDone) => { + it('fails if mkdirP with conflicting parent file path', (done) => { this.timeout(1000); let testPath = path.join(testutil.getTestTemp(), 'mkdirP_conflicting_parent_file_path', 'dir'); @@ -988,7 +1256,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('no-ops if mkdirP directory exists', (done: MochaDone) => { + it('no-ops if mkdirP directory exists', (done) => { this.timeout(1000); let testPath = path.join(testutil.getTestTemp(), 'mkdirP_dir_exists'); @@ -1000,7 +1268,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('no-ops if mkdirP with symlink directory', (done: MochaDone) => { + it('no-ops if mkdirP with symlink directory', (done) => { this.timeout(1000); // create the following layout: @@ -1026,7 +1294,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('no-ops if mkdirP with parent symlink directory', (done: MochaDone) => { + it('no-ops if mkdirP with parent symlink directory', (done) => { this.timeout(1000); // create the following layout: @@ -1052,7 +1320,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('breaks if mkdirP loop out of control', (done: MochaDone) => { + it('breaks if mkdirP loop out of control', (done) => { this.timeout(1000); let testPath = path.join(testutil.getTestTemp(), 'mkdirP_failsafe', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'); @@ -1107,39 +1375,26 @@ describe('Dir Operation Tests', function () { }); it('removes folder with locked file with rmRF', function (done) { - this.timeout(1000); + this.timeout(2000); var testPath = path.join(testutil.getTestTemp(), 'testFolder'); tl.mkdirP(testPath); assert(shell.test('-d', testPath), 'directory created'); - // can't remove folder with locked file on windows + // starting from windows-2022, + // can remove folder with locked file on windows as well, + // using the command `rd /s /q ` var filePath = path.join(testPath, 'file.txt'); fs.appendFileSync(filePath, 'some data'); assert(shell.test('-e', filePath), 'file exists'); var fd = fs.openSync(filePath, 'r'); - var worked = false; - try { - tl.rmRF(testPath); - worked = true; - } - catch (err) { } - - if (os.platform() === 'win32') { - assert(!worked, 'should not work on windows'); - assert(shell.test('-e', testPath), 'directory still exists'); - } - else { - assert(worked, 'should work on nix'); - assert(!shell.test('-e', testPath), 'directory removed'); - } - - fs.closeSync(fd); tl.rmRF(testPath); assert(!shell.test('-e', testPath), 'directory removed'); + fs.closeSync(fd); + done(); }); @@ -1164,7 +1419,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes file with rmRF', (done: MochaDone) => { + it('removes file with rmRF', (done) => { this.timeout(1000); let file: string = path.join(testutil.getTestTemp(), 'rmRF_file'); @@ -1176,7 +1431,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes hidden folder with rmRF', (done: MochaDone) => { + it('removes hidden folder with rmRF', (done) => { this.timeout(1000); let directory: string = path.join(testutil.getTestTemp(), '.rmRF_directory'); @@ -1188,7 +1443,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes hidden file with rmRF', (done: MochaDone) => { + it('removes hidden file with rmRF', (done) => { this.timeout(1000); let file: string = path.join(testutil.getTestTemp(), '.rmRF_file'); @@ -1200,7 +1455,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes symlink folder with rmRF', (done: MochaDone) => { + it('removes symlink folder with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1226,7 +1481,7 @@ describe('Dir Operation Tests', function () { // creating a symlink to a file on Windows requires elevated if (os.platform() != 'win32') { - it('removes symlink file with rmRF', (done: MochaDone) => { + it('removes symlink file with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1247,7 +1502,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes symlink file with missing source using rmRF', (done: MochaDone) => { + it('removes symlink file with missing source using rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1280,7 +1535,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes symlink level 2 file with rmRF', (done: MochaDone) => { + it('removes symlink level 2 file with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1305,7 +1560,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes nested symlink file with rmRF', (done: MochaDone) => { + it('removes nested symlink file with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1333,7 +1588,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes deeply nested symlink file with rmRF', (done: MochaDone) => { + it('removes deeply nested symlink file with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1364,7 +1619,7 @@ describe('Dir Operation Tests', function () { }); } - it('removes symlink folder with missing source using rmRF', (done: MochaDone) => { + it('removes symlink folder with missing source using rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1383,7 +1638,7 @@ describe('Dir Operation Tests', function () { // remove the real directory fs.unlinkSync(realFile); fs.rmdirSync(realDirectory); - assert.throws(() => { fs.statSync(symlinkDirectory) }, (err) => err.code == 'ENOENT', 'stat should throw'); + assert.throws(() => { fs.statSync(symlinkDirectory) }, (err: NodeJS.ErrnoException) => err.code == 'ENOENT', 'stat should throw'); // remove the symlink directory tl.rmRF(symlinkDirectory); @@ -1400,7 +1655,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes symlink level 2 folder with rmRF', (done: MochaDone) => { + it('removes symlink level 2 folder with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1432,7 +1687,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes nested symlink folder with rmRF', (done: MochaDone) => { + it('removes nested symlink folder with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1460,7 +1715,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes deeply nested symlink folder with rmRF', (done: MochaDone) => { + it('removes deeply nested symlink folder with rmRF', (done) => { this.timeout(1000); // create the following layout: @@ -1490,7 +1745,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('removes hidden file with rmRF', (done: MochaDone) => { + it('removes hidden file with rmRF', (done) => { this.timeout(1000); let file: string = path.join(testutil.getTestTemp(), '.rmRF_file'); @@ -1533,7 +1788,7 @@ describe('Dir Operation Tests', function () { done(); }); - it('move to existing destination should fail unless forced', function (done) { + it('move to existing destination should fail if no-clobber is enabled', function (done) { this.timeout(1000); var sourceFile = 'sourceFile'; @@ -1558,7 +1813,7 @@ describe('Dir Operation Tests', function () { var worked: boolean = false; try { - tl.mv(sourceFile, destFile); + tl.mv(sourceFile, destFile, "-n"); worked = true; } catch (err) { @@ -1577,7 +1832,7 @@ describe('Dir Operation Tests', function () { }); // cp tests - it('copies file using -f', (done: MochaDone) => { + it('copies file using -f', (done) => { this.timeout(1000); let root: string = path.join(testutil.getTestTemp(), 'cp_with_-f'); @@ -1592,4 +1847,45 @@ describe('Dir Operation Tests', function () { done(); }); -}); \ No newline at end of file +}); + +function findsExecutableWithScopedPermissions(chmodOptions) { + // create a executable file + let testPath = path.join(testutil.getTestTemp(), 'which-finds-file-name'); + tl.mkdirP(testPath); + let fileName = 'Which-Test-File'; + if (process.platform == 'win32') { + return; + } + + let filePath = path.join(testPath, fileName); + fs.writeFileSync(filePath, ''); + testutil.chmod(filePath, chmodOptions); + + let originalPath = process.env['PATH']; + try { + // update the PATH + process.env['PATH'] = process.env['PATH'] + path.delimiter + testPath; + + // exact file name + assert.equal(tl.which(fileName), filePath); + assert.equal(tl.which(fileName, false), filePath); + assert.equal(tl.which(fileName, true), filePath); + + if (process.platform == 'darwin') { + // not case sensitive on Mac + assert.equal(tl.which(fileName.toUpperCase()), path.join(testPath, fileName.toUpperCase())); + assert.equal(tl.which(fileName.toUpperCase(), false), path.join(testPath, fileName.toUpperCase())); + assert.equal(tl.which(fileName.toUpperCase(), true), path.join(testPath, fileName.toUpperCase())); + } + else { + // case sensitive on Linux + assert.equal(tl.which(fileName.toUpperCase()) || '', ''); + } + } + finally { + process.env['PATH'] = originalPath; + } + + return; +} \ No newline at end of file diff --git a/node/test/disktests.ts b/node/test/disktests.ts index 209c6dda2..e55cf8cde 100644 --- a/node/test/disktests.ts +++ b/node/test/disktests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import path = require('path'); import fs = require('fs'); diff --git a/node/test/fakeTasks/node10task/task.json b/node/test/fakeTasks/node10task/task.json new file mode 100644 index 000000000..9345cf49f --- /dev/null +++ b/node/test/fakeTasks/node10task/task.json @@ -0,0 +1,9 @@ +{ + "id": "id", + "name": "Node10Task", + "execution": { + "Node10": { + "target": "usedotnet.js" + } + } +} diff --git a/node/test/fakeTasks/node16task/task.json b/node/test/fakeTasks/node16task/task.json new file mode 100644 index 000000000..4342d1603 --- /dev/null +++ b/node/test/fakeTasks/node16task/task.json @@ -0,0 +1,9 @@ +{ + "id": "id", + "name": "Node16Task", + "execution": { + "Node16": { + "target": "usedotnet.js" + } + } +} diff --git a/node/test/fakeTasks/node6task/task.json b/node/test/fakeTasks/node6task/task.json new file mode 100644 index 000000000..2cb7e45e2 --- /dev/null +++ b/node/test/fakeTasks/node6task/task.json @@ -0,0 +1,9 @@ +{ + "id": "id", + "name": "Node6Task", + "execution": { + "Node": { + "target": "usedotnet.js" + } + } +} diff --git a/node/test/filtertests.ts b/node/test/filtertests.ts index 46beaec5f..110a7a4a9 100644 --- a/node/test/filtertests.ts +++ b/node/test/filtertests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tl from '../_build/task'; import testutil = require('./testutil'); @@ -23,7 +20,7 @@ describe('Filter Tests', function () { after(function () { }); - it('applies default option nobrace true', (done: MochaDone) => { + it('applies default option nobrace true', (done) => { this.timeout(1000); let list = [ @@ -41,7 +38,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option noglobstar false', (done: MochaDone) => { + it('applies default option noglobstar false', (done) => { this.timeout(1000); let list = [ @@ -61,7 +58,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option dot true', (done: MochaDone) => { + it('applies default option dot true', (done) => { this.timeout(1000); let list = [ @@ -78,7 +75,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option noext false', (done: MochaDone) => { + it('applies default option noext false', (done) => { this.timeout(1000); let list = [ @@ -97,7 +94,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option nocase based on platform', (done: MochaDone) => { + it('applies default option nocase based on platform', (done) => { this.timeout(1000); let list = [ @@ -117,7 +114,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option matchBase false', (done: MochaDone) => { + it('applies default option matchBase false', (done) => { this.timeout(1000); let list = [ @@ -134,7 +131,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option nocomment false', (done: MochaDone) => { + it('applies default option nocomment false', (done) => { this.timeout(1000); let list = [ @@ -148,7 +145,7 @@ describe('Filter Tests', function () { done(); }); - it('applies default option nonegate false', (done: MochaDone) => { + it('applies default option nonegate false', (done) => { this.timeout(1000); let list = [ @@ -165,7 +162,7 @@ describe('Filter Tests', function () { done(); }); - it('supports custom options', (done: MochaDone) => { + it('supports custom options', (done) => { this.timeout(1000); let list = [ diff --git a/node/test/findmatchtests.ts b/node/test/findmatchtests.ts index 13a3b5b63..b09d231f9 100644 --- a/node/test/findmatchtests.ts +++ b/node/test/findmatchtests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tl from '../_build/task'; import * as im from '../_build/internal' @@ -26,7 +23,7 @@ describe('Find and Match Tests', function () { after(function () { }); - it('single pattern', (done: MochaDone) => { + it('single pattern', (done) => { this.timeout(1000); // create the following layout: @@ -44,11 +41,12 @@ describe('Find and Match Tests', function () { path.join(root, 'hello.txt'), path.join(root, 'world.txt'), ]; + assert.deepEqual(actual, expected); done(); }); - it('aggregates matches', (done: MochaDone) => { + it('aggregates matches', (done) => { this.timeout(1000); // create the following layout: @@ -75,7 +73,23 @@ describe('Find and Match Tests', function () { done(); }); - it('does not duplicate matches', (done: MochaDone) => { + it('supports path not found', (done) => { + this.timeout(1000); + + let root: string = path.join(testutil.getTestTemp(), 'find-and-match_supports-path-not-found'); + tl.mkdirP(root); + let patterns: string[] = [ + path.join(root, 'NotFound', '*.proj'), + ]; + + let actual: string[] = tl.findMatch('', patterns); + let expected: string[] = []; + assert.deepEqual(actual, expected); + + done(); + }); + + it('does not duplicate matches', (done) => { this.timeout(1000); // create the following layout: @@ -107,7 +121,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports interleaved exclude patterns', (done: MochaDone) => { + it('supports interleaved exclude patterns', (done) => { this.timeout(1000); // create the following layout: @@ -154,7 +168,7 @@ describe('Find and Match Tests', function () { done(); }); - it('applies default match options', (done: MochaDone) => { + it('applies default match options', (done) => { this.timeout(1000); // create the following layout: @@ -238,7 +252,7 @@ describe('Find and Match Tests', function () { done(); }); - it('trims patterns', (done: MochaDone) => { + it('trims patterns', (done) => { this.timeout(1000); // create the following layout: @@ -260,7 +274,7 @@ describe('Find and Match Tests', function () { done(); }); - it('skips empty patterns', (done: MochaDone) => { + it('skips empty patterns', (done) => { this.timeout(1000); // create the following layout: @@ -284,7 +298,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports nocomment true', (done: MochaDone) => { + it('supports nocomment true', (done) => { this.timeout(1000); // create the following layout: @@ -306,7 +320,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports nobrace false', (done: MochaDone) => { + it('supports nobrace false', (done) => { this.timeout(1000); // create the following layout: @@ -331,7 +345,7 @@ describe('Find and Match Tests', function () { done(); }); - it('brace escaping platform-specific', (done: MochaDone) => { + it('brace escaping platform-specific', (done) => { this.timeout(1000); // create the following layout: @@ -366,7 +380,7 @@ describe('Find and Match Tests', function () { }); - it('supports nonegate true', (done: MochaDone) => { + it('supports nonegate true', (done) => { this.timeout(1000); // create the following layout: @@ -388,7 +402,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports flipNegate true', (done: MochaDone) => { + it('supports flipNegate true', (done) => { this.timeout(1000); // create the following layout: @@ -410,7 +424,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports matchBase include patterns', (done: MochaDone) => { + it('supports matchBase include patterns', (done) => { this.timeout(1000); // create the following layout: @@ -443,7 +457,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports matchBase include patterns with glob', (done: MochaDone) => { + it('supports matchBase include patterns with glob', (done) => { this.timeout(1000); // create the following layout: @@ -476,7 +490,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports matchBase exlude pattern', (done: MochaDone) => { + it('supports matchBase exlude pattern', (done) => { this.timeout(1000); // create the following layout: @@ -513,7 +527,7 @@ describe('Find and Match Tests', function () { done(); }); - it('counts leading negate markers', (done: MochaDone) => { + it('counts leading negate markers', (done) => { this.timeout(1000); // create the following layout: @@ -550,7 +564,7 @@ describe('Find and Match Tests', function () { done(); }); - it('trims whitespace after trimming negate markers', (done: MochaDone) => { + it('trims whitespace after trimming negate markers', (done) => { this.timeout(1000); // create the following layout: @@ -573,7 +587,7 @@ describe('Find and Match Tests', function () { done(); }); - it('evaluates comments before expanding braces', (done: MochaDone) => { + it('evaluates comments before expanding braces', (done) => { this.timeout(1000); // create the following layout: @@ -602,7 +616,7 @@ describe('Find and Match Tests', function () { done(); }); - it('evaluates negation before expanding braces', (done: MochaDone) => { + it('evaluates negation before expanding braces', (done) => { this.timeout(1000); // create the following layout: @@ -627,7 +641,7 @@ describe('Find and Match Tests', function () { done(); }); - it('evaluates comments before negation', (done: MochaDone) => { + it('evaluates comments before negation', (done) => { this.timeout(1000); // create the following layout: @@ -653,7 +667,7 @@ describe('Find and Match Tests', function () { done(); }); - it('escapes default root when rooting patterns', (done: MochaDone) => { + it('escapes default root when rooting patterns', (done) => { this.timeout(1000); // create the following layout: @@ -727,7 +741,7 @@ describe('Find and Match Tests', function () { done(); }); - it('applies default find options', (done: MochaDone) => { + it('applies default find options', (done) => { this.timeout(1000); // create the following layout: @@ -749,7 +763,7 @@ describe('Find and Match Tests', function () { done(); }); - it('supports custom find options', (done: MochaDone) => { + it('supports custom find options', (done) => { this.timeout(1000); // create the following layout: @@ -773,7 +787,7 @@ describe('Find and Match Tests', function () { done(); }); - it('default root falls back to System.DefaultWorkingDirectory', (done: MochaDone) => { + it('default root falls back to System.DefaultWorkingDirectory', (done) => { this.timeout(1000); let originalSystemDefaultWorkingDirectory = process.env['SYSTEM_DEFAULTWORKINGDIRECTORY']; @@ -801,7 +815,7 @@ describe('Find and Match Tests', function () { done(); }); - it('default root falls back to cwd', (done: MochaDone) => { + it('default root falls back to cwd', (done) => { this.timeout(1000); let originalSystemDefaultWorkingDirectory = process.env['SYSTEM_DEFAULTWORKINGDIRECTORY']; @@ -839,7 +853,7 @@ describe('Find and Match Tests', function () { } } - it('ensurePatternRooted()', (done: MochaDone) => { + it('ensurePatternRooted()', (done) => { this.timeout(1000); if (process.platform == 'win32') { @@ -918,7 +932,7 @@ describe('Find and Match Tests', function () { assert.deepEqual(actual, expected); } - it('getFindInfoFromPattern()', (done: MochaDone) => { + it('getFindInfoFromPattern()', (done) => { this.timeout(1000); // basename diff --git a/node/test/gethttpproxytests.ts b/node/test/gethttpproxytests.ts new file mode 100644 index 000000000..b68488412 --- /dev/null +++ b/node/test/gethttpproxytests.ts @@ -0,0 +1,56 @@ +import * as assert from 'assert'; +import * as tl from '../_build/task'; + +enum ProxyEnvironmentEnum { + proxyUrl = 'AGENT_PROXYURL', + proxyUsername = 'AGENT_PROXYUSERNAME', + proxyPassword = 'AGENT_PROXYPASSWORD', + proxyBypass = 'AGENT_PROXYBYPASSLIST' +} + +describe('GetHttpProxyConfiguration Tests', () => { + const proxyHost: string = 'proxy.example.com'; + const proxyPort: number = 8888; + const proxyUrl: string = `http://${proxyHost}:${proxyPort}`; + const proxyUsername: string = 'proxyUser'; + const proxyPassword: string = 'proxyPassword'; + const proxyByPass: string[] = ['http://bypass.example.com']; + const formatedUrlWithoutCrednetials = proxyUrl; + const fortmatedUrlWithCredentials = `http://${proxyUsername}:${proxyPassword}@${proxyHost}:${proxyPort}`; + + it('returns a valid proxy configuration if no credentials set', () => { + process.env[ProxyEnvironmentEnum.proxyUrl] = proxyUrl; + process.env[ProxyEnvironmentEnum.proxyBypass] = JSON.stringify(proxyByPass); + const expected: tl.ProxyConfiguration = { + proxyUrl: proxyUrl, + proxyBypassHosts: proxyByPass, + proxyUsername: undefined, + proxyPassword: undefined, + proxyFormattedUrl: formatedUrlWithoutCrednetials + } + const result = tl.getHttpProxyConfiguration(); + assert.deepStrictEqual(result, expected, 'it should have valid configuration'); + }); + + it('returns valid proxy configuration if credentials set', () => { + process.env[ProxyEnvironmentEnum.proxyUrl] = proxyUrl; + process.env[ProxyEnvironmentEnum.proxyUsername] = proxyUsername; + process.env[ProxyEnvironmentEnum.proxyPassword] = proxyPassword; + process.env[ProxyEnvironmentEnum.proxyBypass] = JSON.stringify(proxyByPass); + const expected: tl.ProxyConfiguration = { + proxyUrl: proxyUrl, + proxyBypassHosts: proxyByPass, + proxyFormattedUrl: fortmatedUrlWithCredentials, + proxyPassword: proxyPassword, + proxyUsername: proxyUsername + } + const result = tl.getHttpProxyConfiguration(); + assert.deepStrictEqual(result, expected, 'it should have credentials in formatted url'); + }); + + it('returns null if host should be bypassed', () => { + process.env[ProxyEnvironmentEnum.proxyUrl] = proxyUrl; + const result = tl.getHttpProxyConfiguration(proxyByPass[0]); + assert.strictEqual(result, null, 'result should be null'); + }); +}) \ No newline at end of file diff --git a/node/test/inputtests.ts b/node/test/inputtests.ts index dd187b5ed..51109f555 100644 --- a/node/test/inputtests.ts +++ b/node/test/inputtests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import path = require('path'); import os = require('os'); @@ -63,14 +60,40 @@ describe('Input Tests', function () { done(); }) - it('gets input value with whitespace', function (done) { + + it('gets input value', function (done) { this.timeout(1000); - process.env['INPUT_UNITTESTINPUT'] = ' test value '; + process.env['INPUT_UNITTESTINPUT'] = 'test value'; im._loadData(); - var inval = tl.getInput('UnitTestInput', true); - assert.equal(inval, 'test value'); + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + + done(); + }) + it('should clear input envvar', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); + + done(); + }) + it('required input throws', function (done) { + this.timeout(1000); + + var worked: boolean = false; + try { + var inval = tl.getInputRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req input should have not have worked'); done(); }) @@ -110,7 +133,7 @@ describe('Input Tests', function () { done(); }) - it('gets a variable with special characters', (done: MochaDone) => { + it('gets a variable with special characters', (done) => { this.timeout(1000); let expected = 'Value of var with special chars'; @@ -133,7 +156,7 @@ describe('Input Tests', function () { done(); }) - it('sets a variable with special chars as an env var', (done: MochaDone) => { + it('sets a variable with special chars as an env var', (done) => { this.timeout(1000); let expected = 'Set value of var with special chars'; @@ -152,6 +175,15 @@ describe('Input Tests', function () { done(); }) + it('sets and gets an output variable', function (done) { + this.timeout(1000); + + tl.setVariable('UnitTestVariable', 'test var value', false, true); + let varVal: string = tl.getVariable('UnitTestVariable'); + assert.equal(varVal, 'test var value'); + + done(); + }) it('sets and gets a secret variable', function (done) { this.timeout(1000); @@ -222,6 +254,92 @@ describe('Input Tests', function () { done(); }) + it('does not allow setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setVariable('my.secret', 'line1\rline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a carriage return'); + + // test line feed + failed = false; + try { + tl.setVariable('my.secret', 'line1\nline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setVariable('my.secret', 'line1\r\nline2', true); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2'); + + done(); + }) + + // setSecret tests + it('does not allow setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setSecret('line1\rline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a carriage return'); + + // test line feed + failed = false; + try { + tl.setSecret('line1\nline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setSecret('line1\r\nline2'); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + done(); + }) // getVariables tests it('gets public variables from initial load', function (done) { @@ -336,6 +454,33 @@ describe('Input Tests', function () { done(); }) + it('gets a required endpoint url', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_URL_id1'] = 'http://url'; + im._loadData(); + + var url = tl.getEndpointUrlRequired('id1'); + assert.equal(url, 'http://url', 'url should match'); + + done(); + }) + it('required endpoint url throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var url = tl.getEndpointUrlRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint url should have not have worked'); + + done(); + }) it('gets an endpoint auth', function (done) { this.timeout(1000); @@ -392,6 +537,34 @@ describe('Input Tests', function () { done(); }) + it('gets required endpoint auth scheme', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationSchemeRequired('id1'); + assert(data, 'should return a string value'); + assert.equal(data, 'scheme1', 'should be correct scheme'); + assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); + + done(); + }) + it('required endpoint auth scheme throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationSchemeRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint auth scheme should have not have worked'); + + done(); + }) it('gets endpoint auth parameters', function (done) { this.timeout(1000); process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; @@ -413,6 +586,33 @@ describe('Input Tests', function () { done(); }) + it('gets required endpoint auth parameters', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'value1', 'should be correct auth param'); + assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); + + done(); + }) + it('throws if endpoint auth parameter is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint authorization parameter should have not have worked'); + + done(); + }) it('gets an endpoint data', function (done) { this.timeout(1000); process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; @@ -433,6 +633,32 @@ describe('Input Tests', function () { done(); }) + it('gets required endpoint data', function (done) { + this.timeout(1000); + process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; + im._loadData(); + + var data = tl.getEndpointDataParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'val1', 'should be correct object'); + + done(); + }) + it('throws if endpoint data is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointDataParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint data should have not have worked'); + + done(); + }) // getSecureFileName/getSecureFileTicket/getSecureFiles tests it('gets a secure file name', function (done) { this.timeout(1000); @@ -534,6 +760,18 @@ describe('Input Tests', function () { done(); }) + it('gets delimited input with a Regexp', function (done) { + this.timeout(1000); + + var inputValue = 'a,b\nc'; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', /[,\n]/, /*required*/false); + assert.equal(outVal.length, 3, 'should return array with 3 elements'); + + done(); + }) // getPathInput tests it('gets path input value', function (done) { @@ -653,6 +891,110 @@ describe('Input Tests', function () { done(); }) + // getPathInputRequired tests + it('gets path input required value', function (done) { + this.timeout(1000); + + var inputValue = 'test.txt' + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + done(); + }) + it('throws if required path not supplied', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired(null, /*check=*/false); + worked = true; + } + catch (err) { } + + assert(!worked, 'req path should have not have worked'); + + done(); + }) + it('get required path invalid checked throws', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('some_missing_path', /*check=*/true); + worked = true; + } + catch (err) { } + + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + it('gets path input required value with space', function (done) { + this.timeout(1000); + + var inputValue = 'file name.txt'; + var expectedValue = 'file name.txt'; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); + + done(); + }) + it('gets path required value with check and exist', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var inputValue = __filename; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/true); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + var errMsg = errStream.getContents(); + assert(errMsg === "", "no err") + + done(); + }) + it('gets path required value with check and not exist', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var inputValue = "someRandomFile.txt"; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('path1', /*check=*/true); + worked = true; + } + catch (err) { + assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); + } + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + // filePathSupplied tests it('filePathSupplied checks not supplied', function (done) { this.timeout(1000); @@ -791,14 +1133,14 @@ describe('Input Tests', function () { // _loadData tests it('_loadData does not run twice', function (done) { - this.timeout(2000); + this.timeout(5000); // intialize an input (stored in vault) process.env['INPUT_SOMEINPUT'] = 'some input value'; im._loadData(); assert.equal(tl.getInput('SomeInput'), 'some input value'); - // copy vsts-task-lib to a different dir and load it from + // copy azure-pipelines-task-lib to a different dir and load it from // there so it will be forced to load again let testDir = path.join(testutil.getTestTemp(), '_loadData-not-called-twice'); tl.mkdirP(testDir); @@ -808,4 +1150,54 @@ describe('Input Tests', function () { assert.equal(tl.getInput('SomeInput'), 'some input value'); done(); }) + + describe('Feature flags tests', () => { + + ([ + ["true", true], + ["TRUE", true], + ["TruE", true], + ["false", false], + ["treu", false], + ["fasle", false], + ["On", false], + ["", false], + [undefined, false] + ] as [string, boolean][]) + .forEach( + ( + [ + input, + expectedResult + ] + ) => { + it(`Should return ${expectedResult} if feature flag env is ${input}`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = input + + const ffValue = tl.getBoolFeatureFlag(ffName, false); + + assert.equal(ffValue, expectedResult); + }) + } + ); + + it(`Should return default value if feature flag env is empty`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = "" + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + + it(`Should return default value if feature flag env is not specified`, () => { + const ffName = "SOME_TEST_FF" + delete process.env[ffName]; + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + }); }); diff --git a/node/test/internalhelpertests.ts b/node/test/internalhelpertests.ts index 6d0d8397c..10d62ee3e 100644 --- a/node/test/internalhelpertests.ts +++ b/node/test/internalhelpertests.ts @@ -1,15 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as testutil from './testutil'; import * as tl from '../_build/task'; import * as im from '../_build/internal'; +import * as mockery from 'mockery' describe('Internal Path Helper Tests', function () { @@ -34,7 +32,7 @@ describe('Internal Path Helper Tests', function () { `expected ensureRooted for input <${path}> to yield <${expected}>`); } - it('ensureRooted roots paths', (done: MochaDone) => { + it('ensureRooted roots paths', (done) => { this.timeout(1000); if (process.platform == 'win32') { @@ -122,7 +120,7 @@ describe('Internal Path Helper Tests', function () { `expected getDirectoryName for input <${path}> to yield <${expected}>`); } - it('getDirectoryName interprets directory name from paths', (done: MochaDone) => { + it('getDirectoryName interprets directory name from paths', (done) => { this.timeout(1000); assertDirectoryName(null, ''); @@ -246,7 +244,7 @@ describe('Internal Path Helper Tests', function () { `expected isRooted for input <${path}> to yield <${expected}>`); } - it('isRooted detects root', (done: MochaDone) => { + it('isRooted detects root', (done) => { this.timeout(1000); if (process.platform == 'win32') { @@ -337,7 +335,7 @@ describe('Internal Path Helper Tests', function () { `expected normalizeSeparators for input <${path}> to yield <${expected}>`); } - it('normalizeSeparators', (done: MochaDone) => { + it('normalizeSeparators', (done) => { this.timeout(1000); if (process.platform == 'win32') { @@ -404,4 +402,32 @@ describe('Internal Path Helper Tests', function () { done(); }); + + it('ReportMissingStrings', (done) => { + + mockery.registerAllowable('../_build/internal') + const fsMock = { + statSync: function (path) { return null; } + }; + mockery.registerMock('fs', fsMock); + mockery.enable({ useCleanCache: true }) + + const local_im = require('../_build/internal'); + + try{ + const localizedMessage : string = local_im._loc("gizmo", "whatever", "music"); + assert.strictEqual(localizedMessage, "gizmo whatever music"); + + }finally{ + mockery.disable(); + mockery.deregisterAll(); + } + done(); + }); + + it('ReportMissingLocalization', (done) => { + const localizedMessage : string = im._loc("gizmo", "whatever", "music"); + assert.strictEqual(localizedMessage, "gizmo whatever music"); + done(); + }); }); \ No newline at end of file diff --git a/node/test/isuncpathtests.ts b/node/test/isuncpathtests.ts new file mode 100644 index 000000000..714ee3ed7 --- /dev/null +++ b/node/test/isuncpathtests.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert = require('assert'); +import * as im from '../_build/internal'; +import testutil = require('./testutil'); + +describe('Is UNC-path Tests', function () { + + before(function (done) { + try { + testutil.initialize(); + } catch (err) { + assert.fail('Failed to load task lib: ' + err.message); + } + done(); + }); + + after(function () { + }); + + it('checks if path is unc path', (done) => { + this.timeout(1000); + + const paths = [ + { inputPath: '\\server\\path\\to\\file', isUNC: false }, + { inputPath: '\\\\server\\path\\to\\file', isUNC: true }, + { inputPath: '\\\\\\server\\path\\to\\file', isUNC: false }, + { inputPath: '!@#$%^&*()_+', isUNC: false }, + { inputPath: '\\\\\\\\\\\\', isUNC: false }, + { inputPath: '1q2w3e4r5t6y', isUNC: false }, + { inputPath: '', isUNC: false } + ]; + + for (let path of paths) { + assert.deepEqual(im._isUncPath(path.inputPath), path.isUNC); + } + + done(); + }); +}); diff --git a/node/test/legacyfindfilestests.ts b/node/test/legacyfindfilestests.ts index 9c86ae56c..01c1b36e0 100644 --- a/node/test/legacyfindfilestests.ts +++ b/node/test/legacyfindfilestests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; @@ -27,7 +24,7 @@ describe('Legacy Find Files Tests', function () { after(function () { }); - it('supports directory name single char wildcard', (done: MochaDone) => { + it('supports directory name single char wildcard', (done) => { this.timeout(1000); // create the following layout: @@ -58,7 +55,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports directory name wildcard', (done: MochaDone) => { + it('supports directory name wildcard', (done) => { this.timeout(1000); // create the following layout: @@ -89,7 +86,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports exclude patterns', (done: MochaDone) => { + it('supports exclude patterns', (done) => { this.timeout(1000); // create the following layout: @@ -130,7 +127,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports file name single char wildcard', (done: MochaDone) => { + it('supports file name single char wildcard', (done) => { this.timeout(1000); // create the following layout: @@ -157,7 +154,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports file name wildcard', (done: MochaDone) => { + it('supports file name wildcard', (done) => { this.timeout(1000); // create the following layout: @@ -184,7 +181,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports globstar', (done: MochaDone) => { + it('supports globstar', (done) => { this.timeout(1000); // create the following layout: @@ -217,7 +214,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports include directories', (done: MochaDone) => { + it('supports include directories', (done) => { this.timeout(1000); // create the following layout: @@ -246,7 +243,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports include directories only', (done: MochaDone) => { + it('supports include directories only', (done) => { this.timeout(1000); // create the following layout: @@ -273,7 +270,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports inter-segment wildcard', (done: MochaDone) => { + it('supports inter-segment wildcard', (done) => { this.timeout(1000); // create the following layout: @@ -313,7 +310,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('unions matches', (done: MochaDone) => { + it('unions matches', (done) => { this.timeout(1000); // create the following layout: @@ -341,7 +338,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('has platform-specific case sensitivity', (done: MochaDone) => { + it('has platform-specific case sensitivity', (done) => { this.timeout(1000); // create the following layout: @@ -376,7 +373,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports literal ; in pattern', (done: MochaDone) => { + it('supports literal ; in pattern', (done) => { this.timeout(1000); // create the following layout: @@ -398,7 +395,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports literal ; in rootDirectory', (done: MochaDone) => { + it('supports literal ; in rootDirectory', (done) => { this.timeout(1000); // create the following layout: @@ -422,7 +419,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports pattern is ;', (done: MochaDone) => { + it('supports pattern is ;', (done) => { this.timeout(1000); // create the following layout: @@ -444,7 +441,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('does not support pattern with trailing slash', (done: MochaDone) => { + it('does not support pattern with trailing slash', (done) => { this.timeout(1000); let pattern = path.join(__dirname, 'hello', 'world') + '/'; @@ -458,7 +455,7 @@ describe('Legacy Find Files Tests', function () { } }); - it('has platform-specific support for pattern with trailing backslash', (done: MochaDone) => { + it('has platform-specific support for pattern with trailing backslash', (done) => { this.timeout(1000); if (process.platform == 'win32') { @@ -496,7 +493,7 @@ describe('Legacy Find Files Tests', function () { } }); - it('follows symlink dirs', (done: MochaDone) => { + it('follows symlink dirs', (done) => { this.timeout(1000); // create the following layout: @@ -531,7 +528,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports alternate include syntax', (done: MochaDone) => { + it('supports alternate include syntax', (done) => { this.timeout(1000); // create the following layout: @@ -559,7 +556,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('appends root directory', (done: MochaDone) => { + it('appends root directory', (done) => { this.timeout(1000); // create the following layout: @@ -587,7 +584,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports hidden files', (done: MochaDone) => { + it('supports hidden files', (done) => { this.timeout(1000); // create the following layout: @@ -614,7 +611,7 @@ describe('Legacy Find Files Tests', function () { done(); }); - it('supports hidden folders', (done: MochaDone) => { + it('supports hidden folders', (done) => { this.timeout(1000); // create the following layout: @@ -665,7 +662,7 @@ describe('Legacy Find Files Tests', function () { `pattern '${pattern}' should not match path '${path}'`); } - it('converts patterns to RegExp', (done: MochaDone) => { + it('converts patterns to RegExp', (done) => { let tlAny = tl as any; if (process.platform == 'win32') { // should convert to forward slashes diff --git a/node/test/loctests.ts b/node/test/loctests.ts index 27412aa3d..432c94577 100644 --- a/node/test/loctests.ts +++ b/node/test/loctests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import path = require('path'); import fs = require('fs'); @@ -13,7 +10,7 @@ import testutil = require('./testutil'); describe('Loc Tests', function () { - before(function (done) { + beforeEach(function (done) { try { testutil.initialize(); } @@ -72,6 +69,54 @@ describe('Loc Tests', function () { done(); }) + it('gets loc string from second loc resources.json', function (done) { + this.timeout(1000); + + // Don't reset values each time we call setResourcesPath for this test. + process.env['TASKLIB_INPROC_UNITS'] = ''; + + // Arrange + var tempFolder = path.join(testutil.getTestTemp(), 'loc-str-from-loc-res-json2'); + shell.mkdir('-p', tempFolder); + + // Create first task.json and resources file + var jsonStr = "{\"messages\": {\"key6\" : \"string for key 6.\"}}"; + var jsonPath = path.join(tempFolder, 'task.json'); + fs.writeFileSync(jsonPath, jsonStr); + + var tempLocFolder = path.join(tempFolder, 'Strings', 'resources.resjson', 'zh-CN'); + shell.mkdir('-p', tempLocFolder); + var locJsonStr = "{\"loc.messages.key6\" : \"loc cn-string for key 6.\"}"; + var locJsonPath = path.join(tempLocFolder, 'resources.resjson'); + fs.writeFileSync(locJsonPath, locJsonStr); + + // Create second task.json and resources file + var nestedLocFolder = path.join(tempFolder, 'nested'); + shell.mkdir('-p', nestedLocFolder); + + var jsonStr2 = "{\"messages\": {\"keySecondFile\" : \"string for keySecondFile.\"}}"; + var jsonPath2 = path.join(nestedLocFolder, 'task.json'); + fs.writeFileSync(jsonPath2, jsonStr2); + + var tempLocFolder2 = path.join(nestedLocFolder, 'Strings', 'resources.resjson', 'zh-CN'); + shell.mkdir('-p', tempLocFolder2); + var locJsonStr2 = "{\"loc.messages.keySecondFile\" : \"loc cn-string for keySecondFile.\"}"; + var locJsonPath2 = path.join(tempLocFolder2, 'resources.resjson'); + fs.writeFileSync(locJsonPath2, locJsonStr2); + + process.env['SYSTEM_CULTURE'] = 'ZH-cn'; // Lib should handle casing differences for culture. + + // Act + tl.setResourcePath(jsonPath); + tl.setResourcePath(jsonPath2); + + // Assert + assert.equal(tl.loc('key6'), 'loc cn-string for key 6.', 'string not found for key.'); + assert.equal(tl.loc('keySecondFile'), 'loc cn-string for keySecondFile.', 'string not found for keySecondFile.'); + + done(); + }) + it('fallback to current string if culture resources.resjson not found', function (done) { this.timeout(1000); diff --git a/node/test/matchtests.ts b/node/test/matchtests.ts index 7a08cb945..373cc2b53 100644 --- a/node/test/matchtests.ts +++ b/node/test/matchtests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tl from '../_build/task'; import testutil = require('./testutil'); @@ -23,7 +20,7 @@ describe('Match Tests', function () { after(function () { }); - it('single pattern', (done: MochaDone) => { + it('single pattern', (done) => { this.timeout(1000); let list: string[] = [ @@ -41,7 +38,7 @@ describe('Match Tests', function () { done(); }); - it('aggregates matches', (done: MochaDone) => { + it('aggregates matches', (done) => { this.timeout(1000); let list: string[] = [ @@ -62,7 +59,7 @@ describe('Match Tests', function () { done(); }); - it('does not duplicate matches', (done: MochaDone) => { + it('does not duplicate matches', (done) => { this.timeout(1000); let list: string[] = [ @@ -86,7 +83,7 @@ describe('Match Tests', function () { done(); }); - it('preserves order', (done: MochaDone) => { + it('preserves order', (done) => { this.timeout(1000); let list: string[] = [ @@ -115,7 +112,7 @@ describe('Match Tests', function () { done(); }); - it('supports interleaved exclude patterns', (done: MochaDone) => { + it('supports interleaved exclude patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -147,7 +144,7 @@ describe('Match Tests', function () { done(); }); - it('applies default options', (done: MochaDone) => { + it('applies default options', (done) => { this.timeout(1000); let list: string[] = [ @@ -203,7 +200,7 @@ describe('Match Tests', function () { done(); }); - it('trims patterns', (done: MochaDone) => { + it('trims patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -222,7 +219,7 @@ describe('Match Tests', function () { done(); }); - it('skips empty patterns', (done: MochaDone) => { + it('skips empty patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -244,7 +241,7 @@ describe('Match Tests', function () { done(); }); - it('supports nocomment true', (done: MochaDone) => { + it('supports nocomment true', (done) => { this.timeout(1000); let list: string[] = [ @@ -263,7 +260,7 @@ describe('Match Tests', function () { done(); }); - it('supports nonegate true', (done: MochaDone) => { + it('supports nonegate true', (done) => { this.timeout(1000); let list: string[] = [ @@ -282,7 +279,7 @@ describe('Match Tests', function () { done(); }); - it('supports flipNegate true', (done: MochaDone) => { + it('supports flipNegate true', (done) => { this.timeout(1000); let list: string[] = [ @@ -301,7 +298,7 @@ describe('Match Tests', function () { done(); }); - it('counts leading negate markers', (done: MochaDone) => { + it('counts leading negate markers', (done) => { this.timeout(1000); let list: string[] = [ @@ -330,7 +327,7 @@ describe('Match Tests', function () { done(); }); - it('trims whitespace after trimming negate markers', (done: MochaDone) => { + it('trims whitespace after trimming negate markers', (done) => { this.timeout(1000); let list: string[] = [ @@ -350,7 +347,7 @@ describe('Match Tests', function () { done(); }); - it('evaluates comments before negation', (done: MochaDone) => { + it('evaluates comments before negation', (done) => { this.timeout(1000); let list: string[] = [ @@ -372,7 +369,7 @@ describe('Match Tests', function () { done(); }); - it('applies pattern root for include patterns', (done: MochaDone) => { + it('applies pattern root for include patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -397,7 +394,7 @@ describe('Match Tests', function () { done(); }); - it('applies pattern root for exclude patterns', (done: MochaDone) => { + it('applies pattern root for exclude patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -424,7 +421,7 @@ describe('Match Tests', function () { done(); }); - it('does not apply pattern root for basename matchBase include patterns', (done: MochaDone) => { + it('does not apply pattern root for basename matchBase include patterns', (done) => { this.timeout(1000); let list: string[] = [ @@ -451,7 +448,7 @@ describe('Match Tests', function () { done(); }); - it('does not apply pattern root for basename matchBase exclude patterns', (done: MochaDone) => { + it('does not apply pattern root for basename matchBase exclude patterns', (done) => { this.timeout(1000); let list: string[] = [ diff --git a/node/test/mocktests.ts b/node/test/mocktests.ts index b813da12f..6c8e44095 100644 --- a/node/test/mocktests.ts +++ b/node/test/mocktests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as mt from '../_build/mock-task'; import * as mtm from '../_build/mock-test'; @@ -11,6 +8,10 @@ import * as mtr from '../_build/mock-toolrunner'; import * as ma from '../_build/mock-answer'; import * as tl from '../_build/task'; +import ncp = require('child_process'); +import os = require('os'); +import path = require('path'); +import semver = require('semver'); import testutil = require('./testutil'); describe('Mock Tests', function () { @@ -29,6 +30,23 @@ describe('Mock Tests', function () { }); + // Verify mock-task exports all the exported functions exported by task. If a task-lib function isn't mocked, + // it's difficult to use in a task with unit tests. + it('mock-task exports every function in task', (done) => { + for (let memberName of Object.keys(tl)) { + const member = tl[memberName]; + + if (typeof member === 'function') { + const mockMember = mt[memberName]; + if (!mockMember || typeof mockMember !== typeof member) { + assert.fail(`mock-task missing function exported by task: "${memberName}"`); + } + } + } + + done(); + }); + it('Mocks which and returns path on exists', (done) => { var a: ma.TaskLibAnswers = { "which": { @@ -144,13 +162,13 @@ describe('Mock Tests', function () { done(); }) - it('Mock loc returns key', (done: MochaDone) => { + it('Mock loc returns key', (done) => { let actual = mt.loc('STR_KEY'); assert.equal(actual, 'loc_mock_STR_KEY'); done(); }) - it('Mock loc returns key and args', (done: MochaDone) => { + it('Mock loc returns key and args', (done) => { let actual = mt.loc('STR_KEY', false, 2, 'three'); assert.equal(actual, 'loc_mock_STR_KEY false 2 three'); done(); @@ -180,8 +198,133 @@ describe('Mock Tests', function () { tool.arg('--arg'); tool.arg('foo'); let rc: number = await tool.exec({}); - + assert(tool, "tool should not be null"); assert(rc == 0, "rc is 0"); - }) + }) + + it('Mock toolRunner returns correct output', async () => { + const expectedStdout = "atool output here" + os.EOL + "abc"; + const expectedStderr = "atool with this stderr output" + os.EOL + "def"; + var a: ma.TaskLibAnswers = { + "exec": { + "/usr/local/bin/atool --arg foo": { + "code": 0, + "stdout": expectedStdout, + "stderr": expectedStderr + } + } + }; + + mt.setAnswers(a); + + let tool: mtr.ToolRunner = mt.tool('/usr/local/bin/atool'); + tool.arg('--arg'); + tool.arg('foo'); + + let firstStdline = true; + let firstErrline = true; + let numStdLineCalls = 0; + let numStdErrCalls = 0; + tool.on('stdout', (out) => { + assert.equal(expectedStdout, out); + }); + tool.on('stderr', (out) => { + assert.equal(expectedStderr, out); + }); + tool.on('stdline', (out) => { + numStdLineCalls += 1; + if (firstStdline) { + assert.equal("atool output here", out); + firstStdline = false; + } + else { + assert.equal("abc", out); + } + }); + tool.on('errline', (out) => { + numStdErrCalls += 1; + if (firstErrline) { + assert.equal("atool with this stderr output", out); + firstErrline = false; + } + else { + assert.equal("def", out); + } + }); + await tool.exec({}); + + assert.equal(numStdLineCalls, 2); + assert.equal(numStdErrCalls, 2); + }) + + it('Mock toolRunner returns correct output when ending on EOL', async () => { + const expectedStdout = os.EOL; + const expectedStderr = os.EOL; + var a: ma.TaskLibAnswers = { + "exec": { + "/usr/local/bin/atool --arg foo": { + "code": 0, + "stdout": expectedStdout, + "stderr": expectedStderr + } + } + }; + + mt.setAnswers(a); + + let tool: mtr.ToolRunner = mt.tool('/usr/local/bin/atool'); + tool.arg('--arg'); + tool.arg('foo'); + let numStdLineCalls = 0; + let numStdErrCalls = 0; + tool.on('stdout', (out) => { + assert.equal(expectedStdout, out); + }); + tool.on('stderr', (out) => { + assert.equal(expectedStderr, out); + }); + tool.on('stdline', (out) => { + numStdLineCalls += 1; + assert.equal("", out); + }); + tool.on('errline', (out) => { + numStdErrCalls += 1; + assert.equal("", out); + }); + await tool.exec({}); + + assert.equal(numStdLineCalls, 1); + assert.equal(numStdErrCalls, 1); + }) + + it('MockTest handles node 6 tasks correctly', function (done) { + this.timeout(30000); + const runner = new mtm.MockTestRunner(path.join(__dirname, 'fakeTasks', 'node6task', 'entry.js')); + const nodePath = runner.nodePath; + assert(nodePath, 'node path should have been correctly set'); + const version = ncp.execSync(nodePath + ' -v').toString().trim(); + assert(semver.satisfies(version, '6.x'), 'Downloaded node version should be Node 6 instead of ' + version); + done(); + }) + + it('MockTest handles node 10 tasks correctly', function (done) { + this.timeout(30000); + const runner = new mtm.MockTestRunner(path.join(__dirname, 'fakeTasks', 'node10task', 'entry.js')); + const nodePath = runner.nodePath; + assert(nodePath, 'node path should have been correctly set'); + const version = ncp.execSync(nodePath + ' -v').toString().trim(); + assert(semver.satisfies(version, '10.x'), 'Downloaded node version should be Node 10 instead of ' + version); + done(); + }) + + it('MockTest handles node 16 tasks correctly', function (done) { + this.timeout(30000); + const runner = new mtm.MockTestRunner(path.join(__dirname, 'fakeTasks', 'node16task', 'entry.js')); + const nodePath = runner.nodePath; + assert(nodePath, 'node path should have been correctly set'); + const version = ncp.execSync(nodePath + ' -v').toString().trim(); + assert(semver.satisfies(version, '16.x'), 'Downloaded node version should be Node 16 instead of ' + version); + done(); + }) }); diff --git a/node/test/resulttests.ts b/node/test/resulttests.ts index c48dfa4cf..ea2bd0238 100644 --- a/node/test/resulttests.ts +++ b/node/test/resulttests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as tl from '../_build/task'; import testutil = require('./testutil'); diff --git a/node/test/retrytests.ts b/node/test/retrytests.ts new file mode 100644 index 000000000..bedd68d1a --- /dev/null +++ b/node/test/retrytests.ts @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert = require('assert'); +import * as tl from '../_build/task'; +import testutil = require('./testutil'); + +describe('Retry Tests', function () { + + before(function (done) { + try { + testutil.initialize(); + } catch (err) { + assert.fail('Failed to load task lib: ' + err.message); + } + done(); + }); + + after(function () { + }); + + it('retries to execute a function', (done) => { + this.timeout(1000); + + const testError = Error('Test Error'); + + function count(num: number) { + return () => num--; + } + + function fail(count: Function) { + if (count()) { + throw testError; + } + + return 'completed'; + } + + function catchError(func: Function, args: any[]) { + try { + func(...args); + } catch (err) { + return err; + } + } + + assert.deepEqual(catchError(tl.retry, [fail, [count(5)], { continueOnError: false, retryCount: 3 }]), testError); + assert.deepEqual(catchError(tl.retry, [fail, [count(5)], { continueOnError: false, retryCount: 4 }]), testError); + assert.deepEqual(tl.retry(fail, [count(5)], { continueOnError: false, retryCount: 5 }), 'completed'); + assert.deepEqual(tl.retry(fail, [count(5)], { continueOnError: false, retryCount: 6 }), 'completed'); + assert.deepEqual(tl.retry(fail, [count(5)], { continueOnError: true, retryCount: 3 }), undefined); + assert.deepEqual(tl.retry(() => 123, [0, 'a', 1, 'b', 2], { continueOnError: false, retryCount: 7 }), 123); + assert.deepEqual(tl.retry((a: string, b: string) => a + b, [''], { continueOnError: false, retryCount: 7 }), 'undefined'); + + done(); + }); +}); diff --git a/node/test/scripts/match-input-exe.cs b/node/test/scripts/match-input-exe.cs new file mode 100644 index 000000000..30ccf8655 --- /dev/null +++ b/node/test/scripts/match-input-exe.cs @@ -0,0 +1,59 @@ +using System; +namespace MatchInput +{ + public static class Program + { + public static Int32 Main(String[] args) + { + // Validate at least two args + if (args.Length < 2) + { + throw new Exception("Expected arguments EXIT_CODE MATCH_VALUE [ERROR]"); + } + + // Validate exit code arg + Int32 code; + if (!Int32.TryParse(args[0] ?? String.Empty, out code)) + { + throw new Exception("Expected EXIT_CODE to be an integer"); + } + + // Validate match value arg + String match = args[1]; + if (String.IsNullOrEmpty(match)) + { + throw new Exception("Expected MATCH_VALUE to not be empty"); + } + + // Optional error message arg + String error = args.Length >= 3 ? args[2] : null; + + String line; + while (true) + { + // Read STDIN + line = Console.ReadLine(); + + // Null indicates end of stream + if (line == null) + { + break; + } + + // Print matches + if (line.IndexOf(match) >= 0) + { + Console.WriteLine(line); + } + } + + // Print optional error message + if (!String.IsNullOrEmpty(error)) + { + Console.Error.WriteLine(error); + } + + return code; + } + } +} diff --git a/node/test/scripts/print-output-exe.cs b/node/test/scripts/print-output-exe.cs new file mode 100644 index 000000000..59755352d --- /dev/null +++ b/node/test/scripts/print-output-exe.cs @@ -0,0 +1,39 @@ +using System; +namespace PrintOutput +{ + public static class Program + { + public static Int32 Main(String[] args) + { + // Validate at least two args + if (args.Length < 2) + { + throw new Exception("Expected arguments EXIT_CODE VALUE [...VALUE]"); + } + + // Validate exit code arg + Int32 code; + if (!Int32.TryParse(args[0] ?? String.Empty, out code)) + { + throw new Exception("Expected EXIT_CODE to be an integer"); + } + + // Validate value args + for (Int32 i = 1 ; i < args.Length ; i++) + { + if (String.IsNullOrEmpty(args[i])) + { + throw new Exception("Expected VALUE to not be empty"); + } + } + + // Write values + for (Int32 i = 1 ; i < args.Length ; i++) + { + Console.WriteLine(args[i]); + } + + return code; + } + } +} diff --git a/node/test/scripts/wait-for-file.js b/node/test/scripts/wait-for-file.js new file mode 100644 index 000000000..70b727d81 --- /dev/null +++ b/node/test/scripts/wait-for-file.js @@ -0,0 +1,35 @@ +var fs = require('fs'); + +// get the command line args that use the format someArg=someValue +var args = {}; +process.argv.forEach(function (arg) { + var match = arg.match(/^(.+)=(.*)$/); + if (match) { + args[match[1]] = match[2]; + } +}); + +var state = { + file: args.file +}; + +if (!state.file) { + throw new Error('file is not specified'); +} + +state.checkFile = function (s) { + try { + fs.statSync(s.file); + } + catch (err) { + if (err.code == 'ENOENT') { + return; + } + + throw err; + } + + setTimeout(s.checkFile, 100, s); +}; + +state.checkFile(state); diff --git a/node/test/testindex.d.ts b/node/test/testindex.d.ts deleted file mode 100644 index 03f5d49c2..000000000 --- a/node/test/testindex.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// this is copied to build output so tests compiling use the just built task lib .d.ts - -/// -/// \ No newline at end of file diff --git a/node/test/testutil.ts b/node/test/testutil.ts index 8ca4beb59..e55dbcd5c 100644 --- a/node/test/testutil.ts +++ b/node/test/testutil.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + import path = require('path'); import util = require('util'); import stream = require('stream'); @@ -35,7 +38,7 @@ var NullStream = function () { util.inherits(NullStream, stream.Writable); export function getNullStream() { - return new NullStream(); + return new NullStream(); } export class StringStream extends stream.Writable { @@ -57,7 +60,7 @@ export class StringStream extends stream.Writable { } export function createStringStream() { - return new StringStream(); + return new StringStream(); } export function buildOutput(lines: string[]): string { diff --git a/node/test/toolrunnerTestsWithExecAsync.ts b/node/test/toolrunnerTestsWithExecAsync.ts new file mode 100644 index 000000000..1b532e092 --- /dev/null +++ b/node/test/toolrunnerTestsWithExecAsync.ts @@ -0,0 +1,1632 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert = require('assert'); +import child_process = require('child_process'); +import fs = require('fs'); +import path = require('path'); +import os = require('os'); +import stream = require('stream'); +import * as tl from '../_build/task'; +import * as trm from '../_build/toolrunner'; + +import testutil = require('./testutil'); + +describe('Toolrunner Tests With ExecAsync', function () { + + before(function (done) { + try { + testutil.initialize(); + } + catch (err) { + assert.fail('Failed to load task lib: ' + err.message); + } + done(); + }); + + after(function () { + + }); + + it('Exec convenience with stdout', function (done) { + this.timeout(10000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + tl.execAsync('cmd', '/c echo \'azure-pipelines-task-lib\'', _testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + tl.execAsync('ls', '-l -a', _testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of ls should be 0'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }) + it('ToolRunner writes debug', function (done) { + this.timeout(10000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var cmdPath = tl.which('cmd', true); + var cmd = tl.tool(cmdPath); + cmd.arg('/c echo \'azure-pipelines-task-lib\''); + + cmd.execAsync(_testExecOptions) + .then(function (code) { + var contents = stdStream.getContents(); + assert(contents.indexOf('exec tool: ' + cmdPath) >= 0, 'should exec cmd'); + assert.equal(code, 0, 'return code of cmd should be 0'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + var ls = tl.tool(tl.which('ls', true)); + ls.arg('-l'); + ls.arg('-a'); + + ls.execAsync(_testExecOptions) + .then(function (code) { + var contents = stdStream.getContents(); + const usr = os.platform() === 'linux' ? '/usr' : ''; + assert(contents.indexOf(`exec tool: ${usr}/bin/ls`) >= 0, 'should exec ls'); + assert.equal(code, 0, 'return code of ls should be 0'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Execs with stdout', function (done) { + this.timeout(10000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + var output = ''; + if (os.platform() === 'win32') { + var cmd = tl.tool(tl.which('cmd', true)) + .arg('/c') + .arg('echo \'azure-pipelines-task-lib\''); + + cmd.on('stdout', (data) => { + output = data.toString(); + }); + + cmd.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + assert(output && output.length > 0, 'should have emitted stdout'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + var ls = tl.tool(tl.which('ls', true)); + ls.arg('-l'); + ls.arg('-a'); + + ls.on('stdout', (data) => { + output = data.toString(); + }); + + ls.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of ls should be 0'); + assert(output && output.length > 0, 'should have emitted stdout'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Fails on return code 1 with stderr', function (done) { + this.timeout(10000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var cmd = tl.tool(tl.which('cmd', true)); + cmd.arg('/c notExist'); + + var output = ''; + cmd.on('stderr', (data) => { + output = data.toString(); + }); + + var succeeded = false; + cmd.execAsync(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('should not have succeeded'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err.message.indexOf('failed with exit code 1') >= 0, `expected error message to indicate "failed with exit code 1". actual error message: "${err}"`); + assert(output && output.length > 0, 'should have emitted stderr'); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var bash = tl.tool(tl.which('bash', true)); + bash.arg('--noprofile'); + bash.arg('--norc'); + bash.arg('-c'); + bash.arg('echo hello from STDERR 1>&2 ; exit 123'); + var output = ''; + bash.on('stderr', (data) => { + output = data.toString(); + }); + + var succeeded = false; + bash.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('should not have succeeded'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err.message.indexOf('failed with exit code 123') >= 0, `expected error message to indicate "failed with exit code 123". actual error message: "${err}"`); + assert(output && output.length > 0, 'should have emitted stderr'); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Succeeds on stderr by default', function (done) { + this.timeout(10000); + + var scriptPath = path.join(__dirname, 'scripts', 'stderroutput.js'); + var ls = tl.tool(tl.which('node', true)); + ls.arg(scriptPath); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + ls.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'should have succeeded on stderr'); + done(); + }) + .catch(function (err) { + done(new Error('did not succeed on stderr')) + }) + }) + it('Fails on stderr if specified', function (done) { + this.timeout(10000); + + var scriptPath = path.join(__dirname, 'scripts', 'stderroutput.js'); + var node = tl.tool(tl.which('node', true)) + .arg(scriptPath); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + } + + var output = ''; + node.on('stderr', (data) => { + output = data.toString(); + }); + + var succeeded = false; + node.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('should not have succeeded'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err.message.indexOf('one or more lines were written to the STDERR stream') >= 0, `expected error message to indicate "one or more lines were written to the STDERR stream". actual error message: "${err}"`); + assert(output && output.length > 0, 'should have emitted stderr'); + done(); + } + }) + .catch(function (err) { + done(err); + }); + }) + it('Fails when process fails to launch', function (done) { + this.timeout(10000); + + var tool = tl.tool(tl.which('node', true)); + var _testExecOptions = { + cwd: path.join(testutil.getTestTemp(), 'nosuchdir'), + env: {}, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + } + + var output = ''; + tool.on('stderr', (data) => { + output = data.toString(); + }); + + var succeeded = false; + tool.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('should not have succeeded'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err.message.indexOf('This may indicate the process failed to start') >= 0, `expected error message to indicate "This may indicate the process failed to start". actual error message: "${err}"`); + done(); + } + }) + .catch(function (err) { + done(err); + }); + }) + it('Handles child process holding streams open', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}""`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' &`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.execAsync(options) + .then(function () { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + done(); + }) + .catch(function (err) { + done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + }); + }) + it('Handles child process holding streams open and non-zero exit code', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}"" & exit /b 123`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' & exit 123`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.execAsync(options) + .then(function () { + done(new Error('should not have been successful')); + done(); + }) + .catch(function (err) { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + assert(err.message.indexOf('failed with exit code 123') >= 0); + done(); + }) + .catch(function (err) { + done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + }); + }) + it('Handles child process holding streams open and stderr', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}"" & echo hi 1>&2`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' & echo hi 1>&2`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.execAsync(options) + .then(function () { + done(new Error('should not have been successful')); + done(); + }) + .catch(function (err) { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + assert(err.message.indexOf('failed because one or more lines were written to the STDERR stream') >= 0); + done(); + }) + .catch(function (err) { + done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + }); + }) + it('Exec pipe output to another tool, succeeds if both tools succeed', function (done) { + this.timeout(30000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + outputExe.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('line 2') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('node'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + ps.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Exec pipe output to another tool, fails if first tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('1') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + outputExe.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('print-output.exe | findstr "line 2" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err && err.message && err.message.indexOf('print-output.exe') >= 0, 'error from print-output.exe is not reported'); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('ssh'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('bad'); + ps.pipeExecOutputToTool(grep); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + ps.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('ps bad | grep ssh was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + //assert(output && output.length > 0 && output.indexOf('ps: illegal option') >= 0, `error output "ps: illegal option" is expected. actual "${output}"`); + assert(err && err.message && err.message.indexOf('/bin/ps') >= 0, 'error from ps is not reported'); + done(); + } + }) + .catch(function (err) { + done(err); + }) + } + }) + it('Exec pipe output to another tool, fails if second tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('1') // exit code + .arg('line 2') // match value + .arg('some error message'); // error + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + outputExe.on('stderr', (data) => { + errOut += data.toString(); + }); + + var succeeded = false; + outputExe.execAsync(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('print-output.exe 0 "line 1" "line 2" "line 3" | match-input.exe 1 "line 2" "some error message" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('some error message') >= 0, 'error output from match-input.exe is expected'); + assert(err && err.message && err.message.indexOf('match-input.exe') >= 0, 'error from find does not match expeced. actual: ' + err.message); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('--?'); + + var node = tl.tool(tl.which('node', true)) + .arg('-e') + .arg('console.log("line1"); setTimeout(function () { console.log("line2"); }, 200);'); // allow long enough to hook up stdout to stdin + node.pipeExecOutputToTool(grep); + + var output = ''; + node.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + node.on('stderr', (data) => { + errOut += data.toString(); + }) + + var succeeded = false; + node.execAsync(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('node [...] | grep --? was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('grep: unrecognized option') >= 0, 'error output from ps command is expected'); + // grep is /bin/grep on Linux and /usr/bin/grep on OSX + assert(err && err.message && err.message.match(/\/[usr\/]?bin\/grep/), 'error from grep is not reported. actual: ' + err.message); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Exec pipe output to file and another tool, succeeds if both tools succeed', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + const testFile = path.join(testutil.getTestTemp(), 'BothToolsSucceed.log'); + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + outputExe.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('line 2') >= 0, 'should have emitted stdout ' + output); + assert(fs.existsSync(testFile), 'Log of first tool output is created when both tools succeed'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('line 2') >= 0, 'Log file of first tool should have stdout from first tool: ' + fileContents); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('node'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep, testFile); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + ps.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); + assert(fs.existsSync(testFile), 'Log of first tool output is created when both tools succeed'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('PID') >= 0, 'Log of first tool should have stdout from first tool: ' + fileContents); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Exec pipe output to file and another tool, fails if first tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + const testFile = path.join(testutil.getTestTemp(), 'FirstToolFails.log'); + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('1') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + outputExe.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('print-output.exe | findstr "line 2" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err && err.message && err.message.indexOf('print-output.exe') >= 0, 'error from print-output.exe is not reported'); + assert(fs.existsSync(testFile), 'Log of first tool output is created when first tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('line 3') >= 0, 'Error from first tool should be written to log file: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('ssh'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('bad'); + ps.pipeExecOutputToTool(grep, testFile); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + ps.execAsync(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('ps bad | grep ssh was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err && err.message && err.message.indexOf('/bin/ps') >= 0, 'error from ps is not reported'); + assert(fs.existsSync(testFile), 'Log of first tool output is created when first tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('illegal option') >= 0 || fileContents.indexOf('unsupported option') >= 0, + 'error from first tool should be written to log file: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }) + } + }) + it('Exec pipe output to file and another tool, fails if second tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + const testFile = path.join(testutil.getTestTemp(), 'SecondToolFails.log'); + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('1') // exit code + .arg('line 2') // match value + .arg('some error message'); // error + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + outputExe.on('stderr', (data) => { + errOut += data.toString(); + }); + + var succeeded = false; + outputExe.execAsync(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('print-output.exe 0 "line 1" "line 2" "line 3" | match-input.exe 1 "line 2" "some error message" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('some error message') >= 0, 'error output from match-input.exe is expected'); + assert(err && err.message && err.message.indexOf('match-input.exe') >= 0, 'error from find does not match expeced. actual: ' + err.message); + assert(fs.existsSync(testFile), 'Log of first tool output is created when second tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('some error message') < 0, 'error from second tool should not be in the log for first tool: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('--?'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep, testFile); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + ps.on('stderr', (data) => { + errOut += data.toString(); + }) + + var succeeded = false; + ps.execAsync(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('ps ax | grep --? was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('grep: unrecognized option') >= 0, 'error output from ps command is expected'); + // grep is /bin/grep on Linux and /usr/bin/grep on OSX + assert(err && err.message && err.message.match(/\/[usr\/]?bin\/grep/), 'error from grep is not reported. actual: ' + err.message); + assert(fs.existsSync(testFile), 'Log of first tool output is created when second tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('unrecognized option') < 0, 'error from second tool should not be in the first tool log file: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + }) + + if (process.platform != 'win32') { + it('exec prints [command] (OSX/Linux)', function (done) { + this.timeout(10000); + let bash = tl.tool(tl.which('bash')) + .arg('--norc') + .arg('--noprofile') + .arg('-c') + .arg('echo hello world'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream, windowsVerbatimArguments: true }; + let output = ''; + bash.on('stdout', (data) => { + output += data.toString(); + }); + bash.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + `[command]${tl.which('bash')} --norc --noprofile -c echo hello world`); + // validate stdout + assert.equal( + output.trim(), + 'hello world'); + done(); + }) + .catch(function (err) { + done(err); + }); + }); + } + else { // process.platform == 'win32' + + // -------------------------- + // exec arg tests (Windows) + // -------------------------- + + it('exec .exe AND verbatim args (Windows)', function (done) { + this.timeout(10000); + + // the echo built-in is a good tool for this test + let exePath = process.env.ComSpec; + let exeRunner = tl.tool(exePath) + .arg('/c') + .arg('echo') + .arg('helloworld') + .arg('hello:"world again"'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream, windowsVerbatimArguments: true }; + let output = ''; + exeRunner.on('stdout', (data) => { + output += data.toString(); + }); + exeRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + `[command]"${exePath}" /c echo helloworld hello:"world again"`); + // validate stdout + assert.equal( + output.trim(), + 'helloworld hello:"world again"'); + done(); + }) + .catch(function (err) { + done(err); + }); + }); + + it('exec .exe AND arg quoting (Windows)', function (done) { + this.timeout(10000); + + // the echo built-in is a good tool for this test + let exePath = process.env.ComSpec; + let exeRunner = tl.tool(exePath) + .arg('/c') + .arg('echo') + .arg('helloworld') + .arg('hello world') + .arg('hello:"world again"') + .arg('hello,world'); // "," should not be quoted for .exe (should be for .cmd) + let outStream = testutil.createStringStream(); + let options = { outStream: outStream }; + let output = ''; + exeRunner.on('stdout', (data) => { + output += data.toString(); + }); + exeRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + '[command]' + exePath + ' /c echo' + + ' helloworld' + + ' "hello world"' + + ' "hello:\\"world again\\""' + + ' hello,world'); + // validate stdout + assert.equal( + output.trim(), + 'helloworld' + + ' "hello world"' + + ' "hello:\\"world again\\""' + + ' hello,world'); + done(); + }) + .catch(function (err) { + done(err); + }); + }); + + it('exec .exe with space AND verbatim args (Windows)', function (done) { + this.timeout(20000); + + // this test validates the quoting that tool runner adds around the tool path + // when using the windowsVerbatimArguments option. otherwise the target process + // interprets the args as starting after the first space in the tool path. + let exePath = compileArgsExe('print args exe with spaces.exe'); + let exeRunner = tl.tool(exePath) + .arg('myarg1 myarg2'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream, windowsVerbatimArguments: true }; + let output = ''; + exeRunner.on('stdout', (data) => { + output += data.toString(); + }); + exeRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + `[command]"${exePath}" myarg1 myarg2`); + // validate stdout + assert.equal( + output.trim(), + "args[0]: 'args'\r\n" + + "args[1]: 'exe'\r\n" + + "args[2]: 'with'\r\n" + + "args[3]: 'spaces.exe'\r\n" + + "args[4]: 'myarg1'\r\n" + + "args[5]: 'myarg2'"); + done(); + }) + .catch(function (err) { + done(err); + }); + }); + + it('exec .cmd with space AND verbatim args (Windows)', function (done) { + this.timeout(10000); + + // this test validates the quoting that tool runner adds around the script path. + // otherwise cmd.exe will not be able to resolve the path to the script. + let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); + let cmdRunner = tl.tool(cmdPath) + .arg('arg1 arg2') + .arg('arg3'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream, windowsVerbatimArguments: true }; + let output = ''; + cmdRunner.on('stdout', (data) => { + output += data.toString(); + }); + cmdRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + `[command]${process.env.ComSpec} /D /S /C ""${cmdPath}" arg1 arg2 arg3"`); + // validate stdout + assert.equal( + output.trim(), + 'args[0]: "arg1"\r\n' + + 'args[1]: "arg2"\r\n' + + 'args[2]: "arg3"'); + done(); + }) + .catch(function (err) { + done(err); + }); + }); + + it('exec .cmd with space AND arg with space (Windows)', function (done) { + this.timeout(10000); + + // this test validates the command is wrapped in quotes (i.e. cmd.exe /S /C ""). + // otherwise the leading quote (around the script with space path) would be stripped + // and cmd.exe would not be able to resolve the script path. + let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); + let cmdRunner = tl.tool(cmdPath) + .arg('my arg 1') + .arg('my arg 2'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream }; + let output = ''; + cmdRunner.on('stdout', (data) => { + output += data.toString(); + }); + cmdRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + `[command]${process.env.ComSpec} /D /S /C ""${cmdPath}" "my arg 1" "my arg 2""`); + // validate stdout + assert.equal( + output.trim(), + 'args[0]: "my arg 1"\r\n' + + 'args[1]: "my arg 2"'); + done(); + }) + .catch(function (err) { + done(err); + }) + }); + + it('exec .cmd AND arg quoting (Windows)', function (done) { + this.timeout(10000); + + // this test validates .cmd quoting rules are applied, not the default libuv rules + let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); + let cmdRunner = tl.tool(cmdPath) + .arg('helloworld') + .arg('hello world') + .arg('hello\tworld') + .arg('hello&world') + .arg('hello(world') + .arg('hello)world') + .arg('hello[world') + .arg('hello]world') + .arg('hello{world') + .arg('hello}world') + .arg('hello^world') + .arg('hello=world') + .arg('hello;world') + .arg('hello!world') + .arg('hello\'world') + .arg('hello+world') + .arg('hello,world') + .arg('hello`world') + .arg('hello~world') + .arg('hello|world') + .arg('helloworld') + .arg('hello:"world again"') + .arg('hello world\\'); + let outStream = testutil.createStringStream(); + let options = { outStream: outStream }; + let output = ''; + cmdRunner.on('stdout', (data) => { + output += data.toString(); + }); + cmdRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + '[command]' + process.env.ComSpec + ' /D /S /C ""' + cmdPath + '"' + + ' helloworld' + + ' "hello world"' + + ' "hello\tworld"' + + ' "hello&world"' + + ' "hello(world"' + + ' "hello)world"' + + ' "hello[world"' + + ' "hello]world"' + + ' "hello{world"' + + ' "hello}world"' + + ' "hello^world"' + + ' "hello=world"' + + ' "hello;world"' + + ' "hello!world"' + + ' "hello\'world"' + + ' "hello+world"' + + ' "hello,world"' + + ' "hello`world"' + + ' "hello~world"' + + ' "hello|world"' + + ' "helloworld"' + + ' "hello:""world again"""' + + ' "hello world\\\\"' + + '"'); + // validate stdout + assert.equal( + output.trim(), + 'args[0]: "helloworld"\r\n' + + 'args[1]: "hello world"\r\n' + + 'args[2]: "hello\tworld"\r\n' + + 'args[3]: "hello&world"\r\n' + + 'args[4]: "hello(world"\r\n' + + 'args[5]: "hello)world"\r\n' + + 'args[6]: "hello[world"\r\n' + + 'args[7]: "hello]world"\r\n' + + 'args[8]: "hello{world"\r\n' + + 'args[9]: "hello}world"\r\n' + + 'args[10]: "hello^world"\r\n' + + 'args[11]: "hello=world"\r\n' + + 'args[12]: "hello;world"\r\n' + + 'args[13]: "hello!world"\r\n' + + 'args[14]: "hello\'world"\r\n' + + 'args[15]: "hello+world"\r\n' + + 'args[16]: "hello,world"\r\n' + + 'args[17]: "hello`world"\r\n' + + 'args[18]: "hello~world"\r\n' + + 'args[19]: "hello|world"\r\n' + + 'args[20]: "hello"\r\n' + + 'args[21]: "hello>world"\r\n' + + 'args[22]: "hello:world again"\r\n' + + 'args[23]: "hello world\\\\"'); + done(); + }) + .catch(function (err) { + done(err); + }) + }); + + // ------------------------------- + // exec pipe arg tests (Windows) + // ------------------------------- + + it('exec pipe .cmd to .exe AND arg quoting (Windows)', function (done) { + this.timeout(10000); + + let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); + let cmdRunner = tl.tool(cmdPath) + .arg('"hello world"'); + + let exePath = path.join(process.env.windir, 'System32', 'find.exe'); + let exeRunner = tl.tool(exePath) + .arg('hello world'); + + let outStream = testutil.createStringStream(); + let options = { outStream: outStream }; + let output = ''; + cmdRunner.on('stdout', (data) => { + output += data.toString(); + }); + cmdRunner.pipeExecOutputToTool(exeRunner); + cmdRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + '[command]' + process.env.ComSpec + ' /D /S /C ""' + cmdPath + '" """hello world""""' + + ' | ' + exePath + ' "hello world"'); + // validate stdout + assert.equal( + output.trim(), + 'args[0]: "hello world"'); + done(); + }) + .catch(function (err) { + done(err); + }) + }); + + it('exec pipe .cmd to .exe AND verbatim args (Windows)', function (done) { + this.timeout(10000); + + let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); + let cmdRunner = tl.tool(cmdPath) + .arg('hello world'); + + let exePath = path.join(process.env.windir, 'System32', 'find.exe'); + let exeRunner = tl.tool(exePath) + .arg('"world"'); + + let outStream = testutil.createStringStream(); + let options = { outStream: outStream, windowsVerbatimArguments: true }; + let output = ''; + cmdRunner.on('stdout', (data) => { + output += data.toString(); + }); + cmdRunner.pipeExecOutputToTool(exeRunner); + cmdRunner.execAsync(options) + .then(function (code) { + assert.equal(code, 0, 'return code should be 0'); + // validate the [command] header + assert.equal( + outStream.getContents().split(os.EOL)[0], + '[command]' + process.env.ComSpec + ' /D /S /C ""' + cmdPath + '" hello world"' + + ' | "' + exePath + '" "world"'); + // validate stdout + assert.equal( + output.trim(), + 'args[1]: "world"'); + done(); + }) + .catch(function (err) { + done(err); + }) + }); + } + + // function to compile a .NET program on Windows. + let compileExe = (sourceFileName: string, targetFileName: string): string => { + let directory = path.join(testutil.getTestTemp(), sourceFileName); + tl.mkdirP(directory); + let exePath = path.join(directory, targetFileName); + + // short-circuit if already compiled + try { + fs.statSync(exePath); + return exePath; + } + catch (err) { + if (err.code != 'ENOENT') { + throw err; + } + } + + let sourceFile = path.join(__dirname, 'scripts', sourceFileName); + let cscPath = 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe'; + fs.statSync(cscPath); + child_process.execFileSync( + cscPath, + [ + '/target:exe', + `/out:${exePath}`, + sourceFile + ]); + return exePath; + } + + describe('Executing inside shell', function () { + + let tempPath: string = testutil.getTestTemp(); + let _testExecOptions: trm.IExecOptions; + + before (function () { + _testExecOptions = { + cwd: __dirname, + env: { + WIN_TEST: 'test value', + TESTPATH: tempPath, + TEST_NODE: 'node', + TEST: 'test value' + }, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + shell: true, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + }) + + it('Exec inside shell', function (done) { + this.timeout(10000); + + let output: string = ''; + if (os.platform() === 'win32') { + let exePath = compileArgsExe('print args with spaces.exe'); + let exeRunner = tl.tool(exePath); + exeRunner.line('%WIN_TEST%'); + exeRunner.on('stdout', (data) => { + output = data.toString(); + }); + exeRunner.execAsync(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + assert.equal(output.trim(), 'args[0]: \'test value\'', 'Command should return \"args[0]: \'test value\'\"'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + let statRunner = tl.tool('stat'); + statRunner.line('$TESTPATH'); + statRunner.on('stdout', (data) => { + output = data.toString(); + }); + statRunner.execAsync(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of stat should be 0'); + assert(output.includes(tempPath), `Result should include \'${tempPath}\'`); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + it('Exec pipe output to another tool inside shell, succeeds if both tools succeed', function (done) { + this.timeout(30000); + + if (os.platform() === 'win32') { + const matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('test value'); // match value + const outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('"%WIN_TEST%"') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + let output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + outputExe.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('test value') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + const grep = tl.tool(tl.which('grep', true)); + grep.arg('$TEST_NODE'); + + const ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep); + + let output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + ps.execAsync(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + it('Should handle arguments with quotes properly', function (done) { + this.timeout(10000); + + let output: string = ''; + if (os.platform() === 'win32') { + let exePath = compileArgsExe('print args with spaces.exe'); + let exeRunner = tl.tool(exePath); + exeRunner.line('-TEST1="space test" "-TEST2=%WIN_TEST%" \'-TEST3=value\''); + exeRunner.on('stdout', (data) => { + output += data.toString(); + }); + exeRunner.execAsync(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + assert.equal(output.trim(), 'args[0]: \'-TEST1=space test\'\r\n' + + 'args[1]: \'-TEST2=test value\'\r\n' + + 'args[2]: \'\'-TEST3=value\'\''); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + let statRunner = tl.tool('echo'); + statRunner.line('-TEST1="$TEST;test" "-TEST2=/one/two/three" \'-TEST3=out:$TEST\''); + statRunner.on('stdout', (data) => { + output = data.toString(); + }); + statRunner.execAsync(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of stat should be 0'); + assert.equal(output, '-TEST1=test value;test -TEST2=/one/two/three -TEST3=out:$TEST\n'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + }) + + // function to compile a .NET program that prints the command line args. + // the helper program is used to validate that command line args are passed correctly. + let compileArgsExe = (targetFileName: string): string => { + return compileExe('print-args-exe.cs', targetFileName); + } + + // function to compile a .NET program that matches input lines. + // the helper program is used on Windows to validate piping output between tools. + let compileMatchExe = (): string => { + return compileExe('match-input-exe.cs', 'match-input.exe'); + } + + // function to compile a .NET program that prints lines. + // the helper program is used on Windows to validate piping output between tools. + let compileOutputExe = (): string => { + return compileExe('print-output-exe.cs', 'print-output.exe'); + } +}); diff --git a/node/test/toolrunnertests.ts b/node/test/toolrunnertests.ts index 67776e5da..ac0e0eeb1 100644 --- a/node/test/toolrunnertests.ts +++ b/node/test/toolrunnertests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import child_process = require('child_process'); import fs = require('fs'); @@ -31,9 +28,8 @@ describe('Toolrunner Tests', function () { }); - it('ExecSync convenience with stdout', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -46,7 +42,7 @@ describe('Toolrunner Tests', function () { }; if (os.platform() === 'win32') { - var ret = tl.execSync('cmd', '/c echo \'vsts-task-lib\'', _testExecOptions); + var ret = tl.execSync('cmd', '/c echo \'azure-pipelines-task-lib\'', _testExecOptions); assert.equal(ret.code, 0, 'return code of cmd should be 0'); } else { @@ -58,7 +54,7 @@ describe('Toolrunner Tests', function () { done(); }) it('ExecSync with stdout', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -72,7 +68,7 @@ describe('Toolrunner Tests', function () { if (os.platform() === 'win32') { var cmd = tl.tool(tl.which('cmd', true)); - cmd.arg('/c echo \'vsts-task-lib\''); + cmd.arg('/c echo \'azure-pipelines-task-lib\''); var ret = cmd.execSync(_testExecOptions); assert.equal(ret.code, 0, 'return code of cmd should be 0'); @@ -90,7 +86,7 @@ describe('Toolrunner Tests', function () { done(); }) it('ExecSync fails with rc 1 and stderr', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -110,17 +106,18 @@ describe('Toolrunner Tests', function () { } else { tool = tl.tool(tl.which('bash', true)); + tool.arg('--norc'); tool.arg('-c'); tool.arg('echo hello from stderr 1>&2 ; exit 123'); } var ret = tool.execSync(_testExecOptions); - assert.equal(ret.code, 123, 'return code of tool should be 1'); + assert.equal(ret.code, 123, 'return code of tool should be 123'); assert.equal(ret.stderr.toString().trim(), 'hello from stderr'); done(); }) it('Exec convenience with stdout', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -133,12 +130,12 @@ describe('Toolrunner Tests', function () { }; if (os.platform() === 'win32') { - tl.exec('cmd', '/c echo \'vsts-task-lib\'', _testExecOptions) + tl.exec('cmd', '/c echo \'azure-pipelines-task-lib\'', _testExecOptions) .then(function (code) { assert.equal(code, 0, 'return code of cmd should be 0'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -148,13 +145,13 @@ describe('Toolrunner Tests', function () { assert.equal(code, 0, 'return code of ls should be 0'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) it('ToolRunner writes debug', function (done) { - this.timeout(1000); + this.timeout(10000); var stdStream = testutil.createStringStream(); tl.setStdStream(stdStream); @@ -172,7 +169,7 @@ describe('Toolrunner Tests', function () { if (os.platform() === 'win32') { var cmdPath = tl.which('cmd', true); var cmd = tl.tool(cmdPath); - cmd.arg('/c echo \'vsts-task-lib\''); + cmd.arg('/c echo \'azure-pipelines-task-lib\''); cmd.exec(_testExecOptions) .then(function (code) { @@ -181,7 +178,7 @@ describe('Toolrunner Tests', function () { assert.equal(code, 0, 'return code of cmd should be 0'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -193,17 +190,18 @@ describe('Toolrunner Tests', function () { ls.exec(_testExecOptions) .then(function (code) { var contents = stdStream.getContents(); - assert(contents.indexOf('exec tool: /bin/ls') >= 0, 'should exec ls'); + const usr = os.platform() === 'linux' ? '/usr' : ''; + assert(contents.indexOf(`exec tool: ${usr}/bin/ls`) >= 0, 'should exec ls'); assert.equal(code, 0, 'return code of ls should be 0'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) it('Execs with stdout', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -217,8 +215,9 @@ describe('Toolrunner Tests', function () { var output = ''; if (os.platform() === 'win32') { - var cmd = tl.tool(tl.which('cmd', true)); - cmd.arg('/c echo \'vsts-task-lib\''); + var cmd = tl.tool(tl.which('cmd', true)) + .arg('/c') + .arg('echo \'azure-pipelines-task-lib\''); cmd.on('stdout', (data) => { output = data.toString(); @@ -230,7 +229,7 @@ describe('Toolrunner Tests', function () { assert(output && output.length > 0, 'should have emitted stdout'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -249,13 +248,13 @@ describe('Toolrunner Tests', function () { assert(output && output.length > 0, 'should have emitted stdout'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) it('Fails on return code 1 with stderr', function (done) { - this.timeout(1000); + this.timeout(10000); var _testExecOptions = { cwd: __dirname, @@ -282,17 +281,17 @@ describe('Toolrunner Tests', function () { succeeded = true; assert.fail('should not have succeeded'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { - assert(err.message.indexOf('return code: 1') >= 0, `expected error message to indicate "return code: 1". actual error message: "${err}"`); + assert(err.message.indexOf('failed with exit code 1') >= 0, `expected error message to indicate "failed with exit code 1". actual error message: "${err}"`); assert(output && output.length > 0, 'should have emitted stderr'); done(); } }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -313,23 +312,23 @@ describe('Toolrunner Tests', function () { succeeded = true; assert.fail('should not have succeeded'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { - assert(err.message.indexOf('return code: 123') >= 0, `expected error message to indicate "return code: 123". actual error message: "${err}"`); + assert(err.message.indexOf('failed with exit code 123') >= 0, `expected error message to indicate "failed with exit code 123". actual error message: "${err}"`); assert(output && output.length > 0, 'should have emitted stderr'); done(); } }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) it('Succeeds on stderr by default', function (done) { - this.timeout(1000); + this.timeout(10000); var scriptPath = path.join(__dirname, 'scripts', 'stderroutput.js'); var ls = tl.tool(tl.which('node', true)); @@ -349,12 +348,12 @@ describe('Toolrunner Tests', function () { assert.equal(code, 0, 'should have succeeded on stderr'); done(); }) - .fail(function (err) { + .catch(function (err) { done(new Error('did not succeed on stderr')) }) }) it('Fails on stderr if specified', function (done) { - this.timeout(1000); + this.timeout(10000); var scriptPath = path.join(__dirname, 'scripts', 'stderroutput.js'); var node = tl.tool(tl.which('node', true)) @@ -381,22 +380,235 @@ describe('Toolrunner Tests', function () { succeeded = true; assert.fail('should not have succeeded'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { - assert(err.message.indexOf('return code: 0') >= 0, `expected error message to indicate "return code: 0". actual error message: "${err}"`); + assert(err.message.indexOf('one or more lines were written to the STDERR stream') >= 0, `expected error message to indicate "one or more lines were written to the STDERR stream". actual error message: "${err}"`); assert(output && output.length > 0, 'should have emitted stderr'); done(); } }) - .fail(function (err) { + .catch(function (err) { + done(err); + }); + }) + it('Fails when process fails to launch', function (done) { + this.timeout(10000); + + var tool = tl.tool(tl.which('node', true)); + var _testExecOptions = { + cwd: path.join(testutil.getTestTemp(), 'nosuchdir'), + env: {}, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + } + + var output = ''; + tool.on('stderr', (data) => { + output = data.toString(); + }); + + var succeeded = false; + tool.exec(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('should not have succeeded'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err.message.indexOf('This may indicate the process failed to start') >= 0, `expected error message to indicate "This may indicate the process failed to start". actual error message: "${err}"`); + done(); + } + }) + .catch(function (err) { + done(err); + }); + }) + it('Handles child process holding streams open', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}""`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' &`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.exec(options) + .then(function () { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + done(); + }) + .catch(function (err) { + done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + }); + }) + it('Handles child process holding streams open and non-zero exit code', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}"" & exit /b 123`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' & exit 123`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.exec(options) + .then(function () { + done(new Error('should not have been successful')); + done(); + }) + .catch(function (err) { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + assert(err.message.indexOf('failed with exit code 123') >= 0); + done(); + }) + .catch(function (err) { + done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + }); + }) + it('Handles child process holding streams open and stderr', function (done) { + this.timeout(10000); + + let semaphorePath = path.join(testutil.getTestTemp(), 'child-process-semaphore.txt'); + fs.writeFileSync(semaphorePath, ''); + + let nodePath = tl.which('node', true); + let scriptPath = path.join(__dirname, 'scripts', 'wait-for-file.js'); + let shell: trm.ToolRunner; + if (os.platform() == 'win32') { + shell = tl.tool(tl.which('cmd.exe', true)) + .arg('/D') // Disable execution of AutoRun commands from registry. + .arg('/E:ON') // Enable command extensions. Note, command extensions are enabled by default, unless disabled via registry. + .arg('/V:OFF') // Disable delayed environment expansion. Note, delayed environment expansion is disabled by default, unless enabled via registry. + .arg('/S') // Will cause first and last quote after /C to be stripped. + .arg('/C') + .arg(`"start "" /B "${nodePath}" "${scriptPath}" "file=${semaphorePath}"" & echo hi 1>&2`); + } + else { + shell = tl.tool(tl.which('bash', true)) + .arg('-c') + .arg(`node '${scriptPath}' 'file=${semaphorePath}' & echo hi 1>&2`); + } + + let toolRunnerDebug = []; + shell.on('debug', function (data) { + toolRunnerDebug.push(data); + }); + + process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY'] = "500"; // 0.5 seconds + + let options = { + cwd: __dirname, + env: process.env, + silent: false, + failOnStdErr: true, + ignoreReturnCode: false, + outStream: process.stdout, + errStream: process.stdout, + windowsVerbatimArguments: true + }; + + shell.exec(options) + .then(function () { + done(new Error('should not have been successful')); + done(); + }) + .catch(function (err) { + assert(toolRunnerDebug.filter((x) => x.indexOf('STDIO streams did not close') >= 0).length == 1, 'Did not find expected debug message'); + assert(err.message.indexOf('failed because one or more lines were written to the STDERR stream') >= 0); + done(); + }) + .catch(function (err) { done(err); + }) + .finally(function () { + fs.unlinkSync(semaphorePath); + delete process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; }); }) - it('Exec pipe output to another tool, succeeds if both tools succeed', function(done) { - this.timeout(1000); + it('Exec pipe output to another tool, succeeds if both tools succeed', function (done) { + this.timeout(30000); var _testExecOptions = { cwd: __dirname, @@ -409,24 +621,28 @@ describe('Toolrunner Tests', function () { }; if (os.platform() === 'win32') { - var find = tl.tool(tl.which('FIND', true)) - .arg('System Idle Process'); - - var tasklist = tl.tool(tl.which('tasklist', true)); - tasklist.pipeExecOutputToTool(find); + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); var output = ''; - tasklist.on('stdout', (data) => { + outputExe.on('stdout', (data) => { output += data.toString(); }); - tasklist.exec(_testExecOptions) + outputExe.exec(_testExecOptions) .then(function (code) { assert.equal(code, 0, 'return code of exec should be 0'); - assert(output && output.length > 0 && output.indexOf('System Idle Process') >= 0, 'should have emitted stdout ' + output); + assert(output && output.length > 0 && output.indexOf('line 2') >= 0, 'should have emitted stdout ' + output); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -449,13 +665,13 @@ describe('Toolrunner Tests', function () { assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) - it('Exec pipe output to another tool, fails if first tool fails', function(done) { - this.timeout(1000); + it('Exec pipe output to another tool, fails if first tool fails', function (done) { + this.timeout(20000); var _testExecOptions = { cwd: __dirname, @@ -468,35 +684,37 @@ describe('Toolrunner Tests', function () { }; if (os.platform() === 'win32') { - var find = tl.tool(tl.which('FIND', true)); - find.arg('System Idle Process'); - - var tasklist = tl.tool(tl.which('tasklist', true)); - tasklist.arg('bad'); - tasklist.pipeExecOutputToTool(find); + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('1') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); var output = ''; - tasklist.on('stdout', (data) => { + outputExe.on('stdout', (data) => { output += data.toString(); }); var succeeded = false; - tasklist.exec(_testExecOptions) + outputExe.exec(_testExecOptions) .then(function () { succeeded = true; - assert.fail('tasklist bad | findstr cmd was a bad command and it did not fail'); + assert.fail('print-output.exe | findstr "line 2" was a bad command and it did not fail'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { - //assert(output && output.length > 0 && output.indexOf('ERROR: Invalid argument/option') >= 0, 'error output from tasklist command does not match expected. actual: ' + output); - assert(err && err.message && err.message.indexOf('tasklist.exe') >=0, 'error from tasklist is not reported'); + assert(err && err.message && err.message.indexOf('print-output.exe') >= 0, 'error from print-output.exe is not reported'); done(); } }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -519,23 +737,190 @@ describe('Toolrunner Tests', function () { succeeded = true; assert.fail('ps bad | grep ssh was a bad command and it did not fail'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { //assert(output && output.length > 0 && output.indexOf('ps: illegal option') >= 0, `error output "ps: illegal option" is expected. actual "${output}"`); - assert(err && err.message && err.message.indexOf('/bin/ps') >=0, 'error from ps is not reported'); + assert(err && err.message && err.message.indexOf('/bin/ps') >= 0, 'error from ps is not reported'); done(); } }) - .fail(function (err) { + .catch(function (err) { + done(err); + }) + } + }) + it('Exec pipe output to another tool, fails if second tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('1') // exit code + .arg('line 2') // match value + .arg('some error message'); // error + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + outputExe.on('stderr', (data) => { + errOut += data.toString(); + }); + + var succeeded = false; + outputExe.exec(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('print-output.exe 0 "line 1" "line 2" "line 3" | match-input.exe 1 "line 2" "some error message" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('some error message') >= 0, 'error output from match-input.exe is expected'); + assert(err && err.message && err.message.indexOf('match-input.exe') >= 0, 'error from find does not match expeced. actual: ' + err.message); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('--?'); + + var node = tl.tool(tl.which('node', true)) + .arg('-e') + .arg('console.log("line1"); setTimeout(function () { console.log("line2"); }, 200);'); // allow long enough to hook up stdout to stdin + node.pipeExecOutputToTool(grep); + + var output = ''; + node.on('stdout', (data) => { + output += data.toString(); + }); + + var errOut = ''; + node.on('stderr', (data) => { + errOut += data.toString(); + }) + + var succeeded = false; + node.exec(_testExecOptions) + .then(function (code) { + succeeded = true; + assert.fail('node [...] | grep --? was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(errOut && errOut.length > 0 && errOut.indexOf('grep: unrecognized option') >= 0, 'error output from ps command is expected'); + // grep is /bin/grep on Linux and /usr/bin/grep on OSX + assert(err && err.message && err.message.match(/\/[usr\/]?bin\/grep/), 'error from grep is not reported. actual: ' + err.message); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + }) + it('Exec pipe output to file and another tool, succeeds if both tools succeed', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + const testFile = path.join(testutil.getTestTemp(), 'BothToolsSucceed.log'); + + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + outputExe.exec(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('line 2') >= 0, 'should have emitted stdout ' + output); + assert(fs.existsSync(testFile), 'Log of first tool output is created when both tools succeed'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('line 2') >= 0, 'Log file of first tool should have stdout from first tool: ' + fileContents); + done(); + }) + .catch(function (err) { done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('node'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep, testFile); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + ps.exec(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); + assert(fs.existsSync(testFile), 'Log of first tool output is created when both tools succeed'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('PID') >= 0, 'Log of first tool should have stdout from first tool: ' + fileContents); + done(); }) + .catch(function (err) { + done(err); + }); } }) - it('Exec pipe output to another tool, fails if second tool fails', function(done) { - this.timeout(1000); + it('Exec pipe output to file and another tool, fails if first tool fails', function (done) { + this.timeout(20000); var _testExecOptions = { cwd: __dirname, @@ -547,40 +932,140 @@ describe('Toolrunner Tests', function () { errStream: testutil.getNullStream() }; + const testFile = path.join(testutil.getTestTemp(), 'FirstToolFails.log'); + if (os.platform() === 'win32') { - var find = tl.tool(tl.which('FIND.exe', true)); - find.arg('bad'); + var matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('line 2'); // match value + var outputExe = tl.tool(compileOutputExe()) + .arg('1') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); + + var output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + outputExe.exec(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('print-output.exe | findstr "line 2" was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err && err.message && err.message.indexOf('print-output.exe') >= 0, 'error from print-output.exe is not reported'); + assert(fs.existsSync(testFile), 'Log of first tool output is created when first tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('line 3') >= 0, 'Error from first tool should be written to log file: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }); + } + else { + var grep = tl.tool(tl.which('grep', true)); + grep.arg('ssh'); + + var ps = tl.tool(tl.which('ps', true)); + ps.arg('bad'); + ps.pipeExecOutputToTool(grep, testFile); + + var output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + var succeeded = false; + ps.exec(_testExecOptions) + .then(function () { + succeeded = true; + assert.fail('ps bad | grep ssh was a bad command and it did not fail'); + }) + .catch(function (err) { + if (succeeded) { + done(err); + } + else { + assert(err && err.message && err.message.indexOf('/bin/ps') >= 0, 'error from ps is not reported'); + assert(fs.existsSync(testFile), 'Log of first tool output is created when first tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('illegal option') >= 0 || fileContents.indexOf('unsupported option') >= 0, + 'error from first tool should be written to log file: ' + fileContents); + done(); + } + }) + .catch(function (err) { + done(err); + }) + } + }) + it('Exec pipe output to file and another tool, fails if second tool fails', function (done) { + this.timeout(20000); + + var _testExecOptions = { + cwd: __dirname, + env: {}, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + const testFile = path.join(testutil.getTestTemp(), 'SecondToolFails.log'); - var tasklist = tl.tool(tl.which('tasklist', true)); - tasklist.pipeExecOutputToTool(find); + if (os.platform() === 'win32') { + var matchExe = tl.tool(compileMatchExe()) + .arg('1') // exit code + .arg('line 2') // match value + .arg('some error message'); // error + var outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('line 2') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe, testFile); var output = ''; - tasklist.on('stdout', (data) => { + outputExe.on('stdout', (data) => { output += data.toString(); }); var errOut = ''; - tasklist.on('stderr', (data) => { + outputExe.on('stderr', (data) => { errOut += data.toString(); }); var succeeded = false; - tasklist.exec(_testExecOptions) + outputExe.exec(_testExecOptions) .then(function (code) { succeeded = true; - assert.fail('tasklist bad | find "cmd" was a bad command and it did not fail'); + assert.fail('print-output.exe 0 "line 1" "line 2" "line 3" | match-input.exe 1 "line 2" "some error message" was a bad command and it did not fail'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } else { - assert(errOut && errOut.length > 0 && errOut.indexOf('FIND: Parameter format not correct') >= 0, 'error output from FIND command is expected'); - assert(err && err.message && err.message.indexOf('FIND.exe') >=0, 'error from find does not match expeced. actual: ' + err.message); + assert(errOut && errOut.length > 0 && errOut.indexOf('some error message') >= 0, 'error output from match-input.exe is expected'); + assert(err && err.message && err.message.indexOf('match-input.exe') >= 0, 'error from find does not match expeced. actual: ' + err.message); + assert(fs.existsSync(testFile), 'Log of first tool output is created when second tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('some error message') < 0, 'error from second tool should not be in the log for first tool: ' + fileContents); done(); } }) - .fail(function (err) { + .catch(function (err) { done(err); }); } @@ -590,7 +1075,7 @@ describe('Toolrunner Tests', function () { var ps = tl.tool(tl.which('ps', true)); ps.arg('ax'); - ps.pipeExecOutputToTool(grep); + ps.pipeExecOutputToTool(grep, testFile); var output = ''; ps.on('stdout', (data) => { @@ -608,7 +1093,7 @@ describe('Toolrunner Tests', function () { succeeded = true; assert.fail('ps ax | grep --? was a bad command and it did not fail'); }) - .fail(function (err) { + .catch(function (err) { if (succeeded) { done(err); } @@ -616,16 +1101,19 @@ describe('Toolrunner Tests', function () { assert(errOut && errOut.length > 0 && errOut.indexOf('grep: unrecognized option') >= 0, 'error output from ps command is expected'); // grep is /bin/grep on Linux and /usr/bin/grep on OSX assert(err && err.message && err.message.match(/\/[usr\/]?bin\/grep/), 'error from grep is not reported. actual: ' + err.message); + assert(fs.existsSync(testFile), 'Log of first tool output is created when second tool fails'); + const fileContents = fs.readFileSync(testFile); + assert(fileContents.indexOf('unrecognized option') < 0, 'error from second tool should not be in the first tool log file: ' + fileContents); done(); } }) - .fail(function (err) { + .catch(function (err) { done(err); }); } }) it('handles single args', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.arg('one'); @@ -635,7 +1123,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles arg chaining', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.arg('one').arg('two').argIf(true, 'three').line('four five'); @@ -643,9 +1131,9 @@ describe('Toolrunner Tests', function () { assert.equal((node as any).args.length, 5, 'should have 5 args'); assert.equal((node as any).args.toString(), 'one,two,three,four,five', 'should be one,two,three,four,five'); done(); - }) + }) it('handles padded spaces', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.arg(' one '); @@ -655,7 +1143,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles basic arg string with spaces', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('one two'); @@ -665,7 +1153,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles arg string with extra spaces', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('one two'); @@ -675,7 +1163,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles arg string with backslash', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('one two\\arg'); @@ -684,8 +1172,17 @@ describe('Toolrunner Tests', function () { assert.equal((node as any).args.toString(), 'one,two\\arg,three', 'should be one,two,three'); done(); }) + it('handles multiple escaped backslashes', function (done) { + this.timeout(10000); + + var node = tl.tool(tl.which('node', true)); + node.line('one "\\\\two\\arg"'); + assert.equal((node as any).args.length, 2, 'should have 2 args'); + assert.equal((node as any).args.toString(), 'one,\\\\two\\arg', 'should be one,\\\\two\\arg'); + done(); + }) it('handles equals and switches', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('foo=bar -x'); @@ -695,7 +1192,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles double quotes', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('foo="bar baz" -x'); @@ -705,7 +1202,7 @@ describe('Toolrunner Tests', function () { done(); }) it('handles quote in double quotes', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.line('foo="bar \\" baz" -x'); @@ -714,8 +1211,18 @@ describe('Toolrunner Tests', function () { assert.equal((node as any).args.toString(), 'foo=bar " baz,-x,-y', 'should be foo=bar " baz,-x,-y'); done(); }) + it('handles empty string', function (done) { + this.timeout(10000); + + var node = tl.tool(tl.which('node', true)); + node.line('"" -x'); + node.arg('-y'); + assert.equal((node as any).args.length, 3, 'should have 3 args'); + assert.equal((node as any).args.toString(), ',-x,-y', 'should be ,-x,-y'); + done(); + }) it('handles literal path', function (done) { - this.timeout(1000); + this.timeout(10000); var node = tl.tool(tl.which('node', true)); node.arg('--path').arg('/bin/working folder1'); @@ -723,10 +1230,18 @@ describe('Toolrunner Tests', function () { assert.equal((node as any).args.toString(), '--path,/bin/working folder1', 'should be --path /bin/working folder1'); done(); }) - + it('handles escaped quotes', function (done) { + this.timeout(10000); + var node = tl.tool(tl.which('node', true)); + node.line('-TEST="escaped\\\"quotes" -x'); + node.arg('-y'); + assert.equal((node as any).args.length, 3, 'should have 3 args'); + assert.equal((node as any).args.toString(), '-TEST=escaped"quotes,-x,-y', 'should be -TEST=escaped"quotes,-x,-y'); + done(); + }) if (process.platform != 'win32') { it('exec prints [command] (OSX/Linux)', function (done) { - this.timeout(1000); + this.timeout(10000); let bash = tl.tool(tl.which('bash')) .arg('--norc') .arg('--noprofile') @@ -751,49 +1266,19 @@ describe('Toolrunner Tests', function () { 'hello world'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); }); } else { // process.platform == 'win32' - // function to compile a .NET program that prints the command line args. - // the helper program is used to validate that command line args are passed correctly. - let compileArgsExe = (fileNameOnly: string): string => { - let directory = path.join(testutil.getTestTemp(), 'print-args-exe'); - tl.mkdirP(directory); - let exePath = path.join(directory, fileNameOnly); - - // short-circuit if already compiled - try { - fs.statSync(exePath); - return exePath; - } - catch (err) { - if (err.code != 'ENOENT') { - throw err; - } - } - - let sourceFile = path.join(__dirname, 'scripts', 'print-args-exe.cs'); - let cscPath = 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe'; - fs.statSync(cscPath); - child_process.execFileSync( - cscPath, - [ - '/target:exe', - `/out:${exePath}`, - sourceFile - ]); - return exePath; - } // -------------------------- // exec arg tests (Windows) // -------------------------- it('exec .exe AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // the echo built-in is a good tool for this test let exePath = process.env.ComSpec; @@ -821,13 +1306,13 @@ describe('Toolrunner Tests', function () { 'helloworld hello:"world again"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); }); it('exec .exe AND arg quoting (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // the echo built-in is a good tool for this test let exePath = process.env.ComSpec; @@ -864,13 +1349,13 @@ describe('Toolrunner Tests', function () { + ' hello,world'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); }); it('exec .exe with space AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(20000); // this test validates the quoting that tool runner adds around the tool path // when using the windowsVerbatimArguments option. otherwise the target process @@ -894,17 +1379,21 @@ describe('Toolrunner Tests', function () { // validate stdout assert.equal( output.trim(), - "args[0]: 'myarg1'\r\n" - + "args[1]: 'myarg2'"); + "args[0]: 'args'\r\n" + + "args[1]: 'exe'\r\n" + + "args[2]: 'with'\r\n" + + "args[3]: 'spaces.exe'\r\n" + + "args[4]: 'myarg1'\r\n" + + "args[5]: 'myarg2'"); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); }); it('exec .cmd with space AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates the quoting that tool runner adds around the script path. // otherwise cmd.exe will not be able to resolve the path to the script. @@ -933,13 +1422,13 @@ describe('Toolrunner Tests', function () { + 'args[2]: "arg3"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }); }); it('exec .cmd with space AND arg with space (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates the command is wrapped in quotes (i.e. cmd.exe /S /C ""). // otherwise the leading quote (around the script with space path) would be stripped @@ -968,13 +1457,13 @@ describe('Toolrunner Tests', function () { + 'args[1]: "my arg 2"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }) }); it('exec .cmd AND arg quoting (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates .cmd quoting rules are applied, not the default libuv rules let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); @@ -1070,7 +1559,7 @@ describe('Toolrunner Tests', function () { + 'args[23]: "hello world\\\\"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }) }); @@ -1080,7 +1569,7 @@ describe('Toolrunner Tests', function () { // ------------------------------- it('exec sync .exe AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // the echo built-in is a good tool for this test let exePath = process.env.ComSpec; @@ -1105,7 +1594,7 @@ describe('Toolrunner Tests', function () { }); it('exec sync .exe AND arg quoting (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // the echo built-in is a good tool for this test let exePath = process.env.ComSpec; @@ -1139,7 +1628,7 @@ describe('Toolrunner Tests', function () { }); it('exec sync .exe with space AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(20000); // this test validates the quoting that tool runner adds around the tool path // when using the windowsVerbatimArguments option. otherwise the target process @@ -1157,13 +1646,17 @@ describe('Toolrunner Tests', function () { // validate stdout assert.equal( result.stdout.trim(), - "args[0]: 'myarg1'\r\n" - + "args[1]: 'myarg2'"); + "args[0]: 'args'\r\n" + + "args[1]: 'exe'\r\n" + + "args[2]: 'with'\r\n" + + "args[3]: 'spaces.exe'\r\n" + + "args[4]: 'myarg1'\r\n" + + "args[5]: 'myarg2'") done(); }); it('exec sync .cmd with space AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates the quoting that tool runner adds around the script path. // otherwise cmd.exe will not be able to resolve the path to the script. @@ -1188,7 +1681,7 @@ describe('Toolrunner Tests', function () { }); it('exec sync .cmd with space AND arg with space (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates the command is wrapped in quotes (i.e. cmd.exe /S /C ""). // otherwise the leading quote (around the script with space path) would be stripped @@ -1213,7 +1706,7 @@ describe('Toolrunner Tests', function () { }); it('exec sync .cmd AND arg quoting (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // this test validates .cmd quoting rules are applied, not the default libuv rules let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); @@ -1309,7 +1802,7 @@ describe('Toolrunner Tests', function () { // ------------------------------- it('exec pipe .cmd to .exe AND arg quoting (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); let cmdRunner = tl.tool(cmdPath) @@ -1340,13 +1833,13 @@ describe('Toolrunner Tests', function () { 'args[0]: "hello world"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }) }); it('exec pipe .cmd to .exe AND verbatim args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); let cmdPath = path.join(__dirname, 'scripts', 'print args cmd with spaces.cmd'); let cmdRunner = tl.tool(cmdPath) @@ -1377,7 +1870,7 @@ describe('Toolrunner Tests', function () { 'args[1]: "world"'); done(); }) - .fail(function (err) { + .catch(function (err) { done(err); }) }); @@ -1387,7 +1880,7 @@ describe('Toolrunner Tests', function () { // -------------------------------------- it('_windowsQuoteCmdArg quotes .exe args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // create a .exe file let testPath = path.join(testutil.getTestTemp(), 'which-finds-file-name'); @@ -1477,7 +1970,7 @@ describe('Toolrunner Tests', function () { }); it('_windowsQuoteCmdArg quotes .cmd args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // create a .cmd file let testPath = path.join(testutil.getTestTemp(), 'which-finds-file-name'); @@ -1536,7 +2029,7 @@ describe('Toolrunner Tests', function () { }); it('_windowsQuoteCmdArg quotes .bat args (Windows)', function (done) { - this.timeout(1000); + this.timeout(10000); // create a .bat file let testPath = path.join(testutil.getTestTemp(), 'which-finds-file-name'); @@ -1552,4 +2045,225 @@ describe('Toolrunner Tests', function () { done(); }); } + + // function to compile a .NET program on Windows. + let compileExe = (sourceFileName: string, targetFileName: string): string => { + let directory = path.join(testutil.getTestTemp(), sourceFileName); + tl.mkdirP(directory); + let exePath = path.join(directory, targetFileName); + + // short-circuit if already compiled + try { + fs.statSync(exePath); + return exePath; + } + catch (err) { + if (err.code != 'ENOENT') { + throw err; + } + } + + let sourceFile = path.join(__dirname, 'scripts', sourceFileName); + let cscPath = 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe'; + fs.statSync(cscPath); + child_process.execFileSync( + cscPath, + [ + '/target:exe', + `/out:${exePath}`, + sourceFile + ]); + return exePath; + } + + describe('Executing inside shell', function () { + + let tempPath: string = testutil.getTestTemp(); + let _testExecOptions: trm.IExecOptions; + + before (function () { + _testExecOptions = { + cwd: __dirname, + env: { + WIN_TEST: 'test value', + TESTPATH: tempPath, + TEST_NODE: 'node', + TEST: 'test value' + }, + silent: false, + failOnStdErr: false, + ignoreReturnCode: false, + shell: true, + outStream: testutil.getNullStream(), + errStream: testutil.getNullStream() + }; + + }) + + it('Exec sync inside shell', function (done) { + this.timeout(10000); + + if (os.platform() === 'win32') { + let exePath = compileArgsExe('print args with spaces.exe'); + let exeRunner = tl.tool(exePath); + exeRunner.line('%WIN_TEST%') + var ret = exeRunner.execSync(_testExecOptions); + assert.equal(ret.code, 0, 'return code of cmd should be 0'); + assert.equal(ret.stdout.trim(), 'args[0]: \'test value\'', 'Command should return \"args[0]: \'test value\'\"'); + } + else { + var ret = tl.execSync('stat', '$TESTPATH', _testExecOptions); + assert.equal(ret.code, 0, 'return code of stat should be 0'); + assert(ret.stdout.includes(tempPath), `Result should include \'${tempPath}\'`); + } + + assert(ret.stdout && ret.stdout.length > 0, 'should have emitted stdout'); + done(); + }); + it('Exec inside shell', function (done) { + this.timeout(10000); + + let output: string = ''; + if (os.platform() === 'win32') { + let exePath = compileArgsExe('print args with spaces.exe'); + let exeRunner = tl.tool(exePath); + exeRunner.line('%WIN_TEST%'); + exeRunner.on('stdout', (data) => { + output = data.toString(); + }); + exeRunner.exec(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + assert.equal(output.trim(), 'args[0]: \'test value\'', 'Command should return \"args[0]: \'test value\'\"'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + let statRunner = tl.tool('stat'); + statRunner.line('$TESTPATH'); + statRunner.on('stdout', (data) => { + output = data.toString(); + }); + statRunner.exec(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of stat should be 0'); + assert(output.includes(tempPath), `Result should include \'${tempPath}\'`); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + it('Exec pipe output to another tool inside shell, succeeds if both tools succeed', function (done) { + this.timeout(30000); + + if (os.platform() === 'win32') { + const matchExe = tl.tool(compileMatchExe()) + .arg('0') // exit code + .arg('test value'); // match value + const outputExe = tl.tool(compileOutputExe()) + .arg('0') // exit code + .arg('line 1') + .arg('"%WIN_TEST%"') + .arg('line 3'); + outputExe.pipeExecOutputToTool(matchExe); + + let output = ''; + outputExe.on('stdout', (data) => { + output += data.toString(); + }); + + outputExe.exec(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('test value') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + const grep = tl.tool(tl.which('grep', true)); + grep.arg('$TEST_NODE'); + + const ps = tl.tool(tl.which('ps', true)); + ps.arg('ax'); + ps.pipeExecOutputToTool(grep); + + let output = ''; + ps.on('stdout', (data) => { + output += data.toString(); + }); + + ps.exec(_testExecOptions) + .then(function (code) { + assert.equal(code, 0, 'return code of exec should be 0'); + assert(output && output.length > 0 && output.indexOf('node') >= 0, 'should have emitted stdout ' + output); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + it('Should handle arguments with quotes properly', function (done) { + this.timeout(10000); + + let output: string = ''; + if (os.platform() === 'win32') { + let exePath = compileArgsExe('print args with spaces.exe'); + let exeRunner = tl.tool(exePath); + exeRunner.line('-TEST1="space test" "-TEST2=%WIN_TEST%" \'-TEST3=value\''); + exeRunner.on('stdout', (data) => { + output += data.toString(); + }); + exeRunner.exec(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of cmd should be 0'); + assert.equal(output.trim(), 'args[0]: \'-TEST1=space test\'\r\n' + + 'args[1]: \'-TEST2=test value\'\r\n' + + 'args[2]: \'\'-TEST3=value\'\''); + done(); + }) + .catch(function (err) { + done(err); + }); + } + else { + let statRunner = tl.tool('echo'); + statRunner.line('-TEST1="$TEST;test" "-TEST2=/one/two/three" \'-TEST3=out:$TEST\''); + statRunner.on('stdout', (data) => { + output = data.toString(); + }); + statRunner.exec(_testExecOptions).then(function (code) { + assert.equal(code, 0, 'return code of stat should be 0'); + assert.equal(output, '-TEST1=test value;test -TEST2=/one/two/three -TEST3=out:$TEST\n'); + done(); + }) + .catch(function (err) { + done(err); + }); + } + }); + }) + + // function to compile a .NET program that prints the command line args. + // the helper program is used to validate that command line args are passed correctly. + let compileArgsExe = (targetFileName: string): string => { + return compileExe('print-args-exe.cs', targetFileName); + } + + // function to compile a .NET program that matches input lines. + // the helper program is used on Windows to validate piping output between tools. + let compileMatchExe = (): string => { + return compileExe('match-input-exe.cs', 'match-input.exe'); + } + + // function to compile a .NET program that prints lines. + // the helper program is used on Windows to validate piping output between tools. + let compileOutputExe = (): string => { + return compileExe('print-output-exe.cs', 'print-output.exe'); + } }); diff --git a/node/test/tsconfig.json b/node/test/tsconfig.json index 5bbd878fc..1a820766b 100644 --- a/node/test/tsconfig.json +++ b/node/test/tsconfig.json @@ -4,7 +4,7 @@ "module": "commonjs", "declaration": false, "outDir": "../_test", - "moduleResolution": "node" + "moduleResolution": "node" }, "files": [ "dirtests.ts", @@ -16,11 +16,15 @@ "legacyfindfilestests.ts", "vaulttests.ts", "toolrunnertests.ts", - "cctests", - "loctests", - "matchtests", - "filtertests", - "findmatchtests", - "mocktests" + "toolrunnerTestsWithExecAsync.ts", + "cctests.ts", + "loctests.ts", + "matchtests.ts", + "filtertests.ts", + "findmatchtests.ts", + "mocktests.ts", + "retrytests.ts", + "isuncpathtests.ts", + "gethttpproxytests.ts" ] -} \ No newline at end of file +} diff --git a/node/test/vaulttests.ts b/node/test/vaulttests.ts index 8991f54c8..4d7dcc2d6 100644 --- a/node/test/vaulttests.ts +++ b/node/test/vaulttests.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -/// -/// - import assert = require('assert'); import * as vm from '../_build/vault'; import * as trm from '../_build/toolrunner'; diff --git a/node/toolrunner.ts b/node/toolrunner.ts index 14c8f60b7..9705e0f45 100644 --- a/node/toolrunner.ts +++ b/node/toolrunner.ts @@ -1,44 +1,47 @@ - - import Q = require('q'); import os = require('os'); import events = require('events'); import child = require('child_process'); import stream = require('stream'); import im = require('./internal'); -import tcm = require('./taskcommand'); +import fs = require('fs'); /** * Interface for exec options */ export interface IExecOptions extends IExecSyncOptions { /** optional. whether to fail if output to stderr. defaults to false */ - failOnStdErr: boolean; + failOnStdErr?: boolean; /** optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller */ - ignoreReturnCode: boolean; -}; + ignoreReturnCode?: boolean; +} /** * Interface for execSync options */ export interface IExecSyncOptions { /** optional working directory. defaults to current */ - cwd: string; + cwd?: string; /** optional envvar dictionary. defaults to current process's env */ - env: { [key: string]: string }; + env?: { [key: string]: string | undefined }; - /** optional. defaults to fales */ - silent: boolean; + /** optional. defaults to false */ + silent?: boolean; - outStream: stream.Writable; + /** Optional. Default is process.stdout. */ + outStream?: NodeJS.WritableStream; - errStream: stream.Writable; + /** Optional. Default is process.stderr. */ + errStream?: NodeJS.WritableStream; - /** optional. foo.whether to skip quoting/escaping arguments if needed. defaults to false. */ - windowsVerbatimArguments: boolean; -}; + /** optional. Whether to skip quoting/escaping arguments if needed. defaults to false. */ + windowsVerbatimArguments?: boolean; + + /** optional. Run command inside of the shell. Defaults to false. */ + shell?: boolean; +} /** * Interface for exec results returned from synchronous exec functions @@ -58,7 +61,7 @@ export interface IExecSyncResult { } export class ToolRunner extends events.EventEmitter { - constructor(toolPath) { + constructor(toolPath: string) { super(); if (!toolPath) { @@ -70,34 +73,53 @@ export class ToolRunner extends events.EventEmitter { this._debug('toolRunner toolPath: ' + toolPath); } + private readonly cmdSpecialChars: string[] = [' ', '\t', '&', '(', ')', '[', ']', '{', '}', '^', '=', ';', '!', '\'', '+', ',', '`', '~', '|', '<', '>', '"']; private toolPath: string; private args: string[]; - private pipeOutputToTool: ToolRunner; + private pipeOutputToTool: ToolRunner | undefined; + private pipeOutputToFile: string | undefined; + private childProcess: child.ChildProcess | undefined; - private _debug(message) { + private _debug(message: string) { this.emit('debug', message); } private _argStringToArray(argString: string): string[] { - var args = []; + var args: string[] = []; var inQuotes = false; - var escaped =false; + var escaped = false; + var lastCharWasSpace = true; var arg = ''; - var append = function(c) { + var append = function (c: string) { // we only escape double quotes. - if (escaped && c !== '"') { - arg += '\\'; + if (escaped) { + if (c !== '"') { + arg += '\\'; + } else { + arg.slice(0, -1); + } } - arg += c; escaped = false; } - for (var i=0; i < argString.length; i++) { + for (var i = 0; i < argString.length; i++) { var c = argString.charAt(i); + if (c === ' ' && !inQuotes) { + if (!lastCharWasSpace) { + args.push(arg); + arg = ''; + } + lastCharWasSpace = true; + continue; + } + else { + lastCharWasSpace = false; + } + if (c === '"') { if (!escaped) { inQuotes = !inQuotes; @@ -107,24 +129,22 @@ export class ToolRunner extends events.EventEmitter { } continue; } - - if (c === "\\" && inQuotes) { - escaped = true; + + if (c === "\\" && escaped) { + append(c); continue; } - if (c === ' ' && !inQuotes) { - if (arg.length > 0) { - args.push(arg); - arg = ''; - } + if (c === "\\" && inQuotes) { + escaped = true; continue; } append(c); + lastCharWasSpace = false; } - if (arg.length > 0) { + if (!lastCharWasSpace) { args.push(arg.trim()); } @@ -135,38 +155,36 @@ export class ToolRunner extends events.EventEmitter { let toolPath: string = this._getSpawnFileName(); let args: string[] = this._getSpawnArgs(options); let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool + let commandParts: string[] = []; if (process.platform == 'win32') { // Windows + cmd file if (this._isCmdFile()) { - cmd += toolPath; - args.forEach((a: string): void => { - cmd += ` ${a}`; - }); + commandParts.push(toolPath); + commandParts = commandParts.concat(args); } // Windows + verbatim else if (options.windowsVerbatimArguments) { - cmd += `"${toolPath}"`; - args.forEach((a: string): void => { - cmd += ` ${a}`; - }); + commandParts.push(`"${toolPath}"`); + commandParts = commandParts.concat(args); + } + else if (options.shell) { + commandParts.push(this._windowsQuoteCmdArg(toolPath)); + commandParts = commandParts.concat(args); } // Windows (regular) else { - cmd += this._windowsQuoteCmdArg(toolPath); - args.forEach((a: string): void => { - cmd += ` ${this._windowsQuoteCmdArg(a)}`; - }); + commandParts.push(this._windowsQuoteCmdArg(toolPath)); + commandParts = commandParts.concat(args.map(arg =>this._windowsQuoteCmdArg(arg))); } } else { // OSX/Linux - this can likely be improved with some form of quoting. // creating processes on Unix is fundamentally different than Windows. // on Unix, execvp() takes an arg array. - cmd += toolPath; - args.forEach((a: string): void => { - cmd += ` ${a}`; - }); + commandParts.push(toolPath); + commandParts = commandParts.concat(args); } + cmd += commandParts.join(' '); // append second tool if (this.pipeOutputToTool) { @@ -176,13 +194,73 @@ export class ToolRunner extends events.EventEmitter { return cmd; } - private _getSpawnFileName(): string { - if (process.platform == 'win32') { - if (this._isCmdFile()) { - return process.env['COMSPEC'] || 'cmd.exe'; - } + private _processLineBuffer(data: Buffer, strBuffer: string, onLine: (line: string) => void): void { + try { + var s = strBuffer + data.toString(); + var n = s.indexOf(os.EOL); + + while (n > -1) { + var line = s.substring(0, n); + onLine(line); + + // the rest of the string ... + s = s.substring(n + os.EOL.length); + n = s.indexOf(os.EOL); + } + + strBuffer = s; + } + catch (err) { + // streaming lines to console is best effort. Don't fail a build. + this._debug('error processing line'); + } + + } + + /** + * Wraps an arg string with specified char if it's not already wrapped + * @returns {string} Arg wrapped with specified char + * @param {string} arg Input argument string + * @param {string} wrapChar A char input string should be wrapped with + */ + private _wrapArg(arg: string, wrapChar: string): string { + if (!this._isWrapped(arg, wrapChar)) { + return `${wrapChar}${arg}${wrapChar}`; + } + return arg; + } + + /** + * Unwraps an arg string wrapped with specified char + * @param arg Arg wrapped with specified char + * @param wrapChar A char to be removed + */ + private _unwrapArg(arg: string, wrapChar: string): string { + if (this._isWrapped(arg, wrapChar)) { + const pattern = new RegExp(`(^\\\\?${wrapChar})|(\\\\?${wrapChar}$)`, 'g'); + return arg.trim().replace(pattern, ''); } + return arg; + } + /** + * Determine if arg string is wrapped with specified char + * @param arg Input arg string + */ + private _isWrapped(arg: string, wrapChar: string): boolean { + const pattern: RegExp = new RegExp(`^\\\\?${wrapChar}.+\\\\?${wrapChar}$`); + return pattern.test(arg.trim()) + } + + private _getSpawnFileName(options?: IExecOptions): string { + if (process.platform == 'win32') { + if (this._isCmdFile()) { + return process.env['COMSPEC'] || 'cmd.exe'; + } + } + if (options && options.shell) { + return this._wrapArg(this.toolPath, '"'); + } return this.toolPath; } @@ -190,13 +268,13 @@ export class ToolRunner extends events.EventEmitter { if (process.platform == 'win32') { if (this._isCmdFile()) { let argline: string = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; - for (let i = 0 ; i < this.args.length ; i++) { + for (let i = 0; i < this.args.length; i++) { argline += ' '; argline += options.windowsVerbatimArguments ? this.args[i] : this._windowsQuoteCmdArg(this.args[i]); } argline += '"'; - return [ argline ]; + return [argline]; } if (options.windowsVerbatimArguments) { @@ -243,17 +321,75 @@ export class ToolRunner extends events.EventEmitter { return Array.prototype.unshift.call(args, `"${arguments[0]}"`); // quote the file name }; return args; + } else if (options.shell) { + let args: string[] = []; + for (let arg of this.args) { + if (this._needQuotesForCmd(arg, '%')) { + args.push(this._wrapArg(arg, '"')); + } else { + args.push(arg); + } + } + return args; } + } else if (options.shell) { + return this.args.map(arg => { + if (this._isWrapped(arg, "'")) { + return arg; + } + // remove wrapping double quotes to avoid escaping + arg = this._unwrapArg(arg, '"'); + arg = this._escapeChar(arg, '"'); + return this._wrapArg(arg, '"'); + }); } return this.args; } + /** + * Escape specified character. + * @param arg String to escape char in + * @param charToEscape Char should be escaped + */ + private _escapeChar(arg: string, charToEscape: string): string { + const escChar: string = "\\"; + let output: string = ''; + let charIsEscaped: boolean = false; + for (const char of arg) { + if (char === charToEscape && !charIsEscaped) { + output += escChar + char; + } else { + output += char; + } + charIsEscaped = char === escChar && !charIsEscaped; + } + return output; + } + private _isCmdFile(): boolean { let upperToolPath: string = this.toolPath.toUpperCase(); return im._endsWith(upperToolPath, '.CMD') || im._endsWith(upperToolPath, '.BAT'); } + /** + * Determine whether the cmd arg needs to be quoted. Returns true if arg contains any of special chars array. + * @param arg The cmd command arg. + * @param additionalChars Additional chars which should be also checked. + */ + private _needQuotesForCmd(arg: string, additionalChars?: string[] | string): boolean { + let specialChars: string[] = this.cmdSpecialChars; + if (additionalChars) { + specialChars = this.cmdSpecialChars.concat(additionalChars); + } + for (let char of arg) { + if (specialChars.some(x => x === char)) { + return true; + } + } + return false; + } + private _windowsQuoteCmdArg(arg: string): string { // for .exe, apply the normal quoting rules that libuv applies if (!this._isCmdFile()) { @@ -273,14 +409,7 @@ export class ToolRunner extends events.EventEmitter { } // determine whether the arg needs to be quoted - const cmdSpecialChars = [ ' ', '\t', '&', '(', ')', '[', ']', '{', '}', '^', '=', ';', '!', '\'', '+', ',', '`', '~', '|', '<', '>', '"' ]; - let needsQuotes = false; - for (let char of arg) { - if (cmdSpecialChars.some(x => x == char)) { - needsQuotes = true; - break; - } - } + const needsQuotes: boolean = this._needQuotesForCmd(arg); // short-circuit if quotes not needed if (!needsQuotes) { @@ -336,7 +465,7 @@ export class ToolRunner extends events.EventEmitter { // % can be escaped within a .cmd file. let reverse: string = '"'; let quote_hit = true; - for (let i = arg.length ; i > 0 ; i--) { // walk the string in reverse + for (let i = arg.length; i > 0; i--) { // walk the string in reverse reverse += arg[i - 1]; if (quote_hit && arg[i - 1] == '\\') { reverse += '\\'; // double the slash @@ -417,7 +546,7 @@ export class ToolRunner extends events.EventEmitter { // but it appears the comment is wrong, it should be "hello world\\" let reverse: string = '"'; let quote_hit = true; - for (let i = arg.length ; i > 0 ; i--) { // walk the string in reverse + for (let i = arg.length; i > 0; i--) { // walk the string in reverse reverse += arg[i - 1]; if (quote_hit && arg[i - 1] == '\\') { reverse += '\\'; @@ -435,7 +564,7 @@ export class ToolRunner extends events.EventEmitter { return reverse.split('').reverse().join(''); } - private _cloneExecOptions(options: IExecOptions): IExecOptions { + private _cloneExecOptions(options?: IExecOptions): IExecOptions { options = options || {}; let result: IExecOptions = { cwd: options.cwd || process.cwd(), @@ -443,47 +572,449 @@ export class ToolRunner extends events.EventEmitter { silent: options.silent || false, failOnStdErr: options.failOnStdErr || false, ignoreReturnCode: options.ignoreReturnCode || false, - windowsVerbatimArguments: options.windowsVerbatimArguments || false + windowsVerbatimArguments: options.windowsVerbatimArguments || false, + shell: options.shell || false }; - result.outStream = options.outStream || process.stdout; - result.errStream = options.errStream || process.stderr; + result.outStream = options.outStream || process.stdout; + result.errStream = options.errStream || process.stderr; return result; } - private _getSpawnOptions(options: IExecOptions): child.SpawnOptions { + private _getSpawnOptions(options?: IExecOptions): child.SpawnOptions { + options = options || {}; let result = {}; result.cwd = options.cwd; result.env = options.env; + result.shell = options.shell; result['windowsVerbatimArguments'] = options.windowsVerbatimArguments || this._isCmdFile(); return result; } private _getSpawnSyncOptions(options: IExecSyncOptions): child.SpawnSyncOptions { let result = {}; + result.maxBuffer = 1024 * 1024 * 1024; result.cwd = options.cwd; result.env = options.env; + result.shell = options.shell; result['windowsVerbatimArguments'] = options.windowsVerbatimArguments || this._isCmdFile(); return result; } + private execWithPipingAsync(pipeOutputToTool: ToolRunner, options?: IExecOptions): Promise { + this._debug('exec tool: ' + this.toolPath); + this._debug('arguments:'); + this.args.forEach((arg) => { + this._debug(' ' + arg); + }); + + let success = true; + const optionsNonNull = this._cloneExecOptions(options); + + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(this._getCommandString(optionsNonNull) + os.EOL); + } + + let cp: child.ChildProcess; + let toolPath: string = pipeOutputToTool.toolPath; + let toolPathFirst: string; + let successFirst = true; + let returnCodeFirst: number; + let fileStream: fs.WriteStream | null; + let waitingEvents: number = 0; // number of process or stream events we are waiting on to complete + let returnCode: number = 0; + let error: any; + + toolPathFirst = this.toolPath; + + // Following node documentation example from this link on how to pipe output of one process to another + // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options + + //start the child process for both tools + waitingEvents++; + var cpFirst = child.spawn( + this._getSpawnFileName(optionsNonNull), + this._getSpawnArgs(optionsNonNull), + this._getSpawnOptions(optionsNonNull)); + + waitingEvents ++; + cp = child.spawn( + pipeOutputToTool._getSpawnFileName(optionsNonNull), + pipeOutputToTool._getSpawnArgs(optionsNonNull), + pipeOutputToTool._getSpawnOptions(optionsNonNull)); + + fileStream = this.pipeOutputToFile ? fs.createWriteStream(this.pipeOutputToFile) : null; + + return new Promise((resolve, reject) => { + if (fileStream) { + waitingEvents++; + fileStream.on('finish', () => { + waitingEvents--; //file write is complete + fileStream = null; + if(waitingEvents == 0) { + if (error) { + reject(error); + } else { + resolve(returnCode); + } + } + }); + fileStream.on('error', (err: Error) => { + waitingEvents--; //there were errors writing to the file, write is done + this._debug(`Failed to pipe output of ${toolPathFirst} to file ${this.pipeOutputToFile}. Error = ${err}`); + fileStream = null; + if(waitingEvents == 0) { + if (error) { + reject(error); + } else { + resolve(returnCode); + } + } + }); + } + + //pipe stdout of first tool to stdin of second tool + cpFirst.stdout?.on('data', (data: Buffer) => { + try { + if (fileStream) { + fileStream.write(data); + } + cp.stdin?.write(data); + } catch (err) { + this._debug('Failed to pipe output of ' + toolPathFirst + ' to ' + toolPath); + this._debug(toolPath + ' might have exited due to errors prematurely. Verify the arguments passed are valid.'); + } + }); + cpFirst.stderr?.on('data', (data: Buffer) => { + if (fileStream) { + fileStream.write(data); + } + successFirst = !optionsNonNull.failOnStdErr; + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; + s.write(data); + } + }); + cpFirst.on('error', (err: Error) => { + waitingEvents--; //first process is complete with errors + if (fileStream) { + fileStream.end(); + } + cp.stdin?.end(); + error = new Error(toolPathFirst + ' failed. ' + err.message); + if(waitingEvents == 0) { + reject(error); + } + }); + cpFirst.on('close', (code: number, signal: any) => { + waitingEvents--; //first process is complete + if (code != 0 && !optionsNonNull.ignoreReturnCode) { + successFirst = false; + returnCodeFirst = code; + returnCode = returnCodeFirst; + } + this._debug('success of first tool:' + successFirst); + if (fileStream) { + fileStream.end(); + } + cp.stdin?.end(); + if(waitingEvents == 0) { + if (error) { + reject(error); + } else { + resolve(returnCode); + } + } + }); + + var stdbuffer: string = ''; + cp.stdout?.on('data', (data: Buffer) => { + this.emit('stdout', data); + + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(data); + } + + this._processLineBuffer(data, stdbuffer, (line: string) => { + this.emit('stdline', line); + }); + }); + + var errbuffer: string = ''; + cp.stderr?.on('data', (data: Buffer) => { + this.emit('stderr', data); + + success = !optionsNonNull.failOnStdErr; + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; + s.write(data); + } + + this._processLineBuffer(data, errbuffer, (line: string) => { + this.emit('errline', line); + }); + }); + + cp.on('error', (err: Error) => { + waitingEvents--; //process is done with errors + error = new Error(toolPath + ' failed. ' + err.message); + if(waitingEvents == 0) { + reject(error); + } + }); + + cp.on('close', (code: number, signal: any) => { + waitingEvents--; //process is complete + this._debug('rc:' + code); + returnCode = code; + + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + + if (code != 0 && !optionsNonNull.ignoreReturnCode) { + success = false; + } + + this._debug('success:' + success); + + if (!successFirst) { //in the case output is piped to another tool, check exit code of both tools + error = new Error(toolPathFirst + ' failed with return code: ' + returnCodeFirst); + } else if (!success) { + error = new Error(toolPath + ' failed with return code: ' + code); + } + + if(waitingEvents == 0) { + if (error) { + reject(error); + } else { + resolve(returnCode); + } + } + }); + }); + } + + private execWithPiping(pipeOutputToTool: ToolRunner, options?: IExecOptions): Q.Promise { + var defer = Q.defer(); + + this._debug('exec tool: ' + this.toolPath); + this._debug('arguments:'); + this.args.forEach((arg) => { + this._debug(' ' + arg); + }); + + let success = true; + const optionsNonNull = this._cloneExecOptions(options); + + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(this._getCommandString(optionsNonNull) + os.EOL); + } + + let cp: child.ChildProcess; + let toolPath: string = pipeOutputToTool.toolPath; + let toolPathFirst: string; + let successFirst = true; + let returnCodeFirst: number; + let fileStream: fs.WriteStream | null; + let waitingEvents: number = 0; // number of process or stream events we are waiting on to complete + let returnCode: number = 0; + let error: any; + + toolPathFirst = this.toolPath; + + // Following node documentation example from this link on how to pipe output of one process to another + // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options + + //start the child process for both tools + waitingEvents++; + var cpFirst = child.spawn( + this._getSpawnFileName(optionsNonNull), + this._getSpawnArgs(optionsNonNull), + this._getSpawnOptions(optionsNonNull)); + + waitingEvents ++; + cp = child.spawn( + pipeOutputToTool._getSpawnFileName(optionsNonNull), + pipeOutputToTool._getSpawnArgs(optionsNonNull), + pipeOutputToTool._getSpawnOptions(optionsNonNull)); + + fileStream = this.pipeOutputToFile ? fs.createWriteStream(this.pipeOutputToFile) : null; + if (fileStream) { + waitingEvents++; + fileStream.on('finish', () => { + waitingEvents--; //file write is complete + fileStream = null; + if(waitingEvents == 0) { + if (error) { + defer.reject(error); + } else { + defer.resolve(returnCode); + } + } + }); + fileStream.on('error', (err: Error) => { + waitingEvents--; //there were errors writing to the file, write is done + this._debug(`Failed to pipe output of ${toolPathFirst} to file ${this.pipeOutputToFile}. Error = ${err}`); + fileStream = null; + if(waitingEvents == 0) { + if (error) { + defer.reject(error); + } else { + defer.resolve(returnCode); + } + } + }); + } + + //pipe stdout of first tool to stdin of second tool + cpFirst.stdout?.on('data', (data: Buffer) => { + try { + if (fileStream) { + fileStream.write(data); + } + cp.stdin?.write(data); + } catch (err) { + this._debug('Failed to pipe output of ' + toolPathFirst + ' to ' + toolPath); + this._debug(toolPath + ' might have exited due to errors prematurely. Verify the arguments passed are valid.'); + } + }); + cpFirst.stderr?.on('data', (data: Buffer) => { + if (fileStream) { + fileStream.write(data); + } + successFirst = !optionsNonNull.failOnStdErr; + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; + s.write(data); + } + }); + cpFirst.on('error', (err: Error) => { + waitingEvents--; //first process is complete with errors + if (fileStream) { + fileStream.end(); + } + cp.stdin?.end(); + error = new Error(toolPathFirst + ' failed. ' + err.message); + if(waitingEvents == 0) { + defer.reject(error); + } + }); + cpFirst.on('close', (code: number, signal: any) => { + waitingEvents--; //first process is complete + if (code != 0 && !optionsNonNull.ignoreReturnCode) { + successFirst = false; + returnCodeFirst = code; + returnCode = returnCodeFirst; + } + this._debug('success of first tool:' + successFirst); + if (fileStream) { + fileStream.end(); + } + cp.stdin?.end(); + if(waitingEvents == 0) { + if (error) { + defer.reject(error); + } else { + defer.resolve(returnCode); + } + } + }); + + var stdbuffer: string = ''; + cp.stdout?.on('data', (data: Buffer) => { + this.emit('stdout', data); + + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(data); + } + + this._processLineBuffer(data, stdbuffer, (line: string) => { + this.emit('stdline', line); + }); + }); + + var errbuffer: string = ''; + cp.stderr?.on('data', (data: Buffer) => { + this.emit('stderr', data); + + success = !optionsNonNull.failOnStdErr; + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; + s.write(data); + } + + this._processLineBuffer(data, errbuffer, (line: string) => { + this.emit('errline', line); + }); + }); + + cp.on('error', (err: Error) => { + waitingEvents--; //process is done with errors + error = new Error(toolPath + ' failed. ' + err.message); + if(waitingEvents == 0) { + defer.reject(error); + } + }); + + cp.on('close', (code: number, signal: any) => { + waitingEvents--; //process is complete + this._debug('rc:' + code); + returnCode = code; + + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + + if (code != 0 && !optionsNonNull.ignoreReturnCode) { + success = false; + } + + this._debug('success:' + success); + + if (!successFirst) { //in the case output is piped to another tool, check exit code of both tools + error = new Error(toolPathFirst + ' failed with return code: ' + returnCodeFirst); + } else if (!success) { + error = new Error(toolPath + ' failed with return code: ' + code); + } + + if(waitingEvents == 0) { + if (error) { + defer.reject(error); + } else { + defer.resolve(returnCode); + } + } + }); + + return >defer.promise; + } + /** * Add argument - * Append an argument or an array of arguments + * Append an argument or an array of arguments * returns ToolRunner for chaining - * + * * @param val string cmdline or array of strings * @returns ToolRunner */ public arg(val: string | string[]): ToolRunner { if (!val) { - return; + return this; } if (val instanceof Array) { this._debug(this.toolPath + ' arg: ' + JSON.stringify(val)); this.args = this.args.concat(val); } - else if (typeof(val) === 'string') { + else if (typeof (val) === 'string') { this._debug(this.toolPath + ' arg: ' + val); this.args = this.args.concat(val.trim()); } @@ -495,20 +1026,20 @@ export class ToolRunner extends events.EventEmitter { * Parses an argument line into one or more arguments * e.g. .line('"arg one" two -z') is equivalent to .arg(['arg one', 'two', '-z']) * returns ToolRunner for chaining - * + * * @param val string argument line * @returns ToolRunner */ public line(val: string): ToolRunner { if (!val) { - return; + return this; } this._debug(this.toolPath + ' arg: ' + val); this.args = this.args.concat(this._argStringToArray(val)); - return this; + return this; } - + /** * Add argument(s) if a condition is met * Wraps arg(). See arg for details @@ -518,7 +1049,7 @@ export class ToolRunner extends events.EventEmitter { * @param val string cmdline or array of strings * @returns ToolRunner */ - public argIf(condition: any, val: any) { + public argIf(condition: unknown, val: string | string[]): ToolRunner { if (condition) { this.arg(val); } @@ -528,10 +1059,12 @@ export class ToolRunner extends events.EventEmitter { /** * Pipe output of exec() to another tool * @param tool + * @param file optional filename to additionally stream the output to. * @returns {ToolRunner} */ - public pipeExecOutputToTool(tool: ToolRunner) : ToolRunner { + public pipeExecOutputToTool(tool: ToolRunner, file?: string): ToolRunner { this.pipeOutputToTool = tool; + this.pipeOutputToFile = file; return this; } @@ -539,13 +1072,15 @@ export class ToolRunner extends events.EventEmitter { * Exec a tool. * Output will be streamed to the live console. * Returns promise with return code - * + * * @param tool path to tool to exec * @param options optional exec options. See IExecOptions * @returns number */ - public exec(options?: IExecOptions): Q.Promise { - var defer = Q.defer(); + public execAsync(options?: IExecOptions): Promise { + if (this.pipeOutputToTool) { + return this.execWithPipingAsync(this.pipeOutputToTool, options); + } this._debug('exec tool: ' + this.toolPath); this._debug('arguments:'); @@ -553,167 +1088,228 @@ export class ToolRunner extends events.EventEmitter { this._debug(' ' + arg); }); - let success = true; - options = this._cloneExecOptions(options); - - if (!options.silent) { - options.outStream.write(this._getCommandString(options) + os.EOL); + const optionsNonNull = this._cloneExecOptions(options); + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(this._getCommandString(optionsNonNull) + os.EOL); } - // TODO: filter process.env - let cp; - let toolPath: string = this.toolPath; - let toolPathFirst: string; - let successFirst = true; - let returnCodeFirst: number; + let state = new ExecState(optionsNonNull, this.toolPath); + state.on('debug', (message: string) => { + this._debug(message); + }); - if(this.pipeOutputToTool) { - toolPath = this.pipeOutputToTool.toolPath; - toolPathFirst = this.toolPath; + let cp = child.spawn(this._getSpawnFileName(options), this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(options)); + this.childProcess = cp; + // it is possible for the child process to end its last line without a new line. + // because stdout is buffered, this causes the last line to not get sent to the parent + // stream. Adding this event forces a flush before the child streams are closed. + cp.stdout?.on('finish', () => { + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(os.EOL); + } + }); - // Following node documentation example from this link on how to pipe output of one process to another - // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options + var stdbuffer: string = ''; + cp.stdout?.on('data', (data: Buffer) => { + this.emit('stdout', data); - //start the child process for both tools - var cpFirst = child.spawn( - this._getSpawnFileName(), - this._getSpawnArgs(options), - this._getSpawnOptions(options)); - cp = child.spawn( - this.pipeOutputToTool._getSpawnFileName(), - this.pipeOutputToTool._getSpawnArgs(options), - this.pipeOutputToTool._getSpawnOptions(options)); + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(data); + } - //pipe stdout of first tool to stdin of second tool - cpFirst.stdout.on('data', (data: Buffer) => { - try { - cp.stdin.write(data); - } catch (err) { - this._debug('Failed to pipe output of ' + toolPathFirst + ' to ' + toolPath); - this._debug(toolPath + ' might have exited due to errors prematurely. Verify the arguments passed are valid.'); - } - }); - cpFirst.stderr.on('data', (data: Buffer) => { - successFirst = !options.failOnStdErr; - if (!options.silent) { - var s = options.failOnStdErr ? options.errStream : options.outStream; - s.write(data); - } + this._processLineBuffer(data, stdbuffer, (line: string) => { + this.emit('stdline', line); }); - cpFirst.on('error', (err) => { - cp.stdin.end(); - defer.reject(new Error(toolPathFirst + ' failed. ' + err.message)); + }); + + + var errbuffer: string = ''; + cp.stderr?.on('data', (data: Buffer) => { + state.processStderr = true; + this.emit('stderr', data); + + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; + s.write(data); + } + + this._processLineBuffer(data, errbuffer, (line: string) => { + this.emit('errline', line); }); - cpFirst.on('close', (code, signal) => { - if (code != 0 && !options.ignoreReturnCode) { - successFirst = false; - returnCodeFirst = code; + }); + + cp.on('error', (err: Error) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); + }); + + cp.on('exit', (code: number, signal: any) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete() + }); + + cp.on('close', (code: number, signal: any) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`) + state.CheckComplete(); + }); + + return new Promise((resolve, reject) => { + state.on('done', (error: Error, exitCode: number) => { + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + + cp.removeAllListeners(); + + if (error) { + reject(error); + } + else { + resolve(exitCode); } - this._debug('success of first tool:' + successFirst); - cp.stdin.end(); }); + }); + } - } else { - cp = child.spawn(this._getSpawnFileName(), this._getSpawnArgs(options), this._getSpawnOptions(options)); + /** + * Exec a tool. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @deprecated Use the `execAsync` method that returns a native Javascript promise instead + * @param tool path to tool to exec + * @param options optional exec options. See IExecOptions + * @returns number + */ + public exec(options?: IExecOptions): Q.Promise { + if (this.pipeOutputToTool) { + return this.execWithPiping(this.pipeOutputToTool, options); } - var processLineBuffer = (data: Buffer, strBuffer: string, onLine:(line: string) => void): void => { - try { - var s = strBuffer + data.toString(); - var n = s.indexOf(os.EOL); + var defer = Q.defer(); - while(n > -1) { - var line = s.substring(0, n); - onLine(line); + this._debug('exec tool: ' + this.toolPath); + this._debug('arguments:'); + this.args.forEach((arg) => { + this._debug(' ' + arg); + }); - // the rest of the string ... - s = s.substring(n + os.EOL.length); - n = s.indexOf(os.EOL); - } + const optionsNonNull = this._cloneExecOptions(options); + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(this._getCommandString(optionsNonNull) + os.EOL); + } - strBuffer = s; - } - catch (err) { - // streaming lines to console is best effort. Don't fail a build. - this._debug('error processing line'); - } + let state = new ExecState(optionsNonNull, this.toolPath); + state.on('debug', (message: string) => { + this._debug(message); + }); - } + let cp = child.spawn(this._getSpawnFileName(options), this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(options)); + this.childProcess = cp; + // it is possible for the child process to end its last line without a new line. + // because stdout is buffered, this causes the last line to not get sent to the parent + // stream. Adding this event forces a flush before the child streams are closed. + cp.stdout?.on('finish', () => { + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(os.EOL); + } + }); var stdbuffer: string = ''; - cp.stdout.on('data', (data: Buffer) => { + cp.stdout?.on('data', (data: Buffer) => { this.emit('stdout', data); - if (!options.silent) { - options.outStream.write(data); + if (!optionsNonNull.silent) { + optionsNonNull.outStream!.write(data); } - processLineBuffer(data, stdbuffer, (line: string) => { - this.emit('stdline', line); + this._processLineBuffer(data, stdbuffer, (line: string) => { + this.emit('stdline', line); }); }); + var errbuffer: string = ''; - cp.stderr.on('data', (data: Buffer) => { + cp.stderr?.on('data', (data: Buffer) => { + state.processStderr = true; this.emit('stderr', data); - success = !options.failOnStdErr; - if (!options.silent) { - var s = options.failOnStdErr ? options.errStream : options.outStream; + if (!optionsNonNull.silent) { + var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!; s.write(data); } - processLineBuffer(data, errbuffer, (line: string) => { - this.emit('errline', line); - }); + this._processLineBuffer(data, errbuffer, (line: string) => { + this.emit('errline', line); + }); }); - cp.on('error', (err) => { - defer.reject(new Error(toolPath + ' failed. ' + err.message)); + cp.on('error', (err: Error) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); }); - cp.on('close', (code, signal) => { - this._debug('rc:' + code); + cp.on('exit', (code: number, signal: any) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete() + }); + cp.on('close', (code: number, signal: any) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`) + state.CheckComplete(); + }); + + state.on('done', (error: Error, exitCode: number) => { if (stdbuffer.length > 0) { this.emit('stdline', stdbuffer); } - + if (errbuffer.length > 0) { this.emit('errline', errbuffer); } - if (code != 0 && !options.ignoreReturnCode) { - success = false; - } + cp.removeAllListeners(); - this._debug('success:' + success); - if(!successFirst) { //in the case output is piped to another tool, check exit code of both tools - defer.reject(new Error(toolPathFirst + ' failed with return code: ' + returnCodeFirst)); - } else if (!success) { - defer.reject(new Error(toolPath + ' failed with return code: ' + code)); + if (error) { + defer.reject(error); } else { - defer.resolve(code); + defer.resolve(exitCode); } }); - return >defer.promise; + return defer.promise; } /** - * Exec a tool synchronously. + * Exec a tool synchronously. * Output will be *not* be streamed to the live console. It will be returned after execution is complete. - * Appropriate for short running tools + * Appropriate for short running tools * Returns IExecSyncResult with output and return code - * + * * @param tool path to tool to exec * @param options optional exec options. See IExecSyncOptions * @returns IExecSyncResult */ public execSync(options?: IExecSyncOptions): IExecSyncResult { - var defer = Q.defer(); - this._debug('exec tool: ' + this.toolPath); this._debug('arguments:'); this.args.forEach((arg) => { @@ -724,22 +1320,118 @@ export class ToolRunner extends events.EventEmitter { options = this._cloneExecOptions(options as IExecOptions); if (!options.silent) { - options.outStream.write(this._getCommandString(options as IExecOptions) + os.EOL); + options.outStream!.write(this._getCommandString(options as IExecOptions) + os.EOL); } - var r = child.spawnSync(this._getSpawnFileName(), this._getSpawnArgs(options as IExecOptions), this._getSpawnSyncOptions(options)); + var r = child.spawnSync(this._getSpawnFileName(options), this._getSpawnArgs(options as IExecOptions), this._getSpawnSyncOptions(options)); if (!options.silent && r.stdout && r.stdout.length > 0) { - options.outStream.write(r.stdout); + options.outStream!.write(r.stdout); } if (!options.silent && r.stderr && r.stderr.length > 0) { - options.errStream.write(r.stderr); + options.errStream!.write(r.stderr); } var res: IExecSyncResult = { code: r.status, error: r.error }; - res.stdout = (r.stdout) ? r.stdout.toString() : null; - res.stderr = (r.stderr) ? r.stderr.toString() : null; + res.stdout = (r.stdout) ? r.stdout.toString() : ''; + res.stderr = (r.stderr) ? r.stderr.toString() : ''; return res; } -} \ No newline at end of file + + /** + * Used to close child process by sending SIGNINT signal. + * It allows executed script to have some additional logic on SIGINT, before exiting. + */ + public killChildProcess(): void { + if (this.childProcess) { + this.childProcess.kill(); + } + } +} + +class ExecState extends events.EventEmitter { + constructor( + options: IExecOptions, + toolPath: string) { + + super(); + + if (!toolPath) { + throw new Error('toolPath must not be empty'); + } + + this.options = options; + this.toolPath = toolPath; + let delay = process.env['TASKLIB_TEST_TOOLRUNNER_EXITDELAY']; + if (delay) { + this.delay = parseInt(delay); + } + } + + processClosed: boolean; // tracks whether the process has exited and stdio is closed + processError: string; + processExitCode: number; + processExited: boolean; // tracks whether the process has exited + processStderr: boolean; // tracks whether stderr was written to + private delay = 10000; // 10 seconds + private done: boolean; + private options: IExecOptions; + private timeout: NodeJS.Timer | null = null; + private toolPath: string; + + public CheckComplete(): void { + if (this.done) { + return; + } + + if (this.processClosed) { + this._setResult(); + } + else if (this.processExited) { + this.timeout = setTimeout(ExecState.HandleTimeout, this.delay, this); + } + } + + private _debug(message: any): void { + this.emit('debug', message); + } + + private _setResult(): void { + // determine whether there is an error + let error: Error | undefined; + if (this.processExited) { + if (this.processError) { + error = new Error(im._loc('LIB_ProcessError', this.toolPath, this.processError)); + } + else if (this.processExitCode != 0 && !this.options.ignoreReturnCode) { + error = new Error(im._loc('LIB_ProcessExitCode', this.toolPath, this.processExitCode)); + } + else if (this.processStderr && this.options.failOnStdErr) { + error = new Error(im._loc('LIB_ProcessStderr', this.toolPath)); + } + } + + // clear the timeout + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + + this.done = true; + this.emit('done', error, this.processExitCode); + } + + private static HandleTimeout(state: ExecState) { + if (state.done) { + return; + } + + if (!state.processClosed && state.processExited) { + console.log(im._loc('LIB_StdioNotClosed', state.delay / 1000, state.toolPath)); + state._debug(im._loc('LIB_StdioNotClosed', state.delay / 1000, state.toolPath)); + } + + state._setResult(); + } +} diff --git a/node/tsconfig.json b/node/tsconfig.json index 00816c570..b6a1a95e8 100644 --- a/node/tsconfig.json +++ b/node/tsconfig.json @@ -3,12 +3,15 @@ "target": "ES5", "module": "commonjs", "declaration": true, - "moduleResolution": "node" + "moduleResolution": "node", + "strictNullChecks": true, + "noImplicitAny": true, }, "files": [ "index.ts", "mock-task.ts", "mock-run.ts", - "mock-test.ts" + "mock-test.ts", + "types/global.d.ts" ] } \ No newline at end of file diff --git a/node/types/global.d.ts b/node/types/global.d.ts new file mode 100644 index 000000000..88b353176 --- /dev/null +++ b/node/types/global.d.ts @@ -0,0 +1,20 @@ +declare module NodeJS { + interface Global { + _vsts_task_lib_loaded?: boolean; + // proxy related + _vsts_task_lib_proxy_url?: string; + _vsts_task_lib_proxy_username?: string; + _vsts_task_lib_proxy_bypass?: string; + _vsts_task_lib_proxy_password?: string; + _vsts_task_lib_proxy?: boolean; + // cert related + _vsts_task_lib_cert_ca?: string; + _vsts_task_lib_cert_clientcert?: string; + _vsts_task_lib_cert_key?: string; + _vsts_task_lib_cert_archive?: string; + _vsts_task_lib_cert_archiveFile?: string; + _vsts_task_lib_cert_passphrase?: string; + _vsts_task_lib_cert?: boolean; + _vsts_task_lib_skip_cert_validation?: boolean; + } + } \ No newline at end of file diff --git a/node/typings.json b/node/typings.json deleted file mode 100644 index c8ee026f6..000000000 --- a/node/typings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "vsts-task-lib", - "version": false, - "dependencies": {}, - "globalDependencies": { - "glob": "registry:dt/glob#5.0.10+20160317120654", - "minimatch": "registry:dt/minimatch#2.0.8+20160317120654", - "mocha": "registry:dt/mocha#2.2.5+20160619032855", - "mockery": "registry:dt/mockery#1.4.0+20160316155526", - "node": "registry:dt/node#6.0.0+20160709114037", - "q": "registry:dt/q#0.0.0+20160613154756", - "semver": "registry:dt/semver#4.3.4+20160608054219", - "shelljs": "registry:dt/shelljs#0.0.0+20160526131156" - } -} diff --git a/node/typings/globals/glob/index.d.ts b/node/typings/globals/glob/index.d.ts deleted file mode 100644 index 4eab63107..000000000 --- a/node/typings/globals/glob/index.d.ts +++ /dev/null @@ -1,106 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/glob/glob.d.ts -declare module "glob" { - - import events = require("events"); - import fs = require('fs'); - import minimatch = require("minimatch"); - - function G(pattern: string, cb: (err: Error, matches: string[]) => void): void; - function G(pattern: string, options: G.IOptions, cb: (err: Error, matches: string[]) => void): void; - - namespace G { - function sync(pattern: string, options?: IOptions): string[]; - - function hasMagic(pattern: string, options?: IOptions): boolean; - - var Glob: IGlobStatic; - var GlobSync: IGlobSyncStatic; - - interface IOptions extends minimatch.IOptions { - cwd?: string; - root?: string; - dot?: boolean; - nomount?: boolean; - mark?: boolean; - nosort?: boolean; - stat?: boolean; - silent?: boolean; - strict?: boolean; - cache?: { [path: string]: any /* boolean | string | string[] */ }; - statCache?: { [path: string]: fs.Stats }; - symlinks?: any; - sync?: boolean; - nounique?: boolean; - nonull?: boolean; - debug?: boolean; - nobrace?: boolean; - noglobstar?: boolean; - noext?: boolean; - nocase?: boolean; - matchBase?: any; - nodir?: boolean; - ignore?: any; /* string | string[] */ - follow?: boolean; - realpath?: boolean; - nonegate?: boolean; - nocomment?: boolean; - - /** Deprecated. */ - globDebug?: boolean; - } - - interface IGlobStatic extends events.EventEmitter { - new (pattern: string, cb?: (err: Error, matches: string[]) => void): IGlob; - new (pattern: string, options: IOptions, cb?: (err: Error, matches: string[]) => void): IGlob; - prototype: IGlob; - } - - interface IGlobSyncStatic { - new (pattern: string, options?: IOptions): IGlobBase - prototype: IGlobBase; - } - - interface IGlobBase { - minimatch: minimatch.IMinimatch; - options: IOptions; - aborted: boolean; - cache: { [path: string]: any /* boolean | string | string[] */ }; - statCache: { [path: string]: fs.Stats }; - symlinks: { [path: string]: boolean }; - realpathCache: { [path: string]: string }; - found: string[]; - } - - interface IGlob extends IGlobBase, events.EventEmitter { - pause(): void; - resume(): void; - abort(): void; - - /** Deprecated. */ - EOF: any; - /** Deprecated. */ - paused: boolean; - /** Deprecated. */ - maxDepth: number; - /** Deprecated. */ - maxLength: number; - /** Deprecated. */ - changedCwd: boolean; - /** Deprecated. */ - cwd: string; - /** Deprecated. */ - root: string; - /** Deprecated. */ - error: any; - /** Deprecated. */ - matches: string[]; - /** Deprecated. */ - log(...args: any[]): void; - /** Deprecated. */ - emitMatch(m: any): void; - } - } - - export = G; -} \ No newline at end of file diff --git a/node/typings/globals/glob/typings.json b/node/typings/globals/glob/typings.json deleted file mode 100644 index 505e919af..000000000 --- a/node/typings/globals/glob/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/glob/glob.d.ts", - "raw": "registry:dt/glob#5.0.10+20160317120654", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/glob/glob.d.ts" - } -} diff --git a/node/typings/globals/minimatch/index.d.ts b/node/typings/globals/minimatch/index.d.ts deleted file mode 100644 index 03e423179..000000000 --- a/node/typings/globals/minimatch/index.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/minimatch/minimatch.d.ts -declare module "minimatch" { - - function M(target: string, pattern: string, options?: M.IOptions): boolean; - - namespace M { - function match(list: string[], pattern: string, options?: IOptions): string[]; - function filter(pattern: string, options?: IOptions): (element: string, indexed: number, array: string[]) => boolean; - function makeRe(pattern: string, options?: IOptions): RegExp; - - var Minimatch: IMinimatchStatic; - - interface IOptions { - debug?: boolean; - nobrace?: boolean; - noglobstar?: boolean; - dot?: boolean; - noext?: boolean; - nocase?: boolean; - nonull?: boolean; - matchBase?: boolean; - nocomment?: boolean; - nonegate?: boolean; - flipNegate?: boolean; - } - - interface IMinimatchStatic { - new (pattern: string, options?: IOptions): IMinimatch; - prototype: IMinimatch; - } - - interface IMinimatch { - pattern: string; - options: IOptions; - /** 2-dimensional array of regexp or string expressions. */ - set: any[][]; // (RegExp | string)[][] - regexp: RegExp; - negate: boolean; - comment: boolean; - empty: boolean; - - makeRe(): RegExp; // regexp or boolean - match(fname: string): boolean; - matchOne(files: string[], pattern: string[], partial: boolean): boolean; - - /** Deprecated. For internal use. */ - debug(): void; - /** Deprecated. For internal use. */ - make(): void; - /** Deprecated. For internal use. */ - parseNegate(): void; - /** Deprecated. For internal use. */ - braceExpand(pattern: string, options: IOptions): void; - /** Deprecated. For internal use. */ - parse(pattern: string, isSub?: boolean): void; - } - } - - export = M; -} \ No newline at end of file diff --git a/node/typings/globals/minimatch/typings.json b/node/typings/globals/minimatch/typings.json deleted file mode 100644 index b06a75ad7..000000000 --- a/node/typings/globals/minimatch/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/minimatch/minimatch.d.ts", - "raw": "registry:dt/minimatch#2.0.8+20160317120654", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/minimatch/minimatch.d.ts" - } -} diff --git a/node/typings/globals/mocha/index.d.ts b/node/typings/globals/mocha/index.d.ts deleted file mode 100644 index b284ea8b7..000000000 --- a/node/typings/globals/mocha/index.d.ts +++ /dev/null @@ -1,234 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/b1daff0be8fa53f645365303d8e0145d055370e9/mocha/mocha.d.ts -interface MochaSetupOptions { - //milliseconds to wait before considering a test slow - slow?: number; - - // timeout in milliseconds - timeout?: number; - - // ui name "bdd", "tdd", "exports" etc - ui?: string; - - //array of accepted globals - globals?: any[]; - - // reporter instance (function or string), defaults to `mocha.reporters.Spec` - reporter?: any; - - // bail on the first test failure - bail?: boolean; - - // ignore global leaks - ignoreLeaks?: boolean; - - // grep string or regexp to filter tests with - grep?: any; -} - -interface MochaDone { - (error?: Error): void; -} - -declare var mocha: Mocha; -declare var describe: Mocha.IContextDefinition; -declare var xdescribe: Mocha.IContextDefinition; -// alias for `describe` -declare var context: Mocha.IContextDefinition; -// alias for `describe` -declare var suite: Mocha.IContextDefinition; -declare var it: Mocha.ITestDefinition; -declare var xit: Mocha.ITestDefinition; -// alias for `it` -declare var test: Mocha.ITestDefinition; -declare var specify: Mocha.ITestDefinition; - -declare function before(action: () => void): void; - -declare function before(action: (done: MochaDone) => void): void; - -declare function before(description: string, action: () => void): void; - -declare function before(description: string, action: (done: MochaDone) => void): void; - -declare function setup(action: () => void): void; - -declare function setup(action: (done: MochaDone) => void): void; - -declare function after(action: () => void): void; - -declare function after(action: (done: MochaDone) => void): void; - -declare function after(description: string, action: () => void): void; - -declare function after(description: string, action: (done: MochaDone) => void): void; - -declare function teardown(action: () => void): void; - -declare function teardown(action: (done: MochaDone) => void): void; - -declare function beforeEach(action: () => void): void; - -declare function beforeEach(action: (done: MochaDone) => void): void; - -declare function beforeEach(description: string, action: () => void): void; - -declare function beforeEach(description: string, action: (done: MochaDone) => void): void; - -declare function suiteSetup(action: () => void): void; - -declare function suiteSetup(action: (done: MochaDone) => void): void; - -declare function afterEach(action: () => void): void; - -declare function afterEach(action: (done: MochaDone) => void): void; - -declare function afterEach(description: string, action: () => void): void; - -declare function afterEach(description: string, action: (done: MochaDone) => void): void; - -declare function suiteTeardown(action: () => void): void; - -declare function suiteTeardown(action: (done: MochaDone) => void): void; - -declare class Mocha { - constructor(options?: { - grep?: RegExp; - ui?: string; - reporter?: string; - timeout?: number; - bail?: boolean; - }); - - /** Setup mocha with the given options. */ - setup(options: MochaSetupOptions): Mocha; - bail(value?: boolean): Mocha; - addFile(file: string): Mocha; - /** Sets reporter by name, defaults to "spec". */ - reporter(name: string): Mocha; - /** Sets reporter constructor, defaults to mocha.reporters.Spec. */ - reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha; - ui(value: string): Mocha; - grep(value: string): Mocha; - grep(value: RegExp): Mocha; - invert(): Mocha; - ignoreLeaks(value: boolean): Mocha; - checkLeaks(): Mocha; - /** - * Function to allow assertion libraries to throw errors directly into mocha. - * This is useful when running tests in a browser because window.onerror will - * only receive the 'message' attribute of the Error. - */ - throwError(error: Error): void; - /** Enables growl support. */ - growl(): Mocha; - globals(value: string): Mocha; - globals(values: string[]): Mocha; - useColors(value: boolean): Mocha; - useInlineDiffs(value: boolean): Mocha; - timeout(value: number): Mocha; - slow(value: number): Mocha; - enableTimeouts(value: boolean): Mocha; - asyncOnly(value: boolean): Mocha; - noHighlighting(value: boolean): Mocha; - /** Runs tests and invokes `onComplete()` when finished. */ - run(onComplete?: (failures: number) => void): Mocha.IRunner; -} - -// merge the Mocha class declaration with a module -declare namespace Mocha { - /** Partial interface for Mocha's `Runnable` class. */ - interface IRunnable { - title: string; - fn: Function; - async: boolean; - sync: boolean; - timedOut: boolean; - } - - /** Partial interface for Mocha's `Suite` class. */ - interface ISuite { - parent: ISuite; - title: string; - - fullTitle(): string; - } - - /** Partial interface for Mocha's `Test` class. */ - interface ITest extends IRunnable { - parent: ISuite; - pending: boolean; - - fullTitle(): string; - } - - /** Partial interface for Mocha's `Runner` class. */ - interface IRunner {} - - interface IContextDefinition { - (description: string, spec: () => void): ISuite; - only(description: string, spec: () => void): ISuite; - skip(description: string, spec: () => void): void; - timeout(ms: number): void; - } - - interface ITestDefinition { - (expectation: string, assertion?: () => void): ITest; - (expectation: string, assertion?: (done: MochaDone) => void): ITest; - only(expectation: string, assertion?: () => void): ITest; - only(expectation: string, assertion?: (done: MochaDone) => void): ITest; - skip(expectation: string, assertion?: () => void): void; - skip(expectation: string, assertion?: (done: MochaDone) => void): void; - timeout(ms: number): void; - } - - export module reporters { - export class Base { - stats: { - suites: number; - tests: number; - passes: number; - pending: number; - failures: number; - }; - - constructor(runner: IRunner); - } - - export class Doc extends Base {} - export class Dot extends Base {} - export class HTML extends Base {} - export class HTMLCov extends Base {} - export class JSON extends Base {} - export class JSONCov extends Base {} - export class JSONStream extends Base {} - export class Landing extends Base {} - export class List extends Base {} - export class Markdown extends Base {} - export class Min extends Base {} - export class Nyan extends Base {} - export class Progress extends Base { - /** - * @param options.open String used to indicate the start of the progress bar. - * @param options.complete String used to indicate a complete test on the progress bar. - * @param options.incomplete String used to indicate an incomplete test on the progress bar. - * @param options.close String used to indicate the end of the progress bar. - */ - constructor(runner: IRunner, options?: { - open?: string; - complete?: string; - incomplete?: string; - close?: string; - }); - } - export class Spec extends Base {} - export class TAP extends Base {} - export class XUnit extends Base { - constructor(runner: IRunner, options?: any); - } - } -} - -declare module "mocha" { - export = Mocha; -} \ No newline at end of file diff --git a/node/typings/globals/mocha/typings.json b/node/typings/globals/mocha/typings.json deleted file mode 100644 index 57933df04..000000000 --- a/node/typings/globals/mocha/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/b1daff0be8fa53f645365303d8e0145d055370e9/mocha/mocha.d.ts", - "raw": "registry:dt/mocha#2.2.5+20160619032855", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/b1daff0be8fa53f645365303d8e0145d055370e9/mocha/mocha.d.ts" - } -} diff --git a/node/typings/globals/mockery/index.d.ts b/node/typings/globals/mockery/index.d.ts deleted file mode 100644 index 70c4ba798..000000000 --- a/node/typings/globals/mockery/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/56295f5058cac7ae458540423c50ac2dcf9fc711/mockery/mockery.d.ts -declare module "mockery" { - - interface MockeryEnableArgs { - useCleanCache?: boolean; - warnOnReplace?: boolean; - warnOnUnregistered?: boolean; - } - - export function enable(args?: MockeryEnableArgs): void; - export function disable(): void; - - export function registerMock(name: string, mock: any): void; - export function deregisterMock(name: string): void; - - export function registerSubstitute(name: string, substitute: string): void; - export function deregisterSubstitute(name: string): void; - - export function registerAllowable(name: string, unhook?: boolean): void; - export function deregisterAllowable(name: string): void; - - export function registerAllowables(names: string[]): void; - export function deregisterAllowables(names: string[]): void; - - export function deregisterAll(): void; - export function resetCache(): void; - export function warnOnUnregistered(value: boolean): void; - export function warnOnReplace(value: boolean): void; -} \ No newline at end of file diff --git a/node/typings/globals/mockery/typings.json b/node/typings/globals/mockery/typings.json deleted file mode 100644 index 334d96df4..000000000 --- a/node/typings/globals/mockery/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/56295f5058cac7ae458540423c50ac2dcf9fc711/mockery/mockery.d.ts", - "raw": "registry:dt/mockery#1.4.0+20160316155526", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/56295f5058cac7ae458540423c50ac2dcf9fc711/mockery/mockery.d.ts" - } -} diff --git a/node/typings/globals/node/index.d.ts b/node/typings/globals/node/index.d.ts deleted file mode 100644 index bba54ec45..000000000 --- a/node/typings/globals/node/index.d.ts +++ /dev/null @@ -1,2578 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/77b1b1709315b03b9b1b67c589d599bebeeef2ee/node/node.d.ts -interface Error { - stack?: string; -} - -interface ErrorConstructor { - captureStackTrace(targetObject: Object, constructorOpt?: Function): void; - stackTraceLimit: number; -} - -// compat for TypeScript 1.8 -// if you use with --target es3 or --target es5 and use below definitions, -// use the lib.es6.d.ts that is bundled with TypeScript 1.8. -interface MapConstructor {} -interface WeakMapConstructor {} -interface SetConstructor {} -interface WeakSetConstructor {} - -/************************************************ -* * -* GLOBAL * -* * -************************************************/ -declare var process: NodeJS.Process; -declare var global: NodeJS.Global; - -declare var __filename: string; -declare var __dirname: string; - -declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; -declare function clearTimeout(timeoutId: NodeJS.Timer): void; -declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; -declare function clearInterval(intervalId: NodeJS.Timer): void; -declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; -declare function clearImmediate(immediateId: any): void; - -interface NodeRequireFunction { - (id: string): any; -} - -interface NodeRequire extends NodeRequireFunction { - resolve(id:string): string; - cache: any; - extensions: any; - main: any; -} - -declare var require: NodeRequire; - -interface NodeModule { - exports: any; - require: NodeRequireFunction; - id: string; - filename: string; - loaded: boolean; - parent: any; - children: any[]; -} - -declare var module: NodeModule; - -// Same as module.exports -declare var exports: any; -declare var SlowBuffer: { - new (str: string, encoding?: string): Buffer; - new (size: number): Buffer; - new (size: Uint8Array): Buffer; - new (array: any[]): Buffer; - prototype: Buffer; - isBuffer(obj: any): boolean; - byteLength(string: string, encoding?: string): number; - concat(list: Buffer[], totalLength?: number): Buffer; -}; - - -// Buffer class -type BufferEncoding = "ascii" | "utf8" | "utf16le" | "ucs2" | "binary" | "hex"; -interface Buffer extends NodeBuffer {} - -/** - * Raw data is stored in instances of the Buffer class. - * A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized. - * Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' - */ -declare var Buffer: { - /** - * Allocates a new buffer containing the given {str}. - * - * @param str String to store in buffer. - * @param encoding encoding to use, optional. Default is 'utf8' - */ - new (str: string, encoding?: string): Buffer; - /** - * Allocates a new buffer of {size} octets. - * - * @param size count of octets to allocate. - */ - new (size: number): Buffer; - /** - * Allocates a new buffer containing the given {array} of octets. - * - * @param array The octets to store. - */ - new (array: Uint8Array): Buffer; - /** - * Produces a Buffer backed by the same allocated memory as - * the given {ArrayBuffer}. - * - * - * @param arrayBuffer The ArrayBuffer with which to share memory. - */ - new (arrayBuffer: ArrayBuffer): Buffer; - /** - * Allocates a new buffer containing the given {array} of octets. - * - * @param array The octets to store. - */ - new (array: any[]): Buffer; - /** - * Copies the passed {buffer} data onto a new {Buffer} instance. - * - * @param buffer The buffer to copy. - */ - new (buffer: Buffer): Buffer; - prototype: Buffer; - /** - * Allocates a new Buffer using an {array} of octets. - * - * @param array - */ - from(array: any[]): Buffer; - /** - * When passed a reference to the .buffer property of a TypedArray instance, - * the newly created Buffer will share the same allocated memory as the TypedArray. - * The optional {byteOffset} and {length} arguments specify a memory range - * within the {arrayBuffer} that will be shared by the Buffer. - * - * @param arrayBuffer The .buffer property of a TypedArray or a new ArrayBuffer() - * @param byteOffset - * @param length - */ - from(arrayBuffer: ArrayBuffer, byteOffset?: number, length?:number): Buffer; - /** - * Copies the passed {buffer} data onto a new Buffer instance. - * - * @param buffer - */ - from(buffer: Buffer): Buffer; - /** - * Creates a new Buffer containing the given JavaScript string {str}. - * If provided, the {encoding} parameter identifies the character encoding. - * If not provided, {encoding} defaults to 'utf8'. - * - * @param str - */ - from(str: string, encoding?: string): Buffer; - /** - * Returns true if {obj} is a Buffer - * - * @param obj object to test. - */ - isBuffer(obj: any): obj is Buffer; - /** - * Returns true if {encoding} is a valid encoding argument. - * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' - * - * @param encoding string to test. - */ - isEncoding(encoding: string): boolean; - /** - * Gives the actual byte length of a string. encoding defaults to 'utf8'. - * This is not the same as String.prototype.length since that returns the number of characters in a string. - * - * @param string string to test. - * @param encoding encoding used to evaluate (defaults to 'utf8') - */ - byteLength(string: string, encoding?: string): number; - /** - * Returns a buffer which is the result of concatenating all the buffers in the list together. - * - * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. - * If the list has exactly one item, then the first item of the list is returned. - * If the list has more than one item, then a new Buffer is created. - * - * @param list An array of Buffer objects to concatenate - * @param totalLength Total length of the buffers when concatenated. - * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. - */ - concat(list: Buffer[], totalLength?: number): Buffer; - /** - * The same as buf1.compare(buf2). - */ - compare(buf1: Buffer, buf2: Buffer): number; - /** - * Allocates a new buffer of {size} octets. - * - * @param size count of octets to allocate. - * @param fill if specified, buffer will be initialized by calling buf.fill(fill). - * If parameter is omitted, buffer will be filled with zeros. - * @param encoding encoding used for call to buf.fill while initalizing - */ - alloc(size: number, fill?: string|Buffer|number, encoding?: string): Buffer; - /** - * Allocates a new buffer of {size} octets, leaving memory not initialized, so the contents - * of the newly created Buffer are unknown and may contain sensitive data. - * - * @param size count of octets to allocate - */ - allocUnsafe(size: number): Buffer; - /** - * Allocates a new non-pooled buffer of {size} octets, leaving memory not initialized, so the contents - * of the newly created Buffer are unknown and may contain sensitive data. - * - * @param size count of octets to allocate - */ - allocUnsafeSlow(size: number): Buffer; -}; - -/************************************************ -* * -* GLOBAL INTERFACES * -* * -************************************************/ -declare namespace NodeJS { - export interface ErrnoException extends Error { - errno?: number; - code?: string; - path?: string; - syscall?: string; - stack?: string; - } - - export interface EventEmitter { - addListener(event: string, listener: Function): this; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - removeAllListeners(event?: string): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: string): Function[]; - emit(event: string, ...args: any[]): boolean; - listenerCount(type: string): number; - } - - export interface ReadableStream extends EventEmitter { - readable: boolean; - read(size?: number): string|Buffer; - setEncoding(encoding: string): void; - pause(): void; - resume(): void; - pipe(destination: T, options?: { end?: boolean; }): T; - unpipe(destination?: T): void; - unshift(chunk: string): void; - unshift(chunk: Buffer): void; - wrap(oldStream: ReadableStream): ReadableStream; - } - - export interface WritableStream extends EventEmitter { - writable: boolean; - write(buffer: Buffer|string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; - end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; - } - - export interface ReadWriteStream extends ReadableStream, WritableStream {} - - export interface Events extends EventEmitter { } - - export interface Domain extends Events { - run(fn: Function): void; - add(emitter: Events): void; - remove(emitter: Events): void; - bind(cb: (err: Error, data: any) => any): any; - intercept(cb: (data: any) => any): any; - dispose(): void; - - addListener(event: string, listener: Function): this; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - removeAllListeners(event?: string): this; - } - - export interface MemoryUsage { - rss: number; - heapTotal: number; - heapUsed: number; - } - - export interface Process extends EventEmitter { - stdout: WritableStream; - stderr: WritableStream; - stdin: ReadableStream; - argv: string[]; - execArgv: string[]; - execPath: string; - abort(): void; - chdir(directory: string): void; - cwd(): string; - env: any; - exit(code?: number): void; - getgid(): number; - setgid(id: number): void; - setgid(id: string): void; - getuid(): number; - setuid(id: number): void; - setuid(id: string): void; - version: string; - versions: { - http_parser: string; - node: string; - v8: string; - ares: string; - uv: string; - zlib: string; - modules: string; - openssl: string; - }; - config: { - target_defaults: { - cflags: any[]; - default_configuration: string; - defines: string[]; - include_dirs: string[]; - libraries: string[]; - }; - variables: { - clang: number; - host_arch: string; - node_install_npm: boolean; - node_install_waf: boolean; - node_prefix: string; - node_shared_openssl: boolean; - node_shared_v8: boolean; - node_shared_zlib: boolean; - node_use_dtrace: boolean; - node_use_etw: boolean; - node_use_openssl: boolean; - target_arch: string; - v8_no_strict_aliasing: number; - v8_use_snapshot: boolean; - visibility: string; - }; - }; - kill(pid:number, signal?: string|number): void; - pid: number; - title: string; - arch: string; - platform: string; - memoryUsage(): MemoryUsage; - nextTick(callback: Function): void; - umask(mask?: number): number; - uptime(): number; - hrtime(time?:number[]): number[]; - domain: Domain; - - // Worker - send?(message: any, sendHandle?: any): void; - disconnect(): void; - connected: boolean; - } - - export interface Global { - Array: typeof Array; - ArrayBuffer: typeof ArrayBuffer; - Boolean: typeof Boolean; - Buffer: typeof Buffer; - DataView: typeof DataView; - Date: typeof Date; - Error: typeof Error; - EvalError: typeof EvalError; - Float32Array: typeof Float32Array; - Float64Array: typeof Float64Array; - Function: typeof Function; - GLOBAL: Global; - Infinity: typeof Infinity; - Int16Array: typeof Int16Array; - Int32Array: typeof Int32Array; - Int8Array: typeof Int8Array; - Intl: typeof Intl; - JSON: typeof JSON; - Map: MapConstructor; - Math: typeof Math; - NaN: typeof NaN; - Number: typeof Number; - Object: typeof Object; - Promise: Function; - RangeError: typeof RangeError; - ReferenceError: typeof ReferenceError; - RegExp: typeof RegExp; - Set: SetConstructor; - String: typeof String; - Symbol: Function; - SyntaxError: typeof SyntaxError; - TypeError: typeof TypeError; - URIError: typeof URIError; - Uint16Array: typeof Uint16Array; - Uint32Array: typeof Uint32Array; - Uint8Array: typeof Uint8Array; - Uint8ClampedArray: Function; - WeakMap: WeakMapConstructor; - WeakSet: WeakSetConstructor; - clearImmediate: (immediateId: any) => void; - clearInterval: (intervalId: NodeJS.Timer) => void; - clearTimeout: (timeoutId: NodeJS.Timer) => void; - console: typeof console; - decodeURI: typeof decodeURI; - decodeURIComponent: typeof decodeURIComponent; - encodeURI: typeof encodeURI; - encodeURIComponent: typeof encodeURIComponent; - escape: (str: string) => string; - eval: typeof eval; - global: Global; - isFinite: typeof isFinite; - isNaN: typeof isNaN; - parseFloat: typeof parseFloat; - parseInt: typeof parseInt; - process: Process; - root: Global; - setImmediate: (callback: (...args: any[]) => void, ...args: any[]) => any; - setInterval: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; - setTimeout: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; - undefined: typeof undefined; - unescape: (str: string) => string; - gc: () => void; - v8debug?: any; - } - - export interface Timer { - ref() : void; - unref() : void; - } -} - -/** - * @deprecated - */ -interface NodeBuffer extends Uint8Array { - write(string: string, offset?: number, length?: number, encoding?: string): number; - toString(encoding?: string, start?: number, end?: number): string; - toJSON(): any; - equals(otherBuffer: Buffer): boolean; - compare(otherBuffer: Buffer): number; - copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; - slice(start?: number, end?: number): Buffer; - writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; - writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; - writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; - writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; - readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; - readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; - readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; - readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; - readUInt8(offset: number, noAssert?: boolean): number; - readUInt16LE(offset: number, noAssert?: boolean): number; - readUInt16BE(offset: number, noAssert?: boolean): number; - readUInt32LE(offset: number, noAssert?: boolean): number; - readUInt32BE(offset: number, noAssert?: boolean): number; - readInt8(offset: number, noAssert?: boolean): number; - readInt16LE(offset: number, noAssert?: boolean): number; - readInt16BE(offset: number, noAssert?: boolean): number; - readInt32LE(offset: number, noAssert?: boolean): number; - readInt32BE(offset: number, noAssert?: boolean): number; - readFloatLE(offset: number, noAssert?: boolean): number; - readFloatBE(offset: number, noAssert?: boolean): number; - readDoubleLE(offset: number, noAssert?: boolean): number; - readDoubleBE(offset: number, noAssert?: boolean): number; - writeUInt8(value: number, offset: number, noAssert?: boolean): number; - writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; - writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; - writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; - writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; - writeInt8(value: number, offset: number, noAssert?: boolean): number; - writeInt16LE(value: number, offset: number, noAssert?: boolean): number; - writeInt16BE(value: number, offset: number, noAssert?: boolean): number; - writeInt32LE(value: number, offset: number, noAssert?: boolean): number; - writeInt32BE(value: number, offset: number, noAssert?: boolean): number; - writeFloatLE(value: number, offset: number, noAssert?: boolean): number; - writeFloatBE(value: number, offset: number, noAssert?: boolean): number; - writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; - writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; - fill(value: any, offset?: number, end?: number): this; - // TODO: encoding param - indexOf(value: string | number | Buffer, byteOffset?: number): number; - // TODO: entries - // TODO: includes - // TODO: keys - // TODO: values -} - -/************************************************ -* * -* MODULES * -* * -************************************************/ -declare module "buffer" { - export var INSPECT_MAX_BYTES: number; - var BuffType: typeof Buffer; - var SlowBuffType: typeof SlowBuffer; - export { BuffType as Buffer, SlowBuffType as SlowBuffer }; -} - -declare module "querystring" { - export interface StringifyOptions { - encodeURIComponent?: Function; - } - - export interface ParseOptions { - maxKeys?: number; - decodeURIComponent?: Function; - } - - export function stringify(obj: T, sep?: string, eq?: string, options?: StringifyOptions): string; - export function parse(str: string, sep?: string, eq?: string, options?: ParseOptions): any; - export function parse(str: string, sep?: string, eq?: string, options?: ParseOptions): T; - export function escape(str: string): string; - export function unescape(str: string): string; -} - -declare module "events" { - export class EventEmitter implements NodeJS.EventEmitter { - static EventEmitter: EventEmitter; - static listenerCount(emitter: EventEmitter, event: string): number; // deprecated - static defaultMaxListeners: number; - - addListener(event: string, listener: Function): this; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - removeAllListeners(event?: string): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: string): Function[]; - emit(event: string, ...args: any[]): boolean; - eventNames(): string[]; - listenerCount(type: string): number; - } -} - -declare module "http" { - import * as events from "events"; - import * as net from "net"; - import * as stream from "stream"; - - export interface RequestOptions { - protocol?: string; - host?: string; - hostname?: string; - family?: number; - port?: number; - localAddress?: string; - socketPath?: string; - method?: string; - path?: string; - headers?: { [key: string]: any }; - auth?: string; - agent?: Agent|boolean; - } - - export interface Server extends events.EventEmitter, net.Server { - setTimeout(msecs: number, callback: Function): void; - maxHeadersCount: number; - timeout: number; - } - /** - * @deprecated Use IncomingMessage - */ - export interface ServerRequest extends IncomingMessage { - connection: net.Socket; - } - export interface ServerResponse extends events.EventEmitter, stream.Writable { - // Extended base methods - write(buffer: Buffer): boolean; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; - write(str: string, encoding?: string, fd?: string): boolean; - - writeContinue(): void; - writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void; - writeHead(statusCode: number, headers?: any): void; - statusCode: number; - statusMessage: string; - headersSent: boolean; - setHeader(name: string, value: string | string[]): void; - sendDate: boolean; - getHeader(name: string): string; - removeHeader(name: string): void; - write(chunk: any, encoding?: string): any; - addTrailers(headers: any): void; - - // Extended base methods - end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; - end(data?: any, encoding?: string): void; - } - export interface ClientRequest extends events.EventEmitter, stream.Writable { - // Extended base methods - write(buffer: Buffer): boolean; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; - write(str: string, encoding?: string, fd?: string): boolean; - - write(chunk: any, encoding?: string): void; - abort(): void; - setTimeout(timeout: number, callback?: Function): void; - setNoDelay(noDelay?: boolean): void; - setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; - - setHeader(name: string, value: string | string[]): void; - getHeader(name: string): string; - removeHeader(name: string): void; - addTrailers(headers: any): void; - - // Extended base methods - end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; - end(data?: any, encoding?: string): void; - } - export interface IncomingMessage extends events.EventEmitter, stream.Readable { - httpVersion: string; - headers: any; - rawHeaders: string[]; - trailers: any; - rawTrailers: any; - setTimeout(msecs: number, callback: Function): NodeJS.Timer; - /** - * Only valid for request obtained from http.Server. - */ - method?: string; - /** - * Only valid for request obtained from http.Server. - */ - url?: string; - /** - * Only valid for response obtained from http.ClientRequest. - */ - statusCode?: number; - /** - * Only valid for response obtained from http.ClientRequest. - */ - statusMessage?: string; - socket: net.Socket; - } - /** - * @deprecated Use IncomingMessage - */ - export interface ClientResponse extends IncomingMessage { } - - export interface AgentOptions { - /** - * Keep sockets around in a pool to be used by other requests in the future. Default = false - */ - keepAlive?: boolean; - /** - * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. - * Only relevant if keepAlive is set to true. - */ - keepAliveMsecs?: number; - /** - * Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity - */ - maxSockets?: number; - /** - * Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256. - */ - maxFreeSockets?: number; - } - - export class Agent { - maxSockets: number; - sockets: any; - requests: any; - - constructor(opts?: AgentOptions); - - /** - * Destroy any sockets that are currently in use by the agent. - * It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled, - * then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise, - * sockets may hang open for quite a long time before the server terminates them. - */ - destroy(): void; - } - - export var METHODS: string[]; - - export var STATUS_CODES: { - [errorCode: number]: string; - [errorCode: string]: string; - }; - export function createServer(requestListener?: (request: IncomingMessage, response: ServerResponse) =>void ): Server; - export function createClient(port?: number, host?: string): any; - export function request(options: RequestOptions, callback?: (res: IncomingMessage) => void): ClientRequest; - export function get(options: any, callback?: (res: IncomingMessage) => void): ClientRequest; - export var globalAgent: Agent; -} - -declare module "cluster" { - import * as child from "child_process"; - import * as events from "events"; - - export interface ClusterSettings { - exec?: string; - args?: string[]; - silent?: boolean; - } - - export interface Address { - address: string; - port: number; - addressType: string; - } - - export class Worker extends events.EventEmitter { - id: string; - process: child.ChildProcess; - suicide: boolean; - send(message: any, sendHandle?: any): void; - kill(signal?: string): void; - destroy(signal?: string): void; - disconnect(): void; - isConnected(): boolean; - isDead(): boolean; - } - - export var settings: ClusterSettings; - export var isMaster: boolean; - export var isWorker: boolean; - export function setupMaster(settings?: ClusterSettings): void; - export function fork(env?: any): Worker; - export function disconnect(callback?: Function): void; - export var worker: Worker; - export var workers: { - [index: string]: Worker - }; - - // Event emitter - export function addListener(event: string, listener: Function): void; - export function on(event: "disconnect", listener: (worker: Worker) => void): void; - export function on(event: "exit", listener: (worker: Worker, code: number, signal: string) => void): void; - export function on(event: "fork", listener: (worker: Worker) => void): void; - export function on(event: "listening", listener: (worker: Worker, address: any) => void): void; - export function on(event: "message", listener: (worker: Worker, message: any) => void): void; - export function on(event: "online", listener: (worker: Worker) => void): void; - export function on(event: "setup", listener: (settings: any) => void): void; - export function on(event: string, listener: Function): any; - export function once(event: string, listener: Function): void; - export function removeListener(event: string, listener: Function): void; - export function removeAllListeners(event?: string): void; - export function setMaxListeners(n: number): void; - export function listeners(event: string): Function[]; - export function emit(event: string, ...args: any[]): boolean; -} - -declare module "zlib" { - import * as stream from "stream"; - export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; } - - export interface Gzip extends stream.Transform { } - export interface Gunzip extends stream.Transform { } - export interface Deflate extends stream.Transform { } - export interface Inflate extends stream.Transform { } - export interface DeflateRaw extends stream.Transform { } - export interface InflateRaw extends stream.Transform { } - export interface Unzip extends stream.Transform { } - - export function createGzip(options?: ZlibOptions): Gzip; - export function createGunzip(options?: ZlibOptions): Gunzip; - export function createDeflate(options?: ZlibOptions): Deflate; - export function createInflate(options?: ZlibOptions): Inflate; - export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; - export function createInflateRaw(options?: ZlibOptions): InflateRaw; - export function createUnzip(options?: ZlibOptions): Unzip; - - export function deflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function deflateSync(buf: Buffer, options?: ZlibOptions): any; - export function deflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function deflateRawSync(buf: Buffer, options?: ZlibOptions): any; - export function gzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function gzipSync(buf: Buffer, options?: ZlibOptions): any; - export function gunzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function gunzipSync(buf: Buffer, options?: ZlibOptions): any; - export function inflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function inflateSync(buf: Buffer, options?: ZlibOptions): any; - export function inflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function inflateRawSync(buf: Buffer, options?: ZlibOptions): any; - export function unzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; - export function unzipSync(buf: Buffer, options?: ZlibOptions): any; - - // Constants - export var Z_NO_FLUSH: number; - export var Z_PARTIAL_FLUSH: number; - export var Z_SYNC_FLUSH: number; - export var Z_FULL_FLUSH: number; - export var Z_FINISH: number; - export var Z_BLOCK: number; - export var Z_TREES: number; - export var Z_OK: number; - export var Z_STREAM_END: number; - export var Z_NEED_DICT: number; - export var Z_ERRNO: number; - export var Z_STREAM_ERROR: number; - export var Z_DATA_ERROR: number; - export var Z_MEM_ERROR: number; - export var Z_BUF_ERROR: number; - export var Z_VERSION_ERROR: number; - export var Z_NO_COMPRESSION: number; - export var Z_BEST_SPEED: number; - export var Z_BEST_COMPRESSION: number; - export var Z_DEFAULT_COMPRESSION: number; - export var Z_FILTERED: number; - export var Z_HUFFMAN_ONLY: number; - export var Z_RLE: number; - export var Z_FIXED: number; - export var Z_DEFAULT_STRATEGY: number; - export var Z_BINARY: number; - export var Z_TEXT: number; - export var Z_ASCII: number; - export var Z_UNKNOWN: number; - export var Z_DEFLATED: number; - export var Z_NULL: number; -} - -declare module "os" { - export interface CpuInfo { - model: string; - speed: number; - times: { - user: number; - nice: number; - sys: number; - idle: number; - irq: number; - }; - } - - export interface NetworkInterfaceInfo { - address: string; - netmask: string; - family: string; - mac: string; - internal: boolean; - } - - export function tmpdir(): string; - export function homedir(): string; - export function endianness(): "BE" | "LE"; - export function hostname(): string; - export function type(): string; - export function platform(): string; - export function arch(): string; - export function release(): string; - export function uptime(): number; - export function loadavg(): number[]; - export function totalmem(): number; - export function freemem(): number; - export function cpus(): CpuInfo[]; - export function networkInterfaces(): {[index: string]: NetworkInterfaceInfo[]}; - export var EOL: string; -} - -declare module "https" { - import * as tls from "tls"; - import * as events from "events"; - import * as http from "http"; - - export interface ServerOptions { - pfx?: any; - key?: any; - passphrase?: string; - cert?: any; - ca?: any; - crl?: any; - ciphers?: string; - honorCipherOrder?: boolean; - requestCert?: boolean; - rejectUnauthorized?: boolean; - NPNProtocols?: any; - SNICallback?: (servername: string) => any; - } - - export interface RequestOptions extends http.RequestOptions { - pfx?: any; - key?: any; - passphrase?: string; - cert?: any; - ca?: any; - ciphers?: string; - rejectUnauthorized?: boolean; - secureProtocol?: string; - } - - export interface Agent extends http.Agent { } - - export interface AgentOptions extends http.AgentOptions { - maxCachedSessions?: number; - } - - export var Agent: { - new (options?: AgentOptions): Agent; - }; - export interface Server extends tls.Server { } - export function createServer(options: ServerOptions, requestListener?: Function): Server; - export function request(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; - export function get(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; - export var globalAgent: Agent; -} - -declare module "punycode" { - export function decode(string: string): string; - export function encode(string: string): string; - export function toUnicode(domain: string): string; - export function toASCII(domain: string): string; - export var ucs2: ucs2; - interface ucs2 { - decode(string: string): number[]; - encode(codePoints: number[]): string; - } - export var version: any; -} - -declare module "repl" { - import * as stream from "stream"; - import * as events from "events"; - - export interface ReplOptions { - prompt?: string; - input?: NodeJS.ReadableStream; - output?: NodeJS.WritableStream; - terminal?: boolean; - eval?: Function; - useColors?: boolean; - useGlobal?: boolean; - ignoreUndefined?: boolean; - writer?: Function; - } - export function start(options: ReplOptions): events.EventEmitter; -} - -declare module "readline" { - import * as events from "events"; - import * as stream from "stream"; - - export interface Key { - sequence?: string; - name?: string; - ctrl?: boolean; - meta?: boolean; - shift?: boolean; - } - - export interface ReadLine extends events.EventEmitter { - setPrompt(prompt: string): void; - prompt(preserveCursor?: boolean): void; - question(query: string, callback: (answer: string) => void): void; - pause(): ReadLine; - resume(): ReadLine; - close(): void; - write(data: string|Buffer, key?: Key): void; - } - - export interface Completer { - (line: string): CompleterResult; - (line: string, callback: (err: any, result: CompleterResult) => void): any; - } - - export interface CompleterResult { - completions: string[]; - line: string; - } - - export interface ReadLineOptions { - input: NodeJS.ReadableStream; - output?: NodeJS.WritableStream; - completer?: Completer; - terminal?: boolean; - historySize?: number; - } - - export function createInterface(input: NodeJS.ReadableStream, output?: NodeJS.WritableStream, completer?: Completer, terminal?: boolean): ReadLine; - export function createInterface(options: ReadLineOptions): ReadLine; - - export function cursorTo(stream: NodeJS.WritableStream, x: number, y: number): void; - export function moveCursor(stream: NodeJS.WritableStream, dx: number|string, dy: number|string): void; - export function clearLine(stream: NodeJS.WritableStream, dir: number): void; - export function clearScreenDown(stream: NodeJS.WritableStream): void; -} - -declare module "vm" { - export interface Context { } - export interface ScriptOptions { - filename?: string; - lineOffset?: number; - columnOffset?: number; - displayErrors?: boolean; - timeout?: number; - cachedData?: Buffer; - produceCachedData?: boolean; - } - export interface RunningScriptOptions { - filename?: string; - lineOffset?: number; - columnOffset?: number; - displayErrors?: boolean; - timeout?: number; - } - export class Script { - constructor(code: string, options?: ScriptOptions); - runInContext(contextifiedSandbox: Context, options?: RunningScriptOptions): any; - runInNewContext(sandbox?: Context, options?: RunningScriptOptions): any; - runInThisContext(options?: RunningScriptOptions): any; - } - export function createContext(sandbox?: Context): Context; - export function isContext(sandbox: Context): boolean; - export function runInContext(code: string, contextifiedSandbox: Context, options?: RunningScriptOptions): any; - export function runInDebugContext(code: string): any; - export function runInNewContext(code: string, sandbox?: Context, options?: RunningScriptOptions): any; - export function runInThisContext(code: string, options?: RunningScriptOptions): any; -} - -declare module "child_process" { - import * as events from "events"; - import * as stream from "stream"; - - export interface ChildProcess extends events.EventEmitter { - stdin: stream.Writable; - stdout: stream.Readable; - stderr: stream.Readable; - stdio: [stream.Writable, stream.Readable, stream.Readable]; - pid: number; - kill(signal?: string): void; - send(message: any, sendHandle?: any): void; - connected: boolean; - disconnect(): void; - unref(): void; - } - - export interface SpawnOptions { - cwd?: string; - env?: any; - stdio?: any; - detached?: boolean; - uid?: number; - gid?: number; - shell?: boolean | string; - } - export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; - - export interface ExecOptions { - cwd?: string; - env?: any; - shell?: string; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - uid?: number; - gid?: number; - } - export interface ExecOptionsWithStringEncoding extends ExecOptions { - encoding: BufferEncoding; - } - export interface ExecOptionsWithBufferEncoding extends ExecOptions { - encoding: string; // specify `null`. - } - export function exec(command: string, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - export function exec(command: string, options: ExecOptionsWithStringEncoding, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - // usage. child_process.exec("tsc", {encoding: null as string}, (err, stdout, stderr) => {}); - export function exec(command: string, options: ExecOptionsWithBufferEncoding, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; - export function exec(command: string, options: ExecOptions, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - - export interface ExecFileOptions { - cwd?: string; - env?: any; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - uid?: number; - gid?: number; - } - export interface ExecFileOptionsWithStringEncoding extends ExecFileOptions { - encoding: BufferEncoding; - } - export interface ExecFileOptionsWithBufferEncoding extends ExecFileOptions { - encoding: string; // specify `null`. - } - export function execFile(file: string, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - export function execFile(file: string, options?: ExecFileOptionsWithStringEncoding, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - // usage. child_process.execFile("file.sh", {encoding: null as string}, (err, stdout, stderr) => {}); - export function execFile(file: string, options?: ExecFileOptionsWithBufferEncoding, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; - export function execFile(file: string, options?: ExecFileOptions, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - export function execFile(file: string, args?: string[], callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - export function execFile(file: string, args?: string[], options?: ExecFileOptionsWithStringEncoding, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - // usage. child_process.execFile("file.sh", ["foo"], {encoding: null as string}, (err, stdout, stderr) => {}); - export function execFile(file: string, args?: string[], options?: ExecFileOptionsWithBufferEncoding, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; - export function execFile(file: string, args?: string[], options?: ExecFileOptions, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; - - export interface ForkOptions { - cwd?: string; - env?: any; - execPath?: string; - execArgv?: string[]; - silent?: boolean; - uid?: number; - gid?: number; - } - export function fork(modulePath: string, args?: string[], options?: ForkOptions): ChildProcess; - - export interface SpawnSyncOptions { - cwd?: string; - input?: string | Buffer; - stdio?: any; - env?: any; - uid?: number; - gid?: number; - timeout?: number; - killSignal?: string; - maxBuffer?: number; - encoding?: string; - shell?: boolean | string; - } - export interface SpawnSyncOptionsWithStringEncoding extends SpawnSyncOptions { - encoding: BufferEncoding; - } - export interface SpawnSyncOptionsWithBufferEncoding extends SpawnSyncOptions { - encoding: string; // specify `null`. - } - export interface SpawnSyncReturns { - pid: number; - output: string[]; - stdout: T; - stderr: T; - status: number; - signal: string; - error: Error; - } - export function spawnSync(command: string): SpawnSyncReturns; - export function spawnSync(command: string, options?: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; - export function spawnSync(command: string, options?: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns; - export function spawnSync(command: string, options?: SpawnSyncOptions): SpawnSyncReturns; - export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; - export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns; - export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptions): SpawnSyncReturns; - - export interface ExecSyncOptions { - cwd?: string; - input?: string | Buffer; - stdio?: any; - env?: any; - shell?: string; - uid?: number; - gid?: number; - timeout?: number; - killSignal?: string; - maxBuffer?: number; - encoding?: string; - } - export interface ExecSyncOptionsWithStringEncoding extends ExecSyncOptions { - encoding: BufferEncoding; - } - export interface ExecSyncOptionsWithBufferEncoding extends ExecSyncOptions { - encoding: string; // specify `null`. - } - export function execSync(command: string): Buffer; - export function execSync(command: string, options?: ExecSyncOptionsWithStringEncoding): string; - export function execSync(command: string, options?: ExecSyncOptionsWithBufferEncoding): Buffer; - export function execSync(command: string, options?: ExecSyncOptions): Buffer; - - export interface ExecFileSyncOptions { - cwd?: string; - input?: string | Buffer; - stdio?: any; - env?: any; - uid?: number; - gid?: number; - timeout?: number; - killSignal?: string; - maxBuffer?: number; - encoding?: string; - } - export interface ExecFileSyncOptionsWithStringEncoding extends ExecFileSyncOptions { - encoding: BufferEncoding; - } - export interface ExecFileSyncOptionsWithBufferEncoding extends ExecFileSyncOptions { - encoding: string; // specify `null`. - } - export function execFileSync(command: string): Buffer; - export function execFileSync(command: string, options?: ExecFileSyncOptionsWithStringEncoding): string; - export function execFileSync(command: string, options?: ExecFileSyncOptionsWithBufferEncoding): Buffer; - export function execFileSync(command: string, options?: ExecFileSyncOptions): Buffer; - export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptionsWithStringEncoding): string; - export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptionsWithBufferEncoding): Buffer; - export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptions): Buffer; -} - -declare module "url" { - export interface Url { - href?: string; - protocol?: string; - auth?: string; - hostname?: string; - port?: string; - host?: string; - pathname?: string; - search?: string; - query?: string | any; - slashes?: boolean; - hash?: string; - path?: string; - } - - export function parse(urlStr: string, parseQueryString?: boolean , slashesDenoteHost?: boolean ): Url; - export function format(url: Url): string; - export function resolve(from: string, to: string): string; -} - -declare module "dns" { - export function lookup(domain: string, family: number, callback: (err: Error, address: string, family: number) =>void ): string; - export function lookup(domain: string, callback: (err: Error, address: string, family: number) =>void ): string; - export function resolve(domain: string, rrtype: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolve(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolve4(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolve6(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolveMx(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolveTxt(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolveSrv(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolveNs(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function resolveCname(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; - export function reverse(ip: string, callback: (err: Error, domains: string[]) =>void ): string[]; -} - -declare module "net" { - import * as stream from "stream"; - - export interface Socket extends stream.Duplex { - // Extended base methods - write(buffer: Buffer): boolean; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; - write(str: string, encoding?: string, fd?: string): boolean; - - connect(port: number, host?: string, connectionListener?: Function): void; - connect(path: string, connectionListener?: Function): void; - bufferSize: number; - setEncoding(encoding?: string): void; - write(data: any, encoding?: string, callback?: Function): void; - destroy(): void; - pause(): void; - resume(): void; - setTimeout(timeout: number, callback?: Function): void; - setNoDelay(noDelay?: boolean): void; - setKeepAlive(enable?: boolean, initialDelay?: number): void; - address(): { port: number; family: string; address: string; }; - unref(): void; - ref(): void; - - remoteAddress: string; - remoteFamily: string; - remotePort: number; - localAddress: string; - localPort: number; - bytesRead: number; - bytesWritten: number; - - // Extended base methods - end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; - end(data?: any, encoding?: string): void; - } - - export var Socket: { - new (options?: { fd?: string; type?: string; allowHalfOpen?: boolean; }): Socket; - }; - - export interface ListenOptions { - port?: number; - host?: string; - backlog?: number; - path?: string; - exclusive?: boolean; - } - - export interface Server extends Socket { - listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): Server; - listen(port: number, hostname?: string, listeningListener?: Function): Server; - listen(port: number, backlog?: number, listeningListener?: Function): Server; - listen(port: number, listeningListener?: Function): Server; - listen(path: string, backlog?: number, listeningListener?: Function): Server; - listen(path: string, listeningListener?: Function): Server; - listen(handle: any, backlog?: number, listeningListener?: Function): Server; - listen(handle: any, listeningListener?: Function): Server; - listen(options: ListenOptions, listeningListener?: Function): Server; - close(callback?: Function): Server; - address(): { port: number; family: string; address: string; }; - getConnections(cb: (error: Error, count: number) => void): void; - ref(): Server; - unref(): Server; - maxConnections: number; - connections: number; - } - export function createServer(connectionListener?: (socket: Socket) =>void ): Server; - export function createServer(options?: { allowHalfOpen?: boolean; }, connectionListener?: (socket: Socket) =>void ): Server; - export function connect(options: { port: number, host?: string, localAddress? : string, localPort? : string, family? : number, allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; - export function connect(port: number, host?: string, connectionListener?: Function): Socket; - export function connect(path: string, connectionListener?: Function): Socket; - export function createConnection(options: { port: number, host?: string, localAddress? : string, localPort? : string, family? : number, allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; - export function createConnection(port: number, host?: string, connectionListener?: Function): Socket; - export function createConnection(path: string, connectionListener?: Function): Socket; - export function isIP(input: string): number; - export function isIPv4(input: string): boolean; - export function isIPv6(input: string): boolean; -} - -declare module "dgram" { - import * as events from "events"; - - interface RemoteInfo { - address: string; - port: number; - size: number; - } - - interface AddressInfo { - address: string; - family: string; - port: number; - } - - export function createSocket(type: string, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket; - - interface Socket extends events.EventEmitter { - send(buf: Buffer, offset: number, length: number, port: number, address: string, callback?: (error: Error, bytes: number) => void): void; - bind(port: number, address?: string, callback?: () => void): void; - close(): void; - address(): AddressInfo; - setBroadcast(flag: boolean): void; - setMulticastTTL(ttl: number): void; - setMulticastLoopback(flag: boolean): void; - addMembership(multicastAddress: string, multicastInterface?: string): void; - dropMembership(multicastAddress: string, multicastInterface?: string): void; - } -} - -declare module "fs" { - import * as stream from "stream"; - import * as events from "events"; - - interface Stats { - isFile(): boolean; - isDirectory(): boolean; - isBlockDevice(): boolean; - isCharacterDevice(): boolean; - isSymbolicLink(): boolean; - isFIFO(): boolean; - isSocket(): boolean; - dev: number; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - rdev: number; - size: number; - blksize: number; - blocks: number; - atime: Date; - mtime: Date; - ctime: Date; - birthtime: Date; - } - - interface FSWatcher extends events.EventEmitter { - close(): void; - } - - export interface ReadStream extends stream.Readable { - close(): void; - destroy(): void; - } - export interface WriteStream extends stream.Writable { - close(): void; - bytesWritten: number; - } - - /** - * Asynchronous rename. - * @param oldPath - * @param newPath - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - /** - * Synchronous rename - * @param oldPath - * @param newPath - */ - export function renameSync(oldPath: string, newPath: string): void; - export function truncate(path: string | Buffer, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function truncate(path: string | Buffer, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function truncateSync(path: string | Buffer, len?: number): void; - export function ftruncate(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function ftruncate(fd: number, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function ftruncateSync(fd: number, len?: number): void; - export function chown(path: string | Buffer, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function chownSync(path: string | Buffer, uid: number, gid: number): void; - export function fchown(fd: number, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function fchownSync(fd: number, uid: number, gid: number): void; - export function lchown(path: string | Buffer, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function lchownSync(path: string | Buffer, uid: number, gid: number): void; - export function chmod(path: string | Buffer, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function chmod(path: string | Buffer, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function chmodSync(path: string | Buffer, mode: number): void; - export function chmodSync(path: string | Buffer, mode: string): void; - export function fchmod(fd: number, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function fchmod(fd: number, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function fchmodSync(fd: number, mode: number): void; - export function fchmodSync(fd: number, mode: string): void; - export function lchmod(path: string | Buffer, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function lchmod(path: string | Buffer, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function lchmodSync(path: string | Buffer, mode: number): void; - export function lchmodSync(path: string | Buffer, mode: string): void; - export function stat(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; - export function lstat(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; - export function fstat(fd: number, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; - export function statSync(path: string | Buffer): Stats; - export function lstatSync(path: string | Buffer): Stats; - export function fstatSync(fd: number): Stats; - export function link(srcpath: string | Buffer, dstpath: string | Buffer, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function linkSync(srcpath: string | Buffer, dstpath: string | Buffer): void; - export function symlink(srcpath: string | Buffer, dstpath: string | Buffer, type?: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function symlinkSync(srcpath: string | Buffer, dstpath: string | Buffer, type?: string): void; - export function readlink(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, linkString: string) => any): void; - export function readlinkSync(path: string | Buffer): string; - export function realpath(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; - export function realpath(path: string | Buffer, cache: {[path: string]: string}, callback: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; - export function realpathSync(path: string | Buffer, cache?: { [path: string]: string }): string; - /* - * Asynchronous unlink - deletes the file specified in {path} - * - * @param path - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function unlink(path: string | Buffer, callback?: (err?: NodeJS.ErrnoException) => void): void; - /* - * Synchronous unlink - deletes the file specified in {path} - * - * @param path - */ - export function unlinkSync(path: string | Buffer): void; - /* - * Asynchronous rmdir - removes the directory specified in {path} - * - * @param path - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function rmdir(path: string | Buffer, callback?: (err?: NodeJS.ErrnoException) => void): void; - /* - * Synchronous rmdir - removes the directory specified in {path} - * - * @param path - */ - export function rmdirSync(path: string | Buffer): void; - /* - * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. - * - * @param path - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function mkdir(path: string | Buffer, callback?: (err?: NodeJS.ErrnoException) => void): void; - /* - * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. - * - * @param path - * @param mode - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function mkdir(path: string | Buffer, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - /* - * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. - * - * @param path - * @param mode - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function mkdir(path: string | Buffer, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; - /* - * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. - * - * @param path - * @param mode - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function mkdirSync(path: string | Buffer, mode?: number): void; - /* - * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. - * - * @param path - * @param mode - * @param callback No arguments other than a possible exception are given to the completion callback. - */ - export function mkdirSync(path: string | Buffer, mode?: string): void; - /* - * Asynchronous mkdtemp - Creates a unique temporary directory. Generates six random characters to be appended behind a required prefix to create a unique temporary directory. - * - * @param prefix - * @param callback The created folder path is passed as a string to the callback's second parameter. - */ - export function mkdtemp(prefix: string, callback?: (err: NodeJS.ErrnoException, folder: string) => void): void; - /* - * Synchronous mkdtemp - Creates a unique temporary directory. Generates six random characters to be appended behind a required prefix to create a unique temporary directory. - * - * @param prefix - * @returns Returns the created folder path. - */ - export function mkdtempSync(prefix: string): string; - export function readdir(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; - export function readdirSync(path: string | Buffer): string[]; - export function close(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function closeSync(fd: number): void; - export function open(path: string | Buffer, flags: string | number, callback: (err: NodeJS.ErrnoException, fd: number) => void): void; - export function open(path: string | Buffer, flags: string | number, mode: number, callback: (err: NodeJS.ErrnoException, fd: number) => void): void; - export function openSync(path: string | Buffer, flags: string | number, mode?: number): number; - export function utimes(path: string | Buffer, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function utimes(path: string | Buffer, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function utimesSync(path: string | Buffer, atime: number, mtime: number): void; - export function utimesSync(path: string | Buffer, atime: Date, mtime: Date): void; - export function futimes(fd: number, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function futimes(fd: number, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function futimesSync(fd: number, atime: number, mtime: number): void; - export function futimesSync(fd: number, atime: Date, mtime: Date): void; - export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; - export function fsyncSync(fd: number): void; - export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; - export function write(fd: number, buffer: Buffer, offset: number, length: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; - export function write(fd: number, data: any, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; - export function write(fd: number, data: any, offset: number, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; - export function write(fd: number, data: any, offset: number, encoding: string, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; - export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number; - export function writeSync(fd: number, data: any, position?: number, enconding?: string): number; - export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void; - export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; - /* - * Asynchronous readFile - Asynchronously reads the entire contents of a file. - * - * @param fileName - * @param encoding - * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. - */ - export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; - /* - * Asynchronous readFile - Asynchronously reads the entire contents of a file. - * - * @param fileName - * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. - * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. - */ - export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void; - /* - * Asynchronous readFile - Asynchronously reads the entire contents of a file. - * - * @param fileName - * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. - * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. - */ - export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; - /* - * Asynchronous readFile - Asynchronously reads the entire contents of a file. - * - * @param fileName - * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. - */ - export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; - /* - * Synchronous readFile - Synchronously reads the entire contents of a file. - * - * @param fileName - * @param encoding - */ - export function readFileSync(filename: string, encoding: string): string; - /* - * Synchronous readFile - Synchronously reads the entire contents of a file. - * - * @param fileName - * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. - */ - export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string; - /* - * Synchronous readFile - Synchronously reads the entire contents of a file. - * - * @param fileName - * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. - */ - export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; - export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; - export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; - export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function appendFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; - export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; - export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; - export function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void; - export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void; - export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void; - export function watch(filename: string, listener?: (event: string, filename: string) => any): FSWatcher; - export function watch(filename: string, encoding: string, listener?: (event: string, filename: string) => any): FSWatcher; - export function watch(filename: string, options: { persistent?: boolean; recursive?: boolean; encoding?: string }, listener?: (event: string, filename: string) => any): FSWatcher; - export function exists(path: string | Buffer, callback?: (exists: boolean) => void): void; - export function existsSync(path: string | Buffer): boolean; - /** Constant for fs.access(). File is visible to the calling process. */ - export var F_OK: number; - /** Constant for fs.access(). File can be read by the calling process. */ - export var R_OK: number; - /** Constant for fs.access(). File can be written by the calling process. */ - export var W_OK: number; - /** Constant for fs.access(). File can be executed by the calling process. */ - export var X_OK: number; - /** Tests a user's permissions for the file specified by path. */ - export function access(path: string | Buffer, callback: (err: NodeJS.ErrnoException) => void): void; - export function access(path: string | Buffer, mode: number, callback: (err: NodeJS.ErrnoException) => void): void; - /** Synchronous version of fs.access. This throws if any accessibility checks fail, and does nothing otherwise. */ - export function accessSync(path: string | Buffer, mode ?: number): void; - export function createReadStream(path: string | Buffer, options?: { - flags?: string; - encoding?: string; - fd?: number; - mode?: number; - autoClose?: boolean; - start?: number; - end?: number; - }): ReadStream; - export function createWriteStream(path: string | Buffer, options?: { - flags?: string; - encoding?: string; - fd?: number; - mode?: number; - }): WriteStream; -} - -declare module "path" { - - /** - * A parsed path object generated by path.parse() or consumed by path.format(). - */ - export interface ParsedPath { - /** - * The root of the path such as '/' or 'c:\' - */ - root: string; - /** - * The full directory path such as '/home/user/dir' or 'c:\path\dir' - */ - dir: string; - /** - * The file name including extension (if any) such as 'index.html' - */ - base: string; - /** - * The file extension (if any) such as '.html' - */ - ext: string; - /** - * The file name without extension (if any) such as 'index' - */ - name: string; - } - - /** - * Normalize a string path, reducing '..' and '.' parts. - * When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used. - * - * @param p string path to normalize. - */ - export function normalize(p: string): string; - /** - * Join all arguments together and normalize the resulting path. - * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. - * - * @param paths string paths to join. - */ - export function join(...paths: any[]): string; - /** - * Join all arguments together and normalize the resulting path. - * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. - * - * @param paths string paths to join. - */ - export function join(...paths: string[]): string; - /** - * The right-most parameter is considered {to}. Other parameters are considered an array of {from}. - * - * Starting from leftmost {from} paramter, resolves {to} to an absolute path. - * - * If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory. - * - * @param pathSegments string paths to join. Non-string arguments are ignored. - */ - export function resolve(...pathSegments: any[]): string; - /** - * Determines whether {path} is an absolute path. An absolute path will always resolve to the same location, regardless of the working directory. - * - * @param path path to test. - */ - export function isAbsolute(path: string): boolean; - /** - * Solve the relative path from {from} to {to}. - * At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve. - * - * @param from - * @param to - */ - export function relative(from: string, to: string): string; - /** - * Return the directory name of a path. Similar to the Unix dirname command. - * - * @param p the path to evaluate. - */ - export function dirname(p: string): string; - /** - * Return the last portion of a path. Similar to the Unix basename command. - * Often used to extract the file name from a fully qualified path. - * - * @param p the path to evaluate. - * @param ext optionally, an extension to remove from the result. - */ - export function basename(p: string, ext?: string): string; - /** - * Return the extension of the path, from the last '.' to end of string in the last portion of the path. - * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string - * - * @param p the path to evaluate. - */ - export function extname(p: string): string; - /** - * The platform-specific file separator. '\\' or '/'. - */ - export var sep: string; - /** - * The platform-specific file delimiter. ';' or ':'. - */ - export var delimiter: string; - /** - * Returns an object from a path string - the opposite of format(). - * - * @param pathString path to evaluate. - */ - export function parse(pathString: string): ParsedPath; - /** - * Returns a path string from an object - the opposite of parse(). - * - * @param pathString path to evaluate. - */ - export function format(pathObject: ParsedPath): string; - - export module posix { - export function normalize(p: string): string; - export function join(...paths: any[]): string; - export function resolve(...pathSegments: any[]): string; - export function isAbsolute(p: string): boolean; - export function relative(from: string, to: string): string; - export function dirname(p: string): string; - export function basename(p: string, ext?: string): string; - export function extname(p: string): string; - export var sep: string; - export var delimiter: string; - export function parse(p: string): ParsedPath; - export function format(pP: ParsedPath): string; - } - - export module win32 { - export function normalize(p: string): string; - export function join(...paths: any[]): string; - export function resolve(...pathSegments: any[]): string; - export function isAbsolute(p: string): boolean; - export function relative(from: string, to: string): string; - export function dirname(p: string): string; - export function basename(p: string, ext?: string): string; - export function extname(p: string): string; - export var sep: string; - export var delimiter: string; - export function parse(p: string): ParsedPath; - export function format(pP: ParsedPath): string; - } -} - -declare module "string_decoder" { - export interface NodeStringDecoder { - write(buffer: Buffer): string; - detectIncompleteChar(buffer: Buffer): number; - } - export var StringDecoder: { - new (encoding: string): NodeStringDecoder; - }; -} - -declare module "tls" { - import * as crypto from "crypto"; - import * as net from "net"; - import * as stream from "stream"; - - var CLIENT_RENEG_LIMIT: number; - var CLIENT_RENEG_WINDOW: number; - - export interface Certificate { - /** - * Country code. - */ - C: string; - /** - * Street. - */ - ST: string; - /** - * Locality. - */ - L: string; - /** - * Organization. - */ - O: string; - /** - * Organizational unit. - */ - OU: string; - /** - * Common name. - */ - CN: string; - } - - export interface CipherNameAndProtocol { - /** - * The cipher name. - */ - name: string; - /** - * SSL/TLS protocol version. - */ - version: string; - } - - export class TLSSocket extends stream.Duplex { - /** - * Returns the bound address, the address family name and port of the underlying socket as reported by - * the operating system. - * @returns {any} - An object with three properties, e.g. { port: 12346, family: 'IPv4', address: '127.0.0.1' }. - */ - address(): { port: number; family: string; address: string }; - /** - * A boolean that is true if the peer certificate was signed by one of the specified CAs, otherwise false. - */ - authorized: boolean; - /** - * The reason why the peer's certificate has not been verified. - * This property becomes available only when tlsSocket.authorized === false. - */ - authorizationError: Error; - /** - * Static boolean value, always true. - * May be used to distinguish TLS sockets from regular ones. - */ - encrypted: boolean; - /** - * Returns an object representing the cipher name and the SSL/TLS protocol version of the current connection. - * @returns {CipherNameAndProtocol} - Returns an object representing the cipher name - * and the SSL/TLS protocol version of the current connection. - */ - getCipher(): CipherNameAndProtocol; - /** - * Returns an object representing the peer's certificate. - * The returned object has some properties corresponding to the field of the certificate. - * If detailed argument is true the full chain with issuer property will be returned, - * if false only the top certificate without issuer property. - * If the peer does not provide a certificate, it returns null or an empty object. - * @param {boolean} detailed - If true; the full chain with issuer property will be returned. - * @returns {any} - An object representing the peer's certificate. - */ - getPeerCertificate(detailed?: boolean): { - subject: Certificate; - issuerInfo: Certificate; - issuer: Certificate; - raw: any; - valid_from: string; - valid_to: string; - fingerprint: string; - serialNumber: string; - }; - /** - * Could be used to speed up handshake establishment when reconnecting to the server. - * @returns {any} - ASN.1 encoded TLS session or undefined if none was negotiated. - */ - getSession(): any; - /** - * NOTE: Works only with client TLS sockets. - * Useful only for debugging, for session reuse provide session option to tls.connect(). - * @returns {any} - TLS session ticket or undefined if none was negotiated. - */ - getTLSTicket(): any; - /** - * The string representation of the local IP address. - */ - localAddress: string; - /** - * The numeric representation of the local port. - */ - localPort: string; - /** - * The string representation of the remote IP address. - * For example, '74.125.127.100' or '2001:4860:a005::68'. - */ - remoteAddress: string; - /** - * The string representation of the remote IP family. 'IPv4' or 'IPv6'. - */ - remoteFamily: string; - /** - * The numeric representation of the remote port. For example, 443. - */ - remotePort: number; - /** - * Initiate TLS renegotiation process. - * - * NOTE: Can be used to request peer's certificate after the secure connection has been established. - * ANOTHER NOTE: When running as the server, socket will be destroyed with an error after handshakeTimeout timeout. - * @param {TlsOptions} options - The options may contain the following fields: rejectUnauthorized, - * requestCert (See tls.createServer() for details). - * @param {Function} callback - callback(err) will be executed with null as err, once the renegotiation - * is successfully completed. - */ - renegotiate(options: TlsOptions, callback: (err: Error) => any): any; - /** - * Set maximum TLS fragment size (default and maximum value is: 16384, minimum is: 512). - * Smaller fragment size decreases buffering latency on the client: large fragments are buffered by - * the TLS layer until the entire fragment is received and its integrity is verified; - * large fragments can span multiple roundtrips, and their processing can be delayed due to packet - * loss or reordering. However, smaller fragments add extra TLS framing bytes and CPU overhead, - * which may decrease overall server throughput. - * @param {number} size - TLS fragment size (default and maximum value is: 16384, minimum is: 512). - * @returns {boolean} - Returns true on success, false otherwise. - */ - setMaxSendFragment(size: number): boolean; - } - - export interface TlsOptions { - host?: string; - port?: number; - pfx?: any; //string or buffer - key?: any; //string or buffer - passphrase?: string; - cert?: any; - ca?: any; //string or buffer - crl?: any; //string or string array - ciphers?: string; - honorCipherOrder?: any; - requestCert?: boolean; - rejectUnauthorized?: boolean; - NPNProtocols?: any; //array or Buffer; - SNICallback?: (servername: string) => any; - } - - export interface ConnectionOptions { - host?: string; - port?: number; - socket?: net.Socket; - pfx?: string | Buffer - key?: string | Buffer - passphrase?: string; - cert?: string | Buffer - ca?: (string | Buffer)[]; - rejectUnauthorized?: boolean; - NPNProtocols?: (string | Buffer)[]; - servername?: string; - } - - export interface Server extends net.Server { - close(): Server; - address(): { port: number; family: string; address: string; }; - addContext(hostName: string, credentials: { - key: string; - cert: string; - ca: string; - }): void; - maxConnections: number; - connections: number; - } - - export interface ClearTextStream extends stream.Duplex { - authorized: boolean; - authorizationError: Error; - getPeerCertificate(): any; - getCipher: { - name: string; - version: string; - }; - address: { - port: number; - family: string; - address: string; - }; - remoteAddress: string; - remotePort: number; - } - - export interface SecurePair { - encrypted: any; - cleartext: any; - } - - export interface SecureContextOptions { - pfx?: string | Buffer; - key?: string | Buffer; - passphrase?: string; - cert?: string | Buffer; - ca?: string | Buffer; - crl?: string | string[] - ciphers?: string; - honorCipherOrder?: boolean; - } - - export interface SecureContext { - context: any; - } - - export function createServer(options: TlsOptions, secureConnectionListener?: (cleartextStream: ClearTextStream) =>void ): Server; - export function connect(options: TlsOptions, secureConnectionListener?: () =>void ): ClearTextStream; - export function connect(port: number, host?: string, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; - export function connect(port: number, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; - export function createSecurePair(credentials?: crypto.Credentials, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean): SecurePair; - export function createSecureContext(details: SecureContextOptions): SecureContext; -} - -declare module "crypto" { - export interface CredentialDetails { - pfx: string; - key: string; - passphrase: string; - cert: string; - ca: string | string[]; - crl: string | string[]; - ciphers: string; - } - export interface Credentials { context?: any; } - export function createCredentials(details: CredentialDetails): Credentials; - export function createHash(algorithm: string): Hash; - export function createHmac(algorithm: string, key: string): Hmac; - export function createHmac(algorithm: string, key: Buffer): Hmac; - export interface Hash { - update(data: any, input_encoding?: string): Hash; - digest(encoding: 'buffer'): Buffer; - digest(encoding: string): any; - digest(): Buffer; - } - export interface Hmac extends NodeJS.ReadWriteStream { - update(data: any, input_encoding?: string): Hmac; - digest(encoding: 'buffer'): Buffer; - digest(encoding: string): any; - digest(): Buffer; - } - export function createCipher(algorithm: string, password: any): Cipher; - export function createCipheriv(algorithm: string, key: any, iv: any): Cipher; - export interface Cipher extends NodeJS.ReadWriteStream { - update(data: Buffer): Buffer; - update(data: string, input_encoding: "utf8"|"ascii"|"binary"): Buffer; - update(data: Buffer, input_encoding: any, output_encoding: "binary"|"base64"|"hex"): string; - update(data: string, input_encoding: "utf8"|"ascii"|"binary", output_encoding: "binary"|"base64"|"hex"): string; - final(): Buffer; - final(output_encoding: string): string; - setAutoPadding(auto_padding: boolean): void; - getAuthTag(): Buffer; - } - export function createDecipher(algorithm: string, password: any): Decipher; - export function createDecipheriv(algorithm: string, key: any, iv: any): Decipher; - export interface Decipher extends NodeJS.ReadWriteStream { - update(data: Buffer): Buffer; - update(data: string, input_encoding: "binary"|"base64"|"hex"): Buffer; - update(data: Buffer, input_encoding: any, output_encoding: "utf8"|"ascii"|"binary"): string; - update(data: string, input_encoding: "binary"|"base64"|"hex", output_encoding: "utf8"|"ascii"|"binary"): string; - final(): Buffer; - final(output_encoding: string): string; - setAutoPadding(auto_padding: boolean): void; - setAuthTag(tag: Buffer): void; - } - export function createSign(algorithm: string): Signer; - export interface Signer extends NodeJS.WritableStream { - update(data: any): void; - sign(private_key: string, output_format: string): string; - } - export function createVerify(algorith: string): Verify; - export interface Verify extends NodeJS.WritableStream { - update(data: any): void; - verify(object: string, signature: string, signature_format?: string): boolean; - } - export function createDiffieHellman(prime_length: number): DiffieHellman; - export function createDiffieHellman(prime: number, encoding?: string): DiffieHellman; - export interface DiffieHellman { - generateKeys(encoding?: string): string; - computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string; - getPrime(encoding?: string): string; - getGenerator(encoding: string): string; - getPublicKey(encoding?: string): string; - getPrivateKey(encoding?: string): string; - setPublicKey(public_key: string, encoding?: string): void; - setPrivateKey(public_key: string, encoding?: string): void; - } - export function getDiffieHellman(group_name: string): DiffieHellman; - export function pbkdf2(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, callback: (err: Error, derivedKey: Buffer) => any): void; - export function pbkdf2(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, digest: string, callback: (err: Error, derivedKey: Buffer) => any): void; - export function pbkdf2Sync(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number) : Buffer; - export function pbkdf2Sync(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, digest: string) : Buffer; - export function randomBytes(size: number): Buffer; - export function randomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; - export function pseudoRandomBytes(size: number): Buffer; - export function pseudoRandomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; - export interface RsaPublicKey { - key: string; - padding?: any; - } - export interface RsaPrivateKey { - key: string; - passphrase?: string, - padding?: any; - } - export function publicEncrypt(public_key: string|RsaPublicKey, buffer: Buffer): Buffer - export function privateDecrypt(private_key: string|RsaPrivateKey, buffer: Buffer): Buffer -} - -declare module "stream" { - import * as events from "events"; - - export class Stream extends events.EventEmitter { - pipe(destination: T, options?: { end?: boolean; }): T; - } - - export interface ReadableOptions { - highWaterMark?: number; - encoding?: string; - objectMode?: boolean; - } - - export class Readable extends events.EventEmitter implements NodeJS.ReadableStream { - readable: boolean; - constructor(opts?: ReadableOptions); - _read(size: number): void; - read(size?: number): any; - setEncoding(encoding: string): void; - pause(): void; - resume(): void; - pipe(destination: T, options?: { end?: boolean; }): T; - unpipe(destination?: T): void; - unshift(chunk: any): void; - wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; - push(chunk: any, encoding?: string): boolean; - } - - export interface WritableOptions { - highWaterMark?: number; - decodeStrings?: boolean; - objectMode?: boolean; - } - - export class Writable extends events.EventEmitter implements NodeJS.WritableStream { - writable: boolean; - constructor(opts?: WritableOptions); - _write(chunk: any, encoding: string, callback: Function): void; - write(chunk: any, cb?: Function): boolean; - write(chunk: any, encoding?: string, cb?: Function): boolean; - end(): void; - end(chunk: any, cb?: Function): void; - end(chunk: any, encoding?: string, cb?: Function): void; - } - - export interface DuplexOptions extends ReadableOptions, WritableOptions { - allowHalfOpen?: boolean; - } - - // Note: Duplex extends both Readable and Writable. - export class Duplex extends Readable implements NodeJS.ReadWriteStream { - writable: boolean; - constructor(opts?: DuplexOptions); - _write(chunk: any, encoding: string, callback: Function): void; - write(chunk: any, cb?: Function): boolean; - write(chunk: any, encoding?: string, cb?: Function): boolean; - end(): void; - end(chunk: any, cb?: Function): void; - end(chunk: any, encoding?: string, cb?: Function): void; - } - - export interface TransformOptions extends ReadableOptions, WritableOptions {} - - // Note: Transform lacks the _read and _write methods of Readable/Writable. - export class Transform extends events.EventEmitter implements NodeJS.ReadWriteStream { - readable: boolean; - writable: boolean; - constructor(opts?: TransformOptions); - _transform(chunk: any, encoding: string, callback: Function): void; - _flush(callback: Function): void; - read(size?: number): any; - setEncoding(encoding: string): void; - pause(): void; - resume(): void; - pipe(destination: T, options?: { end?: boolean; }): T; - unpipe(destination?: T): void; - unshift(chunk: any): void; - wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; - push(chunk: any, encoding?: string): boolean; - write(chunk: any, cb?: Function): boolean; - write(chunk: any, encoding?: string, cb?: Function): boolean; - end(): void; - end(chunk: any, cb?: Function): void; - end(chunk: any, encoding?: string, cb?: Function): void; - } - - export class PassThrough extends Transform {} -} - -declare module "util" { - export interface InspectOptions { - showHidden?: boolean; - depth?: number; - colors?: boolean; - customInspect?: boolean; - } - - export function format(format: any, ...param: any[]): string; - export function debug(string: string): void; - export function error(...param: any[]): void; - export function puts(...param: any[]): void; - export function print(...param: any[]): void; - export function log(string: string): void; - export function inspect(object: any, showHidden?: boolean, depth?: number, color?: boolean): string; - export function inspect(object: any, options: InspectOptions): string; - export function isArray(object: any): boolean; - export function isRegExp(object: any): boolean; - export function isDate(object: any): boolean; - export function isError(object: any): boolean; - export function inherits(constructor: any, superConstructor: any): void; - export function debuglog(key:string): (msg:string,...param: any[])=>void; -} - -declare module "assert" { - function internal (value: any, message?: string): void; - namespace internal { - export class AssertionError implements Error { - name: string; - message: string; - actual: any; - expected: any; - operator: string; - generatedMessage: boolean; - - constructor(options?: {message?: string; actual?: any; expected?: any; - operator?: string; stackStartFunction?: Function}); - } - - export function fail(actual?: any, expected?: any, message?: string, operator?: string): void; - export function ok(value: any, message?: string): void; - export function equal(actual: any, expected: any, message?: string): void; - export function notEqual(actual: any, expected: any, message?: string): void; - export function deepEqual(actual: any, expected: any, message?: string): void; - export function notDeepEqual(acutal: any, expected: any, message?: string): void; - export function strictEqual(actual: any, expected: any, message?: string): void; - export function notStrictEqual(actual: any, expected: any, message?: string): void; - export function deepStrictEqual(actual: any, expected: any, message?: string): void; - export function notDeepStrictEqual(actual: any, expected: any, message?: string): void; - export var throws: { - (block: Function, message?: string): void; - (block: Function, error: Function, message?: string): void; - (block: Function, error: RegExp, message?: string): void; - (block: Function, error: (err: any) => boolean, message?: string): void; - }; - - export var doesNotThrow: { - (block: Function, message?: string): void; - (block: Function, error: Function, message?: string): void; - (block: Function, error: RegExp, message?: string): void; - (block: Function, error: (err: any) => boolean, message?: string): void; - }; - - export function ifError(value: any): void; - } - - export = internal; -} - -declare module "tty" { - import * as net from "net"; - - export function isatty(fd: number): boolean; - export interface ReadStream extends net.Socket { - isRaw: boolean; - setRawMode(mode: boolean): void; - isTTY: boolean; - } - export interface WriteStream extends net.Socket { - columns: number; - rows: number; - isTTY: boolean; - } -} - -declare module "domain" { - import * as events from "events"; - - export class Domain extends events.EventEmitter implements NodeJS.Domain { - run(fn: Function): void; - add(emitter: events.EventEmitter): void; - remove(emitter: events.EventEmitter): void; - bind(cb: (err: Error, data: any) => any): any; - intercept(cb: (data: any) => any): any; - dispose(): void; - } - - export function create(): Domain; -} - -declare module "constants" { - export var E2BIG: number; - export var EACCES: number; - export var EADDRINUSE: number; - export var EADDRNOTAVAIL: number; - export var EAFNOSUPPORT: number; - export var EAGAIN: number; - export var EALREADY: number; - export var EBADF: number; - export var EBADMSG: number; - export var EBUSY: number; - export var ECANCELED: number; - export var ECHILD: number; - export var ECONNABORTED: number; - export var ECONNREFUSED: number; - export var ECONNRESET: number; - export var EDEADLK: number; - export var EDESTADDRREQ: number; - export var EDOM: number; - export var EEXIST: number; - export var EFAULT: number; - export var EFBIG: number; - export var EHOSTUNREACH: number; - export var EIDRM: number; - export var EILSEQ: number; - export var EINPROGRESS: number; - export var EINTR: number; - export var EINVAL: number; - export var EIO: number; - export var EISCONN: number; - export var EISDIR: number; - export var ELOOP: number; - export var EMFILE: number; - export var EMLINK: number; - export var EMSGSIZE: number; - export var ENAMETOOLONG: number; - export var ENETDOWN: number; - export var ENETRESET: number; - export var ENETUNREACH: number; - export var ENFILE: number; - export var ENOBUFS: number; - export var ENODATA: number; - export var ENODEV: number; - export var ENOENT: number; - export var ENOEXEC: number; - export var ENOLCK: number; - export var ENOLINK: number; - export var ENOMEM: number; - export var ENOMSG: number; - export var ENOPROTOOPT: number; - export var ENOSPC: number; - export var ENOSR: number; - export var ENOSTR: number; - export var ENOSYS: number; - export var ENOTCONN: number; - export var ENOTDIR: number; - export var ENOTEMPTY: number; - export var ENOTSOCK: number; - export var ENOTSUP: number; - export var ENOTTY: number; - export var ENXIO: number; - export var EOPNOTSUPP: number; - export var EOVERFLOW: number; - export var EPERM: number; - export var EPIPE: number; - export var EPROTO: number; - export var EPROTONOSUPPORT: number; - export var EPROTOTYPE: number; - export var ERANGE: number; - export var EROFS: number; - export var ESPIPE: number; - export var ESRCH: number; - export var ETIME: number; - export var ETIMEDOUT: number; - export var ETXTBSY: number; - export var EWOULDBLOCK: number; - export var EXDEV: number; - export var WSAEINTR: number; - export var WSAEBADF: number; - export var WSAEACCES: number; - export var WSAEFAULT: number; - export var WSAEINVAL: number; - export var WSAEMFILE: number; - export var WSAEWOULDBLOCK: number; - export var WSAEINPROGRESS: number; - export var WSAEALREADY: number; - export var WSAENOTSOCK: number; - export var WSAEDESTADDRREQ: number; - export var WSAEMSGSIZE: number; - export var WSAEPROTOTYPE: number; - export var WSAENOPROTOOPT: number; - export var WSAEPROTONOSUPPORT: number; - export var WSAESOCKTNOSUPPORT: number; - export var WSAEOPNOTSUPP: number; - export var WSAEPFNOSUPPORT: number; - export var WSAEAFNOSUPPORT: number; - export var WSAEADDRINUSE: number; - export var WSAEADDRNOTAVAIL: number; - export var WSAENETDOWN: number; - export var WSAENETUNREACH: number; - export var WSAENETRESET: number; - export var WSAECONNABORTED: number; - export var WSAECONNRESET: number; - export var WSAENOBUFS: number; - export var WSAEISCONN: number; - export var WSAENOTCONN: number; - export var WSAESHUTDOWN: number; - export var WSAETOOMANYREFS: number; - export var WSAETIMEDOUT: number; - export var WSAECONNREFUSED: number; - export var WSAELOOP: number; - export var WSAENAMETOOLONG: number; - export var WSAEHOSTDOWN: number; - export var WSAEHOSTUNREACH: number; - export var WSAENOTEMPTY: number; - export var WSAEPROCLIM: number; - export var WSAEUSERS: number; - export var WSAEDQUOT: number; - export var WSAESTALE: number; - export var WSAEREMOTE: number; - export var WSASYSNOTREADY: number; - export var WSAVERNOTSUPPORTED: number; - export var WSANOTINITIALISED: number; - export var WSAEDISCON: number; - export var WSAENOMORE: number; - export var WSAECANCELLED: number; - export var WSAEINVALIDPROCTABLE: number; - export var WSAEINVALIDPROVIDER: number; - export var WSAEPROVIDERFAILEDINIT: number; - export var WSASYSCALLFAILURE: number; - export var WSASERVICE_NOT_FOUND: number; - export var WSATYPE_NOT_FOUND: number; - export var WSA_E_NO_MORE: number; - export var WSA_E_CANCELLED: number; - export var WSAEREFUSED: number; - export var SIGHUP: number; - export var SIGINT: number; - export var SIGILL: number; - export var SIGABRT: number; - export var SIGFPE: number; - export var SIGKILL: number; - export var SIGSEGV: number; - export var SIGTERM: number; - export var SIGBREAK: number; - export var SIGWINCH: number; - export var SSL_OP_ALL: number; - export var SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: number; - export var SSL_OP_CIPHER_SERVER_PREFERENCE: number; - export var SSL_OP_CISCO_ANYCONNECT: number; - export var SSL_OP_COOKIE_EXCHANGE: number; - export var SSL_OP_CRYPTOPRO_TLSEXT_BUG: number; - export var SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: number; - export var SSL_OP_EPHEMERAL_RSA: number; - export var SSL_OP_LEGACY_SERVER_CONNECT: number; - export var SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER: number; - export var SSL_OP_MICROSOFT_SESS_ID_BUG: number; - export var SSL_OP_MSIE_SSLV2_RSA_PADDING: number; - export var SSL_OP_NETSCAPE_CA_DN_BUG: number; - export var SSL_OP_NETSCAPE_CHALLENGE_BUG: number; - export var SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: number; - export var SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: number; - export var SSL_OP_NO_COMPRESSION: number; - export var SSL_OP_NO_QUERY_MTU: number; - export var SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: number; - export var SSL_OP_NO_SSLv2: number; - export var SSL_OP_NO_SSLv3: number; - export var SSL_OP_NO_TICKET: number; - export var SSL_OP_NO_TLSv1: number; - export var SSL_OP_NO_TLSv1_1: number; - export var SSL_OP_NO_TLSv1_2: number; - export var SSL_OP_PKCS1_CHECK_1: number; - export var SSL_OP_PKCS1_CHECK_2: number; - export var SSL_OP_SINGLE_DH_USE: number; - export var SSL_OP_SINGLE_ECDH_USE: number; - export var SSL_OP_SSLEAY_080_CLIENT_DH_BUG: number; - export var SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG: number; - export var SSL_OP_TLS_BLOCK_PADDING_BUG: number; - export var SSL_OP_TLS_D5_BUG: number; - export var SSL_OP_TLS_ROLLBACK_BUG: number; - export var ENGINE_METHOD_DSA: number; - export var ENGINE_METHOD_DH: number; - export var ENGINE_METHOD_RAND: number; - export var ENGINE_METHOD_ECDH: number; - export var ENGINE_METHOD_ECDSA: number; - export var ENGINE_METHOD_CIPHERS: number; - export var ENGINE_METHOD_DIGESTS: number; - export var ENGINE_METHOD_STORE: number; - export var ENGINE_METHOD_PKEY_METHS: number; - export var ENGINE_METHOD_PKEY_ASN1_METHS: number; - export var ENGINE_METHOD_ALL: number; - export var ENGINE_METHOD_NONE: number; - export var DH_CHECK_P_NOT_SAFE_PRIME: number; - export var DH_CHECK_P_NOT_PRIME: number; - export var DH_UNABLE_TO_CHECK_GENERATOR: number; - export var DH_NOT_SUITABLE_GENERATOR: number; - export var NPN_ENABLED: number; - export var RSA_PKCS1_PADDING: number; - export var RSA_SSLV23_PADDING: number; - export var RSA_NO_PADDING: number; - export var RSA_PKCS1_OAEP_PADDING: number; - export var RSA_X931_PADDING: number; - export var RSA_PKCS1_PSS_PADDING: number; - export var POINT_CONVERSION_COMPRESSED: number; - export var POINT_CONVERSION_UNCOMPRESSED: number; - export var POINT_CONVERSION_HYBRID: number; - export var O_RDONLY: number; - export var O_WRONLY: number; - export var O_RDWR: number; - export var S_IFMT: number; - export var S_IFREG: number; - export var S_IFDIR: number; - export var S_IFCHR: number; - export var S_IFBLK: number; - export var S_IFIFO: number; - export var S_IFSOCK: number; - export var S_IRWXU: number; - export var S_IRUSR: number; - export var S_IWUSR: number; - export var S_IXUSR: number; - export var S_IRWXG: number; - export var S_IRGRP: number; - export var S_IWGRP: number; - export var S_IXGRP: number; - export var S_IRWXO: number; - export var S_IROTH: number; - export var S_IWOTH: number; - export var S_IXOTH: number; - export var S_IFLNK: number; - export var O_CREAT: number; - export var O_EXCL: number; - export var O_NOCTTY: number; - export var O_DIRECTORY: number; - export var O_NOATIME: number; - export var O_NOFOLLOW: number; - export var O_SYNC: number; - export var O_SYMLINK: number; - export var O_DIRECT: number; - export var O_NONBLOCK: number; - export var O_TRUNC: number; - export var O_APPEND: number; - export var F_OK: number; - export var R_OK: number; - export var W_OK: number; - export var X_OK: number; - export var UV_UDP_REUSEADDR: number; -} \ No newline at end of file diff --git a/node/typings/globals/node/typings.json b/node/typings/globals/node/typings.json deleted file mode 100644 index 788506efd..000000000 --- a/node/typings/globals/node/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/77b1b1709315b03b9b1b67c589d599bebeeef2ee/node/node.d.ts", - "raw": "registry:dt/node#6.0.0+20160709114037", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/77b1b1709315b03b9b1b67c589d599bebeeef2ee/node/node.d.ts" - } -} diff --git a/node/typings/globals/q/index.d.ts b/node/typings/globals/q/index.d.ts deleted file mode 100644 index 4449c3184..000000000 --- a/node/typings/globals/q/index.d.ts +++ /dev/null @@ -1,357 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/623f30ab194a3486e014ca39bc7f2089897d6ce4/q/Q.d.ts -declare function Q(promise: Q.IPromise): Q.Promise; -/** - * If value is not a promise, returns a promise that is fulfilled with value. - */ -declare function Q(value: T): Q.Promise; - -declare namespace Q { - interface IPromise { - then(onFulfill?: (value: T) => U | IPromise, onReject?: (error: any) => U | IPromise): IPromise; - } - - interface Deferred { - promise: Promise; - resolve(value?: T): void; - resolve(value?: IPromise): void; - reject(reason: any): void; - notify(value: any): void; - makeNodeResolver(): (reason: any, value: T) => void; - } - - interface Promise { - /** - * Like a finally clause, allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful for collecting resources regardless of whether a job succeeded, like closing a database connection, shutting a server down, or deleting an unneeded key from an object. - - * finally returns a promise, which will become resolved with the same fulfillment value or rejection reason as promise. However, if callback returns a promise, the resolution of the returned promise will be delayed until the promise returned from callback is finished. - */ - fin(finallyCallback: () => any): Promise; - /** - * Like a finally clause, allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful for collecting resources regardless of whether a job succeeded, like closing a database connection, shutting a server down, or deleting an unneeded key from an object. - - * finally returns a promise, which will become resolved with the same fulfillment value or rejection reason as promise. However, if callback returns a promise, the resolution of the returned promise will be delayed until the promise returned from callback is finished. - */ - finally(finallyCallback: () => any): Promise; - - /** - * The then method from the Promises/A+ specification, with an additional progress handler. - */ - then(onFulfill?: (value: T) => U | IPromise, onReject?: (error: any) => U | IPromise, onProgress?: Function): Promise; - - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * - * This is especially useful in conjunction with all - */ - spread(onFulfill: (...args: any[]) => IPromise | U, onReject?: (reason: any) => IPromise | U): Promise; - - fail(onRejected: (reason: any) => U | IPromise): Promise; - - /** - * A sugar method, equivalent to promise.then(undefined, onRejected). - */ - catch(onRejected: (reason: any) => U | IPromise): Promise; - - /** - * A sugar method, equivalent to promise.then(undefined, undefined, onProgress). - */ - progress(onProgress: (progress: any) => any): Promise; - - /** - * Much like then, but with different behavior around unhandled rejection. If there is an unhandled rejection, either because promise is rejected and no onRejected callback was provided, or because onFulfilled or onRejected threw an error or returned a rejected promise, the resulting rejection reason is thrown as an exception in a future turn of the event loop. - * - * This method should be used to terminate chains of promises that will not be passed elsewhere. Since exceptions thrown in then callbacks are consumed and transformed into rejections, exceptions at the end of the chain are easy to accidentally, silently ignore. By arranging for the exception to be thrown in a future turn of the event loop, so that it won't be caught, it causes an onerror event on the browser window, or an uncaughtException event on Node.js's process object. - * - * Exceptions thrown by done will have long stack traces, if Q.longStackSupport is set to true. If Q.onerror is set, exceptions will be delivered there instead of thrown in a future turn. - * - * The Golden Rule of done vs. then usage is: either return your promise to someone else, or if the chain ends with you, call done to terminate it. - */ - done(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any, onProgress?: (progress: any) => any): void; - - /** - * If callback is a function, assumes it's a Node.js-style callback, and calls it as either callback(rejectionReason) when/if promise becomes rejected, or as callback(null, fulfillmentValue) when/if promise becomes fulfilled. If callback is not a function, simply returns promise. - */ - nodeify(callback: (reason: any, value: any) => void): Promise; - - /** - * Returns a promise to get the named property of an object. Essentially equivalent to - * - * promise.then(function (o) { - * return o[propertyName]; - * }); - */ - get(propertyName: String): Promise; - set(propertyName: String, value: any): Promise; - delete(propertyName: String): Promise; - /** - * Returns a promise for the result of calling the named method of an object with the given array of arguments. The object itself is this in the function, just like a synchronous method call. Essentially equivalent to - * - * promise.then(function (o) { - * return o[methodName].apply(o, args); - * }); - */ - post(methodName: String, args: any[]): Promise; - /** - * Returns a promise for the result of calling the named method of an object with the given variadic arguments. The object itself is this in the function, just like a synchronous method call. - */ - invoke(methodName: String, ...args: any[]): Promise; - fapply(args: any[]): Promise; - fcall(...args: any[]): Promise; - - /** - * Returns a promise for an array of the property names of an object. Essentially equivalent to - * - * promise.then(function (o) { - * return Object.keys(o); - * }); - */ - keys(): Promise; - - /** - * A sugar method, equivalent to promise.then(function () { return value; }). - */ - thenResolve(value: U): Promise; - /** - * A sugar method, equivalent to promise.then(function () { throw reason; }). - */ - thenReject(reason: any): Promise; - - /** - * Attaches a handler that will observe the value of the promise when it becomes fulfilled, returning a promise for that same value, perhaps deferred but not replaced by the promise returned by the onFulfilled handler. - */ - tap(onFulfilled: (value: T) => any): Promise; - - timeout(ms: number, message?: string): Promise; - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - delay(ms: number): Promise; - - /** - * Returns whether a given promise is in the fulfilled state. When the static version is used on non-promises, the result is always true. - */ - isFulfilled(): boolean; - /** - * Returns whether a given promise is in the rejected state. When the static version is used on non-promises, the result is always false. - */ - isRejected(): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - isPending(): boolean; - - valueOf(): any; - - /** - * Returns a "state snapshot" object, which will be in one of three forms: - * - * - { state: "pending" } - * - { state: "fulfilled", value: } - * - { state: "rejected", reason: } - */ - inspect(): PromiseState; - } - - interface PromiseState { - /** - * "fulfilled", "rejected", "pending" - */ - state: string; - value?: T; - reason?: any; - } - - // If no value provided, returned promise will be of void type - export function when(): Promise; - - // if no fulfill, reject, or progress provided, returned promise will be of same type - export function when(value: T | IPromise): Promise; - - // If a non-promise value is provided, it will not reject or progress - export function when(value: T | IPromise, onFulfilled: (val: T) => U | IPromise, onRejected?: (reason: any) => U | IPromise, onProgress?: (progress: any) => any): Promise; - - /** - * Currently "impossible" (and I use the term loosely) to implement due to TypeScript limitations as it is now. - * See: https://github.com/Microsoft/TypeScript/issues/1784 for discussion on it. - */ - // export function try(method: Function, ...args: any[]): Promise; - - export function fbind(method: (...args: any[]) => T | IPromise, ...args: any[]): (...args: any[]) => Promise; - - export function fcall(method: (...args: any[]) => T, ...args: any[]): Promise; - - export function send(obj: any, functionName: string, ...args: any[]): Promise; - export function invoke(obj: any, functionName: string, ...args: any[]): Promise; - export function mcall(obj: any, functionName: string, ...args: any[]): Promise; - - export function denodeify(nodeFunction: Function, ...args: any[]): (...args: any[]) => Promise; - export function nbind(nodeFunction: Function, thisArg: any, ...args: any[]): (...args: any[]) => Promise; - export function nfbind(nodeFunction: Function, ...args: any[]): (...args: any[]) => Promise; - export function nfcall(nodeFunction: Function, ...args: any[]): Promise; - export function nfapply(nodeFunction: Function, args: any[]): Promise; - - export function ninvoke(nodeModule: any, functionName: string, ...args: any[]): Promise; - export function npost(nodeModule: any, functionName: string, args: any[]): Promise; - export function nsend(nodeModule: any, functionName: string, ...args: any[]): Promise; - export function nmcall(nodeModule: any, functionName: string, ...args: any[]): Promise; - - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: [IPromise, IPromise, IPromise, IPromise, IPromise, IPromise]): Promise<[A, B, C, D, E, F]>; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: [IPromise, IPromise, IPromise, IPromise, IPromise]): Promise<[A, B, C, D, E]>; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: [IPromise, IPromise, IPromise, IPromise]): Promise<[A, B, C, D]>; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: [IPromise, IPromise, IPromise]): Promise<[A, B, C]>; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: [IPromise, IPromise]): Promise<[A, B]>; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: IPromise[]): Promise; - - /** - * Returns a promise for the first of an array of promises to become settled. - */ - export function race(promises: IPromise[]): Promise; - - /** - * Returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, i.e. become either fulfilled or rejected. - */ - export function allSettled(promises: IPromise[]): Promise[]>; - - export function allResolved(promises: IPromise[]): Promise[]>; - - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: IPromise[], onFulfilled: (...args: T[]) => U | IPromise, onRejected?: (reason: any) => U | IPromise): Promise; - - /** - * Returns a promise that will have the same result as promise, except that if promise is not fulfilled or rejected before ms milliseconds, the returned promise will be rejected with an Error with the given message. If message is not supplied, the message will be "Timed out after " + ms + " ms". - */ - export function timeout(promise: Promise, ms: number, message?: string): Promise; - - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - export function delay(promise: Promise, ms: number): Promise; - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - export function delay(value: T, ms: number): Promise; - /** - * Returns a promise that will be fulfilled with undefined after at least ms milliseconds have passed. - */ - export function delay(ms: number): Promise ; - /** - * Returns whether a given promise is in the fulfilled state. When the static version is used on non-promises, the result is always true. - */ - export function isFulfilled(promise: Promise): boolean; - /** - * Returns whether a given promise is in the rejected state. When the static version is used on non-promises, the result is always false. - */ - export function isRejected(promise: Promise): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - export function isPending(promise: Promise): boolean; - - /** - * Returns a "deferred" object with a: - * promise property - * resolve(value) method - * reject(reason) method - * notify(value) method - * makeNodeResolver() method - */ - export function defer(): Deferred; - - /** - * Returns a promise that is rejected with reason. - */ - export function reject(reason?: any): Promise; - - export function Promise(resolver: (resolve: (val: T | IPromise) => void , reject: (reason: any) => void , notify: (progress: any) => void ) => void ): Promise; - - /** - * Creates a new version of func that accepts any combination of promise and non-promise values, converting them to their fulfillment values before calling the original func. The returned version also always returns a promise: if func does a return or throw, then Q.promised(func) will return fulfilled or rejected promise, respectively. - * - * This can be useful for creating functions that accept either promises or non-promise values, and for ensuring that the function always returns a promise even in the face of unintentional thrown exceptions. - */ - export function promised(callback: (...args: any[]) => T): (...args: any[]) => Promise; - - /** - * Returns whether the given value is a Q promise. - */ - export function isPromise(object: any): boolean; - /** - * Returns whether the given value is a promise (i.e. it's an object with a then function). - */ - export function isPromiseAlike(object: any): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - export function isPending(object: any): boolean; - /** - * If an object is not a promise, it is as "near" as possible. - * If a promise is rejected, it is as "near" as possible too. - * If it’s a fulfilled promise, the fulfillment value is nearer. - * If it’s a deferred promise and the deferred has been resolved, the - * resolution is "nearer". - */ - export function nearer(promise: Promise): T; - - /** - * This is an experimental tool for converting a generator function into a deferred function. This has the potential of reducing nested callbacks in engines that support yield. - */ - export function async(generatorFunction: any): (...args: any[]) => Promise; - export function nextTick(callback: Function): void; - - /** - * A settable property that will intercept any uncaught errors that would otherwise be thrown in the next tick of the event loop, usually as a result of done. Can be useful for getting the full stack trace of an error in browsers, which is not usually possible with window.onerror. - */ - export var onerror: (reason: any) => void; - /** - * A settable property that lets you turn on long stack trace support. If turned on, "stack jumps" will be tracked across asynchronous promise operations, so that if an uncaught error is thrown by done or a rejection reason's stack property is inspected in a rejection callback, a long stack trace is produced. - */ - export var longStackSupport: boolean; - - /** - * Calling resolve with a pending promise causes promise to wait on the passed promise, becoming fulfilled with its fulfillment value or rejected with its rejection reason (or staying pending forever, if the passed promise does). - * Calling resolve with a rejected promise causes promise to be rejected with the passed promise's rejection reason. - * Calling resolve with a fulfilled promise causes promise to be fulfilled with the passed promise's fulfillment value. - * Calling resolve with a non-promise value causes promise to be fulfilled with that value. - */ - export function resolve(object: IPromise): Promise; - /** - * Calling resolve with a pending promise causes promise to wait on the passed promise, becoming fulfilled with its fulfillment value or rejected with its rejection reason (or staying pending forever, if the passed promise does). - * Calling resolve with a rejected promise causes promise to be rejected with the passed promise's rejection reason. - * Calling resolve with a fulfilled promise causes promise to be fulfilled with the passed promise's fulfillment value. - * Calling resolve with a non-promise value causes promise to be fulfilled with that value. - */ - export function resolve(object: T): Promise; - - /** - * Resets the global "Q" variable to the value it has before Q was loaded. - * This will either be undefined if there was no version or the version of Q which was already loaded before. - * @returns { The last version of Q. } - */ - export function noConflict(): typeof Q; -} - -declare module "q" { - export = Q; -} \ No newline at end of file diff --git a/node/typings/globals/q/typings.json b/node/typings/globals/q/typings.json deleted file mode 100644 index 3d59355a8..000000000 --- a/node/typings/globals/q/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/623f30ab194a3486e014ca39bc7f2089897d6ce4/q/Q.d.ts", - "raw": "registry:dt/q#0.0.0+20160613154756", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/623f30ab194a3486e014ca39bc7f2089897d6ce4/q/Q.d.ts" - } -} diff --git a/node/typings/globals/semver/index.d.ts b/node/typings/globals/semver/index.d.ts deleted file mode 100644 index d645d8199..000000000 --- a/node/typings/globals/semver/index.d.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62da6b4c18258e2ff2b306036a70ac88eec4743e/semver/semver.d.ts -declare namespace SemVerModule { - /** - * Return the parsed version, or null if it's not valid. - */ - function valid(v: string, loose?: boolean): string; - /** - * Returns cleaned (removed leading/trailing whitespace, remove '=v' prefix) and parsed version, or null if version is invalid. - */ - function clean(version: string, loose?: boolean): string; - /** - * Return the version incremented by the release type (major, minor, patch, or prerelease), or null if it's not valid. - */ - function inc(v: string, release: string, loose?: boolean): string; - /** - * Return the major version number. - */ - function major(v: string, loose?: boolean): number; - /** - * Return the minor version number. - */ - function minor(v: string, loose?: boolean): number; - /** - * Return the patch version number. - */ - function patch(v: string, loose?: boolean): number; - - // Comparison - /** - * v1 > v2 - */ - function gt(v1: string, v2: string, loose?: boolean): boolean; - /** - * v1 >= v2 - */ - function gte(v1: string, v2: string, loose?: boolean): boolean; - /** - * v1 < v2 - */ - function lt(v1: string, v2: string, loose?: boolean): boolean; - /** - * v1 <= v2 - */ - function lte(v1: string, v2: string, loose?: boolean): boolean; - /** - * v1 == v2 This is true if they're logically equivalent, even if they're not the exact same string. You already know how to compare strings. - */ - function eq(v1: string, v2: string, loose?: boolean): boolean; - /** - * v1 != v2 The opposite of eq. - */ - function neq(v1: string, v2: string, loose?: boolean): boolean; - /** - * Pass in a comparison string, and it'll call the corresponding semver comparison function. "===" and "!==" do simple string comparison, but are included for completeness. Throws if an invalid comparison string is provided. - */ - function cmp(v1: string, comparator: any, v2: string, loose?: boolean): boolean; - /** - * Return 0 if v1 == v2, or 1 if v1 is greater, or -1 if v2 is greater. Sorts in ascending order if passed to Array.sort(). - */ - function compare(v1: string, v2: string, loose?: boolean): number; - /** - * The reverse of compare. Sorts an array of versions in descending order when passed to Array.sort(). - */ - function rcompare(v1: string, v2: string, loose?: boolean): number; - /** - * Returns difference between two versions by the release type (major, premajor, minor, preminor, patch, prepatch, or prerelease), or null if the versions are the same. - */ - function diff(v1: string, v2: string, loose?: boolean): string; - - // Ranges - /** - * Return the valid range or null if it's not valid - */ - function validRange(range: string, loose?: boolean): string; - /** - * Return true if the version satisfies the range. - */ - function satisfies(version: string, range: string, loose?: boolean): boolean; - /** - * Return the highest version in the list that satisfies the range, or null if none of them do. - */ - function maxSatisfying(versions: string[], range: string, loose?: boolean): string; - /** - * Return true if version is greater than all the versions possible in the range. - */ - function gtr(version: string, range: string, loose?: boolean): boolean; - /** - * Return true if version is less than all the versions possible in the range. - */ - function ltr(version: string, range: string, loose?: boolean): boolean; - /** - * Return true if the version is outside the bounds of the range in either the high or low direction. The hilo argument must be either the string '>' or '<'. (This is the function called by gtr and ltr.) - */ - function outside(version: string, range: string, hilo: string, loose?: boolean): boolean; - - class SemVerBase { - raw: string; - loose: boolean; - format(): string; - inspect(): string; - toString(): string; - } - - class SemVer extends SemVerBase { - constructor(version: string, loose?: boolean); - - major: number; - minor: number; - patch: number; - version: string; - build: string[]; - prerelease: string[]; - - compare(other:SemVer): number; - compareMain(other:SemVer): number; - comparePre(other:SemVer): number; - inc(release: string): SemVer; - } - - class Comparator extends SemVerBase { - constructor(comp: string, loose?: boolean); - - semver: SemVer; - operator: string; - value: boolean; - parse(comp: string): void; - test(version:SemVer): boolean; - } - - class Range extends SemVerBase { - constructor(range: string, loose?: boolean); - - set: Comparator[][]; - parseRange(range: string): Comparator[]; - test(version: SemVer): boolean; - } -} - -interface SemVerStatic { - SemVer(version: string, loose?: boolean): SemVerModule.SemVer; - Comparator(comp: string, loose?: boolean): SemVerModule.Comparator; - Range(range: string, loose?: boolean): SemVerModule.Range; - clean(version: string, loose?: boolean): string; - - SEMVER_SPEC_VERSION: string; - - valid(v: string, loose?: boolean): string; - inc(v: string, release: string, loose?: boolean): string; - major(v: string, loose?: boolean): number; - minor(v: string, loose?: boolean): number; - patch(v: string, loose?: boolean): number; - gt(v1: string, v2: string, loose?: boolean): boolean; - gte(v1: string, v2: string, loose?: boolean): boolean; - lt(v1: string, v2: string, loose?: boolean): boolean; - lte(v1: string, v2: string, loose?: boolean): boolean; - eq(v1: string, v2: string, loose?: boolean): boolean; - neq(v1: string, v2: string, loose?: boolean): boolean; - cmp(v1: string, comparator: any, v2: string, loose?: boolean): boolean; - compare(v1: string, v2: string, loose?: boolean): number; - rcompare(v1: string, v2: string, loose?: boolean): number; - diff(v1: string, v2: string, loose?: boolean): string; - validRange(range: string, loose?: boolean): string; - satisfies(version: string, range: string, loose?: boolean): boolean; - maxSatisfying(versions: string[], range: string, loose?: boolean): string; - gtr(version: string, range: string, loose?: boolean): boolean; - ltr(version: string, range: string, loose?: boolean): boolean; - outside(version: string, range: string, hilo: string, loose?: boolean): boolean; -} - -declare var semver: SemVerStatic; - -declare module "semver" { - export = SemVerModule; -} \ No newline at end of file diff --git a/node/typings/globals/semver/typings.json b/node/typings/globals/semver/typings.json deleted file mode 100644 index 23598e09f..000000000 --- a/node/typings/globals/semver/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62da6b4c18258e2ff2b306036a70ac88eec4743e/semver/semver.d.ts", - "raw": "registry:dt/semver#4.3.4+20160608054219", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62da6b4c18258e2ff2b306036a70ac88eec4743e/semver/semver.d.ts" - } -} diff --git a/node/typings/globals/shelljs/index.d.ts b/node/typings/globals/shelljs/index.d.ts deleted file mode 100644 index da6da179f..000000000 --- a/node/typings/globals/shelljs/index.d.ts +++ /dev/null @@ -1,531 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/ae98aa7dd3b0bac69144340c7771be4aca2c4522/shelljs/shelljs.d.ts -declare module "shelljs" -{ - import child = require("child_process"); - - /** - * Changes to directory dir for the duration of the script - * @param {string} dir Directory to change in. - */ - export function cd(dir: string): void; - - /** - * Returns the current directory. - * @return {string} The current directory. - */ - export function pwd(): string; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string[]} ...paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(...paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) - * @param {string[]} ...paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(options: string, ...paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string[]} paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) - * @param {string[]} paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(options: string, paths: string[]): string[]; - - /** - * Returns array of all files (however deep) in the given paths. - * @param {string[]} ...path The path(s) to search. - * @return {string[]} An array of all files (however deep) in the given path(s). - */ - export function find(...path: string[]): string[]; - - /** - * Returns array of all files (however deep) in the given paths. - * @param {string[]} path The path(s) to search. - * @return {string[]} An array of all files (however deep) in the given path(s). - */ - export function find(path: string[]): string[]; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function cp(source: string, dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function cp(source: string[], dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {strin]} source The source. - * @param {string} dest The destination. - */ - export function cp(options: string, source: string, dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function cp(options: string, source: string[], dest: string): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string[]} ...files Files to remove. - */ - export function rm(...files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string[]} files Files to remove. - */ - export function rm(files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} ...files Files to remove. - */ - export function rm(options: string, ...files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} ...files Files to remove. - */ - export function rm(options: string, files: string[]): void; - - /** - * Moves files. The wildcard * is accepted. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function mv(options: string, source: string, dest: string): void; - export function mv(source: string, dest: string): void; - - /** - * Moves files. The wildcard * is accepted. - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function mv(source: string[], dest: string): void; - - /** - * Creates directories. - * @param {string[]} ...dir Directories to create. - */ - export function mkdir(...dir: string[]): void; - - /** - * Creates directories. - * @param {string[]} dir Directories to create. - */ - export function mkdir(dir: string[]): void; - - /** - * Creates directories. - * @param {string} options Available options: p (full paths, will create intermediate dirs if necessary) - * @param {string[]} ...dir The directories to create. - */ - export function mkdir(options: string, ...dir: string[]): void; - - /** - * Creates directories. - * @param {string} options Available options: p (full paths, will create intermediate dirs if necessary) - * @param {string[]} dir The directories to create. - */ - export function mkdir(options: string, dir: string[]): void; - - /** - * Evaluates expression using the available primaries and returns corresponding value. - * @param {string} option '-b': true if path is a block device; '-c': true if path is a character device; '-d': true if path is a directory; '-e': true if path exists; '-f': true if path is a regular file; '-L': true if path is a symboilc link; '-p': true if path is a pipe (FIFO); '-S': true if path is a socket - * @param {string} path The path. - * @return {boolean} See option parameter. - */ - export function test(option: string, path: string): boolean; - - /** - * Returns a string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). Wildcard * accepted. - * @param {string[]} ...files Files to use. - * @return {string} A string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). - */ - export function cat(...files: string[]): string; - - /** - * Returns a string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). Wildcard * accepted. - * @param {string[]} files Files to use. - * @return {string} A string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). - */ - export function cat(files: string[]): string; - - - // Does not work yet. - export interface String - { - /** - * Analogous to the redirection operator > in Unix, but works with JavaScript strings (such as those returned by cat, grep, etc). Like Unix redirections, to() will overwrite any existing file! - * @param {string} file The file to use. - */ - to(file: string): void; - - /** - * Analogous to the redirect-and-append operator >> in Unix, but works with JavaScript strings (such as those returned by cat, grep, etc). - * @param {string} file The file to append to. - */ - toEnd(file: string): void; - } - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {RegExp} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(searchRegex: RegExp, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(searchRegex: string, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} options Available options: -i (Replace contents of 'file' in-place. Note that no backups will be created!) - * @param {RegExp} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(options: string, searchRegex: RegExp, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} options Available options: -i (Replace contents of 'file' in-place. Note that no backups will be created!) - * @param {string} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(options: string, searchRegex: string, replacement: string, file: string): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {RegExp} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(regex_filter: RegExp, ...files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {RegExp} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(regex_filter: RegExp, files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {string} options Available options: -v (Inverse the sense of the regex and print the lines not matching the criteria.) - * @param {string} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(options: string, regex_filter: string, ...files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {string} options Available options: -v (Inverse the sense of the regex and print the lines not matching the criteria.) - * @param {string} regex_filter The regular expression to use. - * @param {string[]} files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(options: string, regex_filter: string, files: string[]): string; - - /** - * Searches for command in the system's PATH. On Windows looks for .exe, .cmd, and .bat extensions. - * @param {string} command The command to search for. - * @return {string} Returns string containing the absolute path to the command. - */ - export function which(command: string): string; - - /** - * Prints string to stdout, and returns string with additional utility methods like .to(). - * @param {string[]} ...text The text to print. - * @return {string} Returns the string that was passed as argument. - */ - export function echo(...text: string[]): string; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {"+N"} dir Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: "+N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {"-N"} dir Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: "-N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} dir Makes the current working directory be the top of the stack, and then executes the equivalent of cd dir. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: string): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {"+N"} dir Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: "+N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {"-N"} dir Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: "-N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {string} dir Makes the current working directory be the top of the stack, and then executes the equivalent of cd dir. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: string): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {"+N"} dir Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(dir: "+N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {"-N"} dir Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(dir: "-N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} dir You can only use -N and +N. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(dir: string): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {"+N"} dir Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, dir: "+N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {"-N"} dir Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, dir: "-N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {string} dir You can only use -N and +N. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, dir: string): string[]; - - /** - * Clears the directory stack by deleting all of the elements. - * @param {"-c"} options Clears the directory stack by deleting all of the elements. - * @return {string[]} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(options: "-c"): string[]; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {"+N"} options Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero. - * @return {string[]} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(options: "+N"): string; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {"-N"} options Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero. - * @return {string[]} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(options: "-N"): string; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {string} options Available options: -c, -N, +N. You can only use those. - * @return {any} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(options: string): any; - - /** - * Links source to dest. Use -f to force the link, should dest already exist. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function ln(source: string, dest: string): void; - - /** - * Links source to dest. Use -f to force the link, should dest already exist. - * @param {string} options Available options: s (symlink), f (force) - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function ln(options: string, source: string, dest: string): void; - - /** - * Exits the current process with the given exit code. - * @param {number} code The exit code. - */ - export function exit(code: number): void; - - /** - * Object containing environment variables (both getter and setter). Shortcut to process.env. - */ - export var env: { [key: string]: string }; - - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @return {ExecOutputReturnValue} Returns an object containing the return code and output as string. - */ - export function exec(command: string): ExecOutputReturnValue; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecOptions} options Silence and synchronous options. - * @return {ExecOutputReturnValue | child.ChildProcess} Returns an object containing the return code and output as string, or if {async:true} was passed, a ChildProcess. - */ - export function exec(command: string, options: ExecOptions): ExecOutputReturnValue | child.ChildProcess; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecOptions} options Silence and synchronous options. - * @param {ExecCallback} callback Receives code and output asynchronously. - */ - export function exec(command: string, options: ExecOptions, callback: ExecCallback): child.ChildProcess; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecCallback} callback Receives code and output asynchronously. - */ - export function exec(command: string, callback: ExecCallback): child.ChildProcess; - - export interface ExecCallback { - (code: number, output: string, error?: string): any; - } - - export interface ExecOptions { - silent?: boolean; - async?: boolean; - } - - export interface ExecOutputReturnValue - { - code: number; - output: string; - } - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {number} octalMode The access mode. Octal. - * @param {string} file The file to use. - */ - export function chmod(octalMode: number, file: string): void; - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {string} mode The access mode. Can be an octal string or a symbolic mode string. - * @param {string} file The file to use. - */ - export function chmod(mode: string, file: string): void; - - // Non-Unix commands - - /** - * Searches and returns string containing a writeable, platform-dependent temporary directory. Follows Python's tempfile algorithm. - * @return {string} The temp file path. - */ - export function tempdir(): string; - - /** - * Tests if error occurred in the last command. - * @return {string} Returns null if no error occurred, otherwise returns string explaining the error - */ - export function error(): string; - - // Configuration - - interface ShellConfig - { - /** - * Suppresses all command output if true, except for echo() calls. Default is false. - * @type {boolean} - */ - silent: boolean; - - /** - * If true the script will die on errors. Default is false. - * @type {boolean} - */ - fatal: boolean; - } - - /** - * The shelljs configuration. - * @type {ShellConfig} - */ - export var config: ShellConfig; -} \ No newline at end of file diff --git a/node/typings/globals/shelljs/typings.json b/node/typings/globals/shelljs/typings.json deleted file mode 100644 index 9db4a17b7..000000000 --- a/node/typings/globals/shelljs/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/ae98aa7dd3b0bac69144340c7771be4aca2c4522/shelljs/shelljs.d.ts", - "raw": "registry:dt/shelljs#0.0.0+20160526131156", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/ae98aa7dd3b0bac69144340c7771be4aca2c4522/shelljs/shelljs.d.ts" - } -} diff --git a/node/typings/index.d.ts b/node/typings/index.d.ts deleted file mode 100644 index 7fc4fdbc0..000000000 --- a/node/typings/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// -/// -/// -/// -/// -/// -/// -/// diff --git a/node/vault.ts b/node/vault.ts index d7c04a8cb..c7fad048e 100644 --- a/node/vault.ts +++ b/node/vault.ts @@ -1,15 +1,16 @@ -import Q = require('q'); import fs = require('fs'); import path = require('path'); import crypto = require('crypto'); var uuidV4 = require('uuid/v4'); var algorithm = "aes-256-ctr"; +var encryptEncoding: 'hex' = 'hex'; +var unencryptedEncoding: 'utf8' = 'utf8'; // // Store sensitive data in proc. -// Main goal: Protects tasks which would dump envvars from leaking secrets innadvertantly +// Main goal: Protects tasks which would dump envvars from leaking secrets inadvertently // the task lib clears after storing. // Also protects against a dump of a process getting the secrets // The secret is generated and stored externally for the lifetime of the task. @@ -43,24 +44,32 @@ export class Vault { } var key = this.getKey(); - var cipher = crypto.createCipher(algorithm, key); - var crypted = cipher.update(data,'utf8','hex') - crypted += cipher.final('hex'); - this._store[name] = crypted; + var iv = crypto.randomBytes(16); + + var cipher = crypto.createCipheriv(algorithm, key, iv); + var crypted = cipher.update(data, unencryptedEncoding, encryptEncoding) + var cryptedFinal = cipher.final(encryptEncoding); + + this._store[name] = iv.toString(encryptEncoding) + crypted + cryptedFinal; return true; } - public retrieveSecret(name: string): string { - var secret = null; + public retrieveSecret(name: string): string | undefined { + var secret: string | undefined; name = (name || '').toLowerCase() if (this._store.hasOwnProperty(name)) { var key = this.getKey(); var data = this._store[name]; - var decipher = crypto.createDecipher(algorithm, key) - var dec = decipher.update(data,'hex','utf8') - dec += decipher.final('utf8'); - secret = dec; + var ivDataBuffer = Buffer.from(data, encryptEncoding); + var iv = ivDataBuffer.slice(0, 16); + var encryptedText = ivDataBuffer.slice(16); + + var decipher = crypto.createDecipheriv(algorithm, key, iv); + var dec = decipher.update(encryptedText); + var decFinal = decipher.final(unencryptedEncoding); + + secret = dec + decFinal; } return secret; @@ -68,10 +77,12 @@ export class Vault { private getKey() { - return fs.readFileSync(this._keyFile).toString('utf8'); + var key = fs.readFileSync(this._keyFile).toString('utf8'); + // Key needs to be hashed to correct length to match algorithm (aes-256-ctr) + return crypto.createHash('sha256').update(key).digest(); } private genKey(): void { - fs.writeFileSync(this._keyFile, uuidV4(), 'utf8'); - } -} \ No newline at end of file + fs.writeFileSync(this._keyFile, uuidV4(), {encoding: 'utf8'}); + } +} diff --git a/open-pullrequest.ps1 b/open-pullrequest.ps1 new file mode 100644 index 000000000..d00c05170 --- /dev/null +++ b/open-pullrequest.ps1 @@ -0,0 +1,29 @@ +param( + [Parameter(Mandatory)] + [string] + $SourceBranch +) + +# Getting a created PR. Result object has interface in accordance with article https://docs.github.com/en/rest/reference/pulls#get-a-pull-request +function Get-PullRequest() { + return (gh api -X GET repos/:owner/:repo/pulls -F head=":owner:$SourceBranch" -f state=open -f base=master | ConvertFrom-Json) +} + +$openedPR = Get-PullRequest + +if ($openedPR.html_url.length -ne 0) { + throw "A PR from $SourceBranch to master already exists." +} + +$buildUrl = "$env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI$env:SYSTEM_TEAMPROJECT/_build/results?buildId=$env:BUILD_BUILDID&_a=summary" +$body = "This PR was auto-generated with [the localization pipeline build]($buildUrl)." + +gh pr create --head $SourceBranch --title 'Localization update' --body $body + +# Getting a number to the opened PR +$PR_NUMBER = (Get-PullRequest).number +Write-Host "##vso[task.setvariable variable=PR_NUMBER]$PR_NUMBER" + +# Getting a link to the opened PR +$PR_LINK = (Get-PullRequest).html_url +Write-Host "##vso[task.setvariable variable=PR_LINK]$PR_LINK" diff --git a/powershell/CompiledHelpers/VstsTaskSdk.cs b/powershell/CompiledHelpers/VstsTaskSdk.cs index f9d8d8ac9..6c37f1e58 100644 --- a/powershell/CompiledHelpers/VstsTaskSdk.cs +++ b/powershell/CompiledHelpers/VstsTaskSdk.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Security; using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; namespace VstsTaskSdk @@ -89,6 +91,17 @@ private bool IsMatchInBypassList(Uri input) return false; } } + + public sealed class VstsHttpHandlerSettings + { + public static RemoteCertificateValidationCallback UnsafeSkipServerCertificateValidation + { + get + { + return ((object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => { return true; }); + } + } + } } namespace VstsTaskSdk.FS diff --git a/powershell/Docs/Commands.md b/powershell/Docs/Commands.md index e13919894..dee6bf7ee 100644 --- a/powershell/Docs/Commands.md +++ b/powershell/Docs/Commands.md @@ -1,26 +1,30 @@ -# Commands (v0.9.0) +# Commands (v0.11.0) ## Table of Contents - * [Find](#find) +* [Find](#find) * [Find-VstsMatch](#find-vstsmatch) * [New-VstsFindOptions](#new-vstsfindoptions) * [New-VstsMatchOptions](#new-vstsmatchoptions) * [Select-VstsMatch](#select-vstsmatch) - * [Input](#input) +* [Input](#input) * [Get-VstsEndpoint](#get-vstsendpoint) * [Get-VstsInput](#get-vstsinput) + * [Get-VstsSecureFileName](#get-vstssecurefilename) + * [Get-VstsSecureFileTicket](#get-vstssecurefileticket) * [Get-VstsTaskVariable](#get-vststaskvariable) * [Get-VstsTaskVariableInfo](#get-vststaskvariableinfo) * [Set-VstsTaskVariable](#set-vststaskvariable) - * [Legacy Find](#legacyfind) +* [Legacy Find](#legacyfind) * [Find-VstsFiles](#find-vstsfiles) - * [Localization](#localization) +* [Localization](#localization) * [Get-VstsLocString](#get-vstslocstring) * [Import-VstsLocStrings](#import-vstslocstrings) - * [Logging Command](#loggingcommand) +* [Logging Command](#loggingcommand) * [Write-VstsAddAttachment](#write-vstsaddattachment) * [Write-VstsAddBuildTag](#write-vstsaddbuildtag) * [Write-VstsAssociateArtifact](#write-vstsassociateartifact) * [Write-VstsLogDetail](#write-vstslogdetail) + * [Write-VstsPrependPath](#write-vstsprependpath) + * [Write-VstsSetEndpoint](#write-vstssetendpoint) * [Write-VstsSetProgress](#write-vstssetprogress) * [Write-VstsSetResult](#write-vstssetresult) * [Write-VstsSetSecret](#write-vstssetsecret) @@ -30,20 +34,24 @@ * [Write-VstsTaskVerbose](#write-vststaskverbose) * [Write-VstsTaskWarning](#write-vststaskwarning) * [Write-VstsUpdateBuildNumber](#write-vstsupdatebuildnumber) + * [Write-VstsUpdateReleaseName](#write-vstsupdatereleasename) * [Write-VstsUploadArtifact](#write-vstsuploadartifact) * [Write-VstsUploadBuildLog](#write-vstsuploadbuildlog) - * [Server OM](#serverom) + * [Write-VstsUploadFile](#write-vstsuploadfile) + * [Write-VstsUploadSummary](#write-vstsuploadsummary) +* [Server OM](#serverom) * [Get-VstsAssemblyReference](#get-vstsassemblyreference) + * [Get-VstsClientCertificate](#get-vstsclientcertificate) * [Get-VstsTfsClientCredentials](#get-vststfsclientcredentials) * [Get-VstsTfsService](#get-vststfsservice) * [Get-VstsVssCredentials](#get-vstsvsscredentials) * [Get-VstsVssHttpClient](#get-vstsvsshttpclient) * [Get-VstsWebProxy](#get-vstswebproxy) - * [Tool](#tool) +* [Tool](#tool) * [Assert-VstsAgent](#assert-vstsagent) * [Assert-VstsPath](#assert-vstspath) * [Invoke-VstsTool](#invoke-vststool) - * [Trace](#trace) +* [Trace](#trace) * [Trace-VstsEnteringInvocation](#trace-vstsenteringinvocation) * [Trace-VstsLeavingInvocation](#trace-vstsleavinginvocation) * [Trace-VstsPath](#trace-vstspath) @@ -152,6 +160,36 @@ SYNTAX DESCRIPTION Gets the value for the specified input name. ``` +### Get-VstsSecureFileName +[table of contents](#toc) | [full](FullHelp/Get-VstsSecureFileName.md) +``` +NAME + Get-VstsSecureFileName + +SYNOPSIS + Gets a secure file name. + +SYNTAX + Get-VstsSecureFileName [-Id] [-Require] [] + +DESCRIPTION + Gets the name for a secure file. +``` +### Get-VstsSecureFileTicket +[table of contents](#toc) | [full](FullHelp/Get-VstsSecureFileTicket.md) +``` +NAME + Get-VstsSecureFileTicket + +SYNOPSIS + Gets a secure file ticket. + +SYNTAX + Get-VstsSecureFileTicket [-Id] [-Require] [] + +DESCRIPTION + Gets the secure file ticket that can be used to download the secure file contents. +``` ### Get-VstsTaskVariable [table of contents](#toc) | [full](FullHelp/Get-VstsTaskVariable.md) ``` @@ -326,6 +364,31 @@ SYNTAX ] [[-StartTime] ] [[-FinishTime] ] [[-Progress] ] [[-State] ] [[-Result] ] [[-Message] ] [-AsOutput] [] ``` +### Write-VstsPrependPath +[table of contents](#toc) | [full](FullHelp/Write-VstsPrependPath.md) +``` +NAME + Write-VstsPrependPath + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsPrependPath [-Path] [-AsOutput] [] +``` +### Write-VstsSetEndpoint +[table of contents](#toc) | [full](FullHelp/Write-VstsSetEndpoint.md) +``` +NAME + Write-VstsSetEndpoint + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsSetEndpoint [-Id] [-Field] [-Key] [-Value] [-AsOutput] + [] +``` ### Write-VstsSetProgress [table of contents](#toc) | [full](FullHelp/Write-VstsSetProgress.md) ``` @@ -438,6 +501,18 @@ SYNOPSIS SYNTAX Write-VstsUpdateBuildNumber [-Value] [-AsOutput] [] ``` +### Write-VstsUpdateReleaseName +[table of contents](#toc) | [full](FullHelp/Write-VstsUpdateReleaseName.md) +``` +NAME + Write-VstsUpdateReleaseName + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUpdateReleaseName [-Name] [-AsOutput] [] +``` ### Write-VstsUploadArtifact [table of contents](#toc) | [full](FullHelp/Write-VstsUploadArtifact.md) ``` @@ -463,6 +538,30 @@ SYNOPSIS SYNTAX Write-VstsUploadBuildLog [-Path] [-AsOutput] [] ``` +### Write-VstsUploadFile +[table of contents](#toc) | [full](FullHelp/Write-VstsUploadFile.md) +``` +NAME + Write-VstsUploadFile + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUploadFile [-Path] [-AsOutput] [] +``` +### Write-VstsUploadSummary +[table of contents](#toc) | [full](FullHelp/Write-VstsUploadSummary.md) +``` +NAME + Write-VstsUploadSummary + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUploadSummary [-Path] [-AsOutput] [] +``` ## Server OM ### Get-VstsAssemblyReference [table of contents](#toc) | [full](FullHelp/Get-VstsAssemblyReference.md) @@ -477,9 +576,9 @@ SYNTAX Get-VstsAssemblyReference [-LiteralPath] [] DESCRIPTION - Not supported for use during task exection. This function is only intended to help developers resolve the - minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. - The interface and output may change between patch releases of the VSTS Task SDK. + Not supported for use during task execution. This function is only intended to help developers resolve + the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client + SDK. The interface and output may change between patch releases of the VSTS Task SDK. Only a subset of the referenced assemblies may actually be required, depending on the functionality used by your task. It is best to bundle only the DLLs required for your scenario. @@ -488,9 +587,24 @@ DESCRIPTION dependencies, and so on until all nested dependencies have been traversed. Dependencies are searched for in the directory of the specified assembly. NET Framework assemblies are omitted. - See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. ``` +### Get-VstsClientCertificate +[table of contents](#toc) | [full](FullHelp/Get-VstsClientCertificate.md) +``` +NAME + Get-VstsClientCertificate + +SYNOPSIS + Gets a client certificate for current connected TFS instance + +SYNTAX + Get-VstsClientCertificate [] + +DESCRIPTION + Gets an instance of a X509Certificate2 that is the client certificate Build/Release agent used. +``` ### Get-VstsTfsClientCredentials [table of contents](#toc) | [full](FullHelp/Get-VstsTfsClientCredentials.md) ``` @@ -511,7 +625,7 @@ DESCRIPTION Refer to Get-VstsTfsService for a more simple to get a TFS service object. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. ``` ### Get-VstsTfsService @@ -531,7 +645,7 @@ DESCRIPTION Gets an instance of an ITfsTeamProjectCollectionObject. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. ``` ### Get-VstsVssCredentials @@ -554,7 +668,7 @@ DESCRIPTION Refer to Get-VstsVssHttpClient for a more simple to get a VSS HTTP client. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. ``` ### Get-VstsVssHttpClient @@ -568,13 +682,13 @@ SYNOPSIS SYNTAX Get-VstsVssHttpClient [-TypeName] [[-OMDirectory] ] [[-Uri] ] [[-VssCredentials] - ] [[-WebProxy] ] [] + ] [[-WebProxy] ] [[-ClientCert] ] [-IgnoreSslError] [] DESCRIPTION Gets an instance of an VSS HTTP client. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. ``` ### Get-VstsWebProxy diff --git a/powershell/Docs/Consuming.md b/powershell/Docs/Consuming.md index 3e053fc31..7fbe2d20e 100644 --- a/powershell/Docs/Consuming.md +++ b/powershell/Docs/Consuming.md @@ -17,6 +17,15 @@ or install a specific version: Save-Module -Name VstsTaskSdk -Path .\ -RequiredVersion 0.7.0 ``` +Using the Save-Module Cmdlet creates a Folder structure like this +``` +VstsTaskSdk +└─── + [...] + VstsTaskSdk.psd1 +``` +you need to manually adjust it to resemble the structure shown in the [Package the SDK with the task](#package-the-sdk-with-the-task) section. That means you need to move the content of the version Folder one directory up. + ## task.json modifications Use the `PowerShell3` execution handler and set the target to the entry PS1 script. The entry PS1 script should be located in the root of the task folder. ```JSON @@ -30,7 +39,7 @@ Use the `PowerShell3` execution handler and set the target to the entry PS1 scri ``` ## Package the SDK with the task -The SDK should be packaged with the task in a `ps_modules` folder. The `ps_modules` folder should be in the root of the task folder. +The SDK should be packaged with the task in a `ps_modules` folder. The `ps_modules` folder should be at the same level as the entry PS1 script. Example layout: Consider the following layout where `MyTask` is the root folder for the task. ``` diff --git a/powershell/Docs/FullHelp/Get-VstsAssemblyReference.md b/powershell/Docs/FullHelp/Get-VstsAssemblyReference.md index bdb384d9c..ff7622e9a 100644 --- a/powershell/Docs/FullHelp/Get-VstsAssemblyReference.md +++ b/powershell/Docs/FullHelp/Get-VstsAssemblyReference.md @@ -11,9 +11,9 @@ SYNTAX Get-VstsAssemblyReference [-LiteralPath] [] DESCRIPTION - Not supported for use during task exection. This function is only intended to help developers resolve the - minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. - The interface and output may change between patch releases of the VSTS Task SDK. + Not supported for use during task execution. This function is only intended to help developers resolve + the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client + SDK. The interface and output may change between patch releases of the VSTS Task SDK. Only a subset of the referenced assemblies may actually be required, depending on the functionality used by your task. It is best to bundle only the DLLs required for your scenario. @@ -22,7 +22,7 @@ DESCRIPTION dependencies, and so on until all nested dependencies have been traversed. Dependencies are searched for in the directory of the specified assembly. NET Framework assemblies are omitted. - See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. PARAMETERS diff --git a/powershell/Docs/FullHelp/Get-VstsClientCertificate.md b/powershell/Docs/FullHelp/Get-VstsClientCertificate.md new file mode 100644 index 000000000..842a8b197 --- /dev/null +++ b/powershell/Docs/FullHelp/Get-VstsClientCertificate.md @@ -0,0 +1,28 @@ +# Get-VstsClientCertificate +[table of contents](../Commands.md#toc) | [brief](../Commands.md#get-vstsclientcertificate) +``` +NAME + Get-VstsClientCertificate + +SYNOPSIS + Gets a client certificate for current connected TFS instance + +SYNTAX + Get-VstsClientCertificate [] + +DESCRIPTION + Gets an instance of a X509Certificate2 that is the client certificate Build/Release agent used. + +PARAMETERS + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). + + -------------------------- EXAMPLE 1 -------------------------- + + PS C:\>$x509cert = Get-ClientCertificate + + WebRequestHandler.ClientCertificates.Add(x509cert) +``` diff --git a/powershell/Docs/FullHelp/Get-VstsSecureFileName.md b/powershell/Docs/FullHelp/Get-VstsSecureFileName.md new file mode 100644 index 000000000..ccc89dde1 --- /dev/null +++ b/powershell/Docs/FullHelp/Get-VstsSecureFileName.md @@ -0,0 +1,40 @@ +# Get-VstsSecureFileName +[table of contents](../Commands.md#toc) | [brief](../Commands.md#get-vstssecurefilename) +``` +NAME + Get-VstsSecureFileName + +SYNOPSIS + Gets a secure file name. + +SYNTAX + Get-VstsSecureFileName [-Id] [-Require] [] + +DESCRIPTION + Gets the name for a secure file. + +PARAMETERS + -Id + Secure file id. + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Require [] + Writes an error to the error pipeline if the ticket is not found. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Get-VstsSecureFileTicket.md b/powershell/Docs/FullHelp/Get-VstsSecureFileTicket.md new file mode 100644 index 000000000..ce974e3aa --- /dev/null +++ b/powershell/Docs/FullHelp/Get-VstsSecureFileTicket.md @@ -0,0 +1,40 @@ +# Get-VstsSecureFileTicket +[table of contents](../Commands.md#toc) | [brief](../Commands.md#get-vstssecurefileticket) +``` +NAME + Get-VstsSecureFileTicket + +SYNOPSIS + Gets a secure file ticket. + +SYNTAX + Get-VstsSecureFileTicket [-Id] [-Require] [] + +DESCRIPTION + Gets the secure file ticket that can be used to download the secure file contents. + +PARAMETERS + -Id + Secure file id. + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Require [] + Writes an error to the error pipeline if the ticket is not found. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Get-VstsTfsClientCredentials.md b/powershell/Docs/FullHelp/Get-VstsTfsClientCredentials.md index 373d76e55..cf6d9d044 100644 --- a/powershell/Docs/FullHelp/Get-VstsTfsClientCredentials.md +++ b/powershell/Docs/FullHelp/Get-VstsTfsClientCredentials.md @@ -18,7 +18,7 @@ DESCRIPTION Refer to Get-VstsTfsService for a more simple to get a TFS service object. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. PARAMETERS @@ -30,7 +30,7 @@ PARAMETERS If not specified, defaults to the directory of the entry script for the task. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. Required? false diff --git a/powershell/Docs/FullHelp/Get-VstsTfsService.md b/powershell/Docs/FullHelp/Get-VstsTfsService.md index 30110f216..2adea683d 100644 --- a/powershell/Docs/FullHelp/Get-VstsTfsService.md +++ b/powershell/Docs/FullHelp/Get-VstsTfsService.md @@ -15,7 +15,7 @@ DESCRIPTION Gets an instance of an ITfsTeamProjectCollectionObject. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. PARAMETERS @@ -36,7 +36,7 @@ PARAMETERS If not specified, defaults to the directory of the entry script for the task. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. Required? false @@ -56,7 +56,7 @@ PARAMETERS Accept wildcard characters? false -TfsClientCredentials - Credentials to use when intializing the service. If not specified, the default uses the agent job + Credentials to use when initializing the service. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). diff --git a/powershell/Docs/FullHelp/Get-VstsVssCredentials.md b/powershell/Docs/FullHelp/Get-VstsVssCredentials.md index d18876e49..72a19c6b9 100644 --- a/powershell/Docs/FullHelp/Get-VstsVssCredentials.md +++ b/powershell/Docs/FullHelp/Get-VstsVssCredentials.md @@ -18,7 +18,7 @@ DESCRIPTION Refer to Get-VstsVssHttpClient for a more simple to get a VSS HTTP client. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. PARAMETERS @@ -30,7 +30,7 @@ PARAMETERS If not specified, defaults to the directory of the entry script for the task. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. Required? false diff --git a/powershell/Docs/FullHelp/Get-VstsVssHttpClient.md b/powershell/Docs/FullHelp/Get-VstsVssHttpClient.md index 6c44b8e67..681f7870c 100644 --- a/powershell/Docs/FullHelp/Get-VstsVssHttpClient.md +++ b/powershell/Docs/FullHelp/Get-VstsVssHttpClient.md @@ -9,13 +9,13 @@ SYNOPSIS SYNTAX Get-VstsVssHttpClient [-TypeName] [[-OMDirectory] ] [[-Uri] ] [[-VssCredentials] - ] [[-WebProxy] ] [] + ] [[-WebProxy] ] [[-ClientCert] ] [-IgnoreSslError] [] DESCRIPTION Gets an instance of an VSS HTTP client. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. PARAMETERS @@ -36,7 +36,7 @@ PARAMETERS If not specified, defaults to the directory of the entry script for the task. *** DO NOT USE Agent.ServerOMDirectory *** See - https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage + https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. # .PARAMETER Uri @@ -44,15 +44,23 @@ PARAMETERS System.TeamFoundationCollectionUri. # .PARAMETER VssCredentials - # Credentials to use when intializing the HTTP client. If not specified, the default uses the agent + # Credentials to use when initializing the HTTP client. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). # .PARAMETER WebProxy - # WebProxy to use when intializing the HTTP client. If not specified, the default uses the proxy + # WebProxy to use when initializing the HTTP client. If not specified, the default uses the proxy configuration agent current has. + # .PARAMETER ClientCert + # ClientCert to use when initializing the HTTP client. If not specified, the default uses the client + certificate agent current has. + + # .PARAMETER IgnoreSslError + # Skip SSL server certificate validation on all requests made by this HTTP client. If not specified, + the default is to validate SSL server certificate. + Required? false Position? 2 Default value @@ -83,6 +91,22 @@ PARAMETERS Accept pipeline input? false Accept wildcard characters? false + -ClientCert + + Required? false + Position? 6 + Default value (Get-ClientCertificate) + Accept pipeline input? false + Accept wildcard characters? false + + -IgnoreSslError [] + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, diff --git a/powershell/Docs/FullHelp/Get-VstsWebProxy.md b/powershell/Docs/FullHelp/Get-VstsWebProxy.md index 80e4ca630..b6c33ccae 100644 --- a/powershell/Docs/FullHelp/Get-VstsWebProxy.md +++ b/powershell/Docs/FullHelp/Get-VstsWebProxy.md @@ -26,5 +26,5 @@ PARAMETERS PS C:\>$webProxy = Get-VstsWebProxy - $webProxy.GetProxy(New-Object System.Uri("https://github.com/Microsoft/vsts-task-lib")) + $webProxy.GetProxy(New-Object System.Uri("https://github.com/Microsoft/azure-pipelines-task-lib")) ``` diff --git a/powershell/Docs/FullHelp/Write-VstsPrependPath.md b/powershell/Docs/FullHelp/Write-VstsPrependPath.md new file mode 100644 index 000000000..258b17f1d --- /dev/null +++ b/powershell/Docs/FullHelp/Write-VstsPrependPath.md @@ -0,0 +1,36 @@ +# Write-VstsPrependPath +[table of contents](../Commands.md#toc) | [brief](../Commands.md#write-vstsprependpath) +``` +NAME + Write-VstsPrependPath + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsPrependPath [-Path] [-AsOutput] [] + +PARAMETERS + -Path + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -AsOutput [] + Indicates whether to write the logging command directly to the host or to the output pipeline. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Write-VstsSetEndpoint.md b/powershell/Docs/FullHelp/Write-VstsSetEndpoint.md new file mode 100644 index 000000000..9ecf85d9f --- /dev/null +++ b/powershell/Docs/FullHelp/Write-VstsSetEndpoint.md @@ -0,0 +1,61 @@ +# Write-VstsSetEndpoint +[table of contents](../Commands.md#toc) | [brief](../Commands.md#write-vstssetendpoint) +``` +NAME + Write-VstsSetEndpoint + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsSetEndpoint [-Id] [-Field] [-Key] [-Value] [-AsOutput] + [] + +PARAMETERS + -Id + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Field + + Required? true + Position? 2 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Key + + Required? true + Position? 3 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Value + + Required? true + Position? 4 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -AsOutput [] + Indicates whether to write the logging command directly to the host or to the output pipeline. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Write-VstsUpdateReleaseName.md b/powershell/Docs/FullHelp/Write-VstsUpdateReleaseName.md new file mode 100644 index 000000000..1f9916541 --- /dev/null +++ b/powershell/Docs/FullHelp/Write-VstsUpdateReleaseName.md @@ -0,0 +1,36 @@ +# Write-VstsUpdateReleaseName +[table of contents](../Commands.md#toc) | [brief](../Commands.md#write-vstsupdatereleasename) +``` +NAME + Write-VstsUpdateReleaseName + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUpdateReleaseName [-Name] [-AsOutput] [] + +PARAMETERS + -Name + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -AsOutput [] + Indicates whether to write the logging command directly to the host or to the output pipeline. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Write-VstsUploadFile.md b/powershell/Docs/FullHelp/Write-VstsUploadFile.md new file mode 100644 index 000000000..db086ace1 --- /dev/null +++ b/powershell/Docs/FullHelp/Write-VstsUploadFile.md @@ -0,0 +1,36 @@ +# Write-VstsUploadFile +[table of contents](../Commands.md#toc) | [brief](../Commands.md#write-vstsuploadfile) +``` +NAME + Write-VstsUploadFile + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUploadFile [-Path] [-AsOutput] [] + +PARAMETERS + -Path + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -AsOutput [] + Indicates whether to write the logging command directly to the host or to the output pipeline. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/FullHelp/Write-VstsUploadSummary.md b/powershell/Docs/FullHelp/Write-VstsUploadSummary.md new file mode 100644 index 000000000..62ee2e660 --- /dev/null +++ b/powershell/Docs/FullHelp/Write-VstsUploadSummary.md @@ -0,0 +1,36 @@ +# Write-VstsUploadSummary +[table of contents](../Commands.md#toc) | [brief](../Commands.md#write-vstsuploadsummary) +``` +NAME + Write-VstsUploadSummary + +SYNOPSIS + See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +SYNTAX + Write-VstsUploadSummary [-Path] [-AsOutput] [] + +PARAMETERS + -Path + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -AsOutput [] + Indicates whether to write the logging command directly to the host or to the output pipeline. + + Required? false + Position? named + Default value False + Accept pipeline input? false + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). +``` diff --git a/powershell/Docs/README.md b/powershell/Docs/README.md index 377bafc80..e01b5facb 100644 --- a/powershell/Docs/README.md +++ b/powershell/Docs/README.md @@ -5,7 +5,7 @@ The VSTS Task SDK for PowerShell is designed to work with the agent's new PowerS ## Reference Examples -The [MSBuild Task](https://github.com/Microsoft/vsts-tasks/blob/master/Tasks/MSBuild/MSBuild.ps1) and [VSBuild Task](https://github.com/Microsoft/vsts-tasks/blob/master/Tasks/VSBuild/VSBuild.ps1) are good examples. +The [MSBuild Task](https://github.com/Microsoft/vsts-tasks/blob/master/Tasks/MSBuildV1/MSBuild.ps1) and [VSBuild Task](https://github.com/Microsoft/vsts-tasks/blob/master/Tasks/VSBuildV1/VSBuild.ps1) are good examples. ## Documentation @@ -17,4 +17,5 @@ The [MSBuild Task](https://github.com/Microsoft/vsts-tasks/blob/master/Tasks/MSB ### [Using the VSTS .NET SDKs](UsingOM.md) ### [Minimum agent version](../../node/docs/minagent.md) ### [Proxy](../../node/docs/proxy.md) +### [Certificate](../../node/docs/cert.md) ### [Release notes](ReleaseNotes.md) diff --git a/powershell/Docs/ReleaseNotes.md b/powershell/Docs/ReleaseNotes.md index 072110f07..b4180428a 100644 --- a/powershell/Docs/ReleaseNotes.md +++ b/powershell/Docs/ReleaseNotes.md @@ -1,54 +1,69 @@ # Release Notes +## 0.14.0 +* Improved error handling in function `Find-Files` + +## 0.13.0 +* Added parameter `IgnoreHostException` for function `Invoke-Tool` to suppress `System.Management.Automation.Host.HostException` + +## 0.12.0 +* Fixed issue when Find-Match in powershell does not support searching a pattern in a path that does not exist (#365) +* Resolved issue with loading `Newtonsoft.Json.dll` +* Made `Write-LoggingCommand` exported + +## 0.11.0 +* Added input functions `Get-SecureFileTicket` and `Get-SecureFileName`. +* Added missing agent commands. + ## 0.10.0 - * Updated `Get-VstsVssHttpClient`. Added `-WebProxy` parameter. `Get-VstsVssHttpClient` will follow agent proxy setting by default. - * Added `Get-VstsWebProxy` to retrieve agent proxy settings. +* Updated `Get-VstsVssHttpClient`. Added `-WebProxy` parameter. `Get-VstsVssHttpClient` will follow agent proxy setting by default. +* Added `Get-VstsWebProxy` to retrieve agent proxy settings. ## 0.9.0 - * Breaking change for `Select-VstsMatch` due to positional parameter changes and partial parameter name impact. - * Updated `Select-VstsMatch`. Added `-PatternRoot` parameter. - * Added `Assert-VstsAgent`. +* Breaking change for `Select-VstsMatch` due to positional parameter changes and partial parameter name impact. +* Updated `Select-VstsMatch`. Added `-PatternRoot` parameter. +* Added `Assert-VstsAgent`. ## 0.8.2 - * Fixed issue with env block size limit. +* Fixed issue with env block size limit. ## 0.8.0 - * Added `Find-VstsMatch` and `Select-VstsMatch` for finding files with advanced pattern matching. +* Added `Find-VstsMatch` and `Select-VstsMatch` for finding files with advanced pattern matching. ## 0.7.1 - * Updated `Find-VstsFiles` to fix error when traversing files with IntegrityStream, Virtual, or NoScrubData attribute. +* Updated `Find-VstsFiles` to fix error when traversing files with IntegrityStream, Virtual, or NoScrubData attribute. ## 0.7.0 - * Breaking changes for `Get-VstsTfsClientCredentials` and `Get-VstsVssCredentials`. See [Using the VSTS REST SDK and TFS Extended Client SDK](UsingOM.md). - * Added `Get-VstsTfsService` and `Get-VstsVssHttpClient`. - * Added `Get-VstsTaskVariableInfo` to get all task variables, secret and non-secret. +* Breaking changes for `Get-VstsTfsClientCredentials` and `Get-VstsVssCredentials`. See [Using the VSTS REST SDK and TFS Extended Client SDK](UsingOM.md). +* Added `Get-VstsTfsService` and `Get-VstsVssHttpClient`. +* Added `Get-VstsTaskVariableInfo` to get all task variables, secret and non-secret. ## 0.6.4 - * Updated `Get-VstsTfsClientCredentials` to fix authentication bugs. +* Updated `Get-VstsTfsClientCredentials` to fix authentication bugs. ## 0.6.3 - * Updated `Find-VstsFiles` to fix `-IncludeDirectories` functionality. - * Updated initialization (`Invoke-VstsTaskScript`) to remove secret variables from the environment drive. The variables are stored within the module as `PSCredential` objects. `Get-VstsTaskVariable` has been updated to retrieve the internally stored secret variables and return the plain values. Otherwise `Get-VstsTaskVariable` falls back to checking for the variable as a non-secret variable on the environment drive. +* Updated `Find-VstsFiles` to fix `-IncludeDirectories` functionality. +* Updated initialization (`Invoke-VstsTaskScript`) to remove secret variables from the environment drive. The variables are stored within the module as `PSCredential` objects. `Get-VstsTaskVariable` has been updated to retrieve the internally stored secret variables and return the plain values. Otherwise `Get-VstsTaskVariable` falls back to checking for the variable as a non-secret variable on the environment drive. ## 0.6.2 - * Updated initialization (`Invoke-VstsTaskScript`) to run within the global session state. Modules imported by the task script will now be imported into the global session state. +* Updated initialization (`Invoke-VstsTaskScript`) to run within the global session state. Modules imported by the task script will now be imported into the global session state. ## 0.6.1 - * Updated initialization (`Invoke-VstsTaskScript`) to globally set `ErrorActionPreference` to `Stop`. - * Updated initialization (`Invoke-VstsTaskScript`) to remove input and endpoint variables from the environment drive. The variables are stored within the module as `PSCredential` objects. `Get-VstsInput` and `Get-VstsEndpoint` have been updated to retrieve the internally stored variables and return the plain values. - * Updated `Invoke-VstsTool`. The command line being invoked is now written to the host stream. - * Added `Write-VstsSetSecret`. +* Updated initialization (`Invoke-VstsTaskScript`) to globally set `ErrorActionPreference` to `Stop`. +* Updated initialization (`Invoke-VstsTaskScript`) to remove input and endpoint variables from the environment drive. The variables are stored within the module as `PSCredential` objects. `Get-VstsInput` and `Get-VstsEndpoint` have been updated to retrieve the internally stored variables and return the plain values. +* Updated `Invoke-VstsTool`. The command line being invoked is now written to the host stream. +* Added `Write-VstsSetSecret`. ## 0.6.0 - * Updated `Get-VstsEndpoint`. Added a `Data` property to the endpoint object. - * Updated `Write-VstsSetVariable`. Added a `Secret` switch. - * Added `Write-VstsAddBuildTag`. +* Updated `Get-VstsEndpoint`. Added a `Data` property to the endpoint object. +* Updated `Write-VstsSetVariable`. Added a `Secret` switch. +* Added `Write-VstsAddBuildTag`. ## 0.5.4 - * Loc string updates for TFS 2015 Update 2. +* Loc string updates for TFS 2015 Update 2. ## 0.5.1 - * Updated `Write-VstsAssociateArtifact`. Added a mandatory `Type` parameter. Added an optional `Properties` parameter so that additional properties can be stored on an artifact. +* Updated `Write-VstsAssociateArtifact`. Added a mandatory `Type` parameter. Added an optional `Properties` parameter so that additional properties can be stored on an artifact. ## 0.5.0 - * Initial release. +* Initial release. diff --git a/powershell/Docs/TestingAndDebugging.md b/powershell/Docs/TestingAndDebugging.md index b3fed5fe6..01d2489df 100644 --- a/powershell/Docs/TestingAndDebugging.md +++ b/powershell/Docs/TestingAndDebugging.md @@ -24,7 +24,7 @@ $env:INPUT_MYINPUT = [...] $env:INPUT_MYENDPOINT = 'EP1' $env:ENDPOINT_URL_EP1 = 'https://[...]' $env:ENDPOINT_AUTH_EP1 = '{ "Parameters": { "UserName": "Some user", "Password": "Some password" }, "Scheme": "Some scheme" }' -$env:ENDPOINT_DATA_EP1 = '{ "Key1": "Value1", "Key2", "Value2" }' +$env:ENDPOINT_DATA_EP1 = '{ "Key1": "Value1", "Key2": "Value2" }' ``` For the convenience of interactive testing, the module will prompt for undefined task variables and inputs. For example, `Get-VstsTaskInput -Name SomeVariable` will prompt for the value if the task variable is not defined. If a value is entered, then it will be stored so that subsequent calls will return the same value. Task variables are stored as environment variables. Inputs and endpoints are stored internally within the VstsTaskSdk module and can be cleared by removing and re-importing the module. @@ -49,4 +49,4 @@ To view the verbose output when testing interactively: ```PowerShell Import-Module .\ps_modules\VstsTaskSdk Invoke-VstsTaskScript -ScriptBlock { . .\MyTask.ps1 } -Verbose -``` \ No newline at end of file +``` diff --git a/powershell/Docs/Update-Docs.ps1 b/powershell/Docs/Update-Docs.ps1 index cbc63bcec..c8b38e924 100644 --- a/powershell/Docs/Update-Docs.ps1 +++ b/powershell/Docs/Update-Docs.ps1 @@ -25,6 +25,7 @@ function Get-HelpString { foreach ($line in $str.Trim().Replace("`r", "").Split("`n")) { $line = $line.TrimEnd() $line = $line.Replace("http://go.microsoft.com", "https://go.microsoft.com") + $line = $line.Replace("https:/go.microsoft.com", "https://go.microsoft.com") # Add the blank line. if (!$line) { # Prevent multiple blank lines. @@ -118,7 +119,6 @@ foreach ($functionName in $module.ExportedFunctions.Keys) { $functionToFullHelpMap[$functionName] = Get-HelpString -Name $functionName -Full } - # Build a mapping of help sections to functions. Write-Host "Resolving section information." $sectionToFunctionsMap = @{ } @@ -167,7 +167,7 @@ $null = $tocContent.AppendLine("## Table of Contents") foreach ($sectionName in ($sectionToFunctionsMap.Keys | Sort-Object)) { $functionNames = $sectionToFunctionsMap[$sectionName] | Sort-Object $sectionId = $sectionName.Replace(" ", "").ToLowerInvariant() - $null = $tocContent.AppendLine(" * [$sectionName](#$sectionId)") + $null = $tocContent.AppendLine("* [$sectionName](#$sectionId)") $null = $commandsContent.AppendLine("## $sectionName") foreach ($functionName in $functionNames) { $functionId = $functionName.Replace(" ", "").ToLowerInvariant() diff --git a/powershell/Docs/UsingOM.md b/powershell/Docs/UsingOM.md index 321b8e149..23d4ff5cf 100644 --- a/powershell/Docs/UsingOM.md +++ b/powershell/Docs/UsingOM.md @@ -1,4 +1,4 @@ -# Using the VSTS .NET SDKs +# Using the Azure DevOps .NET SDKs ## Bundle the SDK with your task Bundle the subset of the SDK required by your task. diff --git a/powershell/Tests/L0/Assert-Path.DoesNotThrowWhenPathExists.ps1 b/powershell/Tests/L0/Assert-Path.DoesNotThrowWhenPathExists.ps1 index 1b02fb314..804078738 100644 --- a/powershell/Tests/L0/Assert-Path.DoesNotThrowWhenPathExists.ps1 +++ b/powershell/Tests/L0/Assert-Path.DoesNotThrowWhenPathExists.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { $directory = (New-Item -Path $tempDirectory\SomeDir -ItemType Directory).FullName diff --git a/powershell/Tests/L0/Assert-Path.ThrowsWhenNotFound.ps1 b/powershell/Tests/L0/Assert-Path.ThrowsWhenNotFound.ps1 index c6985224b..a38da6874 100644 --- a/powershell/Tests/L0/Assert-Path.ThrowsWhenNotFound.ps1 +++ b/powershell/Tests/L0/Assert-Path.ThrowsWhenNotFound.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { $directory = New-Item -Path $tempDirectory\SomeDir -ItemType Directory diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameSingleCharWildcard.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameSingleCharWildcard.ps1 index 9dab57e86..a3f3caf11 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameSingleCharWildcard.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameSingleCharWildcard.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameWildcard.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameWildcard.ps1 index 27dc04731..c5817627b 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameWildcard.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsDirectoryNameWildcard.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsExcludePattern.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsExcludePattern.ps1 index 1023e7fa5..5f9bf8cf2 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsExcludePattern.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsExcludePattern.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameSingleCharWildcard.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameSingleCharWildcard.ps1 index d58799e61..76096710d 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameSingleCharWildcard.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameSingleCharWildcard.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameWildcard.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameWildcard.ps1 index 9c3e4eef1..bc83655b3 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameWildcard.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsFileNameWildcard.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsGlobstar.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsGlobstar.ps1 index ede36ea1f..8b533876a 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsGlobstar.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsGlobstar.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectories.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectories.ps1 index ea7a0c7a0..d0da9c667 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectories.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectories.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectoriesOnly.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectoriesOnly.ps1 index d58fbcbe8..98abdea34 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectoriesOnly.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsIncludeDirectoriesOnly.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsInterSegmentWildcard.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsInterSegmentWildcard.ps1 index 7d00cb3ae..6df41d905 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternSupportsInterSegmentWildcard.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternSupportsInterSegmentWildcard.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Files.LegacyPatternUnionsMatches.ps1 b/powershell/Tests/L0/Find-Files.LegacyPatternUnionsMatches.ps1 index 23398dc35..8ad3c1592 100644 --- a/powershell/Tests/L0/Find-Files.LegacyPatternUnionsMatches.ps1 +++ b/powershell/Tests/L0/Find-Files.LegacyPatternUnionsMatches.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.AggregatesMatches.ps1 b/powershell/Tests/L0/Find-Match.AggregatesMatches.ps1 index 24d3ae5ba..6815b9b92 100644 --- a/powershell/Tests/L0/Find-Match.AggregatesMatches.ps1 +++ b/powershell/Tests/L0/Find-Match.AggregatesMatches.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.AppliesDefaultFindOptions.ps1 b/powershell/Tests/L0/Find-Match.AppliesDefaultFindOptions.ps1 index b8fb4e991..f416ece16 100644 --- a/powershell/Tests/L0/Find-Match.AppliesDefaultFindOptions.ps1 +++ b/powershell/Tests/L0/Find-Match.AppliesDefaultFindOptions.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.AppliesDefaultMatchOptions.ps1 b/powershell/Tests/L0/Find-Match.AppliesDefaultMatchOptions.ps1 index aaadf5ea3..c2eac9d4b 100644 --- a/powershell/Tests/L0/Find-Match.AppliesDefaultMatchOptions.ps1 +++ b/powershell/Tests/L0/Find-Match.AppliesDefaultMatchOptions.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.BracesNotEscaped.ps1 b/powershell/Tests/L0/Find-Match.BracesNotEscaped.ps1 index d3c7f465e..5bd3ade13 100644 --- a/powershell/Tests/L0/Find-Match.BracesNotEscaped.ps1 +++ b/powershell/Tests/L0/Find-Match.BracesNotEscaped.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.CountsLeadingNegateMarkers.ps1 b/powershell/Tests/L0/Find-Match.CountsLeadingNegateMarkers.ps1 index d01d58d21..cb0921fc4 100644 --- a/powershell/Tests/L0/Find-Match.CountsLeadingNegateMarkers.ps1 +++ b/powershell/Tests/L0/Find-Match.CountsLeadingNegateMarkers.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToCwd.ps1 b/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToCwd.ps1 index 1aa997b4e..90111ae92 100644 --- a/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToCwd.ps1 +++ b/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToCwd.ps1 @@ -6,7 +6,7 @@ param() Invoke-VstsTaskScript -ScriptBlock { $originalSystemDefaultWorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY $originalCwd = Get-Location - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToSystemDefaultWorkingDirectory.ps1 b/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToSystemDefaultWorkingDirectory.ps1 index 468e773db..eb459811f 100644 --- a/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToSystemDefaultWorkingDirectory.ps1 +++ b/powershell/Tests/L0/Find-Match.DefaultRootFallsBackToSystemDefaultWorkingDirectory.ps1 @@ -5,7 +5,7 @@ param() . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { $originalSystemDefaultWorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.DoesNotDuplicateMatches.ps1 b/powershell/Tests/L0/Find-Match.DoesNotDuplicateMatches.ps1 index 10ad93984..f84fba7d6 100644 --- a/powershell/Tests/L0/Find-Match.DoesNotDuplicateMatches.ps1 +++ b/powershell/Tests/L0/Find-Match.DoesNotDuplicateMatches.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.EscapesDefaultRootWhenRootingPatterns.ps1 b/powershell/Tests/L0/Find-Match.EscapesDefaultRootWhenRootingPatterns.ps1 index 585a23958..b9a276c1a 100644 --- a/powershell/Tests/L0/Find-Match.EscapesDefaultRootWhenRootingPatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.EscapesDefaultRootWhenRootingPatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeExpandingBraces.ps1 b/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeExpandingBraces.ps1 index 1490cf110..28f1c9737 100644 --- a/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeExpandingBraces.ps1 +++ b/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeExpandingBraces.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeNegation.ps1 b/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeNegation.ps1 index cc543d192..963830029 100644 --- a/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeNegation.ps1 +++ b/powershell/Tests/L0/Find-Match.EvaluatesCommentsBeforeNegation.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.EvaluatesNegationBeforeExpandingBraces.ps1 b/powershell/Tests/L0/Find-Match.EvaluatesNegationBeforeExpandingBraces.ps1 index c7ef6b815..de8a2b605 100644 --- a/powershell/Tests/L0/Find-Match.EvaluatesNegationBeforeExpandingBraces.ps1 +++ b/powershell/Tests/L0/Find-Match.EvaluatesNegationBeforeExpandingBraces.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SinglePattern.ps1 b/powershell/Tests/L0/Find-Match.SinglePattern.ps1 index 31f23727d..af22e61c6 100644 --- a/powershell/Tests/L0/Find-Match.SinglePattern.ps1 +++ b/powershell/Tests/L0/Find-Match.SinglePattern.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SkipsEmptyPatterns.ps1 b/powershell/Tests/L0/Find-Match.SkipsEmptyPatterns.ps1 index df6553164..aa670f4cd 100644 --- a/powershell/Tests/L0/Find-Match.SkipsEmptyPatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.SkipsEmptyPatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsCustomFindOptions.ps1 b/powershell/Tests/L0/Find-Match.SupportsCustomFindOptions.ps1 index 417f1e0e1..bf530537f 100644 --- a/powershell/Tests/L0/Find-Match.SupportsCustomFindOptions.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsCustomFindOptions.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsFlipNegateTrue.ps1 b/powershell/Tests/L0/Find-Match.SupportsFlipNegateTrue.ps1 index 4982884e1..b840e70cf 100644 --- a/powershell/Tests/L0/Find-Match.SupportsFlipNegateTrue.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsFlipNegateTrue.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsInterleavedExcludePatterns.ps1 b/powershell/Tests/L0/Find-Match.SupportsInterleavedExcludePatterns.ps1 index 7114207c0..b41d862ee 100644 --- a/powershell/Tests/L0/Find-Match.SupportsInterleavedExcludePatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsInterleavedExcludePatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsMatchBaseExcludePatterns.ps1 b/powershell/Tests/L0/Find-Match.SupportsMatchBaseExcludePatterns.ps1 index 9e354aaa3..36b2bcec9 100644 --- a/powershell/Tests/L0/Find-Match.SupportsMatchBaseExcludePatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsMatchBaseExcludePatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatterns.ps1 b/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatterns.ps1 index 3927299fa..fe6c50fe6 100644 --- a/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatternsWithGlob.ps1 b/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatternsWithGlob.ps1 index ccc044e4b..2a6446b6d 100644 --- a/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatternsWithGlob.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsMatchBaseIncludePatternsWithGlob.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsNoBraceFalse.ps1 b/powershell/Tests/L0/Find-Match.SupportsNoBraceFalse.ps1 index 4982884e1..b840e70cf 100644 --- a/powershell/Tests/L0/Find-Match.SupportsNoBraceFalse.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsNoBraceFalse.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsNoCommentTrue.ps1 b/powershell/Tests/L0/Find-Match.SupportsNoCommentTrue.ps1 index 6d5caa2e6..ad2c1b3a5 100644 --- a/powershell/Tests/L0/Find-Match.SupportsNoCommentTrue.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsNoCommentTrue.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsNoNegateTrue.ps1 b/powershell/Tests/L0/Find-Match.SupportsNoNegateTrue.ps1 index dfd746288..fc04a5fc6 100644 --- a/powershell/Tests/L0/Find-Match.SupportsNoNegateTrue.ps1 +++ b/powershell/Tests/L0/Find-Match.SupportsNoNegateTrue.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.SupportsPathNotFound.ps1 b/powershell/Tests/L0/Find-Match.SupportsPathNotFound.ps1 new file mode 100644 index 000000000..bd629a730 --- /dev/null +++ b/powershell/Tests/L0/Find-Match.SupportsPathNotFound.ps1 @@ -0,0 +1,22 @@ +[CmdletBinding()] +param() + +# Arrange. +. $PSScriptRoot\..\lib\Initialize-Test.ps1 +Invoke-VstsTaskScript -ScriptBlock { + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) + New-Item -Path $tempDirectory -ItemType Directory | + ForEach-Object { $_.FullName } + try { + $patterns = "$tempDirectory\NotFound\*.proj" + + # Act. + $actual = Find-VstsMatch -Pattern $patterns + + # Assert. + $expected = $null + Assert-AreEqual $expected $actual + } finally { + Remove-Item $tempDirectory -Recurse + } +} \ No newline at end of file diff --git a/powershell/Tests/L0/Find-Match.TrimsPatterns.ps1 b/powershell/Tests/L0/Find-Match.TrimsPatterns.ps1 index 2f514ded7..816c4ab95 100644 --- a/powershell/Tests/L0/Find-Match.TrimsPatterns.ps1 +++ b/powershell/Tests/L0/Find-Match.TrimsPatterns.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Find-Match.TrimsWhitespaceAfterNegateMarkers.ps1 b/powershell/Tests/L0/Find-Match.TrimsWhitespaceAfterNegateMarkers.ps1 index ad42003dc..623eca703 100644 --- a/powershell/Tests/L0/Find-Match.TrimsWhitespaceAfterNegateMarkers.ps1 +++ b/powershell/Tests/L0/Find-Match.TrimsWhitespaceAfterNegateMarkers.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSpecifiedSymlink.ps1 b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSpecifiedSymlink.ps1 index 7aba63703..00b40514a 100644 --- a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSpecifiedSymlink.ps1 +++ b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSpecifiedSymlink.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlink.ps1 b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlink.ps1 index 68f5d8f3f..86bccec1e 100644 --- a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlink.ps1 +++ b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlink.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlinkWhen-H.ps1 b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlinkWhen-H.ps1 index 8445d8489..c7ebe2672 100644 --- a/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlinkWhen-H.ps1 +++ b/powershell/Tests/L0/Get-FindResult.DoesNotFollowSymlinkWhen-H.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-H.ps1 b/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-H.ps1 index 5312d951a..efdd79c1f 100644 --- a/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-H.ps1 +++ b/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-H.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-L.ps1 b/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-L.ps1 index e5624acf9..8ea902aa8 100644 --- a/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-L.ps1 +++ b/powershell/Tests/L0/Get-FindResult.FollowsSpecifiedSymlinkWhen-L.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.FollowsSymlinkWhen-L.ps1 b/powershell/Tests/L0/Get-FindResult.FollowsSymlinkWhen-L.ps1 index 4091dcc11..238a04934 100644 --- a/powershell/Tests/L0/Get-FindResult.FollowsSymlinkWhen-L.ps1 +++ b/powershell/Tests/L0/Get-FindResult.FollowsSymlinkWhen-L.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.ReturnsEmptyWhenNotExists.ps1 b/powershell/Tests/L0/Get-FindResult.ReturnsEmptyWhenNotExists.ps1 index 844430834..7e211cf6c 100644 --- a/powershell/Tests/L0/Get-FindResult.ReturnsEmptyWhenNotExists.ps1 +++ b/powershell/Tests/L0/Get-FindResult.ReturnsEmptyWhenNotExists.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-FindResult.ReturnsHiddenFiles.ps1 b/powershell/Tests/L0/Get-FindResult.ReturnsHiddenFiles.ps1 index aa605a85c..a7423deb8 100644 --- a/powershell/Tests/L0/Get-FindResult.ReturnsHiddenFiles.ps1 +++ b/powershell/Tests/L0/Get-FindResult.ReturnsHiddenFiles.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { diff --git a/powershell/Tests/L0/Get-LocString.HandlesDotNetFormatException.ps1 b/powershell/Tests/L0/Get-LocString.HandlesDotNetFormatException.ps1 index 5d8f8c2a0..380432ac3 100644 --- a/powershell/Tests/L0/Get-LocString.HandlesDotNetFormatException.ps1 +++ b/powershell/Tests/L0/Get-LocString.HandlesDotNetFormatException.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { New-Item -Path $tempDirectory\my.json -ItemType File -Value @' diff --git a/powershell/Tests/L0/Get-LocString.OverlaysWithCultureSpecific.ps1 b/powershell/Tests/L0/Get-LocString.OverlaysWithCultureSpecific.ps1 index 39a61ee30..0d3d10324 100644 --- a/powershell/Tests/L0/Get-LocString.OverlaysWithCultureSpecific.ps1 +++ b/powershell/Tests/L0/Get-LocString.OverlaysWithCultureSpecific.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { New-Item -Path $tempDirectory\my.json -ItemType File -Value @' diff --git a/powershell/Tests/L0/Get-LocString.SupportsDotNetFormatStrings.ps1 b/powershell/Tests/L0/Get-LocString.SupportsDotNetFormatStrings.ps1 index 2975c9637..079203559 100644 --- a/powershell/Tests/L0/Get-LocString.SupportsDotNetFormatStrings.ps1 +++ b/powershell/Tests/L0/Get-LocString.SupportsDotNetFormatStrings.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { New-Item -Path $tempDirectory\my.json -ItemType File -Value @' diff --git a/powershell/Tests/L0/Get-SecureFileName.ps1 b/powershell/Tests/L0/Get-SecureFileName.ps1 new file mode 100644 index 000000000..3689588b6 --- /dev/null +++ b/powershell/Tests/L0/Get-SecureFileName.ps1 @@ -0,0 +1,17 @@ +[CmdletBinding()] +param() + +# Arrange. +. $PSScriptRoot\..\lib\Initialize-Test.ps1 +$env:SECUREFILE_NAME_10 = 'securefile10.p12' + +Invoke-VstsTaskScript -ScriptBlock { + # Act. + $actual = Get-VstsSecureFileName -Id '10' + + # Assert. + Assert-IsNotNullOrEmpty $actual + Assert-AreEqual 'securefile10.p12' $actual + + Assert-IsNullOrEmpty $env:SECUREFILE_NAME_10 +} \ No newline at end of file diff --git a/powershell/Tests/L0/Get-SecureFileTicket.ps1 b/powershell/Tests/L0/Get-SecureFileTicket.ps1 new file mode 100644 index 000000000..4100f13c7 --- /dev/null +++ b/powershell/Tests/L0/Get-SecureFileTicket.ps1 @@ -0,0 +1,15 @@ +[CmdletBinding()] +param() + +# Arrange. +. $PSScriptRoot\..\lib\Initialize-Test.ps1 +$env:SECUREFILE_TICKET_10 = 'rsaticket10' +Invoke-VstsTaskScript -ScriptBlock { + # Act. + $actual = Get-VstsSecureFileTicket -Id '10' + + # Assert. + Assert-IsNotNullOrEmpty $actual + Assert-AreEqual 'rsaticket10' $actual + Assert-IsNullOrEmpty $env:SECUREFILE_TICKET_10 +} \ No newline at end of file diff --git a/powershell/Tests/L0/Invoke-Tool.SupportsOutputEncoding.ps1 b/powershell/Tests/L0/Invoke-Tool.SupportsOutputEncoding.ps1 index 5508e9df3..54cfb6c5e 100644 --- a/powershell/Tests/L0/Invoke-Tool.SupportsOutputEncoding.ps1 +++ b/powershell/Tests/L0/Invoke-Tool.SupportsOutputEncoding.ps1 @@ -4,7 +4,7 @@ param() # Arrange. . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { set-Content -LiteralPath $tempDirectory\Program.cs -Value @" diff --git a/powershell/Tests/L0/Invoke-Tool.SupportsWorkingDirectory.ps1 b/powershell/Tests/L0/Invoke-Tool.SupportsWorkingDirectory.ps1 index c3c9de0eb..f42f3e7e8 100644 --- a/powershell/Tests/L0/Invoke-Tool.SupportsWorkingDirectory.ps1 +++ b/powershell/Tests/L0/Invoke-Tool.SupportsWorkingDirectory.ps1 @@ -5,13 +5,13 @@ param() . $PSScriptRoot\..\lib\Initialize-Test.ps1 Invoke-VstsTaskScript -ScriptBlock { $originalLocation = $PWD - $tempDirectory = [System.IO.Path]::Combine($env:TMP, [System.IO.Path]::GetRandomFileName()) + $tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName } try { Set-Location $env:TMP $variableSets = @( - @{ Expected = $env:TMP ; Splat = @{ } } - @{ Expected = $env:TMP ; Splat = @{ WorkingDirectory = $env:TMP } } + @{ Expected = [System.IO.Path]::GetTempPath().TrimEnd('\') ; Splat = @{ } } + @{ Expected = [System.IO.Path]::GetTempPath().TrimEnd('\') ; Splat = @{ WorkingDirectory = [System.IO.Path]::GetTempPath() } } @{ Expected = $tempDirectory ; Splat = @{ WorkingDirectory = $tempDirectory } } ) foreach ($variableSet in $variableSets) { @@ -22,7 +22,7 @@ Invoke-VstsTaskScript -ScriptBlock { # Assert. Assert-AreEqual $variableSet.Expected $actual - Assert-AreEqual $env:TMP (Get-Location).Path + Assert-AreEqual ([System.IO.Path]::GetTempPath().TrimEnd('\')) (Get-Location).Path } } finally { Set-Location $originalLocation diff --git a/powershell/Tests/L0/_suite.ts b/powershell/Tests/L0/_suite.ts index 64c97c981..27b9bd0eb 100644 --- a/powershell/Tests/L0/_suite.ts +++ b/powershell/Tests/L0/_suite.ts @@ -1,8 +1,6 @@ /// /// -/// -import Q = require('q'); import assert = require('assert'); import psRunner = require('../lib/psRunner'); import path = require('path'); diff --git a/powershell/Tests/lib/Initialize-Test.ps1 b/powershell/Tests/lib/Initialize-Test.ps1 index f383223ab..19ddb1f44 100644 --- a/powershell/Tests/lib/Initialize-Test.ps1 +++ b/powershell/Tests/lib/Initialize-Test.ps1 @@ -14,6 +14,12 @@ Import-Module 'Microsoft.PowerShell.Utility' -Verbose:$false Write-Verbose "Importing module: Microsoft.PowerShell.Security" Import-Module 'Microsoft.PowerShell.Security' -Verbose:$false +if ($env:AGENT_TEMPDIRECTORY) { + Write-Verbose "Overriding env:TMP and env:TEMP with env:AGENT_TEMPDIRECTORY" + $env:TEMP = $env:AGENT_TEMPDIRECTORY + $env:TMP = $env:AGENT_TEMPDIRECTORY +} + Write-Verbose "Importing module: TestHelpersModule" Import-Module $PSScriptRoot\TestHelpersModule -Verbose:$false diff --git a/powershell/Tests/lib/Start-Engine.ps1 b/powershell/Tests/lib/Start-Engine.ps1 index 51bf66980..b65bfe378 100644 --- a/powershell/Tests/lib/Start-Engine.ps1 +++ b/powershell/Tests/lib/Start-Engine.ps1 @@ -57,8 +57,19 @@ function Invoke-Test { # Record the original environment variables. $originalEnv = @{ } -foreach ($envVar in (Get-ChildItem -LiteralPath env:)) { - $originalEnv[$envVar.Name] = $envVar.Value +foreach ($key in ([Environment]::GetEnvironmentVariables()).Keys) { + if (!$originalEnv.ContainsKey($key)) { + $value = [Environment]::GetEnvironmentVariable($key) + $originalEnv[$key] = "$value" + } else { + # NPM on Windows is somehow able to create a duplicate environment variable cased differently. + # For example, if the environment variable NPM_CONFIG_CACHE is defined, npm test is somehow able + # to create a duplicate variable npm_config_cache. This causes powershell to error "An item with + # the same key has already been added" when attempting to: Get-ChildItem -LiteralPath env: + Write-Host "Squashing duplicate environment variable: $key" + [Environment]::SetEnvironmentVariable($key, $null) + [Environment]::SetEnvironmentVariable($key, $originalEnv[$key]) + } } while ($true) { @@ -71,23 +82,26 @@ while ($true) { # Cleanup the environment variables. $currentMatches = @{ } - foreach ($envVar in (Get-ChildItem -LiteralPath env:)) { + foreach ($key in ([Environment]::GetEnvironmentVariables().Keys)) { + $value = [Environment]::GetEnvironmentVariable($key) + # Remove the environment variable if it is new. - if (!$originalEnv.ContainsKey($envVar.Name)) { - Remove-Item -LiteralPath $envVar.PSPath - } elseif ($originalEnv[$envVar.Name] -ceq $envVar.Value) { + if (!$originalEnv.ContainsKey($key)) { + [Environment]::SetEnvironmentVariable($key, $null) + } elseif ($originalEnv[$key] -ceq $value) { # Otherwise record it if it matches. - $currentMatches[$envVar.Name] = $envVar.Value + $currentMatches[$key] = $true } } # Add or update the environment variables that are missing or changed. foreach ($key in $originalEnv.Keys) { if (!$currentMatches.ContainsKey($key)) { - Set-Content -LiteralPath "env:$key" -Value "$($originalEnv[$key])" + [Environment]::SetEnvironmentVariable($key, $originalEnv[$key]) } } # Write a special "end-of-test" message over STDOUT. Write-Host '_END_OF_TEST_ce10a77a_' + [Console]::Out.Flush() } diff --git a/powershell/Tests/lib/psRunner.ts b/powershell/Tests/lib/psRunner.ts index abdc55967..ec143d5f6 100644 --- a/powershell/Tests/lib/psRunner.ts +++ b/powershell/Tests/lib/psRunner.ts @@ -1,7 +1,5 @@ /// -/// -import Q = require('q'); import events = require('events'); import fs = require('fs'); import path = require('path'); @@ -21,7 +19,7 @@ class PSEngineRunner extends events.EventEmitter { private _childProcess: child.ChildProcess; private _errors: string[]; - private _runDeferred: Q.Deferred; + private _runDeferred: Promise; public stderr: string; public stdout: string; @@ -39,7 +37,7 @@ class PSEngineRunner extends events.EventEmitter { .then(() => { done(); }) - .fail((err) => { + .catch((err) => { done(err); }); } @@ -50,8 +48,7 @@ class PSEngineRunner extends events.EventEmitter { } this.emit('starting'); - var defer = Q.defer(); - var powershell = shell.which('powershell.exe'); + var powershell = shell.which('powershell.exe').stdout; this._childProcess = child.spawn( powershell, // command [ // args @@ -74,9 +71,9 @@ class PSEngineRunner extends events.EventEmitter { // Check for special ouput indicating end of test. if (('' + data).indexOf('_END_OF_TEST_ce10a77a_') >= 0) { if (this._errors.length > 0) { - this._runDeferred.reject(this._errors.join('\n')); + this._runDeferred = Promise.reject(this._errors.join('\n')); } else { - this._runDeferred.resolve(null); + this._runDeferred = Promise.resolve(); } } else if (data != '\n') { if (('' + data).match(/##vso\[task.logissue .*type=error/)) { @@ -98,12 +95,12 @@ class PSEngineRunner extends events.EventEmitter { }); } - private runPromise(psPath: string): Q.Promise { + private runPromise(psPath: string): Promise { this.emit('running test'); this._errors = []; - this._runDeferred = Q.defer(); + this._runDeferred = Promise.resolve(); this._childProcess.stdin.write(psPath + '\n') - return >this._runDeferred.promise; + return this._runDeferred; } } diff --git a/powershell/VstsTaskSdk/FindFunctions.ps1 b/powershell/VstsTaskSdk/FindFunctions.ps1 index 8687f1bf4..c0278ea01 100644 --- a/powershell/VstsTaskSdk/FindFunctions.ps1 +++ b/powershell/VstsTaskSdk/FindFunctions.ps1 @@ -155,7 +155,7 @@ function Find-Match { $findResults += $findPath } } else { - $findResults = Get-FindResult -Path $findPath -Options $FindOptions + $findResults = @( Get-FindResult -Path $findPath -Options $FindOptions ) } Write-Verbose "Found $($findResults.Count) paths." diff --git a/powershell/VstsTaskSdk/InputFunctions.ps1 b/powershell/VstsTaskSdk/InputFunctions.ps1 index 21c4ade1a..d7eb7c282 100644 --- a/powershell/VstsTaskSdk/InputFunctions.ps1 +++ b/powershell/VstsTaskSdk/InputFunctions.ps1 @@ -64,6 +64,74 @@ function Get-Endpoint { } } +<# +.SYNOPSIS +Gets a secure file ticket. + +.DESCRIPTION +Gets the secure file ticket that can be used to download the secure file contents. + +.PARAMETER Id +Secure file id. + +.PARAMETER Require +Writes an error to the error pipeline if the ticket is not found. +#> +function Get-SecureFileTicket { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Id, + [switch]$Require) + + $originalErrorActionPreference = $ErrorActionPreference + try { + $ErrorActionPreference = 'Stop' + + $description = Get-LocString -Key PSLIB_Input0 -ArgumentList $Id + $key = "SECUREFILE_TICKET_$Id" + + Get-VaultValue -Description $description -Key $key -Require:$Require + } catch { + $ErrorActionPreference = $originalErrorActionPreference + Write-Error $_ + } +} + +<# +.SYNOPSIS +Gets a secure file name. + +.DESCRIPTION +Gets the name for a secure file. + +.PARAMETER Id +Secure file id. + +.PARAMETER Require +Writes an error to the error pipeline if the ticket is not found. +#> +function Get-SecureFileName { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Id, + [switch]$Require) + + $originalErrorActionPreference = $ErrorActionPreference + try { + $ErrorActionPreference = 'Stop' + + $description = Get-LocString -Key PSLIB_Input0 -ArgumentList $Id + $key = "SECUREFILE_NAME_$Id" + + Get-VaultValue -Description $description -Key $key -Require:$Require + } catch { + $ErrorActionPreference = $originalErrorActionPreference + Write-Error $_ + } +} + <# .SYNOPSIS Gets an input. diff --git a/powershell/VstsTaskSdk/LegacyFindFunctions.ps1 b/powershell/VstsTaskSdk/LegacyFindFunctions.ps1 index f6aaa5a09..9e9e9ecc2 100644 --- a/powershell/VstsTaskSdk/LegacyFindFunctions.ps1 +++ b/powershell/VstsTaskSdk/LegacyFindFunctions.ps1 @@ -132,7 +132,7 @@ function Find-Files { } # Validate pattern does not end with a \. - if ($pattern[$pattern.Length - 1] -eq [System.IO.Path]::DirectorySeparatorChar) { + if ($pattern.EndsWith([System.IO.Path]::DirectorySeparatorChar)) { throw (Get-LocString -Key PSLIB_InvalidPattern0 -ArgumentList $pattern) } diff --git a/powershell/VstsTaskSdk/LocalizationFunctions.ps1 b/powershell/VstsTaskSdk/LocalizationFunctions.ps1 index 6f3b6bb06..b5549700f 100644 --- a/powershell/VstsTaskSdk/LocalizationFunctions.ps1 +++ b/powershell/VstsTaskSdk/LocalizationFunctions.ps1 @@ -60,10 +60,10 @@ function Get-LocString { <# .SYNOPSIS -Imports resource strings for use with Get-VstsLocString. +Imports resource strings for use with GetVstsLocString. .DESCRIPTION -Imports resource strings for use with Get-VstsLocString. The imported strings are stored in an internal resource string dictionary. Optionally, if a separate resource file for the current culture exists, then the localized strings from that file then imported (overlaid) into the same internal resource string dictionary. +Imports resource strings for use with GetVstsLocString. The imported strings are stored in an internal resource string dictionary. Optionally, if a separate resource file for the current culture exists, then the localized strings from that file then imported (overlaid) into the same internal resource string dictionary. Resource strings from the SDK are prefixed with "PSLIB_". This prefix should be avoided for custom resource strings. diff --git a/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 b/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 index a8462efbf..4e8eb7323 100644 --- a/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 +++ b/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 @@ -3,8 +3,8 @@ $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%" New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } + New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } ) -# TODO: BUG: Escape ] # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". @@ -36,6 +36,50 @@ function Write-AddAttachment { .SYNOPSIS See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-UploadSummary { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + Write-LoggingCommand -Area 'task' -Event 'uploadsummary' -Data $Path -AsOutput:$AsOutput +} + +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-SetEndpoint { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Id, + [Parameter(Mandatory = $true)] + [string]$Field, + [Parameter(Mandatory = $true)] + [string]$Key, + [Parameter(Mandatory = $true)] + [string]$Value, + [switch]$AsOutput) + + Write-LoggingCommand -Area 'task' -Event 'setendpoint' -Data $Value -Properties @{ + 'id' = $Id + 'field' = $Field + 'key' = $Key + } -AsOutput:$AsOutput +} + +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + .PARAMETER AsOutput Indicates whether to write the logging command directly to the host or to the output pipeline. #> @@ -287,6 +331,40 @@ function Write-TaskWarning { .SYNOPSIS See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-UploadFile { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + Write-LoggingCommand -Area 'task' -Event 'uploadfile' -Data $Path -AsOutput:$AsOutput +} + +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-PrependPath { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput +} + +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + .PARAMETER AsOutput Indicates whether to write the logging command directly to the host or to the output pipeline. #> @@ -341,6 +419,58 @@ function Write-UploadBuildLog { Write-LoggingCommand -Area 'build' -Event 'uploadlog' -Data $Path -AsOutput:$AsOutput } +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-UpdateReleaseName { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [switch]$AsOutput) + + Write-LoggingCommand -Area 'release' -Event 'updatereleasename' -Data $Name -AsOutput:$AsOutput +} + +<# +.SYNOPSIS +See https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + +.PARAMETER AsOutput +Indicates whether to write the logging command directly to the host or to the output pipeline. +#> +function Write-LoggingCommand { + [CmdletBinding(DefaultParameterSetName = 'Parameters')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Area, + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Event, + [Parameter(ParameterSetName = 'Parameters')] + [string]$Data, + [Parameter(ParameterSetName = 'Parameters')] + [hashtable]$Properties, + [Parameter(Mandatory = $true, ParameterSetName = 'Object')] + $Command, + [switch]$AsOutput) + + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput + return + } + + $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties + if ($AsOutput) { + $command + } else { + Write-Host $command + } +} + ######################################## # Private functions. ######################################## @@ -403,34 +533,6 @@ function Format-LoggingCommand { $sb.Append(']').Append($Data).ToString() } -function Write-LoggingCommand { - [CmdletBinding(DefaultParameterSetName = 'Parameters')] - param( - [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] - [string]$Area, - [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] - [string]$Event, - [Parameter(ParameterSetName = 'Parameters')] - [string]$Data, - [Parameter(ParameterSetName = 'Parameters')] - [hashtable]$Properties, - [Parameter(Mandatory = $true, ParameterSetName = 'Object')] - $Command, - [switch]$AsOutput) - - if ($PSCmdlet.ParameterSetName -eq 'Object') { - Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput - return - } - - $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties - if ($AsOutput) { - $command - } else { - Write-Host $command - } -} - function Write-LogIssue { [CmdletBinding()] param( diff --git a/powershell/VstsTaskSdk/ServerOMFunctions.ps1 b/powershell/VstsTaskSdk/ServerOMFunctions.ps1 index c0cddc67c..6fd19ea13 100644 --- a/powershell/VstsTaskSdk/ServerOMFunctions.ps1 +++ b/powershell/VstsTaskSdk/ServerOMFunctions.ps1 @@ -3,13 +3,13 @@ Gets assembly reference information. .DESCRIPTION -Not supported for use during task exection. This function is only intended to help developers resolve the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. The interface and output may change between patch releases of the VSTS Task SDK. +Not supported for use during task execution. This function is only intended to help developers resolve the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. The interface and output may change between patch releases of the VSTS Task SDK. Only a subset of the referenced assemblies may actually be required, depending on the functionality used by your task. It is best to bundle only the DLLs required for your scenario. Walks an assembly's references to determine all of it's dependencies. Also walks the references of the dependencies, and so on until all nested dependencies have been traversed. Dependencies are searched for in the directory of the specified assembly. NET Framework assemblies are omitted. -See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. +See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. .PARAMETER LiteralPath Assembly to walk. @@ -24,7 +24,7 @@ function Get-AssemblyReference { [string]$LiteralPath) $ErrorActionPreference = 'Stop' - Write-Warning "Not supported for use during task exection. This function is only intended to help developers resolve the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. The interface and output may change between patch releases of the VSTS Task SDK." + Write-Warning "Not supported for use during task execution. This function is only intended to help developers resolve the minimal set of DLLs that need to be bundled when consuming the VSTS REST SDK or TFS Extended Client SDK. The interface and output may change between patch releases of the VSTS Task SDK." Write-Output '' Write-Warning "Only a subset of the referenced assemblies may actually be required, depending on the functionality used by your task. It is best to bundle only the DLLs required for your scenario." $directory = [System.IO.Path]::GetDirectoryName($LiteralPath) @@ -106,14 +106,14 @@ The agent job token is used to construct the credentials object. The identity as Refer to Get-VstsTfsService for a more simple to get a TFS service object. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. .PARAMETER OMDirectory Directory where the extended client object model DLLs are located. If the DLLs for the credential types are not already loaded, an attempt will be made to automatically load the required DLLs from the object model directory. If not specified, defaults to the directory of the entry script for the task. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. .EXAMPLE # @@ -139,6 +139,31 @@ function Get-TfsClientCredentials { # Get the endpoint. $endpoint = Get-Endpoint -Name SystemVssConnection -Require + # Test if the Newtonsoft.Json DLL exists in the OM directory. + $newtonsoftDll = [System.IO.Path]::Combine($OMDirectory, "Newtonsoft.Json.dll") + Write-Verbose "Testing file path: '$newtonsoftDll'" + if (!(Test-Path -LiteralPath $newtonsoftDll -PathType Leaf)) { + Write-Verbose 'Not found. Rethrowing exception.' + throw + } + + # Add a binding redirect and try again. Parts of the Dev15 preview SDK have a + # dependency on the 6.0.0.0 Newtonsoft.Json DLL, while other parts reference + # the 8.0.0.0 Newtonsoft.Json DLL. + Write-Verbose "Adding assembly resolver." + $onAssemblyResolve = [System.ResolveEventHandler] { + param($sender, $e) + + if ($e.Name -like 'Newtonsoft.Json, *') { + Write-Verbose "Resolving '$($e.Name)' to '$newtonsoftDll'." + + return [System.Reflection.Assembly]::LoadFrom($newtonsoftDll) + } + + return $null + } + [System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolve) + # Validate the type can be found. $null = Get-OMType -TypeName 'Microsoft.TeamFoundation.Client.TfsClientCredentials' -OMKind 'ExtendedClient' -OMDirectory $OMDirectory -Require @@ -162,7 +187,7 @@ Gets a TFS extended client service. .DESCRIPTION Gets an instance of an ITfsTeamProjectCollectionObject. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. .PARAMETER TypeName Namespace-qualified type name of the service to get. @@ -172,13 +197,13 @@ Directory where the extended client object model DLLs are located. If the DLLs f If not specified, defaults to the directory of the entry script for the task. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the TFS extended client SDK from a task. .PARAMETER Uri URI to use when initializing the service. If not specified, defaults to System.TeamFoundationCollectionUri. .PARAMETER TfsClientCredentials -Credentials to use when intializing the service. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). +Credentials to use when initializing the service. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). .EXAMPLE $versionControlServer = Get-VstsTfsService -TypeName Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer @@ -239,14 +264,14 @@ The agent job token is used to construct the credentials object. The identity as Refer to Get-VstsVssHttpClient for a more simple to get a VSS HTTP client. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. .PARAMETER OMDirectory Directory where the REST client object model DLLs are located. If the DLLs for the credential types are not already loaded, an attempt will be made to automatically load the required DLLs from the object model directory. If not specified, defaults to the directory of the entry script for the task. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. .EXAMPLE # @@ -306,7 +331,7 @@ Gets a VSS HTTP client. .DESCRIPTION Gets an instance of an VSS HTTP client. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. .PARAMETER TypeName Namespace-qualified type name of the HTTP client to get. @@ -316,16 +341,22 @@ Directory where the REST client object model DLLs are located. If the DLLs for t If not specified, defaults to the directory of the entry script for the task. -*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/vsts-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. +*** DO NOT USE Agent.ServerOMDirectory *** See https://github.com/Microsoft/azure-pipelines-task-lib/tree/master/powershell/Docs/UsingOM.md for reliable usage when working with the VSTS REST SDK from a task. # .PARAMETER Uri # URI to use when initializing the HTTP client. If not specified, defaults to System.TeamFoundationCollectionUri. # .PARAMETER VssCredentials -# Credentials to use when intializing the HTTP client. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). +# Credentials to use when initializing the HTTP client. If not specified, the default uses the agent job token to construct the credentials object. The identity associated with the token depends on the scope selected in the build/release definition (either the project collection build/release service identity, or the project build/release service identity). # .PARAMETER WebProxy -# WebProxy to use when intializing the HTTP client. If not specified, the default uses the proxy configuration agent current has. +# WebProxy to use when initializing the HTTP client. If not specified, the default uses the proxy configuration agent current has. + +# .PARAMETER ClientCert +# ClientCert to use when initializing the HTTP client. If not specified, the default uses the client certificate agent current has. + +# .PARAMETER IgnoreSslError +# Skip SSL server certificate validation on all requests made by this HTTP client. If not specified, the default is to validate SSL server certificate. .EXAMPLE $projectHttpClient = Get-VstsVssHttpClient -TypeName Microsoft.TeamFoundation.Core.WebApi.ProjectHttpClient @@ -343,7 +374,11 @@ function Get-VssHttpClient { $VssCredentials, - $WebProxy = (Get-WebProxy)) + $WebProxy = (Get-WebProxy), + + $ClientCert = (Get-ClientCertificate), + + [switch]$IgnoreSslError) Trace-EnteringInvocation -InvocationInfo $MyInvocation $originalErrorActionPreference = $ErrorActionPreference @@ -369,10 +404,35 @@ function Get-VssHttpClient { # Update proxy setting for vss http client [Microsoft.VisualStudio.Services.Common.VssHttpMessageHandler]::DefaultWebProxy = $WebProxy + # Update client certificate setting for vss http client + $null = Get-OMType -TypeName 'Microsoft.VisualStudio.Services.Common.VssHttpRequestSettings' -OMKind 'WebApi' -OMDirectory $OMDirectory -Require + $null = Get-OMType -TypeName 'Microsoft.VisualStudio.Services.WebApi.VssClientHttpRequestSettings' -OMKind 'WebApi' -OMDirectory $OMDirectory -Require + [Microsoft.VisualStudio.Services.Common.VssHttpRequestSettings]$Settings = [Microsoft.VisualStudio.Services.WebApi.VssClientHttpRequestSettings]::Default.Clone() + + if ($ClientCert) { + $null = Get-OMType -TypeName 'Microsoft.VisualStudio.Services.WebApi.VssClientCertificateManager' -OMKind 'WebApi' -OMDirectory $OMDirectory -Require + $null = [Microsoft.VisualStudio.Services.WebApi.VssClientCertificateManager]::Instance.ClientCertificates.Add($ClientCert) + + $Settings.ClientCertificateManager = [Microsoft.VisualStudio.Services.WebApi.VssClientCertificateManager]::Instance + } + + # Skip SSL server certificate validation + [bool]$SkipCertValidation = (Get-TaskVariable -Name Agent.SkipCertValidation -AsBool) -or $IgnoreSslError + if ($SkipCertValidation) { + if ($Settings.GetType().GetProperty('ServerCertificateValidationCallback')) { + Write-Verbose "Ignore any SSL server certificate validation errors."; + $Settings.ServerCertificateValidationCallback = [VstsTaskSdk.VstsHttpHandlerSettings]::UnsafeSkipServerCertificateValidation + } + else { + # OMDirectory has older version of Microsoft.VisualStudio.Services.Common.dll + Write-Verbose "The version of 'Microsoft.VisualStudio.Services.Common.dll' does not support skip SSL server certificate validation." + } + } + # Try to construct the HTTP client. Write-Verbose "Constructing HTTP client." try { - return New-Object $TypeName($Uri, $VssCredentials) + return New-Object $TypeName($Uri, $VssCredentials, $Settings) } catch { # Rethrow if the exception is not due to Newtonsoft.Json DLL not found. if ($_.Exception.InnerException -isnot [System.IO.FileNotFoundException] -or @@ -399,7 +459,7 @@ function Get-VssHttpClient { # dependency on the 6.0.0.0 Newtonsoft.Json DLL, while other parts reference # the 8.0.0.0 Newtonsoft.Json DLL. Write-Verbose "Adding assembly resolver." - $onAssemblyResolve = [System.ResolveEventHandler]{ + $onAssemblyResolve = [System.ResolveEventHandler] { param($sender, $e) if ($e.Name -like 'Newtonsoft.Json, *') { @@ -414,7 +474,7 @@ function Get-VssHttpClient { try { # Try again to construct the HTTP client. Write-Verbose "Trying again to construct the HTTP client." - return New-Object $TypeName($Uri, $VssCredentials) + return New-Object $TypeName($Uri, $VssCredentials, $Settings) } finally { # Unregister the assembly resolver. Write-Verbose "Removing assemlby resolver." @@ -440,15 +500,14 @@ VstsTaskSdk.VstsWebProxy implement System.Net.IWebProxy interface. .EXAMPLE $webProxy = Get-VstsWebProxy -$webProxy.GetProxy(New-Object System.Uri("https://github.com/Microsoft/vsts-task-lib")) +$webProxy.GetProxy(New-Object System.Uri("https://github.com/Microsoft/azure-pipelines-task-lib")) #> function Get-WebProxy { [CmdletBinding()] param() Trace-EnteringInvocation -InvocationInfo $MyInvocation - try - { + try { # Min agent version that supports proxy Assert-Agent -Minimum '2.105.7' @@ -465,6 +524,38 @@ function Get-WebProxy { } } +<# +.SYNOPSIS +Gets a client certificate for current connected TFS instance + +.DESCRIPTION +Gets an instance of a X509Certificate2 that is the client certificate Build/Release agent used. + +.EXAMPLE +$x509cert = Get-ClientCertificate +WebRequestHandler.ClientCertificates.Add(x509cert) +#> +function Get-ClientCertificate { + [CmdletBinding()] + param() + + Trace-EnteringInvocation -InvocationInfo $MyInvocation + try { + # Min agent version that supports client certificate + Assert-Agent -Minimum '2.122.0' + + [string]$clientCert = Get-TaskVariable -Name Agent.ClientCertArchive + [string]$clientCertPassword = Get-TaskVariable -Name Agent.ClientCertPassword + + if ($clientCert -and (Test-Path -LiteralPath $clientCert -PathType Leaf)) { + return New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($clientCert, $clientCertPassword) + } + } + finally { + Trace-LeavingInvocation -InvocationInfo $MyInvocation + } +} + ######################################## # Private functions. ######################################## diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/de-de/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/de-DE/resources.resjson similarity index 86% rename from powershell/VstsTaskSdk/Strings/resources.resjson/de-de/resources.resjson rename to powershell/VstsTaskSdk/Strings/resources.resjson/de-DE/resources.resjson index ec97e9b64..90e1f1554 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/de-de/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/de-DE/resources.resjson @@ -1,11 +1,12 @@ { + "loc.messages.PSLIB_AgentVersion0Required": "Agentversion {0} oder höher ist erforderlich.", "loc.messages.PSLIB_ContainerPathNotFound0": "Der Containerpfad wurde nicht gefunden: \"{0}\".", "loc.messages.PSLIB_EndpointAuth0": "\"{0}\"-Dienstendpunkt-Anmeldeinformationen", "loc.messages.PSLIB_EndpointUrl0": "\"{0}\"-Dienstendpunkt-URL", "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Fehler beim Aufzählen von Unterverzeichnissen für den folgenden Pfad: \"{0}\"", "loc.messages.PSLIB_FileNotFound0": "Die Datei wurde nicht gefunden: \"{0}\".", "loc.messages.PSLIB_Input0": "\"{0}\"-Eingabe", - "loc.messages.PSLIB_InvalidPattern0": "Ungültiges Muster: \"{0}\"", + "loc.messages.PSLIB_InvalidPattern0": "Der Pfad darf nicht mit einem Verzeichnistrennzeichen enden: „{0}“", "loc.messages.PSLIB_LeafPathNotFound0": "Der Blattpfad wurde nicht gefunden: \"{0}\".", "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Fehler bei der Normalisierung bzw. Erweiterung des Pfads. Die Pfadlänge wurde vom Kernel32-Subsystem nicht zurückgegeben für: \"{0}\"", "loc.messages.PSLIB_PathNotFound0": "Der Pfad wurde nicht gefunden: \"{0}\".", diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/es-es/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/es-ES/resources.resjson similarity index 85% rename from powershell/VstsTaskSdk/Strings/resources.resjson/es-es/resources.resjson rename to powershell/VstsTaskSdk/Strings/resources.resjson/es-ES/resources.resjson index d09d96cc6..e17ac0c0a 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/es-es/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/es-ES/resources.resjson @@ -1,11 +1,12 @@ { + "loc.messages.PSLIB_AgentVersion0Required": "Se require la versión {0} o posterior del agente.", "loc.messages.PSLIB_ContainerPathNotFound0": "No se encuentra la ruta de acceso del contenedor: '{0}'", "loc.messages.PSLIB_EndpointAuth0": "Credenciales del punto de conexión de servicio '{0}'", "loc.messages.PSLIB_EndpointUrl0": "URL del punto de conexión de servicio '{0}'", "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "No se pudieron enumerar los subdirectorios de la ruta de acceso: '{0}'", "loc.messages.PSLIB_FileNotFound0": "Archivo no encontrado: '{0}'", "loc.messages.PSLIB_Input0": "Entrada '{0}'", - "loc.messages.PSLIB_InvalidPattern0": "Patrón no válido: '{0}'", + "loc.messages.PSLIB_InvalidPattern0": "La ruta de acceso no puede terminar con un carácter separador de directorios: '{0}'", "loc.messages.PSLIB_LeafPathNotFound0": "No se encuentra la ruta de acceso de la hoja: '{0}'", "loc.messages.PSLIB_PathLengthNotReturnedFor0": "No se pudo normalizar o expandir la ruta de acceso. El subsistema Kernel32 no devolvió la longitud de la ruta de acceso para: '{0}'", "loc.messages.PSLIB_PathNotFound0": "No se encuentra la ruta de acceso: '{0}'", diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/fr-fr/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/fr-FR/resources.resjson similarity index 83% rename from powershell/VstsTaskSdk/Strings/resources.resjson/fr-fr/resources.resjson rename to powershell/VstsTaskSdk/Strings/resources.resjson/fr-FR/resources.resjson index 6606ddda5..bf964d5c5 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/fr-fr/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/fr-FR/resources.resjson @@ -1,11 +1,12 @@ { + "loc.messages.PSLIB_AgentVersion0Required": "L'agent version {0} (ou une version ultérieure) est obligatoire.", "loc.messages.PSLIB_ContainerPathNotFound0": "Le chemin du conteneur est introuvable : '{0}'", "loc.messages.PSLIB_EndpointAuth0": "Informations d'identification du point de terminaison de service '{0}'", "loc.messages.PSLIB_EndpointUrl0": "URL du point de terminaison de service '{0}'", "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Échec de l'énumération des sous-répertoires pour le chemin : '{0}'", "loc.messages.PSLIB_FileNotFound0": "Fichier introuvable : {0}.", "loc.messages.PSLIB_Input0": "Entrée '{0}'", - "loc.messages.PSLIB_InvalidPattern0": "Modèle non valide : '{0}'", + "loc.messages.PSLIB_InvalidPattern0": "Le chemin d’accès ne peut pas se terminer par un caractère de séparateur de répertoire : « {0} »", "loc.messages.PSLIB_LeafPathNotFound0": "Le chemin feuille est introuvable : '{0}'", "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Échec de la normalisation/l'expansion du chemin. La longueur du chemin n'a pas été retournée par le sous-système Kernel32 pour : '{0}'", "loc.messages.PSLIB_PathNotFound0": "Chemin introuvable : '{0}'", diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson index 0b54d99aa..1e69d7453 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson @@ -1,17 +1,18 @@ -{ - "loc.messages.PSLIB_ContainerPathNotFound0": "Percorso del contenitore non trovato: '{0}'", - "loc.messages.PSLIB_EndpointAuth0": "Credenziali dell'endpoint servizio '{0}'", - "loc.messages.PSLIB_EndpointUrl0": "URL dell'endpoint servizio '{0}'", - "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "L'enumerazione delle sottodirectory per il percorso '{0}' non è riuscita", - "loc.messages.PSLIB_FileNotFound0": "File non trovato: '{0}'", - "loc.messages.PSLIB_Input0": "Input di '{0}'", - "loc.messages.PSLIB_InvalidPattern0": "Criterio non valido: '{0}'", - "loc.messages.PSLIB_LeafPathNotFound0": "Percorso foglia non trovato: '{0}'", - "loc.messages.PSLIB_PathLengthNotReturnedFor0": "La normalizzazione o l'espansione del percorso non è riuscita. Il sottosistema Kernel32 non ha restituito la lunghezza del percorso per '{0}'", - "loc.messages.PSLIB_PathNotFound0": "Percorso non trovato: '{0}'", - "loc.messages.PSLIB_Process0ExitedWithCode1": "Il processo '{0}' è stato terminato ed è stato restituito il codice '{1}'.", - "loc.messages.PSLIB_Required0": "Obbligatorio: {0}", - "loc.messages.PSLIB_StringFormatFailed": "Errore nel formato della stringa.", - "loc.messages.PSLIB_StringResourceKeyNotFound0": "La chiave della risorsa stringa non è stata trovata: '{0}'", - "loc.messages.PSLIB_TaskVariable0": "Variabile dell'attività '{0}'" +{ + "loc.messages.PSLIB_AgentVersion0Required": "È richiesta la versione dell'agente {0} o superiore.", + "loc.messages.PSLIB_ContainerPathNotFound0": "Percorso del contenitore non trovato: '{0}'", + "loc.messages.PSLIB_EndpointAuth0": "Credenziali dell'endpoint servizio '{0}'", + "loc.messages.PSLIB_EndpointUrl0": "URL dell'endpoint servizio '{0}'", + "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "L'enumerazione delle sottodirectory per il percorso '{0}' non è riuscita", + "loc.messages.PSLIB_FileNotFound0": "File non trovato: '{0}'", + "loc.messages.PSLIB_Input0": "Input di '{0}'", + "loc.messages.PSLIB_InvalidPattern0": "Il percorso non può terminare con un carattere separatore di directory: '{0}'", + "loc.messages.PSLIB_LeafPathNotFound0": "Percorso foglia non trovato: '{0}'", + "loc.messages.PSLIB_PathLengthNotReturnedFor0": "La normalizzazione o l'espansione del percorso non è riuscita. Il sottosistema Kernel32 non ha restituito la lunghezza del percorso per '{0}'", + "loc.messages.PSLIB_PathNotFound0": "Percorso non trovato: '{0}'", + "loc.messages.PSLIB_Process0ExitedWithCode1": "Il processo '{0}' è stato terminato ed è stato restituito il codice '{1}'.", + "loc.messages.PSLIB_Required0": "Obbligatorio: {0}", + "loc.messages.PSLIB_StringFormatFailed": "Errore nel formato della stringa.", + "loc.messages.PSLIB_StringResourceKeyNotFound0": "La chiave della risorsa stringa non è stata trovata: '{0}'", + "loc.messages.PSLIB_TaskVariable0": "Variabile dell'attività '{0}'" } \ No newline at end of file diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/ja-jp/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/ja-JP/resources.resjson similarity index 84% rename from powershell/VstsTaskSdk/Strings/resources.resjson/ja-jp/resources.resjson rename to powershell/VstsTaskSdk/Strings/resources.resjson/ja-JP/resources.resjson index b0e2bdfc3..9c5102b95 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/ja-jp/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/ja-JP/resources.resjson @@ -1,11 +1,12 @@ { + "loc.messages.PSLIB_AgentVersion0Required": "バージョン {0} 以降のエージェントが必要です。", "loc.messages.PSLIB_ContainerPathNotFound0": "コンテナーのパスが見つかりません: '{0}'", "loc.messages.PSLIB_EndpointAuth0": "'{0}' サービス エンドポイントの資格情報", "loc.messages.PSLIB_EndpointUrl0": "'{0}' サービス エンドポイントの URL", "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "パス '{0}' のサブディレクトリを列挙できませんでした", "loc.messages.PSLIB_FileNotFound0": "ファイルが見つかりません: '{0}'", "loc.messages.PSLIB_Input0": "'{0}' 入力", - "loc.messages.PSLIB_InvalidPattern0": "使用できないパターンです: '{0}'", + "loc.messages.PSLIB_InvalidPattern0": "パスの末尾をディレクトリ区切り文字にすることはできません: '{0}'", "loc.messages.PSLIB_LeafPathNotFound0": "リーフ パスが見つかりません: '{0}'", "loc.messages.PSLIB_PathLengthNotReturnedFor0": "パスの正規化/展開に失敗しました。Kernel32 サブシステムからパス '{0}' の長さが返されませんでした", "loc.messages.PSLIB_PathNotFound0": "パスが見つかりません: '{0}'", diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson index 60df35b60..a66485ecb 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson @@ -1,17 +1,18 @@ -{ - "loc.messages.PSLIB_ContainerPathNotFound0": "컨테이너 경로를 찾을 수 없음: '{0}'", - "loc.messages.PSLIB_EndpointAuth0": "'{0}' 서비스 끝점 자격 증명", - "loc.messages.PSLIB_EndpointUrl0": "'{0}' 서비스 끝점 URL", - "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "경로에 대해 하위 디렉터리를 열거하지 못함: '{0}'", - "loc.messages.PSLIB_FileNotFound0": "{0} 파일을 찾을 수 없습니다.", - "loc.messages.PSLIB_Input0": "'{0}' 입력", - "loc.messages.PSLIB_InvalidPattern0": "잘못된 패턴: '{0}'", - "loc.messages.PSLIB_LeafPathNotFound0": "Leaf 경로를 찾을 수 없음: '{0}'", - "loc.messages.PSLIB_PathLengthNotReturnedFor0": "경로 정규화/확장에 실패했습니다. 다음에 대해 Kernel32 subsystem에서 경로 길이를 반환하지 않음: '{0}'", - "loc.messages.PSLIB_PathNotFound0": "경로를 찾을 수 없음: '{0}'", - "loc.messages.PSLIB_Process0ExitedWithCode1": "'{1}' 코드로 '{0}' 프로세스가 종료되었습니다.", - "loc.messages.PSLIB_Required0": "필수: {0}", - "loc.messages.PSLIB_StringFormatFailed": "문자열을 포맷하지 못했습니다.", - "loc.messages.PSLIB_StringResourceKeyNotFound0": "문자열 리소스 키를 찾을 수 없음: '{0}'", - "loc.messages.PSLIB_TaskVariable0": "'{0}' 작업 변수" +{ + "loc.messages.PSLIB_AgentVersion0Required": "에이전트 버전 {0} 이상이 필요합니다.", + "loc.messages.PSLIB_ContainerPathNotFound0": "컨테이너 경로를 찾을 수 없음: '{0}'", + "loc.messages.PSLIB_EndpointAuth0": "'{0}' 서비스 엔드포인트 자격 증명", + "loc.messages.PSLIB_EndpointUrl0": "'{0}' 서비스 엔드포인트 URL", + "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "경로에 대해 하위 디렉터리를 열거하지 못함: '{0}'", + "loc.messages.PSLIB_FileNotFound0": "{0} 파일을 찾을 수 없습니다.", + "loc.messages.PSLIB_Input0": "'{0}' 입력", + "loc.messages.PSLIB_InvalidPattern0": "경로는 디렉터리 구분 문자로 끝날 수 없습니다. '{0}'", + "loc.messages.PSLIB_LeafPathNotFound0": "Leaf 경로를 찾을 수 없음: '{0}'", + "loc.messages.PSLIB_PathLengthNotReturnedFor0": "경로 정규화/확장에 실패했습니다. 다음에 대해 Kernel32 subsystem에서 경로 길이를 반환하지 않음: '{0}'", + "loc.messages.PSLIB_PathNotFound0": "경로를 찾을 수 없음: '{0}'", + "loc.messages.PSLIB_Process0ExitedWithCode1": "'{1}' 코드로 '{0}' 프로세스가 종료되었습니다.", + "loc.messages.PSLIB_Required0": "필수: {0}", + "loc.messages.PSLIB_StringFormatFailed": "문자열을 포맷하지 못했습니다.", + "loc.messages.PSLIB_StringResourceKeyNotFound0": "문자열 리소스 키를 찾을 수 없음: '{0}'", + "loc.messages.PSLIB_TaskVariable0": "'{0}' 작업 변수" } \ No newline at end of file diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson index 2d422087d..1157f2f27 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson @@ -1,17 +1,18 @@ -{ - "loc.messages.PSLIB_ContainerPathNotFound0": "Путь к контейнеру не найден: \"{0}\".", - "loc.messages.PSLIB_EndpointAuth0": "Учетные данные конечной точки службы \"{0}\"", - "loc.messages.PSLIB_EndpointUrl0": "URL-адрес конечной точки службы \"{0}\"", - "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Сбой перечисления подкаталогов для пути: \"{0}\".", - "loc.messages.PSLIB_FileNotFound0": "Файл не найден: \"{0}\".", - "loc.messages.PSLIB_Input0": "Входные данные \"{0}\".", - "loc.messages.PSLIB_InvalidPattern0": "Недопустимый шаблон: \"{0}\".", - "loc.messages.PSLIB_LeafPathNotFound0": "Путь к конечному объекту не найден: \"{0}\".", - "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Сбой нормализации и расширения пути. Длина пути не была возвращена подсистемой Kernel32 для: \"{0}\".", - "loc.messages.PSLIB_PathNotFound0": "Путь не найден: \"{0}\".", - "loc.messages.PSLIB_Process0ExitedWithCode1": "Процесс \"{0}\" завершил работу с кодом \"{1}\".", - "loc.messages.PSLIB_Required0": "Требуется: {0}", - "loc.messages.PSLIB_StringFormatFailed": "Сбой формата строки.", - "loc.messages.PSLIB_StringResourceKeyNotFound0": "Ключ ресурса строки не найден: \"{0}\".", - "loc.messages.PSLIB_TaskVariable0": "Переменная задачи \"{0}\"" +{ + "loc.messages.PSLIB_AgentVersion0Required": "Требуется версия агента {0} или более поздняя.", + "loc.messages.PSLIB_ContainerPathNotFound0": "Путь к контейнеру не найден: \"{0}\".", + "loc.messages.PSLIB_EndpointAuth0": "Учетные данные конечной точки службы \"{0}\"", + "loc.messages.PSLIB_EndpointUrl0": "URL-адрес конечной точки службы \"{0}\"", + "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Сбой перечисления подкаталогов для пути: \"{0}\".", + "loc.messages.PSLIB_FileNotFound0": "Файл не найден: \"{0}\"", + "loc.messages.PSLIB_Input0": "Входные данные \"{0}\".", + "loc.messages.PSLIB_InvalidPattern0": "Путь не может заканчиваться символом разделителя каталогов: \"{0}\"", + "loc.messages.PSLIB_LeafPathNotFound0": "Путь к конечному объекту не найден: \"{0}\".", + "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Сбой нормализации и расширения пути. Длина пути не была возвращена подсистемой Kernel32 для: \"{0}\".", + "loc.messages.PSLIB_PathNotFound0": "Путь не найден: \"{0}\".", + "loc.messages.PSLIB_Process0ExitedWithCode1": "Процесс \"{0}\" завершил работу с кодом \"{1}\".", + "loc.messages.PSLIB_Required0": "Требуется: {0}", + "loc.messages.PSLIB_StringFormatFailed": "Сбой формата строки.", + "loc.messages.PSLIB_StringResourceKeyNotFound0": "Ключ ресурса строки не найден: \"{0}\".", + "loc.messages.PSLIB_TaskVariable0": "Переменная задачи \"{0}\"" } \ No newline at end of file diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson index 80adce28a..901b46b7a 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson @@ -1,17 +1,18 @@ -{ - "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路径:“{0}”", - "loc.messages.PSLIB_EndpointAuth0": "“{0}”服务终结点凭据", - "loc.messages.PSLIB_EndpointUrl0": "“{0}”服务终结点 URL", - "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "枚举路径的子目录失败:“{0}”", - "loc.messages.PSLIB_FileNotFound0": "找不到文件: {0}。", - "loc.messages.PSLIB_Input0": "“{0}”输入", - "loc.messages.PSLIB_InvalidPattern0": "无效的模式:“{0}”", - "loc.messages.PSLIB_LeafPathNotFound0": "找不到叶路径:“{0}”", - "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路径规范化/扩展失败。路径长度不是由“{0}”的 Kernel32 子系统返回的", - "loc.messages.PSLIB_PathNotFound0": "找不到路径:“{0}”", - "loc.messages.PSLIB_Process0ExitedWithCode1": "过程“{0}”已退出,代码为“{1}”。", - "loc.messages.PSLIB_Required0": "必需: {0}", - "loc.messages.PSLIB_StringFormatFailed": "字符串格式无效。", - "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字符串资源关键字:“{0}”", - "loc.messages.PSLIB_TaskVariable0": "“{0}”任务变量" +{ + "loc.messages.PSLIB_AgentVersion0Required": "需要代理版本 {0} 或更高版本。", + "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路径:“{0}”", + "loc.messages.PSLIB_EndpointAuth0": "“{0}”服务终结点凭据", + "loc.messages.PSLIB_EndpointUrl0": "“{0}”服务终结点 URL", + "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "枚举路径的子目录失败:“{0}”", + "loc.messages.PSLIB_FileNotFound0": "找不到文件: {0}。", + "loc.messages.PSLIB_Input0": "“{0}”输入", + "loc.messages.PSLIB_InvalidPattern0": "路径不能以目录分隔符结尾:“{0}”", + "loc.messages.PSLIB_LeafPathNotFound0": "找不到叶路径:“{0}”", + "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路径规范化/扩展失败。路径长度不是由“{0}”的 Kernel32 子系统返回的", + "loc.messages.PSLIB_PathNotFound0": "找不到路径:“{0}”", + "loc.messages.PSLIB_Process0ExitedWithCode1": "过程“{0}”已退出,代码为“{1}”。", + "loc.messages.PSLIB_Required0": "必需: {0}", + "loc.messages.PSLIB_StringFormatFailed": "字符串格式无效。", + "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字符串资源关键字:“{0}”", + "loc.messages.PSLIB_TaskVariable0": "“{0}”任务变量" } \ No newline at end of file diff --git a/powershell/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson b/powershell/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson index d6a041cfa..ac2765879 100644 --- a/powershell/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson +++ b/powershell/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson @@ -1,17 +1,18 @@ -{ - "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路徑: '{0}'", - "loc.messages.PSLIB_EndpointAuth0": "'{0}' 服務端點認證", - "loc.messages.PSLIB_EndpointUrl0": "'{0}' 服務端點 URL", - "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "為路徑列舉子目錄失敗: '{0}'", - "loc.messages.PSLIB_FileNotFound0": "找不到檔案: '{0}'", - "loc.messages.PSLIB_Input0": "'{0}' 輸入", - "loc.messages.PSLIB_InvalidPattern0": "模式無效: '{0}'", - "loc.messages.PSLIB_LeafPathNotFound0": "找不到分葉路徑: '{0}'", - "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路徑正規化/展開失敗。Kernel32 子系統未傳回 '{0}' 的路徑長度", - "loc.messages.PSLIB_PathNotFound0": "找不到路徑: '{0}'", - "loc.messages.PSLIB_Process0ExitedWithCode1": "處理序 '{0}' 以返回碼 '{1}' 結束。", - "loc.messages.PSLIB_Required0": "必要項: {0}", - "loc.messages.PSLIB_StringFormatFailed": "字串格式失敗。", - "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字串資源索引鍵: '{0}'", - "loc.messages.PSLIB_TaskVariable0": "'{0}' 工作變數" +{ + "loc.messages.PSLIB_AgentVersion0Required": "需要代理程式版本 {0} 或更新的版本。", + "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路徑: '{0}'", + "loc.messages.PSLIB_EndpointAuth0": "'{0}' 服務端點認證", + "loc.messages.PSLIB_EndpointUrl0": "'{0}' 服務端點 URL", + "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "為路徑列舉子目錄失敗: '{0}'", + "loc.messages.PSLIB_FileNotFound0": "找不到檔案: '{0}'", + "loc.messages.PSLIB_Input0": "'{0}' 輸入", + "loc.messages.PSLIB_InvalidPattern0": "路徑不能以目錄分隔符號結尾: '{0}'", + "loc.messages.PSLIB_LeafPathNotFound0": "找不到分葉路徑: '{0}'", + "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路徑正規化/展開失敗。Kernel32 子系統未傳回 '{0}' 的路徑長度", + "loc.messages.PSLIB_PathNotFound0": "找不到路徑: '{0}'", + "loc.messages.PSLIB_Process0ExitedWithCode1": "處理序 '{0}' 以返回碼 '{1}' 結束。", + "loc.messages.PSLIB_Required0": "必要項: {0}", + "loc.messages.PSLIB_StringFormatFailed": "字串格式失敗。", + "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字串資源索引鍵: '{0}'", + "loc.messages.PSLIB_TaskVariable0": "'{0}' 工作變數" } \ No newline at end of file diff --git a/powershell/VstsTaskSdk/ToolFunctions.ps1 b/powershell/VstsTaskSdk/ToolFunctions.ps1 index 7e62ebd6e..1f236aed0 100644 --- a/powershell/VstsTaskSdk/ToolFunctions.ps1 +++ b/powershell/VstsTaskSdk/ToolFunctions.ps1 @@ -86,7 +86,8 @@ function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS? [string]$Arguments, [string]$WorkingDirectory, [System.Text.Encoding]$Encoding, - [switch]$RequireExitCodeZero) + [switch]$RequireExitCodeZero, + [bool]$IgnoreHostException) Trace-EnteringInvocation $MyInvocation $isPushed = $false @@ -104,7 +105,15 @@ function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS? $FileName = $FileName.Replace('"', '').Replace("'", "''") Write-Host "##[command]""$FileName"" $Arguments" - Invoke-Expression "& '$FileName' --% $Arguments" + try { + Invoke-Expression "& '$FileName' --% $Arguments" + } catch [System.Management.Automation.Host.HostException] { + if ($IgnoreHostException -eq $False) { + throw + } + + Write-Host "##[warning]Host Exception was thrown by Invoke-Expression, suppress it due IgnoreHostException setting" + } Write-Verbose "Exit code: $LASTEXITCODE" if ($RequireExitCodeZero -and $LASTEXITCODE -ne 0) { Write-Error (Get-LocString -Key PSLIB_Process0ExitedWithCode1 -ArgumentList ([System.IO.Path]::GetFileName($FileName)), $LASTEXITCODE) diff --git a/powershell/VstsTaskSdk/VstsTaskSdk.psd1 b/powershell/VstsTaskSdk/VstsTaskSdk.psd1 index 7229eb9a2..3c606fdf7 100644 Binary files a/powershell/VstsTaskSdk/VstsTaskSdk.psd1 and b/powershell/VstsTaskSdk/VstsTaskSdk.psd1 differ diff --git a/powershell/VstsTaskSdk/VstsTaskSdk.psm1 b/powershell/VstsTaskSdk/VstsTaskSdk.psm1 index e6a5058b3..bba3e2bff 100644 --- a/powershell/VstsTaskSdk/VstsTaskSdk.psm1 +++ b/powershell/VstsTaskSdk/VstsTaskSdk.psm1 @@ -38,6 +38,8 @@ Export-ModuleMember -Function @( 'Select-Match' # Input functions. 'Get-Endpoint' + 'Get-SecureFileTicket' + 'Get-SecureFileName' 'Get-Input' 'Get-TaskVariable' 'Get-TaskVariableInfo' @@ -52,6 +54,9 @@ Export-ModuleMember -Function @( 'Write-AddBuildTag' 'Write-AssociateArtifact' 'Write-LogDetail' + 'Write-LoggingCommand' + 'Write-PrependPath' + 'Write-SetEndpoint' 'Write-SetProgress' 'Write-SetResult' 'Write-SetSecret' @@ -61,8 +66,11 @@ Export-ModuleMember -Function @( 'Write-TaskVerbose' 'Write-TaskWarning' 'Write-UpdateBuildNumber' + 'Write-UpdateReleaseName' 'Write-UploadArtifact' 'Write-UploadBuildLog' + 'Write-UploadFile' + 'Write-UploadSummary' # Out functions. 'Out-Default' # Server OM functions. @@ -81,6 +89,8 @@ Export-ModuleMember -Function @( 'Trace-Path' # Proxy functions 'Get-WebProxy' + # Client cert functions + 'Get-ClientCertificate' ) # Override Out-Default globally. diff --git a/powershell/definitions/Q.d.ts b/powershell/definitions/Q.d.ts deleted file mode 100644 index f47aa4f42..000000000 --- a/powershell/definitions/Q.d.ts +++ /dev/null @@ -1,386 +0,0 @@ -// Type definitions for Q -// Project: https://github.com/kriskowal/q -// Definitions by: Barrie Nemetchek , Andrew Gaspar , John Reilly -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -/** - * If value is a Q promise, returns the promise. - * If value is a promise from another library it is coerced into a Q promise (where possible). - */ -declare function Q(promise: Q.IPromise): Q.Promise; -/** - * If value is not a promise, returns a promise that is fulfilled with value. - */ -declare function Q(value: T): Q.Promise; - -declare module Q { - interface IPromise { - then(onFulfill: (value: T) => IPromise, onReject?: (reason: any) => IPromise): IPromise; - then(onFulfill: (value: T) => IPromise, onReject?: (reason: any) => U): IPromise; - then(onFulfill: (value: T) => U, onReject?: (reason: any) => IPromise): IPromise; - then(onFulfill: (value: T) => U, onReject?: (reason: any) => U): IPromise; - } - - interface Deferred { - promise: Promise; - resolve(value: T): void; - reject(reason: any): void; - notify(value: any): void; - makeNodeResolver(): (reason: any, value: T) => void; - } - - interface Promise { - /** - * Like a finally clause, allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful for collecting resources regardless of whether a job succeeded, like closing a database connection, shutting a server down, or deleting an unneeded key from an object. - - * finally returns a promise, which will become resolved with the same fulfillment value or rejection reason as promise. However, if callback returns a promise, the resolution of the returned promise will be delayed until the promise returned from callback is finished. - */ - fin(finallyCallback: () => any): Promise; - /** - * Like a finally clause, allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful for collecting resources regardless of whether a job succeeded, like closing a database connection, shutting a server down, or deleting an unneeded key from an object. - - * finally returns a promise, which will become resolved with the same fulfillment value or rejection reason as promise. However, if callback returns a promise, the resolution of the returned promise will be delayed until the promise returned from callback is finished. - */ - finally(finallyCallback: () => any): Promise; - - /** - * The then method from the Promises/A+ specification, with an additional progress handler. - */ - then(onFulfill: (value: T) => IPromise, onReject?: (reason: any) => IPromise, onProgress?: Function): Promise; - /** - * The then method from the Promises/A+ specification, with an additional progress handler. - */ - then(onFulfill: (value: T) => IPromise, onReject?: (reason: any) => U, onProgress?: Function): Promise; - /** - * The then method from the Promises/A+ specification, with an additional progress handler. - */ - then(onFulfill: (value: T) => U, onReject?: (reason: any) => IPromise, onProgress?: Function): Promise; - /** - * The then method from the Promises/A+ specification, with an additional progress handler. - */ - then(onFulfill: (value: T) => U, onReject?: (reason: any) => U, onProgress?: Function): Promise; - - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * - * This is especially useful in conjunction with all - */ - spread(onFulfilled: Function, onRejected?: Function): Promise; - - fail(onRejected: (reason: any) => IPromise): Promise; - fail(onRejected: (reason: any) => U): Promise; - /** - * A sugar method, equivalent to promise.then(undefined, onRejected). - */ - catch(onRejected: (reason: any) => U): Promise; - /** - * A sugar method, equivalent to promise.then(undefined, onRejected). - */ - catch(onRejected: (reason: any) => IPromise): Promise; - - /** - * A sugar method, equivalent to promise.then(undefined, undefined, onProgress). - */ - progress(onProgress: (progress: any) => any): Promise; - - /** - * Much like then, but with different behavior around unhandled rejection. If there is an unhandled rejection, either because promise is rejected and no onRejected callback was provided, or because onFulfilled or onRejected threw an error or returned a rejected promise, the resulting rejection reason is thrown as an exception in a future turn of the event loop. - * - * This method should be used to terminate chains of promises that will not be passed elsewhere. Since exceptions thrown in then callbacks are consumed and transformed into rejections, exceptions at the end of the chain are easy to accidentally, silently ignore. By arranging for the exception to be thrown in a future turn of the event loop, so that it won't be caught, it causes an onerror event on the browser window, or an uncaughtException event on Node.js's process object. - * - * Exceptions thrown by done will have long stack traces, if Q.longStackSupport is set to true. If Q.onerror is set, exceptions will be delivered there instead of thrown in a future turn. - * - * The Golden Rule of done vs. then usage is: either return your promise to someone else, or if the chain ends with you, call done to terminate it. - */ - done(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any, onProgress?: (progress: any) => any): void; - - /** - * If callback is a function, assumes it's a Node.js-style callback, and calls it as either callback(rejectionReason) when/if promise becomes rejected, or as callback(null, fulfillmentValue) when/if promise becomes fulfilled. If callback is not a function, simply returns promise. - */ - nodeify(callback: (reason: any, value: any) => void): Promise; - - /** - * Returns a promise to get the named property of an object. Essentially equivalent to - * - * promise.then(function (o) { - * return o[propertyName]; - * }); - */ - get(propertyName: String): Promise; - set(propertyName: String, value: any): Promise; - delete(propertyName: String): Promise; - /** - * Returns a promise for the result of calling the named method of an object with the given array of arguments. The object itself is this in the function, just like a synchronous method call. Essentially equivalent to - * - * promise.then(function (o) { - * return o[methodName].apply(o, args); - * }); - */ - post(methodName: String, args: any[]): Promise; - /** - * Returns a promise for the result of calling the named method of an object with the given variadic arguments. The object itself is this in the function, just like a synchronous method call. - */ - invoke(methodName: String, ...args: any[]): Promise; - fapply(args: any[]): Promise; - fcall(...args: any[]): Promise; - - /** - * Returns a promise for an array of the property names of an object. Essentially equivalent to - * - * promise.then(function (o) { - * return Object.keys(o); - * }); - */ - keys(): Promise; - - /** - * A sugar method, equivalent to promise.then(function () { return value; }). - */ - thenResolve(value: U): Promise; - /** - * A sugar method, equivalent to promise.then(function () { throw reason; }). - */ - thenReject(reason: any): Promise; - timeout(ms: number, message?: string): Promise; - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - delay(ms: number): Promise; - - /** - * Returns whether a given promise is in the fulfilled state. When the static version is used on non-promises, the result is always true. - */ - isFulfilled(): boolean; - /** - * Returns whether a given promise is in the rejected state. When the static version is used on non-promises, the result is always false. - */ - isRejected(): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - isPending(): boolean; - - valueOf(): any; - - /** - * Returns a "state snapshot" object, which will be in one of three forms: - * - * - { state: "pending" } - * - { state: "fulfilled", value: } - * - { state: "rejected", reason: } - */ - inspect(): PromiseState; - } - - interface PromiseState { - /** - * "fulfilled", "rejected", "pending" - */ - state: string; - value?: T; - reason?: any; - } - - // If no value provided, returned promise will be of void type - export function when(): Promise; - - // if no fulfill, reject, or progress provided, returned promise will be of same type - export function when(value: IPromise): Promise; - export function when(value: T): Promise; - - // If a non-promise value is provided, it will not reject or progress - export function when(value: T, onFulfilled: (val: T) => IPromise): Promise; - export function when(value: T, onFulfilled: (val: T) => U): Promise; - - export function when(value: IPromise, onFulfilled: (val: T) => IPromise, onRejected?: (reason: any) => IPromise, onProgress?: (progress: any) => any): Promise; - export function when(value: IPromise, onFulfilled: (val: T) => IPromise, onRejected?: (reason: any) => U, onProgress?: (progress: any) => any): Promise; - export function when(value: IPromise, onFulfilled: (val: T) => U, onRejected?: (reason: any) => IPromise, onProgress?: (progress: any) => any): Promise; - export function when(value: IPromise, onFulfilled: (val: T) => U, onRejected?: (reason: any) => U, onProgress?: (progress: any) => any): Promise; - - //export function try(method: Function, ...args: any[]): Promise; // <- This is broken currently - not sure how to fix. - - export function fbind(method: (...args: any[]) => IPromise, ...args: any[]): (...args: any[]) => Promise; - export function fbind(method: (...args: any[]) => T, ...args: any[]): (...args: any[]) => Promise; - - export function fcall(method: (...args: any[]) => T, ...args: any[]): Promise; - - export function send(obj: any, functionName: string, ...args: any[]): Promise; - export function invoke(obj: any, functionName: string, ...args: any[]): Promise; - export function mcall(obj: any, functionName: string, ...args: any[]): Promise; - - export function denodeify(nodeFunction: Function, ...args: any[]): (...args: any[]) => Promise; - export function nbind(nodeFunction: Function, thisArg: any, ...args: any[]): (...args: any[]) => Promise; - export function nfbind(nodeFunction: Function, ...args: any[]): (...args: any[]) => Promise; - export function nfcall(nodeFunction: Function, ...args: any[]): Promise; - export function nfapply(nodeFunction: Function, args: any[]): Promise; - - export function ninvoke(nodeModule: any, functionName: string, ...args: any[]): Promise; - export function npost(nodeModule: any, functionName: string, args: any[]): Promise; - export function nsend(nodeModule: any, functionName: string, ...args: any[]): Promise; - export function nmcall(nodeModule: any, functionName: string, ...args: any[]): Promise; - - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: IPromise[]): Promise; - /** - * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. - */ - export function all(promises: any[]): Promise; - - /** - * Returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, i.e. become either fulfilled or rejected. - */ - export function allSettled(promises: IPromise[]): Promise[]>; - /** - * Returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, i.e. become either fulfilled or rejected. - */ - export function allSettled(promises: any[]): Promise[]>; - - export function allResolved(promises: IPromise[]): Promise[]>; - export function allResolved(promises: any[]): Promise[]>; - - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: any[], onFulfilled: (...args: any[]) => IPromise, onRejected?: (reason: any) => IPromise): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: any[], onFulfilled: (...args: any[]) => IPromise, onRejected?: (reason: any) => U): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: any[], onFulfilled: (...args: any[]) => U, onRejected?: (reason: any) => IPromise): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: any[], onFulfilled: (...args: any[]) => U, onRejected?: (reason: any) => U): Promise; - - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: IPromise[], onFulfilled: (...args: T[]) => IPromise, onRejected?: (reason: any) => IPromise): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: IPromise[], onFulfilled: (...args: T[]) => IPromise, onRejected?: (reason: any) => U): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: IPromise[], onFulfilled: (...args: T[]) => U, onRejected?: (reason: any) => IPromise): Promise; - /** - * Like then, but "spreads" the array into a variadic fulfillment handler. If any of the promises in the array are rejected, instead calls onRejected with the first rejected promise's rejection reason. - * This is especially useful in conjunction with all. - */ - export function spread(promises: IPromise[], onFulfilled: (...args: T[]) => U, onRejected?: (reason: any) => U): Promise; - - /** - * Returns a promise that will have the same result as promise, except that if promise is not fulfilled or rejected before ms milliseconds, the returned promise will be rejected with an Error with the given message. If message is not supplied, the message will be "Timed out after " + ms + " ms". - */ - export function timeout(promise: Promise, ms: number, message?: string): Promise; - - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - export function delay(promise: Promise, ms: number): Promise; - /** - * Returns a promise that will have the same result as promise, but will only be fulfilled or rejected after at least ms milliseconds have passed. - */ - export function delay(value: T, ms: number): Promise; - /** - * Returns a promise that will be fulfilled with undefined after at least ms milliseconds have passed. - */ - export function delay(ms: number): Promise ; - /** - * Returns whether a given promise is in the fulfilled state. When the static version is used on non-promises, the result is always true. - */ - export function isFulfilled(promise: Promise): boolean; - /** - * Returns whether a given promise is in the rejected state. When the static version is used on non-promises, the result is always false. - */ - export function isRejected(promise: Promise): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - export function isPending(promise: Promise): boolean; - - /** - * Returns a "deferred" object with a: - * promise property - * resolve(value) method - * reject(reason) method - * notify(value) method - * makeNodeResolver() method - */ - export function defer(): Deferred; - - /** - * Returns a promise that is rejected with reason. - */ - export function reject(reason?: any): Promise; - - export function Promise(resolver: (resolve: (val: IPromise) => void , reject: (reason: any) => void , notify: (progress: any) => void ) => void ): Promise; - export function Promise(resolver: (resolve: (val: T) => void , reject: (reason: any) => void , notify: (progress: any) => void ) => void ): Promise; - - /** - * Creates a new version of func that accepts any combination of promise and non-promise values, converting them to their fulfillment values before calling the original func. The returned version also always returns a promise: if func does a return or throw, then Q.promised(func) will return fulfilled or rejected promise, respectively. - * - * This can be useful for creating functions that accept either promises or non-promise values, and for ensuring that the function always returns a promise even in the face of unintentional thrown exceptions. - */ - export function promised(callback: (...args: any[]) => T): (...args: any[]) => Promise; - - /** - * Returns whether the given value is a Q promise. - */ - export function isPromise(object: any): boolean; - /** - * Returns whether the given value is a promise (i.e. it's an object with a then function). - */ - export function isPromiseAlike(object: any): boolean; - /** - * Returns whether a given promise is in the pending state. When the static version is used on non-promises, the result is always false. - */ - export function isPending(object: any): boolean; - - /** - * This is an experimental tool for converting a generator function into a deferred function. This has the potential of reducing nested callbacks in engines that support yield. - */ - export function async(generatorFunction: any): (...args: any[]) => Promise; - export function nextTick(callback: Function): void; - - /** - * A settable property that will intercept any uncaught errors that would otherwise be thrown in the next tick of the event loop, usually as a result of done. Can be useful for getting the full stack trace of an error in browsers, which is not usually possible with window.onerror. - */ - export var onerror: (reason: any) => void; - /** - * A settable property that lets you turn on long stack trace support. If turned on, "stack jumps" will be tracked across asynchronous promise operations, so that if an uncaught error is thrown by done or a rejection reason's stack property is inspected in a rejection callback, a long stack trace is produced. - */ - export var longStackSupport: boolean; - - /** - * Calling resolve with a pending promise causes promise to wait on the passed promise, becoming fulfilled with its fulfillment value or rejected with its rejection reason (or staying pending forever, if the passed promise does). - * Calling resolve with a rejected promise causes promise to be rejected with the passed promise's rejection reason. - * Calling resolve with a fulfilled promise causes promise to be fulfilled with the passed promise's fulfillment value. - * Calling resolve with a non-promise value causes promise to be fulfilled with that value. - */ - export function resolve(object: IPromise): Promise; - /** - * Calling resolve with a pending promise causes promise to wait on the passed promise, becoming fulfilled with its fulfillment value or rejected with its rejection reason (or staying pending forever, if the passed promise does). - * Calling resolve with a rejected promise causes promise to be rejected with the passed promise's rejection reason. - * Calling resolve with a fulfilled promise causes promise to be fulfilled with the passed promise's fulfillment value. - * Calling resolve with a non-promise value causes promise to be fulfilled with that value. - */ - export function resolve(object: T): Promise; -} - -declare module "q" { - export = Q; -} diff --git a/powershell/make-util.js b/powershell/make-util.js index 06c532ea3..aac7268c9 100644 --- a/powershell/make-util.js +++ b/powershell/make-util.js @@ -99,12 +99,12 @@ var ensureTool = function (name, versionArgs, validate) { if (versionArgs) { var result = exec(name + ' ' + versionArgs); if (typeof validate == 'string') { - if (result.output.trim() != validate) { + if (result.stdout.trim() != validate) { throw new Error('expected version: ' + validate); } } else { - validate(result.output.trim()); + validate(result.stdout.trim()); } } diff --git a/powershell/make.js b/powershell/make.js index 73112823b..56c4f66c0 100644 --- a/powershell/make.js +++ b/powershell/make.js @@ -28,7 +28,7 @@ target.build = function() { var minimatchPackage = util.downloadArchive('https://www.nuget.org/api/v2/package/minimatch/1.1.0'); util.cp(path.join(minimatchPackage, 'lib', 'portable-net40%2Bsl50%2Bwin%2Bwp80', 'Minimatch.dll'), path.join(buildPath, 'VstsTaskSdk')); - var compiledHelperPackage = util.downloadArchive('https://vstsagenttools.blob.core.windows.net/tools/VstsTaskSdkCompiledHelpers/2/VstsTaskSdk.zip'); + var compiledHelperPackage = util.downloadArchive('https://vstsagenttools.blob.core.windows.net/tools/VstsTaskSdkCompiledHelpers/3/VstsTaskSdk.zip'); util.cp(path.join(compiledHelperPackage, 'VstsTaskSdk.dll'), path.join(buildPath, 'VstsTaskSdk')); // stamp the version number from the package.json onto the PowerShell module definition @@ -62,14 +62,14 @@ target.build = function() { target.test = function() { util.ensureTool('tsc', '--version', 'Version 1.8.7'); - util.ensureTool('mocha', '--version', '2.3.3'); + util.ensureTool('mocha', '--version', '5.2.0'); target.build(); util.mkdir('-p', testPath); - util.run(`tsc --outDir ${testPath} --module commonjs --rootDir Tests Tests/lib/psRunner.ts`); - util.run(`tsc --outDir ${testPath} --rootDir Tests Tests/L0/_suite.ts`); + util.run(`tsc --outDir "${testPath}" --module commonjs --target es6 --rootDir Tests Tests/lib/psRunner.ts`); + util.run(`tsc --outDir "${testPath}" --module commonjs --target es6 --rootDir Tests Tests/L0/_suite.ts`); util.cp('-r', path.join('Tests', '*'), testPath); - util.run('mocha ' + path.join(testPath, 'L0', '_suite.js')); + util.run('mocha "' + path.join(testPath, 'L0', '_suite.js') + '"'); } target.loc = function() { diff --git a/powershell/package-lock.json b/powershell/package-lock.json new file mode 100644 index 000000000..5712393fb --- /dev/null +++ b/powershell/package-lock.json @@ -0,0 +1,475 @@ +{ + "name": "vsts-task-sdk", + "version": "0.14.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "adm-zip": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "dev": true, + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "~2.0.3" + } + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "dev": true, + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-1.8.7.tgz", + "integrity": "sha1-NeODjeMckc/h2MIODleF04aTikk=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/powershell/package.json b/powershell/package.json index 86531977e..95b8d4a56 100644 --- a/powershell/package.json +++ b/powershell/package.json @@ -1,6 +1,6 @@ { "name": "vsts-task-sdk", - "version": "0.10.0", + "version": "0.14.0", "description": "VSTS Task SDK", "scripts": { "build": "node make.js build", @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/Microsoft/vsts-task-lib" + "url": "https://github.com/Microsoft/azure-pipelines-task-lib" }, "keywords": [ "vsts", @@ -19,14 +19,13 @@ "author": "Microsoft", "license": "MIT", "bugs": { - "url": "https://github.com/Microsoft/vsts-task-lib/issues" + "url": "https://github.com/Microsoft/azure-pipelines-task-lib/issues" }, - "homepage": "https://github.com/Microsoft/vsts-task-lib#readme", + "homepage": "https://github.com/Microsoft/azure-pipelines-task-lib#readme", "devDependencies": { - "adm-zip": "0.4.7", - "mocha": "2.3.3", - "q": "1.4.1", - "shelljs": "^0.3.0", + "adm-zip": "^0.5.9", + "mocha": "5.2.0", + "shelljs": "^0.8.5", "sync-request": "3.0.1", "typescript": "1.8.7" } diff --git a/res/UseNode5.ps1 b/res/UseNode5.ps1 deleted file mode 100644 index 9cae99f4a..000000000 --- a/res/UseNode5.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -$ErrorActionPreference='Stop' -$m=[version]'2.115.0' -if (([version]$env:AGENT_VERSION) -lt $m) { throw "Min agent $m" } -$v='5.10.1' -$d="$env:AGENT_TOOLSDIRECTORY\node\$v\x64" -$c="$d.complete" -$u="https://nodejs.org/dist/v5.10.1/win-x64/node" -if (!(Test-Path $c)) { - "rm $d" - ri $d -rec -for -ea 0 - md $d - "downloading" - $w=New-Object System.Net.WebClient - $w.DownloadFile("$u.exe", "$d\node.exe") - $w.DownloadFile("$u.lib", "$d\node.lib") - New-Item $c -Type File -} -"##vso[task.prependpath]$d" diff --git a/send-notifications.ps1 b/send-notifications.ps1 new file mode 100644 index 000000000..4de65d33b --- /dev/null +++ b/send-notifications.ps1 @@ -0,0 +1,45 @@ +param( + [Parameter(Mandatory = $true)] + [bool]$IsPRCreated, + [Parameter(Mandatory = $true)] + [string]$RepoName +) + +# Function sends Office 365 connector card to webhook. +# It requires title and message text displyed in card and theme color used to hignlight card. +function Send-Notification { + param ( + [Parameter(Mandatory = $true)] + [string]$titleText, + [Parameter(Mandatory = $true)] + [string]$messageText, + [Parameter(Mandatory = $true)] + [string]$themeColor + ) + + $body = [PSCustomObject]@{ + title = $titleText + text = $messageText + themeColor = $themeColor + } | ConvertTo-Json + + Invoke-RestMethod -Uri $($env:TEAMS_WEBHOOK) -Method Post -Body $body -ContentType 'application/json' +} + +$wikiLink = "[Wiki](https://mseng.visualstudio.com/AzureDevOps/_wiki/wikis/AzureDevOps.wiki/16150/Localization-update)" + +if ($IsPRCreated) { + $pullRequestLink = "[PR $($env:PR_NUMBER)]($($env:PR_LINK))" + $titleText = "Azure Pipelines $RepoName Localization update PR created - ID $($env:PR_NUMBER)" + $messageText = "Created $RepoName Localization update PR. Please review and approve/merge $pullRequestLink. Related article in $wikiLink." + $themeColor = "#FFFF00" +} +else { + $buildUrl = "$env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI$env:SYSTEM_TEAMPROJECT/_build/results?buildId=$($env:BUILD_BUILDID)&_a=summary" + $buildLink = "[ID $($env:BUILD_BUILDID)]($($buildUrl))" + $titleText = "Azure Pipelines $RepoName Localization build failed - ID $($env:BUILD_BUILDID)" + $messageText = "Failed to create $RepoName Localization update PR. Please review the results of failed build $buildLink. Related article in $wikiLink." + $themeColor = "#FF0000" +} + +Send-Notification -titleText $titleText -messageText $messageText -themeColor $themeColor diff --git a/tasks.schema.json b/tasks.schema.json new file mode 100644 index 000000000..08dcde4d4 --- /dev/null +++ b/tasks.schema.json @@ -0,0 +1,541 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "id": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "title": "Azure DevOps Tasks schema", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "A unique guid for this task", + "pattern": "^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$" + }, + "name": { + "type": "string", + "description": "Name with no spaces", + "pattern": "^[A-Za-z0-9\\-]+$" + }, + "friendlyName": { + "type": "string", + "description": "Descriptive name (spaces allowed). Must be <= 40 chars" + }, + "description": { + "type": "string", + "description": "Detailed description of what your task does" + }, + "helpUrl": { + "type": "string" + }, + "helpMarkDown": { + "type": "string" + }, + "author": { + "type": "string" + }, + "preview": { + "type": "boolean" + }, + "deprecated": { + "type": "boolean", + "description": "Task is deprecated only when the latest version is marked as deprecated. Deprecated tasks appear at the end of searches under a section that is collapsed by default." + }, + "showEnvironmentVariables": { + "type": "boolean", + "description": "Toggles showing the environment variable editor in the task editor UI. Allows passing environment variables to script based tasks." + }, + "runsOn": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Agent", + "MachineGroup", + "Server", + "ServerGate", + "DeploymentGroup" + ] + } + }, + "visibility": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Build", + "Release" + ] + } + }, + "category": { + "type": "string", + "description": "Where the task appears in Azure DevOps. Use the 'Azure *' categories for Azure DevOps and Azure DevOps Server 2019. Use the other categories for Team Foundation Server 2018 and below.", + "enum": [ + "Build", + "Utility", + "Test", + "Package", + "Deploy", + + "Azure Repos", + "Azure Boards", + "Azure Pipelines", + "Azure Test Plans", + "Azure Artifacts" + ] + }, + "groups": { + "type": "array", + "description": "Describes groups that task properties may be logically grouped by in the UI.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "displayName" + ], + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "isExpanded": { + "type": "boolean" + }, + "visibleRule": { + "type": "string", + "description": "Allow's you to define a rule which dictates when the group will be visible to a user, for example \"variableName1 != \\\"\\\" && variableName2 = value || variableName3 NotEndsWith value\"" + } + } + } + }, + "demands": { + "type": "array", + "description": "Allows you to define a list of demands that a build agent requires to run this build task.", + "items": { + "type": "string" + } + }, + "minimumAgentVersion": { + "type": "string", + "pattern": "^\\d+\\.\\d+(\\.\\d+)?$" + }, + "version": { + "type": "object", + "additionalProperties": false, + "description": "Always update this when you release your task, so that the agents utilize the latest code.", + "required": [ + "Major", + "Minor", + "Patch" + ], + "properties": { + "Major": { + "type": "number" + }, + "Minor": { + "type": "number" + }, + "Patch": { + "type": "number" + } + } + }, + "instanceNameFormat": { + "type": "string", + "description": "This is how the task will be displayed within the build step list - you can use variable values by using $(variablename)" + }, + "releaseNotes": { + "type": "string" + }, + "inputs": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "label", + "type" + ], + "properties": { + "name": { + "type": "string", + "description": "The variable name to use to store the user-supplied value", + "pattern": "^[A-Za-z][A-Za-z0-9]*$" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "label": { + "type": "string", + "description": "The text displayed to the user for the input label" + }, + "type": { + "type": "string", + "description": "The type that dictates the control rendered to the user.", + "anyOf": [ + { + "enum": [ + "boolean", + "filePath", + "multiLine", + "pickList", + "radio", + "secureFile", + "string", + "int", + "identities", + "querycontrol" + ] + }, + { + "type": "string", + "pattern": "^connectedService\\:.+$" + } + ] + }, + "defaultValue": { + "type": [ + "string", + "boolean" + ], + "description": "The default value to apply to this input." + }, + "required": { + "type": "boolean", + "description": "Whether the input is a required field (default is false).", + "default": false + }, + "helpMarkDown": { + "type": "string", + "description": "Help to be displayed when hovering over the help icon for the input. To display URLs use the format [Text To Display](http://Url)" + }, + "groupName": { + "type": "string", + "description": "Setting this to the name of a group defined in 'groups' will place the input into that group." + }, + "visibleRule": { + "type": "string", + "description": "Allow's you to define a rule which dictates when the input will be visible to a user, for example \"variableName1 != \\\"\\\" && variableName2 = value || variableName3 NotEndsWith value\"" + }, + "properties": { + "type": "object", + "properties": { + "EditableOptions": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "MultiSelect": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "MultiSelectFlatList": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "DisableManageLink": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "IsSearchable": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "PopulateDefaultValue": { + "type": "string", + "enum": [ + "True", + "False" + ] + }, + "isVariableOrNonNegativeNumber": { + "type": "string", + "enum": [ + "true", + "false" + ] + }, + "resizable": { + "type": "boolean" + }, + "rows": { + "type": "string", + "pattern": "^\\d+$" + }, + "maxLength": { + "type": "string", + "pattern": "^\\d+$" + }, + "editorExtension": { + "type": "string" + }, + "EndpointFilterRule": { + "type": "string" + } + } + }, + "options": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "dataSourceBindings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "type": "string" + }, + "endpointId": { + "type": "string" + }, + "dataSourceName": { + "type": "string" + }, + "parameters": { + "type": "object" + }, + "resultTemplate": { + "type": "string" + }, + "endpointUrl": { + "type": "string" + }, + "resultSelector": { + "type": "string" + }, + "RequestVerb": { + "type": "string", + "enum": [ + "GET", + "POST", + "DELETE", + "OPTIONS", + "HEAD", + "PUT", + "TRACE", + "PATCH" + ] + }, + "requestContent": { + "type": "string" + }, + "callbackContextTemplate": { + "type": "string" + }, + "callbackRequiredTemplate": { + "type": "string" + }, + "initialContextTemplate": { + "type": "string" + } + } + } + }, + "sourceDefinitions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "selector": { + "type": "string" + }, + "keySelector": { + "type": "string" + }, + "authKey": { + "type": "string" + } + } + } + }, + "prejobexecution": { + "type": "object", + "additionalProperties": false, + "description": "Execution options for this task (on Pre-Job stage)", + "properties": { + "Node16": { + "$ref": "#/definitions/executionObject" + }, + "Node10": { + "$ref": "#/definitions/executionObject" + }, + "Node": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell3": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell": { + "$ref": "#/definitions/executionObject" + } + } + }, + "execution": { + "type": "object", + "additionalProperties": false, + "description": "Execution options for this task", + "properties": { + "Node16": { + "$ref": "#/definitions/executionObject" + }, + "Node10": { + "$ref": "#/definitions/executionObject" + }, + "Node": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell3": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell": { + "$ref": "#/definitions/executionObject" + } + } + }, + "postjobexecution": { + "type": "object", + "additionalProperties": false, + "description": "Execution options for this task (on Post-Job stage)", + "properties": { + "Node16": { + "$ref": "#/definitions/executionObject" + }, + "Node10": { + "$ref": "#/definitions/executionObject" + }, + "Node": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell3": { + "$ref": "#/definitions/executionObject" + }, + "PowerShell": { + "$ref": "#/definitions/executionObject" + } + } + }, + "messages": { + "type": "object" + }, + "outputVariables": { + "type": "array", + "description": "Describes output variables of task.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The variable name", + "pattern": "^[A-Za-z][A-Za-z0-9]*$" + }, + "description": { + "type": "string", + "description": "Detailed description of the variable" + } + } + } + }, + "restrictions": { + "type": "object", + "additionalProperties": false, + "description": "Restrictions on tasks", + "properties": { + "commands": { + "type": "object", + "additionalProperties": false, + "description": "Restrictions on available task commands", + "properties": { + "mode": { + "type": "string", + "enum": [ + "any", + "restricted" + ] + } + } + }, + "settableVariables": { + "type": "object", + "additionalProperties": false, + "description": "Restrictions on which variables can be set via commands", + "properties": { + "allowed": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "$schema": { + "type": "string" + } + }, + "definitions": { + "executionObject": { + "type": "object", + "additionalProperties": true, + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "The target file to be executed. You can use variables here in brackets e.g. $(currentDirectory)\filename.ps1" + }, + "platforms": { + "type": "array", + "items": { + "enum": [ + "windows" + ] + } + }, + "argumentFormat": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + } + } + } + } +}