diff --git a/.directory b/.directory new file mode 100644 index 00000000..9f533015 --- /dev/null +++ b/.directory @@ -0,0 +1,5 @@ +[Dolphin] +HeaderColumnWidths=469,82,146 +Timestamp=2016,7,15,0,17,26 +Version=3 +ViewMode=1 diff --git a/.gitignore b/.gitignore index da548435..587fb5ce 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,13 @@ node_modules # VS Code Settings .settings +.vscode +*_sample.json # Generated by nexe -tmp/ \ No newline at end of file +tmp/ + +# ignore npm i generated package-lock.json +package-lock.json + + diff --git a/.vscode/settings.json b/.vscode/settings.json index dd38fa54..32a68e9f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,3 @@ -// Place your settings in this file to overwrite default and user settings. { "files.exclude": { "**/.git": true, @@ -8,5 +7,9 @@ "tmp/": true }, "editor.insertSpaces": false, - "typescript.tsdk": "./node_modules/typescript/lib" -} \ No newline at end of file + "typescript.tsdk": "./node_modules/typescript/lib", + "vsicons.presets.angular": false, + "editor.formatOnPaste": true, + "editor.minimap.enabled": true, + "editor.snippetSuggestions": "top" +} diff --git a/README.md b/README.md index 526ecd6f..0bd5155f 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,18 @@ > NOTE: If you are looking for the new Azure DevOps CLI, see [vsts-cli](https://github.com/microsoft/vsts-cli) [![NPM version](https://badge.fury.io/js/tfx-cli.svg)](http://badge.fury.io/js/tfx-cli) +#### Internal Deploy / Pull Request validation +[![build passing](https://shfeldma.visualstudio.com/_apis/public/build/definitions/f4b6db46-e446-49f0-a424-0bfb52c0925d/2/badge)](https://shfeldma.visualstudio.com/_apis/public/build/definitions/f4b6db46-e446-49f0-a424-0bfb52c0925d/2/badge) Command utility for interacting with Microsoft Team Foundation Server and Azure DevOps Services (formerly VSTS). It is cross platform and supported on Windows, OS X, and Linux. ## Setup -First, download and install [Node.js](http://nodejs.org) 4.0.x or later and NPM (included with the installer) +First, download and install [Node.js](http://nodejs.org) 7.0.x or later and NPM (included with the installer or from sources) ### Linux/OSX ```bash -sudo npm install -g tfx-cli +sudo -E npm install -g tfx-cli ``` ### Windows @@ -38,6 +40,7 @@ tfx --help * `tfx build` ([builds](docs/builds.md)): Queue, view, and get details for builds * `tfx build tasks` ([build tasks](docs/buildtasks.md)): Create, list, upload and delete build tasks +* `tfx build definition` ([build definition/definitions](docs/definitions.md)): Create, manage, show, list, export, upload and delete build definitions * `tfx extension` ([extensions](docs/extensions.md)): Package, manage, publisher Team Foundation Server / Azure DevOps extensions * `tfx workitem` ([work items](docs/workitems.md)): Create, query and view work items. @@ -126,3 +129,34 @@ We take contributions and fixes via Pull Request. [Read here](docs/contributions ## Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Manual installation (from sources) +* refer to installation for your OS (and follow the installation steps). +* clone the repository. +* to compile the sources run (see additional node modules): +```bash +npm update +npm run build `(from the repository root)` +``` +`link the executable (and make executable)` +### Linux (bash) +```bash +sudo ln -s \app.js /usr/bin/tfx +sudo chmod 755 /usr/bin/tfx +``` +### windows +replace the content of `%appdata%\npm\tfx.cmd` with the following: +```bash +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\node_modules\tfx-cli\_build\app.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\node_modules\tfx-cli\_build\app.js" %* +) +``` +### additional node modules +`run "npm outdated / update" to resolve modules dependecy or install the following modules (this may need to happen befor compilation)` +```bash +npm install archiver colors graceful-fs gulp-filter gulp-mocha gulp-tsb gulp-util is-utf8 pug jszip node-uuid prompt q readable-stream ts-promise typescript unique-stream user-home validator azure-devops-node-api xml2js del os-homedir copy-paste shelljs lodash minimatch@3.0.2 pretty-hrtime liftoff tildify interpret v8flags minimist onecolor winreg glob json-in-place mkdirp +``` diff --git a/app/app.ts b/app/app.ts index 8196a3c4..7ef94f87 100644 --- a/app/app.ts +++ b/app/app.ts @@ -1,3 +1,4 @@ +#!/usr/bin/env node import command = require("./lib/command"); import common = require("./lib/common"); import errHandler = require("./lib/errorhandler"); diff --git a/app/exec/build/agents/default.ts b/app/exec/build/agents/default.ts new file mode 100644 index 00000000..53100ec4 --- /dev/null +++ b/app/exec/build/agents/default.ts @@ -0,0 +1,39 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import buildBase = require("../default"); + +export interface AgentArguments extends buildBase.BuildArguments { + agentId: args.IntArgument; + agentName: args.StringArgument; + poolId: args.IntArgument; + userCapabilityKey: args.StringArgument; + userCapabilityValue: args.StringArgument; + disable: args.StringArgument; + deleteAgent: args.StringArgument; + parallel: args.IntArgument; + waitForInProgressRequests: args.StringArgument; +} + +export function getCommand(args: string[]): AgentBase { + return new AgentBase(args); +} + +export class AgentBase extends buildBase.BuildBase { + protected description = "Commands for managing Agents."; + protected serverCommand = false; + protected setCommandArgs(): void { + super.setCommandArgs(); + this.registerCommandArgument("agentId", "Agent ID", "Identifies a particular Agent.", args.IntArgument,null); + this.registerCommandArgument("agentName", "Agent Name", "Required Agent Name.", args.StringArgument, null); + this.registerCommandArgument("poolId", "Agent Pool Id", "Required Agent pool ID For Edit.", args.IntArgument, null); + this.registerCommandArgument("userCapabilityKey", "Capability to add / edit", "Capability to add / edit to the Agent.", args.StringArgument, null); + this.registerCommandArgument("userCapabilityValue", "Value to add / edit", "Value to add / edit to the Agent User Capabilities.", args.StringArgument, null); + this.registerCommandArgument("disable", "disable / enable agent", "Update the agent status.", args.StringArgument, null); + this.registerCommandArgument("deleteAgent", "deleteagent", "Delete an agent.", args.StringArgument, null); + this.registerCommandArgument("parallel", "max agent parallelism", "Maximum parallel agent runs.", args.IntArgument, null); + this.registerCommandArgument("waitForInProgressRequests", "Wait For Active Requests", "Waiting for active Agent jobs / requests", args.StringArgument, null); + } + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} diff --git a/app/exec/build/agents/delete.ts b/app/exec/build/agents/delete.ts new file mode 100644 index 00000000..c7249f55 --- /dev/null +++ b/app/exec/build/agents/delete.ts @@ -0,0 +1,93 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import agentBase = require("./default"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import trace = require("../../../lib/trace"); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); + +export function describe(): string { + return "Delete an Agent"; +} + +export function getCommand(args: string[]): AgentDelete { + return new AgentDelete(args); +} + +export class AgentDelete extends agentBase.AgentBase { + protected serverCommand = true; + protected description = "Delete a Agent."; + protected getHelpArgs(): string[] { + return ["poolId", "agentId", "agentName", "deleteAgent"]; + } + + + public exec(): Promise { + trace.debug("delete-agents.exec"); + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + + return Promise.all([ + this.commandArgs.poolId.val(), + this.commandArgs.agentId.val(), + this.commandArgs.agentName.val(), + this.commandArgs.deleteAgent.val() + ]).then((values) => { + const [poolId, agentid, agentname, deleteAgent] = values; + var agents: number[] = null; + trace.debug("getting pool (id) : %s", poolId); + trace.debug("getting agent (id) : %s", agentid); + trace.debug("getting agent (name) : %s", agentname); + trace.info("Deleting Agent..."); + if (agentid) { + agents = [agentid as number]; + } + else if (agentname) { + trace.debug("No agent Id provided, checking for agent with name " + agentname); + return agentapi.then((api) => { api.getAgents(poolId as number, agentname as string).then((ao: taskAgentContracts.TaskAgent[]) => { + if (ao.length > 0) { + var aid = ao[0].id; + var an = ao[0].name; + trace.debug("found, agent id %s for agent name %s", aid, an); + return this._deleteAgent(agentapi, poolId as number, agentid as number, deleteAgent as string); + } + else { + trace.debug("No agents found with name " + agentname); + throw new Error("No agents found with name " + agentname); + + } + }); + }); + } + + trace.debug("deleting agent: %s", agentname); + return this._deleteAgent(agentapi, poolId as number, agentid as number, deleteAgent as string); + }); + } + + public friendlyOutput(agent: taskAgentContracts.TaskAgent): void { + trace.println(); + trace.success('Agent %s deleted successfully!', agent.name); + } + + private _deleteAgent(agentapi, pool: number, agentid: number, deleteAgent: string) { + return agentapi.getAgent(pool, agentid, true, true, null).then((agent) => { + trace.debug("deleting Agent: %s", deleteAgent); + + if (deleteAgent) { + if (deleteAgent == "true") { + return agentapi.deleteAgent(pool, agentid).then(() => { + trace.debug("agent set for deletion : %s"); + agent.id = null; + return agent; + }) + } + if (deleteAgent != "true") { + trace.error("allowed value is [true] only!") + } + } + }); + } +} diff --git a/app/exec/build/agents/list.ts b/app/exec/build/agents/list.ts new file mode 100644 index 00000000..14b5e71a --- /dev/null +++ b/app/exec/build/agents/list.ts @@ -0,0 +1,43 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import agentBase = require("./default"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import trace = require("../../../lib/trace"); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); + +export function getCommand(args: string[]): AgentDetails { + return new AgentDetails(args); +} + +export class AgentDetails extends agentBase.AgentBase { + protected serverCommand = true; + protected description = "Display extended Agent details."; + protected getHelpArgs(): string[] { + return ["project", "poolId"]; + } + + public exec(): Promise { + trace.debug("list-agents.exec"); + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return this.commandArgs.poolId.val().then((pool) => { + trace.debug("getting pool : %s", pool); + return agentapi.then((api) => { return api.getAgents(pool); + }); + }); + } + + public friendlyOutput(agents: taskAgentContracts.TaskAgent[]): void { + if (!agents) { + throw new Error("pool not supplied or not found"); + } + trace.info("Agents in pool:") + trace.println(); + agents.forEach((agent) => { + trace.info(" %s (%s) : %s ", agent.id, agent.version, agent.name); + }); + } +} diff --git a/app/exec/build/agents/update.ts b/app/exec/build/agents/update.ts new file mode 100644 index 00000000..0ce766f4 --- /dev/null +++ b/app/exec/build/agents/update.ts @@ -0,0 +1,157 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import agentBase = require("./default"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import trace = require("../../../lib/trace"); +import taskAgentApi = require("azure-devops-node-api/TaskAgentApi"); + + +export function getCommand(args: string[]): AgentUpdate { + return new AgentUpdate(args); +} + +export class AgentUpdate extends agentBase.AgentBase { + protected serverCommand = true; + protected description = "Show / Update task agent details."; + protected getHelpArgs(): string[] { + return ["poolId", "agentId", "agentName", "userCapabilityKey", "userCapabilityValue", "disable", "parallel", "waitForInProgressRequests"]; + } + + public exec(): Promise { + trace.debug("update-agents.exec"); + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return Promise.all([ + this.commandArgs.agentId.val(), + this.commandArgs.agentName.val(), + this.commandArgs.poolId.val(), + this.commandArgs.userCapabilityKey.val(), + this.commandArgs.userCapabilityValue.val(), + this.commandArgs.disable.val(), + this.commandArgs.parallel.val(), + this.commandArgs.waitForInProgressRequests.val(), + ]).then((values) => { + const [agentid, agentname, pool, newkey, value, disable, maxParallel, waitForInProgressRequests] = values; + var agents: number[] = null; + trace.debug("getting pool : %s", pool); + trace.debug("getting agent (id) : %s", agentid); + trace.debug("getting agent (name) : %s", agentname); + var include: boolean = true; + if (agentid) { + agents = [agentid as number]; + } + else if (agentname) { + trace.debug("No agent Id provided, checking for agent with name " + agentname); + return agentapi.then((api) => {api.getAgents(pool as number, agentname as string).then((ao: taskAgentContracts.TaskAgent[]) => { + if (ao.length > 0) { + var aid = ao[0].id; + var an = ao[0].name; + trace.debug("found, agent id %s for agent name %s", aid, an); + return this._getOrUpdateAgent(api, pool as number, aid, newkey as string, value as string, include, disable as string, maxParallel as number, waitForInProgressRequests as string); + } + else { + trace.debug("No agents found with name " + agentname); + throw new Error("No agents found with name " + agentname); + + } + }); + trace.debug("disable request: %s", disable); + return this._getOrUpdateAgent(api, pool as number, agentid as number, newkey as string, value as string, include, disable as string, maxParallel as number, waitForInProgressRequests as string); + }); + } + }); + }; + + public friendlyOutput(agent: taskAgentContracts.TaskAgent): void { + if (!agent) { + throw new Error("agent / pool not supplied or not found"); + } + else { + trace.println(); + trace.info("Agent id : %s", agent.id ? agent.id : "unknown"); + trace.info("Agent name : %s", agent.name ? agent.name : "unknown"); + trace.info("Version : %s", agent.version ? agent.version : "unknown"); + trace.info("status : %s", agent.status ? taskAgentContracts.TaskAgentStatus[agent.status] : "unknown"); + trace.info("enabled : %s", agent.enabled ? agent.enabled : "unknown"); + trace.info("maxParallelism : %s", agent.maxParallelism); + if (agent.systemCapabilities) { + trace.info("System capabilities : "); + } + for (var key in agent.systemCapabilities) { + trace.info(" %s : %s", key, agent.systemCapabilities[key]); + } + if (agent.userCapabilities) { + trace.info("User capabilities : "); + } + for (var key in agent.userCapabilities) { + trace.info(" %s : %s", key, agent.userCapabilities[key]); + } + } + } + + private _getOrUpdateAgent(agentapi: agentClient.ITaskAgentApiBase, pool: number, agentid: number, newkey: string, value: string, include: boolean, disable: string, Parallel: number, waitForInProgressRequests: string) { + return agentapi.getAgent(pool, agentid, true, true, null).then((agent) => { + trace.debug("disable request: %s", disable); + if (Parallel) { + agent.maxParallelism = Parallel; + agentapi.updateAgent(agent, pool, agentid); + } + if (disable) { + if (disable == "true") { + include = false; + trace.debug("agent status (enabled): %s", agent.enabled); + agent.enabled = false; + agentapi.updateAgent(agent, pool, agentid); + trace.debug("agent status (enabled): %s", agent.enabled); + } + if (disable == "false") { + include = false; + trace.debug("agent status (enabled): %s", agent.enabled); + agent.enabled = true; + agentapi.updateAgent(agent, pool, agentid); + trace.debug("agent status (enabled): %s", agent.enabled); + } + if (disable != "true" && disable != "false") { + trace.error("allowed values are [true] or [false]!") + } + } + if (newkey) { + include = false; + var capabilities: { [key: string]: string; } = agent.userCapabilities; + //capabilities[newkey] = value; + let userCapabilitiesObj = {}; + for (var attrname in capabilities) { userCapabilitiesObj[attrname] = capabilities[attrname] } + userCapabilitiesObj[newkey] = value; + + agentapi.updateAgentUserCapabilities(userCapabilitiesObj, pool, agentid); + }; + + if (waitForInProgressRequests == "true") { + var timer = setInterval(function () { + return agentapi.getAgentRequestsForAgent(pool, agent.id, 0).then(function (requests) { + if (requests.length <= 0) { + clearInterval(timer); + timer = null; + trace.info("-------------- There are no requests which are 'in progress' state "); + } + else { + trace.info("-------------- The agent [ %s ] is currently running the job [ %s ] ", agent.name, requests[0].definition.name); + } + }).catch(function (e) { + trace.info("==== ERROR Occurred ===== "); + trace.error(e.stack); + trace.error(e.message); + clearInterval(timer); + timer = null; + }); + }, 60000); + } + return agentapi.getAgent(pool, agentid, include, include, null) + }); + } +} + diff --git a/app/exec/build/default.ts b/app/exec/build/default.ts index d5d12fc9..111440e9 100644 --- a/app/exec/build/default.ts +++ b/app/exec/build/default.ts @@ -7,6 +7,14 @@ export interface BuildArguments extends CoreArguments { status: args.StringArgument; top: args.IntArgument; buildId: args.IntArgument; + parameters: args.StringArgument; + priority: args.IntArgument; + version: args.StringArgument; + shelveset: args.StringArgument; + demands: args.StringArgument; + wait: args.BooleanArgument; + timeout: args.IntArgument; + } export function getCommand(args: string[]): BuildBase { @@ -37,7 +45,23 @@ export class BuildBase extends TfCom this.registerCommandArgument("status", "Build Status", "Build status filter.", args.StringArgument, null); this.registerCommandArgument("top", "Number of builds", "Maximum number of builds to return.", args.IntArgument, null); this.registerCommandArgument("buildId", "Build ID", "Identifies a particular Build.", args.IntArgument); - } + this.registerCommandArgument("parameters", "parameter file path or JSON string ", "Build process Parameters JSON file / string.", args.StringArgument,null); + this.registerCommandArgument("priority", "build queue priority", "Queue a build with priority 1 [High] - 5 [Low] default = 3 [Normal]).", args.IntArgument, null); + this.registerCommandArgument("version","Build Sources Version", "the source version for the queued build.",args.StringArgument,null); + this.registerCommandArgument("shelveset", "Shelveset to validate", "the shelveset to queue in the build.", args.StringArgument,null ); + this.registerCommandArgument("poolId", "Agent Pool Id", "Required Agent pool ID For Edit.", args.IntArgument,null); + this.registerCommandArgument("agentId", "Agent ID", "Required Agent ID.", args.IntArgument,null); + this.registerCommandArgument("agentName", "Agent Name", "Required Agent Name.", args.StringArgument,null); + this.registerCommandArgument("userCapabilityKey", "Capability to add / edit", "Capability to add / edit to the Agent.", args.StringArgument,null); + this.registerCommandArgument("userCapabilityValue", "Value to add / edit", "Value to add / edit to the Agent User Capabilities.", args.StringArgument,null); + this.registerCommandArgument("demands","Build demand key","Demands string [semi-colon separator] for Queued Build [key / key -equals value].",args.StringArgument,null); + this.registerCommandArgument("disable","disable / enable agent","Update the agent status.",args.StringArgument,null); + this.registerCommandArgument("deleteAgent", "deleteagent", "Delete an agent.", args.StringArgument, null); + this.registerCommandArgument("wait","wait for the build","wait for the triggered build",args.BooleanArgument,"false"); + this.registerCommandArgument("timeout","max time to wait","Maximum time to wait for the build to complete (in seconds).",args.IntArgument,"0"); + this.registerCommandArgument("parallel", "max agent parallelism", "Maximum parallel agent runs.", args.IntArgument, null); + this.registerCommandArgument("waitForInProgressRequests", "Wait For Active Requests", "Waiting for active Agent jobs / requests", args.StringArgument,null); +} public exec(cmd?: any): Promise { return this.getHelp(cmd); diff --git a/app/exec/build/definition.ts b/app/exec/build/definition.ts new file mode 100644 index 00000000..d33bcafa --- /dev/null +++ b/app/exec/build/definition.ts @@ -0,0 +1,67 @@ +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); + +export function getCommand(args: string[]): BuildDefinition { + return new BuildDefinition(args); +} + +export class BuildDefinition extends buildBase.BuildBase { + protected serverCommand = true; + protected description = "Display build definition details."; + + protected getHelpArgs(): string[] { + return ["project", "definitionId", "definitionName"]; + } + + public exec(): Promise { + trace.debug("build-definition.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.definitionId.val().then((definitionId) => { + return this.commandArgs.definitionName.val().then((definitionName) => { + if (definitionId){ + return buildapi.then((api) => { return api.getDefinition(project,definitionId,null,null) }); + } else { + if (definitionName) { + return this._getDefinitionByName(definitionName, project, buildapi); + } + } + }); + }); + }); + } + + public friendlyOutput(definition: buildContracts.DefinitionReference): void { + if (!definition) { + throw new Error("no definition supplied"); + } + + trace.println(); + trace.info("name : %s", definition.name); + trace.info("id : %s", definition.id); + trace.info("revision : %s", definition.revision); + trace.info("Created Date : %s", definition.createdDate ? definition.createdDate.toDateString():"unknown"); + trace.info("Queue Status : %s", definition.queueStatus ? buildContracts.DefinitionQueueStatus[definition.queueStatus]: buildContracts.DefinitionQueueStatus[0]) + trace.info("type : %s", buildContracts.DefinitionType[definition.type]); + trace.info("url : %s", definition.url ? definition.url :"unknown"); + } + + /** + * _getDefinitionByName +definitionName: string, project: string */ + public _getDefinitionByName(definitionName: string, project: string, buildapi: Promise) :Promise { + return buildapi.then((bapi) => { + var definitionsPromise = bapi.getDefinitions(project, definitionName) + return definitionsPromise.then((definitions) => { + if (definitions.length > 0) { + return definitions[0]; + } else { + trace.debug("No definition found with name " + definitionName); + throw new Error("No definition found with name " + definitionName); + } + }); + }); + } +} \ No newline at end of file diff --git a/app/exec/build/definitions/create.ts b/app/exec/build/definitions/create.ts new file mode 100644 index 00000000..b6f91271 --- /dev/null +++ b/app/exec/build/definitions/create.ts @@ -0,0 +1,57 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require('fs'); + +export function getCommand(args: string[]): CreateDefinition { + return new CreateDefinition(args); +} + +export interface CreateDefinitionArguments extends CoreArguments { + name: args.StringArgument + definitionPath: args.StringArgument +} + +export class CreateDefinition extends TfCommand { + protected serverCommand = true; + protected description = "Create a build definition"; + + protected getHelpArgs(): string[] { + return ["project", "definitionPath", "name"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("name", "Name of the Build Definition", "", args.StringArgument); + this.registerCommandArgument("definitionPath", "Definition path", "Local path to a Build Definition.", args.ExistingFilePathsArgument); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.name.val(), + this.commandArgs.definitionPath.val(), + ]).then((values) => { + const [project, name, definitionPath] = values; + let definition: buildContracts.BuildDefinition = JSON.parse(fs.readFileSync(definitionPath.toString(), 'utf-8')); + definition.name = name as string; + + trace.debug("Updating build definition %s...", name); + return api.then((defapi) => { return defapi.createDefinition(definition, project as string).then((definition) => { + return definition; + }); + }); + }); + } + + public friendlyOutput(definition: buildContracts.BuildDefinition): void { + trace.println(); + trace.info('id : %s', definition.id); + trace.info('name : %s', definition.name); + trace.info('type : %s', definition.type == buildContracts.DefinitionType.Xaml ? "Xaml" : "Build"); + } +} diff --git a/app/exec/build/definitions/default.ts b/app/exec/build/definitions/default.ts new file mode 100644 index 00000000..4d1d0b7e --- /dev/null +++ b/app/exec/build/definitions/default.ts @@ -0,0 +1,18 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; + +export function getCommand(args: string[]): HelpCommand { + return new HelpCommand(args); +} + +export class HelpCommand extends TfCommand { + protected serverCommand = false; + protected description = "Commands for managing Build Definitions."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + } + + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} diff --git a/app/exec/build/definitions/delete.ts b/app/exec/build/definitions/delete.ts new file mode 100644 index 00000000..5ccc3e64 --- /dev/null +++ b/app/exec/build/definitions/delete.ts @@ -0,0 +1,50 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require("fs"); + +export function getCommand(args: string[]): DeleteDefinition { + return new DeleteDefinition(args); +} + +export interface DeleteDefinitionArguments extends CoreArguments { + definitionId: args.IntArgument +} + +export class DeleteDefinition extends TfCommand { + protected serverCommand = true; + protected description = "Delete a build definition"; + + protected getHelpArgs(): string[] { + return ["project", "definitionId"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("definitionId", "Build Definition ID", "Identifies a build definition.", args.IntArgument, null); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.definitionId.val(), + ]).then((values) => { + const [project, definitionId] = values; + trace.debug("Deleting build definition %s...", definitionId); + return api.then((defapi) => { + return defapi.deleteDefinition(project as string,definitionId as number).then((definition) => { + return { id: definitionId } + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinition): void { + trace.println(); + trace.success('Build Definition %s deleted successfully!', data.id); + } +} diff --git a/app/exec/build/definitions/export.ts b/app/exec/build/definitions/export.ts new file mode 100644 index 00000000..f31006e0 --- /dev/null +++ b/app/exec/build/definitions/export.ts @@ -0,0 +1,68 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require("fs"); + +export function getCommand(args: string[]): ExportDefinition { + return new ExportDefinition(args); +} + +export interface ExportDefinitionArguments extends CoreArguments { + definitionId: args.IntArgument + definitionPath: args.StringArgument + overwrite: args.BooleanArgument + revision: args.IntArgument +} + +export class ExportDefinition extends TfCommand { + protected serverCommand = true; + protected description = "Export a build definition to a local file"; + + protected getHelpArgs(): string[] { + return ["project", "definitionId", "definitionPath", "overwrite","revision"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("definitionId", "Build Definition ID", "Identifies a build definition.", args.IntArgument, null); + this.registerCommandArgument("definitionPath", "Definition Path", "Local path to a Build Definition.", args.FilePathsArgument,null); + this.registerCommandArgument("overwrite", "Overwrite?", "Overwrite existing Build Definition.", args.BooleanArgument, "false"); + this.registerCommandArgument("revision", "Revision", "Get specific definition revision.", args.IntArgument, null); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.definitionId.val(), + this.commandArgs.definitionPath.val(), + this.commandArgs.overwrite.val(), + this.commandArgs.revision.val() + ]).then((values) => { + const [project, definitionId, definitionPath, overwrite, revision] = values; + trace.debug("Retrieving build definition %s...", definitionId); + return api.then((defapi) => { + return defapi.getDefinition(project as string, definitionId as number, revision as number).then((definition) => { + var defpath = ""; + if (!definitionPath) { + defpath = definition.name + '-' + definition.id + '-' + definition.revision + '.json'; + } else { + defpath = definitionPath as string; + } + if (fs.existsSync(defpath) && !overwrite) { + return null//Promise.reject(new Error("Build definition file already exists")); + } + fs.writeFileSync(defpath, JSON.stringify(definition, null, ' ')); + return definition; + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinition): void { + trace.info('Build Definition %s exported successfully', data.id); + } +} diff --git a/app/exec/build/definitions/list.ts b/app/exec/build/definitions/list.ts new file mode 100644 index 00000000..452b22b5 --- /dev/null +++ b/app/exec/build/definitions/list.ts @@ -0,0 +1,45 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import buildClient = require("azure-devops-node-api/BuildApi"); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); + +export function getCommand(args: string[]): ListDefinitions { + return new ListDefinitions(args); +} + +export class ListDefinitions extends TfCommand { + protected serverCommand = true; + protected description = "Get a list of build definitions"; + + protected getHelpArgs(): string[] { + return ["project"]; + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(this.connection.getCollectionUrl()); + trace.debug("Searching for build definitions..."); + + return this.commandArgs.project.val().then((project) => { + return api.then((defapi) => {return defapi.getDefinitions(project as string).then((definitions) => { + trace.debug("Retrieved " + definitions.length + " build definitions from server."); + return definitions; + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinition[]): void { + if (!data) { + throw new Error('no definitions supplied'); + } + + if (!(data instanceof Array)) { + throw new Error('expected an array of definitions'); + } + + data.forEach((definition) => { + trace.info('%s: %s: %s%s',definition.type == buildContracts.DefinitionType.Xaml ? "Xaml" : "Build", definition.id,definition.name,definition.draftOf ? " - (Draft of: " + definition.draftOf.id +")":""); + }); + } +} diff --git a/app/exec/build/definitions/queuestatus.ts b/app/exec/build/definitions/queuestatus.ts new file mode 100644 index 00000000..8a498440 --- /dev/null +++ b/app/exec/build/definitions/queuestatus.ts @@ -0,0 +1,72 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require("fs"); + +export function getCommand(args: string[]): DefinitionQueueStatus { + return new DefinitionQueueStatus(args); +} + +export interface DefinitionQueueStatusArguments extends CoreArguments { + definitionId: args.IntArgument + status: args.StringArgument +} + +export class DefinitionQueueStatus extends TfCommand { + protected serverCommand = true; + protected description = "Manage a build definition queue status"; + + protected getHelpArgs(): string[] { + return ["project", "definitionId", "status"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + this.registerCommandArgument("definitionId", "Build Definition ID", "Identifies a build definition.", args.IntArgument, null); + this.registerCommandArgument("status", "Build Definition queue status", "definition queue status (enabled / paused / disabled).", args.StringArgument, null); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.definitionId.val(), + this.commandArgs.status.val(), + ]).then((values) => { + const [project, definitionId, status] = values; + return api.then((defapi) => { + return defapi.getDefinition(project as string, definitionId as number).then((definition) => { + var currentStatus = buildContracts.DefinitionQueueStatus[definition.queueStatus]; + if (!currentStatus){ + currentStatus = buildContracts.DefinitionQueueStatus[0] + } + trace.info("build definition %s (current status is: %s)", definition.name, currentStatus); + switch (status as string) + { + case "enable" : + definition.queueStatus = 0; + trace.info("setting definition %s to %s",definition.name, buildContracts.DefinitionQueueStatus[definition.queueStatus]) + break; + case "pause" : + definition.queueStatus = 1; + trace.info("setting definition %s to %s",definition.name, buildContracts.DefinitionQueueStatus[definition.queueStatus]) + break; + case "disable" : + definition.queueStatus = 2; + trace.info("setting definition %s to %s",definition.name, buildContracts.DefinitionQueueStatus[definition.queueStatus]) + break; + default : trace.error("queue status allowd values are: enable / pause / disable"); + } + return api.then((bapi) => { return bapi.updateDefinition(definition, definition.project.name, definition.id); }); + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinition): void { + trace.println(); + trace.success('Build Definition %s %s successfully!',data.name, buildContracts.DefinitionQueueStatus[data.queueStatus]); + } +} \ No newline at end of file diff --git a/app/exec/build/definitions/update.ts b/app/exec/build/definitions/update.ts new file mode 100644 index 00000000..1967af13 --- /dev/null +++ b/app/exec/build/definitions/update.ts @@ -0,0 +1,65 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require("fs"); + +export function getCommand(args: string[]): UpdateDefinition { + return new UpdateDefinition(args); +} + +export interface UpdateDefinitionArguments extends CoreArguments { + definitionId: args.IntArgument + definitionPath: args.StringArgument +} + +export class UpdateDefinition extends TfCommand { + protected serverCommand = true; + protected description = "Update build definition"; + protected getHelpArgs(): string[] { + return ["project", "definitionId", "definitionPath"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("definitionId", "Build Definition ID", "Identifies a build definition.", args.IntArgument, null); + this.registerCommandArgument("definitionPath", "Definition Path", "Local path to a Build Definition.", args.ExistingFilePathsArgument); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.definitionId.val(), + this.commandArgs.definitionPath.val(), + ]).then((values) => { + const [project, definitionId, definitionPath] = values; + // Get the current definition so we can grab the revision id + trace.debug("Retrieving build definition %s...", definitionId); + return api.then((defapi) => { + return defapi.getDefinition(project as string, definitionId as number).then(currentDefinition => { + trace.debug("Reading build definition from %s...", definitionPath.toString()); + let definition: buildContracts.BuildDefinition = JSON.parse(fs.readFileSync(definitionPath.toString(), 'utf-8')); + definition.id = currentDefinition.id; + definition.revision = currentDefinition.revision; + + trace.debug("Updating build definition %s...", definitionId); + return api.then((defapi) => { + return defapi.updateDefinition(definition, project as string, definitionId as number,currentDefinition.id,currentDefinition.revision).then((definition) => { + return definition; + }); + }); + }) + }); + }); + } + + public friendlyOutput(definition: buildContracts.BuildDefinition): void { + trace.println(); + trace.info('id : %s', definition.id); + trace.info('name : %s', definition.name); + trace.info('revision : %s', definition.revision); + } +} diff --git a/app/exec/build/delete.ts b/app/exec/build/delete.ts new file mode 100644 index 00000000..b6a6f20d --- /dev/null +++ b/app/exec/build/delete.ts @@ -0,0 +1,60 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); + +export function describe(): string { + return "delete a build"; +} + +export function getCommand(args: string[]): BuildDelete { + return new BuildDelete(args); +} + +export class BuildDelete extends buildBase.BuildBase { + protected serverCommand = true; + protected description = "Delete a build."; + + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("delete-build.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return this._deleteBuild(buildapi, buildId, project); + }); + }); + + } + + public friendlyOutput(build: buildContracts.Build): void { + trace.println(); + } + + private _deleteBuild(buildapi: Promise, buildId: number, project: string) { + trace.info("Deleting build...") + return buildapi.then((api) => { + api.getBuild(project, buildId).then((build: buildContracts.Build) => { + if (!build.keepForever) { + build.deleted = true; + build.status = buildContracts.BuildStatus.Completed + build.result = buildContracts.BuildResult.Failed + if (build.deleted && build.status == buildContracts.BuildStatus.Completed) { + buildapi.then((api) => { api.updateBuild(build, project ,build.id) }); + buildapi.then((api) => { api.deleteBuild(build.project.name, build.id) }); + trace.info("build deleted") + } else { + trace.error("failed to delete") + } + } else { + trace.warn("build is marked for retention"); + } + }); + }); + } +} \ No newline at end of file diff --git a/app/exec/build/details.ts b/app/exec/build/details.ts new file mode 100644 index 00000000..8890f565 --- /dev/null +++ b/app/exec/build/details.ts @@ -0,0 +1,51 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); + +export function getCommand(args: string[]): BuildDetails { + return new BuildDetails(args); +} + +export class BuildDetails extends buildBase.BuildBase { + protected serverCommand = true; + protected description = "Display extended build details."; + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("build-details.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return buildapi.then((api) => { return api.getBuild(project, buildId); }); + }); + }); + + } + + public friendlyOutput(build: buildContracts.Build): void { + if (!build) { + throw new Error("no build supplied"); + } + + trace.println(); + trace.info("id : %s", build.id); + trace.info("number (name) : %s", build.buildNumber); + trace.info("definition name : %s", build.definition ? build.definition.name : "unknown"); + trace.info("definition id : %s", build.definition ? build.definition.id :"unknown"); + trace.info("requested by : %s", build.requestedBy ? build.requestedBy.displayName : "unknown"); + trace.info("status : %s", buildContracts.BuildStatus[build.status]); + trace.info("result : %s", build.result ? buildContracts.BuildResult[build.result] : "unknown") + trace.info("queue time : %s", build.queueTime ? build.queueTime.toJSON() : "unknown"); + trace.info("start time : %s", build.startTime ? build.startTime.toJSON() : "not started"); + trace.info("finish time : %s", build.finishTime ? build.finishTime.toJSON() : "in progress"); + trace.info("quality : %s", build.quality); + trace.info("reason : %s", buildContracts.BuildReason[build.reason]); + trace.info("version : %s", build.sourceVersion ? build.sourceVersion.replace("C","") : "unknown"); + trace.info("API URL : %s", build.url); + } +} \ No newline at end of file diff --git a/app/exec/build/keep.ts b/app/exec/build/keep.ts new file mode 100644 index 00000000..e60672c7 --- /dev/null +++ b/app/exec/build/keep.ts @@ -0,0 +1,54 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); + +export function describe(): string { + return "change build retention policy"; +} + +export function getCommand(args: string[]): BuildKeep { + return new BuildKeep(args); +} + +export class BuildKeep extends buildBase.BuildBase { + protected serverCommand = true; + protected description = "change build retention policy."; + + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("keep-build.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return this._keepBuild(buildapi, buildId, project); + }); + }); + + } + + public friendlyOutput(build: buildContracts.Build): void { + trace.println(); + } + + private _keepBuild(buildapi: Promise, buildId: number, project: string) :Promise { + trace.info("Searching for build...") + return buildapi.then((api) => { + return api.getBuild(project, buildId).then((build: buildContracts.Build) => { + if (build.keepForever) { + trace.warn("Retention unlocked for %s", build.buildNumber); + build.keepForever = false; + } else { + trace.warn("Build %s Retained indefinatly", build.buildNumber); + build.keepForever = true; + } + return buildapi.then((api) => { return api.updateBuild(build, project, build.id); }); + }); + }); + } +} \ No newline at end of file diff --git a/app/exec/build/logs.ts b/app/exec/build/logs.ts new file mode 100644 index 00000000..8abe5a49 --- /dev/null +++ b/app/exec/build/logs.ts @@ -0,0 +1,50 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); +import fs = require("fs"); + +export function getCommand(args: string[]): BuildLogs { + return new BuildLogs(args); +} + +export class BuildLogs extends buildBase.BuildBase { + protected description = "Download build logs to zip archive."; + protected serverCommand = true; + + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("build-logs.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return buildapi.then((api) => { + return api.getBuild(project, buildId).then((build) => { + return buildapi.then((api) => { + return api.getBuildLogsZip(build.project.name, build.id).then((stream) => { + var archiveName = build.definition.name + "-" + build.buildNumber + "_" + build.id + ".zip"; + trace.info('Downloading ... '); + trace.info('File: %s Created', archiveName); + stream.pipe(fs.createWriteStream(archiveName)); + return build; + }); + }); + }); + }); + }); + }); + } + public friendlyOutput(build: buildContracts.Build): void { + if (!build) { + throw new Error("no build supplied"); + } + trace.println(); + trace.info("build id : %s", build.id); + trace.info("Logs location : %s", build.logs.url); + } +} \ No newline at end of file diff --git a/app/exec/build/pool/create.ts b/app/exec/build/pool/create.ts new file mode 100644 index 00000000..8645cd0b --- /dev/null +++ b/app/exec/build/pool/create.ts @@ -0,0 +1,105 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require('fs'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); + +export function getCommand(args: string[]): CreatePool { + return new CreatePool(args); +} + +export interface CreatePoolArguments extends CoreArguments { + name: args.StringArgument +} + +export class CreatePool extends TfCommand { + protected serverCommand = true; + protected description = "Create a build agent pool"; + + protected getHelpArgs(): string[] { + return ["name"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("name", "Name of the Build Agent Pool", "", args.StringArgument); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.name.val(), + ]).then((values) => { + const [name] = values; + + trace.debug("Trying to Create build agent pool %s...", name); + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return agentapi.then((api) =>{return api.getAgentPools(name).then((pools) => { + if (pools.length <= 0){ + trace.debug("build agent pool %s does not exist", name); + var NewPool: AP = new AP; + NewPool.name = name; + NewPool.autoProvision = true; + return agentapi.then((api) => {return api.addAgentPool(NewPool).then((pool) => { + return pool; + }); + }); + } else { + var exists = false; + pools.forEach((pool) => { + if (pool.name == name){ + trace.warn("Agent pool %s already exsits (id: %s)", pool.name, pool.id) + exists = true; + return pool; + } + }); + if (!exists){ + trace.debug("build agent pool %s", name); + var NewPool: AP = new AP; + NewPool.name = name; + NewPool.autoProvision = true; + return agentapi.then((api) => {return api.addAgentPool(NewPool).then((pool) => { + return pool; + }); + }); + } + + } + }) + }); + }); + } + + public friendlyOutput(pool: taskAgentContracts.TaskAgentPool): void { + if (pool) { + trace.println(); + trace.info('id : %s', pool.id); + trace.info('name : %s', pool.name); + } + } + +} + +class AP implements taskAgentContracts.TaskAgentPool { + id: number; + name: string; + scope: string; + administratorsGroup: VSSInterfaces.IdentityRef; + autoProvision: boolean; + createdBy: VSSInterfaces.IdentityRef; + createdOn: Date; + groupScopeId: string; + isHosted: boolean; + properties: any; + provisioned: boolean; + serviceAccountsGroup: VSSInterfaces.IdentityRef; + size: number; +} diff --git a/app/exec/build/pool/default.ts b/app/exec/build/pool/default.ts new file mode 100644 index 00000000..86b931f8 --- /dev/null +++ b/app/exec/build/pool/default.ts @@ -0,0 +1,19 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); + +export function getCommand(args: string[]): HelpCommand { + return new HelpCommand(args); +} + +export class HelpCommand extends TfCommand { + protected serverCommand = false; + protected description = "Commands for managing Build Agent Pools."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + } + + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} diff --git a/app/exec/build/pool/delete.ts b/app/exec/build/pool/delete.ts new file mode 100644 index 00000000..b1b17929 --- /dev/null +++ b/app/exec/build/pool/delete.ts @@ -0,0 +1,80 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require('fs'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); + +export function getCommand(args: string[]): CreatePool { + return new CreatePool(args); +} + +export interface DeletePoolArguments extends CoreArguments { + name: args.StringArgument +} + +export class CreatePool extends TfCommand { + protected serverCommand = true; + protected description = "Delete a build agent pool"; + + protected getHelpArgs(): string[] { + return ["id"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("id", "id of the Build Agent Pool", "", args.StringArgument); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.id.val(), + ]).then((values) => { + const [id] = values; + var Id = id as number; + + + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return agentapi.then((api)=>{return api.getAgentPool(Id).then((pool) => { + trace.debug("found build agent pool %s...", pool.name); + return agentapi.then((api) => {return api.deleteAgentPool(pool.id).then((deletedpool) => { + return pool; + }); + }); + }); + }); + + }); + } + + public friendlyOutput(pool: taskAgentContracts.TaskAgentPool): void { + trace.println(); + trace.info('id : %s', pool.id); + trace.info('name : %s', pool.name); + } + +} + +class AP implements taskAgentContracts.TaskAgentPool { + id: number; + name: string; + scope: string; + administratorsGroup: VSSInterfaces.IdentityRef; + autoProvision: boolean; + createdBy: VSSInterfaces.IdentityRef; + createdOn: Date; + groupScopeId: string; + isHosted: boolean; + properties: any; + provisioned: boolean; + serviceAccountsGroup: VSSInterfaces.IdentityRef; + size: number; +} diff --git a/app/exec/build/pool/details.ts b/app/exec/build/pool/details.ts new file mode 100644 index 00000000..8b936472 --- /dev/null +++ b/app/exec/build/pool/details.ts @@ -0,0 +1,62 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require('fs'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); + +export function getCommand(args: string[]): PoolDetails { + return new PoolDetails(args); +} + +export interface PoolDetailsArguments extends CoreArguments { + id: args.IntArgument; +} + +export class PoolDetails extends TfCommand { + protected serverCommand = true; + protected description = "Create a build agent pool"; + + protected getHelpArgs(): string[] { + return ["id"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("id", "Id of the Build Agent Pool", "", args.IntArgument); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(); + + return Promise.all([ + this.commandArgs.id.val(), + ]).then((values) => { + const [id] = values; + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return agentapi.then((api) => {return api.getAgentPool(id).then((pool) => { + trace.debug("found build agent pool %s", pool.id); + return pool; + }); + }); + }); + } + + public friendlyOutput(pool: taskAgentContracts.TaskAgentPool): void { + if (pool) { + trace.println(); + trace.info('id : %s', pool.id); + trace.info('name : %s', pool.name); + trace.info('auto provision: %s', pool.autoProvision); + trace.info('created by : %s', pool.createdBy.displayName); + trace.info('scope : %s', pool.scope); + trace.info('size : %s', pool.size); + } + } +} \ No newline at end of file diff --git a/app/exec/build/pool/list.ts b/app/exec/build/pool/list.ts new file mode 100644 index 00000000..11c5bc79 --- /dev/null +++ b/app/exec/build/pool/list.ts @@ -0,0 +1,43 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); +import agentBase = require("./default"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import trace = require("../../../lib/trace"); +import taskAgentApi = require("azure-devops-node-api/TaskAgentApi"); + +export function getCommand(args: string[]): List { + return new List(args); +} + +export interface ListPoolArguments extends CoreArguments { +} + +export class List extends TfCommand { + protected serverCommand = true; + protected description = "Show agent pool list."; + protected getHelpArgs(): string[] { + return []; + } + + public exec(): Promise { + trace.debug("pools.exec"); + if (this.connection.getCollectionUrl().includes("DefaultCollection")) { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl().substring(0, this.connection.getCollectionUrl().lastIndexOf("/"))); + } else { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + } + return agentapi.then((api) => { return api.getAgentPools() }); + } + + public friendlyOutput(pools: taskAgentContracts.TaskAgentPool[]): void { + if (!pools) { + throw new Error("pool not supplied or not found"); + } + trace.info("Pools on server:") + trace.println(); + pools.forEach((pool) => { + trace.info(" %s : %s ", pool.id, pool.name); + }); + } +} diff --git a/app/exec/build/queue.ts b/app/exec/build/queue.ts index dc2fb5c8..e9801dc5 100644 --- a/app/exec/build/queue.ts +++ b/app/exec/build/queue.ts @@ -4,6 +4,7 @@ import buildBase = require("./default"); import buildClient = require("azure-devops-node-api/BuildApi"); import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); import trace = require("../../lib/trace"); +import fs = require('fs'); export function describe(): string { return "queue a build"; @@ -16,9 +17,8 @@ export function getCommand(args: string[]): BuildQueue { export class BuildQueue extends buildBase.BuildBase { protected description = "Queue a build."; protected serverCommand = true; - protected getHelpArgs(): string[] { - return ["project", "definitionId", "definitionName"]; + return ["project", "definitionId", "definitionName", "parameters","priority","version","shelveset","demands", "wait","timeout"]; } public async exec(): Promise { @@ -32,21 +32,40 @@ export class BuildQueue extends buildBase.BuildBase { trace.debug("No definition id provided, Searching for definitions with name: " + definitionName); - return buildapi - .getDefinitions(project, definitionName) - .then((definitions: buildContracts.DefinitionReference[]) => { - if (definitions.length > 0) { - var definition = definitions[0]; - return definition; - } else { - trace.debug("No definition found with name " + definitionName); - throw new Error("No definition found with name " + definitionName); - } - }); + return buildapi.getDefinitions(project, definitionName).then((definitions: buildContracts.DefinitionReference[]) => { + if(definitionName && definitions.length > 0) { + var definition = definitions[0]; + return definition; + } + else { + trace.debug("No definition found with name " + definitionName); + throw new Error("No definition found with name " + definitionName); + } + }); }); } - return definitionPromise.then(definition => { - return this._queueBuild(buildapi, definition, project); + return definitionPromise.then((definition) => { + return this.commandArgs.parameters.val().then((parameters) => { + return this.commandArgs.priority.val(true).then((priority) =>{ + trace.debug("build parameters file : %s",parameters ? parameters: "none"); + trace.debug("build queue priority : %s", priority ? priority: "3") + return this.commandArgs.version.val().then((version) => { + trace.debug("build source version: %s", version ? version: "Latest") + return this.commandArgs.shelveset.val().then((shelveset) => { + trace.debug("shelveset name: %s", shelveset ? shelveset: "none") + return this.commandArgs.demands.val().then((demands) => { + trace.debug("build demands : %s", demands ? demands: "none") + return this.commandArgs.wait.val().then((wait) => { + return this.commandArgs.timeout.val().then((timeout) => { + return this._queueBuild(buildapi, definition, project, parameters, priority, version, shelveset,demands ? demands:"",wait,timeout as number); + }); + + }); + }); + }); + }); + }); + }); }); }); }); @@ -58,18 +77,94 @@ export class BuildQueue extends buildBase.BuildBase{ + private _queueBuild(buildapi: buildClient.IBuildApi, + definition: buildContracts.DefinitionReference, + project: string, parameters: string, + priority: number, + version: string, + shelveset: string, + demands :string, + wait:boolean, + timeout: number) { + trace.debug("Queueing build...") + if (parameters){ + if (fs.existsSync(parameters)) { + var parameters = fs.readFileSync(parameters,'utf8'); + trace.debug("trying to get parameters from path: %s", parameters); + } + else { + trace.debug("failed to get parameters from path, assuming parameters are JSON string: %s", parameters); + } + } + + if (demands && demands.indexOf(";") >= 0) { + var demandList: string[] = demands.split(";"); + } + var build = { definition: definition, + priority: priority ? priority: 3, + parameters: parameters, + sourceVersion: version, + sourceBranch: shelveset, + demands: demandList ? demandList : [(demands)] + }; - return buildapi.queueBuild(build, project); + if (!wait){ + return buildapi.queueBuild(build, project); + } else { + + return buildapi.queueBuild(build, project).then((queuedBuild) => { + trace.info("waiting for build %s to complete",queuedBuild.buildNumber); + var counter: number = 0; + var currentOperation:string ; + var time = setInterval(function(){ + counter++; + return buildapi.updateBuild(queuedBuild, project, queuedBuild.id).then((updatedQueuedBuild) =>{ + if (updatedQueuedBuild.status == buildContracts.BuildStatus.Completed || (timeout != 0 && counter >= timeout)) { + if (updatedQueuedBuild.status != buildContracts.BuildStatus.Completed){ + trace.println(); + trace.warn("stopped waiting for build to complete, due to timeout expiration (%s Seconds)",timeout) + process.exitCode = 5; + } else { + if (updatedQueuedBuild.result == buildContracts.BuildResult.Succeeded){ + trace.println(); + trace.info("build %s Completed Successfully in %s Seconds",updatedQueuedBuild.buildNumber,counter); + } else { + trace.warn("build %s Completed in %s Seconds with result %s",updatedQueuedBuild.buildNumber,counter,buildContracts.BuildResult[updatedQueuedBuild.result]); + trace.println(); + process.exitCode = 1; + } + } + clearInterval(time); + } else { + return buildapi.getBuildTimeline(updatedQueuedBuild.project.name,updatedQueuedBuild.id).then((timeline) => { + timeline.records.forEach(Record => { + if (Record.currentOperation && Record.currentOperation != "Initializing"){ + if(Record.currentOperation != currentOperation){ + process.stdout.write('\n'); + process.stdout.write(Record.currentOperation.replace("Starting ","")); + currentOperation = Record.currentOperation; + } + } else { + process.stdout.write('.'); + } + }); + }); + } + }); + },1000); + return queuedBuild; + }); + } } } diff --git a/app/exec/build/report.ts b/app/exec/build/report.ts new file mode 100644 index 00000000..66fc2f3e --- /dev/null +++ b/app/exec/build/report.ts @@ -0,0 +1,46 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); +import fs = require("fs"); + +export function getCommand(args: string[]): BuildReport { + return new BuildReport(args); +} + +export class BuildReport extends buildBase.BuildBase { + protected description = "Download build logs to zip archive."; + protected serverCommand = true; + + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("build-logs.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return buildapi.then((api) => { + return api.getBuild(project, buildId).then((build) => { + return buildapi.then((api) => { + return api.getBuildReport(build.project.name, build.id).then((report) => { + return report + }); + }); + }); + }); + }); + }); + } + public friendlyOutput(report: buildContracts.BuildReportMetadata): void { + if (!report) { + throw new Error("no build supplied"); + } + trace.println(); + trace.info("build id : %s", report.buildId); + trace.info("%s", report.content); + } +} \ No newline at end of file diff --git a/app/exec/build/tasks/create.ts b/app/exec/build/tasks/create.ts index 4640f50d..0929eede 100644 --- a/app/exec/build/tasks/create.ts +++ b/app/exec/build/tasks/create.ts @@ -2,13 +2,13 @@ import { TfCommand } from "../../../lib/tfcommand"; import check = require("validator"); import common = require("../../../lib/common"); import fs = require("fs"); - import path = require("path"); import shell = require("shelljs"); import tasksBase = require("./default"); import trace = require("../../../lib/trace"); import uuid = require("uuid"); + export interface TaskCreateResult { taskPath: string; definition: TaskDefinition; @@ -56,7 +56,7 @@ export class TaskCreate extends tasksBase.BuildTaskBase { ]).then(values => { const [taskName, friendlyName, description, author] = values; if (!taskName || !check.isAlphanumeric(taskName)) { - throw new Error("name is a required alphanumeric string with no spaces"); + throw new Error("taskName is a required alphanumeric string with no spaces"); } if (!friendlyName || !check.isLength(friendlyName, 1, 40)) { diff --git a/app/exec/build/tasks/default.ts b/app/exec/build/tasks/default.ts index dd80c952..aa1999a6 100644 --- a/app/exec/build/tasks/default.ts +++ b/app/exec/build/tasks/default.ts @@ -11,6 +11,10 @@ export interface TaskArguments extends buildBase.BuildArguments { friendlyName: args.StringArgument; description: args.StringArgument; author: args.StringArgument; + taskVersion: args.StringArgument; + filter: args.StringArgument; + name: args.StringArgument; + id: args.StringArgument; } export function getCommand(args: string[]): BuildTaskBase { @@ -28,11 +32,14 @@ export class BuildTaskBase extends buildBase.BuildBase { this.registerCommandArgument("taskId", "Task ID", "Identifies a particular Build Task.", args.StringArgument); this.registerCommandArgument("taskPath", "Task path", "Local path to a Build Task.", args.ExistingDirectoriesArgument); this.registerCommandArgument("overwrite", "Overwrite?", "Overwrite existing Build Task.", args.BooleanArgument, "false"); - this.registerCommandArgument("taskName", "Task Name", "Name of the Build Task.", args.StringArgument); this.registerCommandArgument("friendlyName", "Friendly Task Name", null, args.StringArgument); this.registerCommandArgument("description", "Task Description", null, args.StringArgument); this.registerCommandArgument("author", "Task Author", null, args.StringArgument); + this.registerCommandArgument("taskVersion", "Task Version", "Build Task version.", args.StringArgument, null); + this.registerCommandArgument("filter", "name filter", "Filter list by name match case.", args.StringArgument, null); + this.registerCommandArgument("name", "Task Name", "Name of the Build Task to download.", args.StringArgument, null); + this.registerCommandArgument("id", "Task ID", "Identifies a particular Build Task.", args.StringArgument, null); } public exec(cmd?: any): Promise { diff --git a/app/exec/build/tasks/download.ts b/app/exec/build/tasks/download.ts new file mode 100644 index 00000000..b13da221 --- /dev/null +++ b/app/exec/build/tasks/download.ts @@ -0,0 +1,136 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import agentContracts = require('azure-devops-node-api/interfaces/TaskAgentInterfaces'); +import args = require("../../../lib/arguments"); +import fs = require('fs'); +import path = require('path'); +import tasksBase = require("./default"); +import trace = require('../../../lib/trace'); +import vm = require('../../../lib/jsonvalidate') + +export function getCommand(args: string[]): BuildTaskDownload { + return new BuildTaskDownload(args); +} + +var c_taskJsonFile: string = 'task.json'; + +export class BuildTaskDownload extends tasksBase.BuildTaskBase { + protected serverCommand = true; + protected description = "Download a Build Task."; + + protected getHelpArgs(): string[] { + return ["id","taskVersion","name"]; + } + + public exec(): Promise { + return this.commandArgs.id.val().then((Id) => { + if (!Id) { + Id = ""; + } + return this.commandArgs.taskVersion.val().then((Version) =>{ + let agentApi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + trace.info("retriving tasks from the server ...") + return agentApi.then((api) => {return api.getTaskDefinitions(null, ['build'], null).then((tasks) => { + var taskDictionary = this._getNewestTasks(tasks); + return this.commandArgs.name.val().then((Name) => { + if (!Id) { + taskDictionary.forEach(element => { + if (element.name == Name) { + Id = element.id; + if (!Version) { + Version = element.version.major + "." + element.version.minor + "." + element.version.patch;; + } + } + }); + trace.info("found %s with version %s ...",Name,Version); + } + else + { + taskDictionary.forEach(element => { + if (element.id == Id) { + Name = element.name; + if (!Version) { + Version = element.version.major + "." + element.version.minor + "." + element.version.patch;; + } + } + }); + trace.info("found %s with version %s ...",Name,Version); + } + if (!Id && !Version) { + var error = ("error: No Tasks found with this name ["+Name+"]"); + throw(error); + } + return agentApi.then((api) => {return api.getTaskContentZip(Id,Version).then((task) => { + var archiveName = Name+"-"+Version+".zip"; + trace.info('Downloading ... '); + task.pipe(fs.createWriteStream(archiveName)); + return { + id: Id, + name: Name, + }; + }); + }); + }); + }); + }); + }); + }); + } + + public friendlyOutput(task: agentContracts.TaskDefinition): void { + trace.success('[%s] Downloaded successfully!', task.name); + } + + private _getNewestTasks(allTasks: agentContracts.TaskDefinition[]): agentContracts.TaskDefinition[] { + var taskDictionary: { [id: string]: agentContracts.TaskDefinition; } = {}; + for (var i = 0; i < allTasks.length; i++) { + var currTask: agentContracts.TaskDefinition = allTasks[i]; + if(taskDictionary[currTask.id]) + { + var newVersion: TaskVersion = new TaskVersion(currTask.version); + var knownVersion: TaskVersion = new TaskVersion(taskDictionary[currTask.id].version); + trace.debug("Found additional version of " + currTask.name + " and comparing to the previously encountered version."); + if (this._compareTaskVersion(newVersion, knownVersion) > 0) { + trace.debug("Found newer version of " + currTask.name + ". Previous: " + knownVersion.toString() + "; New: " + newVersion.toString()); + taskDictionary[currTask.id] = currTask; + } + } + else { + trace.debug("Found task " + currTask.name); + taskDictionary[currTask.id] = currTask; + } + } + var newestTasks: agentContracts.TaskDefinition[] = []; + for(var id in taskDictionary) { + newestTasks.push(taskDictionary[id]); + } + return newestTasks; + } + + private _compareTaskVersion(version1: TaskVersion, version2: TaskVersion): number { + if(version1.major != version2.major) { + return version1.major - version2.major; + } + if(version1.minor != version2.minor) { + return version1.minor - version2.minor; + } + if(version1.patch != version2.patch) { + return version1.patch - version2.patch; + } + return 0; + } +} +class TaskVersion { + major: number; + minor: number; + patch: number; + + constructor(versionData: any) { + this.major = versionData.major || 0; + this.minor = versionData.minor || 0; + this.patch = versionData.patch || 0; + } + + public toString(): string { + return this.major + "." + this.minor + "." + this.patch; + } +} diff --git a/app/exec/build/tasks/list.ts b/app/exec/build/tasks/list.ts index 9216a25f..ce22cc79 100644 --- a/app/exec/build/tasks/list.ts +++ b/app/exec/build/tasks/list.ts @@ -13,7 +13,7 @@ export class BuildTaskList extends tasksBase.BuildTaskBase { diff --git a/app/exec/build/tasks/show.ts b/app/exec/build/tasks/show.ts new file mode 100644 index 00000000..910c0e03 --- /dev/null +++ b/app/exec/build/tasks/show.ts @@ -0,0 +1,142 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import agentContracts = require('azure-devops-node-api/interfaces/TaskAgentInterfaces'); +import args = require("../../../lib/arguments"); +import fs = require('fs'); +import path = require('path'); +import tasksBase = require("./default"); +import trace = require('../../../lib/trace'); +import vm = require('../../../lib/jsonvalidate') + +export function getCommand(args: string[]): BuildTaskDownload { + return new BuildTaskDownload(args); +} + +var c_taskJsonFile: string = 'task.json'; + +export class BuildTaskDownload extends tasksBase.BuildTaskBase { + protected serverCommand = true; + protected description = "Download a Build Task."; + + protected getHelpArgs(): string[] { + return ["id","taskVersion","name"]; + } + + public exec(): Promise { + return this.commandArgs.id.val().then((Id) => { + if (!Id) { + Id = ""; + } + return this.commandArgs.taskVersion.val().then((Version) =>{ + let agentApi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + trace.debug("retriving tasks from the server ...") + return agentApi.then((api) => {return api.getTaskDefinitions(null, ['build'], null).then((tasks) => { + var taskDictionary = this._getNewestTasks(tasks); + return this.commandArgs.name.val().then((Name) => { + if (!Id) { + taskDictionary.forEach(element => { + if (element.name == Name) { + Id = element.id; + if (!Version) { + Version = element.version.major + "." + element.version.minor + "." + element.version.patch;; + } + } + }); + trace.info("found %s with version %s ...",Name,Version); + } + else + { + taskDictionary.forEach(element => { + if (element.id == Id) { + Name = element.name; + if (!Version) { + Version = element.version.major + "." + element.version.minor + "." + element.version.patch;; + } + } + }); + trace.info("found %s with version %s ...",Name,Version); + } + if (!Id && !Version) { + var error = ("error: No Tasks found with this name ["+Name+"]"); + throw(error); + } + return agentApi.then((api) => { return api.getTaskDefinition(Id,Version).then((task) => { + return task; + }); + }); + }); + }); + }); + }); + }); + } + + public friendlyOutput(task: agentContracts.TaskDefinition): void { + if (!task) { + throw new Error('no tasks supplied'); + } + trace.println(); + trace.info('id : %s', task.id); + trace.info('name : %s', task.name); + trace.info('friendly name : %s', task.friendlyName); + trace.info('description : %s', task.description); + trace.info('visibility : %s', task.visibility ? task.visibility.join(",") : ""); + trace.info('category : %s', task.category); + trace.info('demands : %s', JSON.stringify(task.demands)); + trace.info('version : %s', new TaskVersion(task.version).toString()); + }; + + + private _getNewestTasks(allTasks: agentContracts.TaskDefinition[]): agentContracts.TaskDefinition[] { + var taskDictionary: { [id: string]: agentContracts.TaskDefinition; } = {}; + for (var i = 0; i < allTasks.length; i++) { + var currTask: agentContracts.TaskDefinition = allTasks[i]; + if(taskDictionary[currTask.id]) + { + var newVersion: TaskVersion = new TaskVersion(currTask.version); + var knownVersion: TaskVersion = new TaskVersion(taskDictionary[currTask.id].version); + trace.debug("Found additional version of " + currTask.name + " and comparing to the previously encountered version."); + if (this._compareTaskVersion(newVersion, knownVersion) > 0) { + trace.debug("Found newer version of " + currTask.name + ". Previous: " + knownVersion.toString() + "; New: " + newVersion.toString()); + taskDictionary[currTask.id] = currTask; + } + } + else { + trace.debug("Found task " + currTask.name); + taskDictionary[currTask.id] = currTask; + } + } + var newestTasks: agentContracts.TaskDefinition[] = []; + for(var id in taskDictionary) { + newestTasks.push(taskDictionary[id]); + } + return newestTasks; + } + + private _compareTaskVersion(version1: TaskVersion, version2: TaskVersion): number { + if(version1.major != version2.major) { + return version1.major - version2.major; + } + if(version1.minor != version2.minor) { + return version1.minor - version2.minor; + } + if(version1.patch != version2.patch) { + return version1.patch - version2.patch; + } + return 0; + } +} +class TaskVersion { + major: number; + minor: number; + patch: number; + + constructor(versionData: any) { + this.major = versionData.major || 0; + this.minor = versionData.minor || 0; + this.patch = versionData.patch || 0; + } + + public toString(): string { + return this.major + "." + this.minor + "." + this.patch; + } +} diff --git a/app/exec/build/tasks/validate.ts b/app/exec/build/tasks/validate.ts new file mode 100644 index 00000000..20314fd0 --- /dev/null +++ b/app/exec/build/tasks/validate.ts @@ -0,0 +1,53 @@ +import { TfCommand } from "../../../lib/tfcommand"; +import agentContracts = require('azure-devops-node-api/interfaces/TaskAgentInterfaces'); +import archiver = require('archiver'); +import args = require("../../../lib/arguments"); +import fs = require('fs'); +import path = require('path'); +import tasksBase = require("./default"); +import trace = require('../../../lib/trace'); +import vm = require('../../../lib/jsonvalidate') +var check = require('validator'); + +export function getCommand(args: string[]): BuildTaskValidate { + return new BuildTaskValidate(args); +} + +var c_taskJsonFile: string = 'task.json'; + +export class BuildTaskValidate extends tasksBase.BuildTaskBase { + protected description = "Validate a Build Task."; + protected serverCommand = true; + + protected getHelpArgs(): string[] { + return ["taskPath"]; + } + + public exec(): Promise { + return this.commandArgs.taskPath.val().then((taskPaths) => { + let taskPath = taskPaths[0]; + return this.commandArgs.overwrite.val().then((overwrite) => { + vm.exists(taskPath, 'specified directory ' + taskPath + ' does not exist.'); + //directory is good, check json + + let tp = path.join(taskPath, c_taskJsonFile); + return vm.validate(tp, 'no ' + c_taskJsonFile + ' in specified directory').then((taskJson) => { + let archive = archiver('zip'); + archive.on('error', function (error) { + trace.debug('Archiving error: ' + error.message); + error.message = 'Archiving error: ' + error.message; + throw error; + }); + return { + sourceLocation: taskPath + }; + }); + }); + }); + } + + public friendlyOutput(data: agentContracts.TaskDefinition): void { + trace.println(); + trace.success('Task at %s Validated successfully!', data.sourceLocation); + } +} diff --git a/app/exec/build/templates/default.ts b/app/exec/build/templates/default.ts new file mode 100644 index 00000000..bfa3bb85 --- /dev/null +++ b/app/exec/build/templates/default.ts @@ -0,0 +1,18 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; + +export function getCommand(args: string[]): HelpCommand { + return new HelpCommand(args); +} + +export class HelpCommand extends TfCommand { + protected serverCommand = false; + protected description = "Commands for managing Build templates."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + } + + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} diff --git a/app/exec/build/templates/export.ts b/app/exec/build/templates/export.ts new file mode 100644 index 00000000..8e67d9d6 --- /dev/null +++ b/app/exec/build/templates/export.ts @@ -0,0 +1,64 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import fs = require("fs"); + +export function getCommand(args: string[]): ExportTemplate { + return new ExportTemplate(args); +} + +export interface ExportTemplateArguments extends CoreArguments { + templateId: args.StringArgument + templatePath: args.StringArgument + overwrite: args.BooleanArgument +} + +export class ExportTemplate extends TfCommand { + protected serverCommand = true; + protected description = "Export a build template to a local file"; + + protected getHelpArgs(): string[] { + return ["project", "templateId", "templatePath", "overwrite"]; + } + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument("templateId", "Build Template ID", "Identifies a Build Template.", args.StringArgument, null); + this.registerCommandArgument("templatePath", "Template Path", "Local path to a Build Template.", args.FilePathsArgument,null); + this.registerCommandArgument("overwrite", "Overwrite?", "Overwrite existing Build Template.", args.BooleanArgument, "false"); + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(this.connection.getCollectionUrl()); + + return Promise.all([ + this.commandArgs.project.val(), + this.commandArgs.templateId.val(), + this.commandArgs.templatePath.val(), + this.commandArgs.overwrite.val(), + ]).then((values) => { + const [project, templateId, templatePath, overwrite, revision] = values; + trace.debug("Retrieving build template %s...", templateId); + return api.then((tempapi) => {return tempapi.getTemplate(project as string, templateId as string).then((template) => { + var tempPath = ""; + if (!templatePath) { + tempPath = template.name + '-' + template.id + '.json'; + } else { + tempPath = templatePath as string; + } + if (fs.existsSync(tempPath.toString()) && !overwrite) { + return null//Promise.reject(new Error("Build template file already exists")); + } + fs.writeFileSync(tempPath.toString(), JSON.stringify(template, null, ' ')); + return template; + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinitionTemplate): void { + trace.info('Build Template %s exported successfully', data.id); + } +} diff --git a/app/exec/build/templates/list.ts b/app/exec/build/templates/list.ts new file mode 100644 index 00000000..e1d48da1 --- /dev/null +++ b/app/exec/build/templates/list.ts @@ -0,0 +1,48 @@ +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); + +export function getCommand(args: string[]): ListTemplates { + return new ListTemplates(args); +} + +export class ListTemplates extends TfCommand { + protected serverCommand = true; + protected description = "Get a list of build templates"; + + protected getHelpArgs(): string[] { + return ["project"]; + } + + public exec(): Promise { + var api = this.webApi.getBuildApi(this.connection.getCollectionUrl()); + trace.debug("Searching for build templates..."); + + return this.commandArgs.project.val().then((project) => { + return api.then((tempapi) => {return tempapi.getTemplates(project).then((templates) => { + trace.debug("Retrieved " + templates.length + " build templates from server."); + return templates; + }); + }); + }); + } + + public friendlyOutput(data: buildContracts.BuildDefinitionTemplate[]): void { + if (!data) { + throw new Error('no templates supplied'); + } + + if (!(data instanceof Array)) { + throw new Error('expected an array of templates'); + } + + data.forEach((template) => { + trace.println(); + trace.info('id : %s', template.id); + trace.info('name : %s', template.name); + trace.info('category : %s', template.category); + trace.info('description : %s', template.description); + }); + } +} diff --git a/app/exec/build/timeline.ts b/app/exec/build/timeline.ts new file mode 100644 index 00000000..10d819ac --- /dev/null +++ b/app/exec/build/timeline.ts @@ -0,0 +1,55 @@ +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import buildBase = require("./default"); +import buildClient = require("azure-devops-node-api/BuildApi"); +import buildContracts = require("azure-devops-node-api/interfaces/BuildInterfaces"); +import trace = require("../../lib/trace"); +import fs = require("fs"); +import { TLSSocket } from "tls"; +import { Stream } from "stream"; + +export function getCommand(args: string[]): BuildLogs { + return new BuildLogs(args); +} + +export class BuildLogs extends buildBase.BuildBase { + protected description = "Download build logs to zip archive."; + protected serverCommand = true; + + protected getHelpArgs(): string[] { + return ["project", "buildId"]; + } + + public exec(): Promise { + trace.debug("build-logs.exec"); + var buildapi = this.webApi.getBuildApi(); + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.buildId.val().then((buildId) => { + return buildapi.then((api) => { + return api.getBuild(project, buildId).then((build) => { + return buildapi.then((api) => { + return api.getBuildTimeline(build.project.name, build.id).then((timeline) => { + var filename = build.definition.name + "-" + build.buildNumber + "_" + build.id + ".json"; + trace.info('Downloading ... '); + fs.writeFile(filename, JSON.stringify(timeline), function (err) { + if (err) { + trace.error(err); + } + }); + trace.info('File: %s Created', filename); + return timeline; + }); + }); + }); + }); + }); + }); + } + public friendlyOutput(timeline: buildContracts.Timeline): void { + if (!timeline) { + throw new Error("no build supplied"); + } + trace.println(); + trace.info("%s", timeline.id); + } +} \ No newline at end of file diff --git a/app/exec/code/default.ts b/app/exec/code/default.ts new file mode 100644 index 00000000..78df06a1 --- /dev/null +++ b/app/exec/code/default.ts @@ -0,0 +1,18 @@ +import { TfCommand, CoreArguments } from "../../lib/tfcommand"; + +export function getCommand(args: string[]): HelpCommand { + return new HelpCommand(args); +} + +export class HelpCommand extends TfCommand { + protected serverCommand = false; + protected description = "Git commands."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + } + + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} \ No newline at end of file diff --git a/app/exec/code/git/abandon.ts b/app/exec/code/git/abandon.ts new file mode 100644 index 00000000..8714b586 --- /dev/null +++ b/app/exec/code/git/abandon.ts @@ -0,0 +1,124 @@ +import { PullRequest } from './pullrequest'; +import { PullRequestAsyncStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require('../../../lib/arguments'); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi'); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); +import codedBase = require('./default'); + +export function getCommand(args: string[]): Abandon { + return new Abandon(args); +} + +export class Abandon extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Abandon a pull request"; + protected getHelpArgs(): string[] { + return ["project", "repositoryname", "pullrequestname", "pullrequestid"]; + } + public async exec(): Promise { + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + var repositoryName = await this.commandArgs.repositoryname.val(); + var pullRequestName = await this.commandArgs.pullrequestname.val(); + var pullRequestId; + if (!pullRequestName) { + pullRequestId = await this.commandArgs.pullrequestid.val(); + } + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var gitRepositorie; + var myPullRequest + + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + var pullRequestes = await gitApi.then((api) => { return api.getPullRequests(gitRepositorie.id, null); }); + var myPullRequestId + var count = 0; + pullRequestes.forEach(request => { + if (!pullRequestId) { + if (request.title == pullRequestName) { + myPullRequestId = request.pullRequestId; + myPullRequest = request; + count++; + } + } + if (!pullRequestName) { + if (request.pullRequestId == pullRequestId) { + myPullRequestId = request.pullRequestId; + myPullRequest = request; + count++; + } + } + }) + if (!myPullRequest) { + errLog('No pull requests found') + process.exit(1); + } + else if (count > 1) { + errLog('More then one pullrequest was found, please use Pull Request Id') + process.exit(1); + } + pullRequestId = myPullRequestId; + var updatedPullRequest: GR = new GR; + updatedPullRequest.status = 2 //abandoned; + + return await gitApi.then((api) => { api.updatePullRequest(updatedPullRequest, gitRepositorie.id, pullRequestId, project) }); + }; + + public friendlyOutput(data: gi.GitPullRequest): void { + if (!data) { + throw new Error("no pull requests supplied"); + } + + console.log(' '); + success('Pull request abandoned'); + console.log(''); + trace.info('Title : %s', data.title); + trace.info('id : %s', data.pullRequestId); + + } +}; +//Classes +class GR implements gi.GitPullRequest { + _links: any; + artifactId: string; + autoCompleteSetBy: VSSInterfaces.IdentityRef; + closedBy: VSSInterfaces.IdentityRef; + closedDate: Date; + codeReviewId: number; + commits: gi.GitCommitRef[]; + completionOptions: CO = new CO; + completionQueueTime: Date; + createdBy: VSSInterfaces.IdentityRef; + creationDate: Date; + description: string; + lastMergeCommit: gi.GitCommitRef; + lastMergeSourceCommit: gi.GitCommitRef; + lastMergeTargetCommit: gi.GitCommitRef; + mergeId: string; + mergeStatus: gi.PullRequestAsyncStatus; + pullRequestId: number; + remoteUrl: string; + repository: gi.GitRepository; + reviewers: gi.IdentityRefWithVote[]; + sourceRefName: string; + status: gi.PullRequestStatus; + supportsIterations: boolean; + targetRefName: string; + title: string; + url: string; + workItemRefs: VSSInterfaces.ResourceRef[]; +} + +class CO implements gi.GitPullRequestCompletionOptions { + deleteSourceBranch: boolean; + mergeCommitMessage: string; + squashMerge: boolean; +} \ No newline at end of file diff --git a/app/exec/code/git/complete.ts b/app/exec/code/git/complete.ts new file mode 100644 index 00000000..537871b6 --- /dev/null +++ b/app/exec/code/git/complete.ts @@ -0,0 +1,135 @@ +import { PullRequest } from './pullrequest'; +import { PullRequestAsyncStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require('../../../lib/arguments'); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi'); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); +import codedBase = require('./default'); + +export function getCommand(args: string[]): Complete { + return new Complete(args); +} + +export class Complete extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Complete a pull request"; + protected getHelpArgs(): string[] { + return ["project", "repositoryname", "pullrequestname", "pullrequestid", "deletesourcebranch"]; + } + public async exec(): Promise { + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + var delSources = await this.commandArgs.deletesourcebranch.val(); + var repositoryName = await this.commandArgs.repositoryname.val(); + var pullRequestName = await this.commandArgs.pullrequestname.val(); + var pullRequestId; + if (!pullRequestName) { + pullRequestId = await this.commandArgs.pullrequestid.val(); + } + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var gitRepositorie; + var myPullRequest + + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + var pullRequestes = await gitApi.then((api) => { return api.getPullRequests(gitRepositorie.id, null); }); + var myPullRequestId + var count = 0; + pullRequestes.forEach(request => { + if (!pullRequestId) { + if (request.title == pullRequestName) { + myPullRequestId = request.pullRequestId; + myPullRequest = request; + count++; + } + } + if (!pullRequestName) { + if (request.pullRequestId == pullRequestId) { + myPullRequestId = request.pullRequestId; + myPullRequest = request; + count++; + } + } + }) + if (!myPullRequest) { + errLog('No pull requests found') + process.exit(1); + } + else if (count > 1) { + errLog('More then one pullrequest was found, please use Pull Request Id') + process.exit(1); + } + //console.log(myPullRequest); + pullRequestId = myPullRequestId; + + var updatedPullRequest: GR = new GR; + updatedPullRequest.lastMergeSourceCommit = myPullRequest.lastMergeSourceCommit; + updatedPullRequest.status = 3; //completed; + var completionOptions: CO = new CO; + if (delSources) { + trace.debug('delete source branch option selected') + completionOptions.deleteSourceBranch = delSources + updatedPullRequest.completionOptions = completionOptions; + } + return await gitApi.then((api) => { return api.updatePullRequest(updatedPullRequest, gitRepositorie.id, pullRequestId, project); }); + + }; + + + public friendlyOutput(data: gi.GitPullRequest): void { + if (!data) { + throw new Error("no pull requests supplied"); + } + + console.log(' '); + success('Pull request completed'); + console.log(''); + trace.info('Title : %s', data.title); + trace.info('id : %s', data.pullRequestId); + + } +}; + + +class GR implements gi.GitPullRequest { + _links: any; + artifactId: string; + autoCompleteSetBy: VSSInterfaces.IdentityRef; + closedBy: VSSInterfaces.IdentityRef; + closedDate: Date; + codeReviewId: number; + commits: gi.GitCommitRef[]; + completionOptions: gi.GitPullRequestCompletionOptions; + completionQueueTime: Date; + createdBy: VSSInterfaces.IdentityRef; + creationDate: Date; + description: string; + lastMergeCommit: gi.GitCommitRef; + lastMergeSourceCommit: gi.GitCommitRef; + lastMergeTargetCommit: gi.GitCommitRef; + mergeId: string; + mergeStatus: gi.PullRequestAsyncStatus; + pullRequestId: number; + remoteUrl: string; + repository: gi.GitRepository; + reviewers: gi.IdentityRefWithVote[]; + sourceRefName: string; + status: gi.PullRequestStatus; + supportsIterations: boolean; + targetRefName: string; + title: string; + url: string; + workItemRefs: VSSInterfaces.ResourceRef[]; +} +class CO implements gi.GitPullRequestCompletionOptions { + deleteSourceBranch: boolean; + mergeCommitMessage: string; + squashMerge: boolean; +} \ No newline at end of file diff --git a/app/exec/code/git/createrepo.ts b/app/exec/code/git/createrepo.ts new file mode 100644 index 00000000..99a99ed0 --- /dev/null +++ b/app/exec/code/git/createrepo.ts @@ -0,0 +1,54 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi') +import VSSInterfaces = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); +import codedBase = require("./default"); +import TfsCoreInterfaces = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): CreateRepository { + return new CreateRepository(args); +} +class GR implements gi.GitRepository{ + _links: any; + defaultBranch: string; + id: string; + name: string; + project: TfsCoreInterfaces.TeamProjectReference; + remoteUrl: string; + url: string; + validRemoteUrls: string[]; +} + +export class CreateRepository extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Create a git repository"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryname"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + var repositoryName = await this.commandArgs.repositoryname.val(); + var NewRepo:GR = new GR; + NewRepo.name = repositoryName; + return await gitApi.then((api) => { return api.createRepository(NewRepo, project); }); + }; + + public friendlyOutput(data: gi.GitRepository): void { + if (!data) { + throw new Error("no pull requests supplied"); + } + console.log(' '); + success('New repository created'); + trace.info('name : %s', data.name); + trace.info('id : %s', data.id); + + } + +}; diff --git a/app/exec/code/git/default.ts b/app/exec/code/git/default.ts new file mode 100644 index 00000000..826d8671 --- /dev/null +++ b/app/exec/code/git/default.ts @@ -0,0 +1,47 @@ +import { PullRequest } from '../git/pullrequest'; +import { TfCommand, CoreArguments } from "../../../lib/tfcommand"; +import args = require("../../../lib/arguments"); + +export function getCommand(args: string[]): CodeBase { + return new CodeBase(args); +} + +export interface CodeArguments extends CoreArguments { + source: args.StringArgument; + target: args.StringArgument; + title: args.StringArgument; + repositoryname: args.StringArgument; + pullrequestid: args.StringArgument; + pullrequestname: args.StringArgument; + requeststatus: args.StringArgument; + top: args.IntArgument; + deletesourcebranch: args.BooleanArgument; + repositoryid: args.StringArgument; + autocomplete: args.BooleanArgument; + mergemethod: args.StringArgument; +} + +export class CodeBase extends TfCommand { + protected serverCommand = false; + protected description = "Commands for managing source control."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + + this.registerCommandArgument(["repositoryname"], "Repository name", null, args.StringArgument); + this.registerCommandArgument(["repositoryid"], "Repository id", null, args.StringArgument); + this.registerCommandArgument(["source"], "Repository source branch name", null, args.StringArgument); + this.registerCommandArgument(["target"], "Repository target branch name", null, args.StringArgument, null); + this.registerCommandArgument(["title"], "Title", null, args.StringArgument, null); + this.registerCommandArgument(["pullrequestname"], "Pull request name", null, args.StringArgument, null); + this.registerCommandArgument(["pullrequestid"], "Pull request id", null, args.StringArgument); + this.registerCommandArgument(["top"], "Number of results to get", null, args.IntArgument, null); + this.registerCommandArgument(["requeststatus"], "filter by status (Active, Abandoned, Completed, All)", null, args.StringArgument, null); + this.registerCommandArgument(["deletesourcebranch"], "delete source branch", "delete source branch on successfull merge",args.BooleanArgument,null); + this.registerCommandArgument(["autocomplete"], "Auto Complete", "Set auto completion for a new pull request.", args.BooleanArgument, null); + this.registerCommandArgument(["mergemethod"], "Merge Method", "Set auto merge method for completing the pull request.", args.IntArgument, '1'); + } + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} \ No newline at end of file diff --git a/app/exec/code/git/deleterepo.ts b/app/exec/code/git/deleterepo.ts new file mode 100644 index 00000000..b026861c --- /dev/null +++ b/app/exec/code/git/deleterepo.ts @@ -0,0 +1,41 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi') +import VSSInterfaces = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); +import codedBase = require("./default"); +import TfsCoreInterfaces = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): DeleteRepository { + return new DeleteRepository(args); +} + +export class DeleteRepository extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Create a git repository"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryid"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + var repositoryid = await this.commandArgs.repositoryid.val(); + return await gitApi.then((api) => { return api.deleteRepository(repositoryid, project); }); + }; + + public friendlyOutput(data: gi.GitRepository): void { + if (!data) { + trace.warn("repository deleted"); + } else { + console.log(' '); + trace.error('unable to delte repository'); + trace.info('name : %s', data.name); + trace.info('id : %s', data.id); + } + } +}; diff --git a/app/exec/code/git/pullrequest.ts b/app/exec/code/git/pullrequest.ts new file mode 100644 index 00000000..32287813 --- /dev/null +++ b/app/exec/code/git/pullrequest.ts @@ -0,0 +1,167 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi') +import VSSInterfaces = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); +import codedBase = require("./default"); + +export function getCommand(args: string[]): PullRequest { + return new PullRequest(args); +} + +class GR implements gi.GitPullRequest { + _links: any; + artifactId: string; + autoCompleteSetBy: VSSInterfaces.IdentityRef; + closedBy: VSSInterfaces.IdentityRef; + closedDate: Date; + codeReviewId: number; + commits: gi.GitCommitRef[]; + completionOptions: completionOptions = new completionOptions; + completionQueueTime: Date; + createdBy: VSSInterfaces.IdentityRef; + creationDate: Date; + description: string; + lastMergeCommit: gi.GitCommitRef; + lastMergeSourceCommit: gi.GitCommitRef; + lastMergeTargetCommit: gi.GitCommitRef; + mergeId: string; + mergeStatus: gi.PullRequestAsyncStatus; + pullRequestId: number; + remoteUrl: string; + repository: gi.GitRepository; + reviewers: gi.IdentityRefWithVote[]; + sourceRefName: string; + status: gi.PullRequestStatus; + supportsIterations: boolean; + targetRefName: string; + title: string; + url: string; + workItemRefs: VSSInterfaces.ResourceRef[]; +} +class GSC implements gi.GitPullRequestSearchCriteria { + creatorId?: string; + includeLinks?: boolean; + repositoryId?: string; + reviewerId?: string; + sourceRefName?: string; + sourceRepositoryId?: string; + status?: gi.PullRequestStatus; + targetRefName?: string; +} + +class completionOptions implements gi.GitPullRequestCompletionOptions { + deleteSourceBranch: boolean; + mergeCommitMessage: string; + squashMerge: boolean; + mergeStrategy?: gi.GitPullRequestMergeStrategy; +} + +export class PullRequest extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Create a pull request"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryname", 'source', 'target', 'title', 'autocomplete', 'mergemethod', 'deletesourcebranch']; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + var repositoryName = await this.commandArgs.repositoryname.val(); + var source = await this.commandArgs.source.val(); + var target = await this.commandArgs.target.val(); + var title = await this.commandArgs.title.val(); + var autoComplete = await this.commandArgs.autocomplete.val(); + var delSources = await this.commandArgs.deletesourcebranch.val(); + var tempmergeMethod = await this.commandArgs.mergemethod.val(); + var mergeMethod = +tempmergeMethod + + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var gitRepositorie; + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + if (!gitRepositorie) { + errLog('No repository found'); + process.exit(1); + } + var gitRepositorieId = gitRepositorie.id + var searchCriteria = new GSC + var pullRequests = await gitApi.then((api) => { return api.getPullRequests(gitRepositorieId, searchCriteria) }); + var myBranchComment = ''; + var newPullrequest: GR = new GR; + newPullrequest.sourceRefName = 'refs/heads/' + source; + //Getting source branch name + if (target && target.length > 0) + newPullrequest.targetRefName = 'refs/heads/' + target; + else { + warn('No target branch specified, using master.') + var masterPath = await gitApi.then((api) => { return api.getSuggestions(gitRepositorieId, project) }); + target = gitRepositorie.defaultBranch.split('/')[gitRepositorie.defaultBranch.split('/').length - 1]; + newPullrequest.targetRefName = gitRepositorie.defaultBranch; + } + //Getting title. + if (title && title.length > 0) + myBranchComment = title; + else { + var myBranch; + warn('No title specified, using last comment of source branch.') + var branches = await gitApi.then((api) => { return api.getBranches(gitRepositorieId, project); }); + branches.forEach(branch => { + if (branch.name == source) { + myBranch = branch; + } + }); + if (!myBranch) { + errLog('Source Branch ' + source + ' not found (brach name is key sensitive)'); + process.exit(1); + } + myBranchComment += myBranch.commit.comment + ' from ' + source + ' to ' + target; + + } + + newPullrequest.title = myBranchComment; + + //Creating the request + if (!autoComplete) + return await gitApi.then((api) => { return api.createPullRequest(newPullrequest, gitRepositorieId, project) }); + + var createdRequest = await gitApi.then((api) => { return api.createPullRequest(newPullrequest, gitRepositorieId, project) }); + var newPullrequest: GR = new GR; + if (delSources) { + newPullrequest.completionOptions = new completionOptions + newPullrequest.completionOptions.deleteSourceBranch = true + + if (mergeMethod < 1 && mergeMethod > 4) + { + errLog('Merge Method ' + mergeMethod + ' is not valid, should be one of 1 (NoFastForward),2 (Squash),3 (Rebase),4 (RebaseMerge)'); + process.exit(1); + } + + newPullrequest.completionOptions.mergeStrategy = gi.GitPullRequestMergeStrategy[gi.GitPullRequestMergeStrategy[mergeMethod]] + } + newPullrequest.autoCompleteSetBy = createdRequest.createdBy; + return await gitApi.then((api) => { return api.updatePullRequest(newPullrequest, gitRepositorieId, createdRequest.pullRequestId, project); }); + }; + + public friendlyOutput(data: gi.GitPullRequest): void { + if (!data) { + throw new Error("no pull requests supplied"); + } + console.log(' '); + success('New pull request created'); + trace.info('Title : %s', data.title); + trace.info('id : %s', data.pullRequestId); + if (data.autoCompleteSetBy) + trace.info('AutoCompleteSetBy : %s', data.autoCompleteSetBy.displayName); + if (data.completionOptions && data.completionOptions.deleteSourceBranch) + trace.info('Delete Source Branch : %s', data.completionOptions.deleteSourceBranch); + } +}; diff --git a/app/exec/code/git/repodetails.ts b/app/exec/code/git/repodetails.ts new file mode 100644 index 00000000..fcceb82a --- /dev/null +++ b/app/exec/code/git/repodetails.ts @@ -0,0 +1,43 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi') +import VSSInterfaces = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); +import codedBase = require("./default"); +import TfsCoreInterfaces = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): GetRepositoryDetails { + return new GetRepositoryDetails(args); +} + +export class GetRepositoryDetails extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Get a git repository details"; + + protected getHelpArgs(): string[] { + return ["repositoryid"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var id = await this.commandArgs.repositoryid.val(); + return await gitApi.then((api) => { return api.getRepository(id); }); + }; + + public friendlyOutput(data: gi.GitRepository): void { + if (!data) { + throw new Error("no repository supplied"); + } + console.log(' '); + trace.println(); + trace.info('name : %s', data.name); + trace.info('id : %s', data.id); + trace.info('clone (https) : %s', data.remoteUrl); + trace.info('clone (ssh) : %s', data.remoteUrl.replace("https", "ssh").replace(".visualstudio.com","@vs-ssh.visualstudio.com:22").replace("_git","_ssh")); + trace.info('API URL : %s', data.url); + } + +}; diff --git a/app/exec/code/git/repolist.ts b/app/exec/code/git/repolist.ts new file mode 100644 index 00000000..574511f9 --- /dev/null +++ b/app/exec/code/git/repolist.ts @@ -0,0 +1,42 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require("../../../lib/arguments"); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi') +import VSSInterfaces = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); +import codedBase = require("./default"); +import TfsCoreInterfaces = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): ListRepositories { + return new ListRepositories(args); +} + +export class ListRepositories extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Create a git repository"; + + protected getHelpArgs(): string[] { + return ["project"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + return await gitApi.then((api) => { return api.getRepositories(project); }); + }; + + public friendlyOutput(data: gi.GitRepository[]): void { + if (!data) { + throw new Error("no repository supplied"); + } + console.log(' '); + data.forEach((repo) =>{ + trace.println(); + trace.info('name : %s', repo.name); + trace.info('id : %s', repo.id); + }); + } + +}; diff --git a/app/exec/code/git/requestdetails.ts b/app/exec/code/git/requestdetails.ts new file mode 100644 index 00000000..1b853258 --- /dev/null +++ b/app/exec/code/git/requestdetails.ts @@ -0,0 +1,58 @@ +import { PullRequestAsyncStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require('../../../lib/arguments'); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi'); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); +import codedBase = require('./default'); +var repositoryName; + +export function getCommand(args: string[]): RequestDetails { + return new RequestDetails(args); +} + +export class RequestDetails extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Get a list of pull requests"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryname","pullrequestid"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + repositoryName = await this.commandArgs.repositoryname.val(); + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var requestId = await this.commandArgs.pullrequestid.val(); + var gitRepositorie; + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + + return await gitApi.then((api) => { return api.getPullRequest(gitRepositorie.id, +requestId); }); + }; + + public friendlyOutput(req: gi.GitPullRequest): void { + if (!req) { + throw new Error("no pull requests supplied"); + } + trace.info('Title : %s', req.title); + trace.info('Description : %s', req.description); + trace.info('id : %s', req.pullRequestId); + trace.info('Created by : %s', req.createdBy.displayName); + trace.info('Status : %s',gi.PullRequestStatus[req.status]); + trace.info('Created Date : %s', req.creationDate.toString()); + trace.info('Merge Status : %s', PullRequestAsyncStatus[req.mergeStatus]); + trace.info('Last commit : %s', req.lastMergeCommit.commitId); + trace.info('API Url : %s', req.url); + trace.println(); + } +}; + diff --git a/app/exec/code/git/requestlist.ts b/app/exec/code/git/requestlist.ts new file mode 100644 index 00000000..492cbc10 --- /dev/null +++ b/app/exec/code/git/requestlist.ts @@ -0,0 +1,89 @@ +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require('../../../lib/arguments'); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi'); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); +import codedBase = require('./default'); +var repositoryName; + +export function getCommand(args: string[]): RequestList { + return new RequestList(args); +} + +export class RequestList extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Get a list of pull requests"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryname", "requeststatus", "top"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + repositoryName = await this.commandArgs.repositoryname.val(); + var requestStatus = await this.commandArgs.requeststatus.val(); + var top = await this.commandArgs.top.val(); + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var gitRepositorie; + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + var searchCriteria: SearchCriteria = new SearchCriteria; + if (requestStatus) { + searchCriteria.status = gi.PullRequestStatus[requestStatus]; + } + else{ + searchCriteria.status = 4; + } + return await gitApi.then((api) => { return api.getPullRequests(gitRepositorie.id, searchCriteria, null, null, null, top); }); + }; + + public friendlyOutput(data: gi.GitPullRequest[]): void { + if (!data) { + throw new Error("no pull requests supplied"); + } + + if (!(data instanceof Array)) { + throw new Error("expected an array of pull requests"); + } + console.log(' '); + success('Pull Requestes for ' + repositoryName + ':') + data.forEach(req => { + var reviewerList = ''; + if (req.reviewers) { + req.reviewers.forEach(reviewers => { + reviewerList += reviewers.displayName + '; ' + }); + }; + trace.info('Title : %s', req.title); + trace.info('id : %s', req.pullRequestId); + trace.info('Created by : %s', req.createdBy.displayName); + trace.info('Created Date : %s', req.creationDate.toString()); + trace.info('Status : %s', gi.PullRequestStatus[req.status]); + trace.info('Merge Status : %s', gi.PullRequestAsyncStatus[req.mergeStatus]); + trace.info('Url : %s', req.url); + trace.info('Reviewers : %s', reviewerList); + console.log(' '); + }); + } +}; + +class SearchCriteria implements gi.GitPullRequestSearchCriteria { + creatorId: string; + /** + * Whether to include the _links field on the shallow references + */ + includeLinks: boolean; + repositoryId: string; + reviewerId: string; + sourceRefName: string; + status: gi.PullRequestStatus; + targetRefName: string; +} diff --git a/app/exec/code/git/requestoptions.ts b/app/exec/code/git/requestoptions.ts new file mode 100644 index 00000000..99bbd45a --- /dev/null +++ b/app/exec/code/git/requestoptions.ts @@ -0,0 +1,68 @@ +import { PullRequestAsyncStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { success, warn } from '../../../lib/trace'; +import { errLog } from '../../../lib/errorhandler'; +import args = require('../../../lib/arguments'); +import trace = require('../../../lib/trace'); +import gi = require('azure-devops-node-api/interfaces/GitInterfaces'); +import git_Api = require('azure-devops-node-api/GitApi'); +import VSSInterfaces = require('azure-devops-node-api/interfaces/common/VSSInterfaces'); +import codedBase = require('./default'); +var repositoryName; + +export function getCommand(args: string[]): RequestOptions { + return new RequestOptions(args); +} + +export class RequestOptions extends codedBase.CodeBase { + protected serverCommand = true; + protected description = "Get a list of pull requests"; + + protected getHelpArgs(): string[] { + return ["project", "repositoryname","pullrequestid"]; + } + + public async exec(): Promise { + //getting variables. + var gitApi = this.webApi.getGitApi(); + var project = await this.commandArgs.project.val(); + repositoryName = await this.commandArgs.repositoryname.val(); + var gitRepositories = await gitApi.then((api) => { return api.getRepositories(project); }); + var requestId = await this.commandArgs.pullrequestid.val(); + var gitRepositorie; + gitRepositories.forEach(repo => { + if (repo.name.toLowerCase() == repositoryName.toLowerCase()) { + gitRepositorie = repo; + return; + }; + }); + var request = await gitApi.then((api) => { return api.getPullRequest(gitRepositorie.id, +requestId); }); + var gprco = request.completionOptions as GPRCO; + gprco.requestId = request.pullRequestId; + return gprco; + }; + + public friendlyOutput(opt: GPRCO): void { + if (!opt) { + throw new Error("no pull requests supplied"); + } + trace.info('Request Id : %s', opt.requestId); + trace.info('Bypass Reason : %s', opt.bypassReason ? opt.bypassReason : "None"); + trace.info('Auto Complete : %s', opt.triggeredByAutoComplete ? opt.triggeredByAutoComplete : false); + trace.info('Delete Branch : %s', opt.deleteSourceBranch ? opt.deleteSourceBranch : false); + trace.info('Squash Merge : %s', opt.squashMerge ? opt.squashMerge : false); + trace.info('Commit Message : _____ \n%s', opt.mergeCommitMessage ? opt.mergeCommitMessage : "No Comment"); + trace.println(); + } +}; + + + +class GPRCO implements gi.GitPullRequestCompletionOptions { + requestId: number; + deleteSourceBranch: boolean; + mergeCommitMessage: string; + squashMerge: boolean; + triggeredByAutoComplete: boolean; + bypassReason: string; +} + diff --git a/app/exec/endpoints/default.ts b/app/exec/endpoints/default.ts new file mode 100644 index 00000000..9ed85b86 --- /dev/null +++ b/app/exec/endpoints/default.ts @@ -0,0 +1,22 @@ +import { TfCommand, CoreArguments } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); + +export interface BuildArguments extends CoreArguments { + parameters: args.StringArgument; +} + +export function getCommand(args: string[]): HelpCommand { + return new HelpCommand(args); +} + +export class HelpCommand extends TfCommand { + protected serverCommand = false; + protected description = "Commands for managing Build Definitions."; + + protected setCommandArgs(): void { + super.setCommandArgs(); + } + public exec(cmd?: any): Promise { + return this.getHelp(cmd); + } +} \ No newline at end of file diff --git a/app/exec/endpoints/list.ts b/app/exec/endpoints/list.ts new file mode 100644 index 00000000..1e238c05 --- /dev/null +++ b/app/exec/endpoints/list.ts @@ -0,0 +1,54 @@ +import { TfCommand, CoreArguments } from "../../lib/tfcommand"; +import trace = require('../../lib/trace'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import CoreContracts = require("azure-devops-node-api/interfaces/CoreInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); + +export function getCommand(args: string[]): ListEndpoints { + return new ListEndpoints(args); +} + +export class ListEndpoints extends TfCommand { + protected serverCommand = true; + protected description = "Get a list of build definitions"; + + protected getHelpArgs(): string[] { + return ["project"]; + } + + public exec(): Promise { + var coreapi = this.webApi.getCoreApi(this.connection.getCollectionUrl()) + trace.debug("Searching for Service Endpoints ..."); + return this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()).then((agentapi: agentClient.ITaskAgentApiBase) => { + return this.commandArgs.project.val().then((project) => { + return coreapi.then((api) =>{ + return api.getProject(project).then((projectObj) =>{ + return api.getConnectedServices(projectObj.id).then((endpoints) => { + trace.debug("Retrieved " + endpoints.length + " build endpoints from server."); + return endpoints; + }); + }); + }); + }); + }); + } + + public friendlyOutput(data: CoreContracts.WebApiConnectedServiceDetails[]): void { + if (!data) { + throw new Error('no endpoints supplied'); + } + + if (!(data instanceof Array)) { + throw new Error('expected an array of endpoints'); + } + + data.forEach((endpoint) => { + trace.println(); + trace.info('name : %s',endpoint.connectedServiceMetaData.friendlyName); + trace.info('description : %s', endpoint.connectedServiceMetaData.description); + trace.info('author : %s', endpoint.connectedServiceMetaData.authenticatedBy.displayName); + trace.info('id : %s',endpoint.connectedServiceMetaData.id); + trace.info('type : %s',endpoint.connectedServiceMetaData.kind); + }); + } +} diff --git a/app/exec/endpoints/set.ts b/app/exec/endpoints/set.ts new file mode 100644 index 00000000..fa8f99a9 --- /dev/null +++ b/app/exec/endpoints/set.ts @@ -0,0 +1,64 @@ +import { TfCommand, CoreArguments } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import trace = require('../../lib/trace'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import CoreContracts = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): ShowEndpoint { + return new ShowEndpoint(args); +} +export interface EndpointArguments extends CoreArguments { + id: args.StringArgument, +} +export class ShowEndpoint extends TfCommand { + protected serverCommand = true; + protected description = "Get a list of build definitions"; + protected setCommandArgs(): void { + super.setCommandArgs(); + this.registerCommandArgument("id", "Endpoint ID", "Endpoint Guid Identifier.", args.StringArgument, null); + this.registerCommandArgument("parameters", "parameter file path or JSON string", "Endpoint authorization parameters JSON file / string.", args.StringArgument, null); + } + + protected getHelpArgs(): string[] { + return ["project", "id", "parameters"]; + } + + public exec(): Promise { + var agentapi = this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()); + var coreapi = this.webApi.getCoreApi(this.connection.getCollectionUrl()) + trace.debug("Searching for Service Endpoints ..."); + return this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()).then((agentapi: agentClient.ITaskAgentApiBase) => { + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.id.val().then((id) =>{ + return this.commandArgs.parameters.val().then((params) => { + return coreapi.then((api) => { + return api.getProject(project).then((projectObj) =>{ + return api.getConnectedServiceDetails(projectObj.id,id).then((endpoint) => { + endpoint.credentialsXml = JSON.parse(params); + return api.createConnectedService(endpoint,projectObj.id).then(() => { + return endpoint; + }); + }); + }); + }); + }); + }); + }); + }); + } + + public friendlyOutput(data: CoreContracts.WebApiConnectedServiceDetails): void { + if (!data) { + throw new Error('no endpoints supplied'); + } + + trace.println(); + trace.info('id : %s', data.connectedServiceMetaData.id); + trace.info('name : %s', data.connectedServiceMetaData.friendlyName); + trace.info('type : %s', data.connectedServiceMetaData.kind); + trace.info('description : %s', data.connectedServiceMetaData.description); + trace.info('auth scheme : %s', JSON.stringify(data.credentialsXml)); + trace.info('created By : %s', data.connectedServiceMetaData.authenticatedBy.displayName); + } +} diff --git a/app/exec/endpoints/show.ts b/app/exec/endpoints/show.ts new file mode 100644 index 00000000..1734edd8 --- /dev/null +++ b/app/exec/endpoints/show.ts @@ -0,0 +1,58 @@ +import { TfCommand, CoreArguments } from "../../lib/tfcommand"; +import buildContracts = require('azure-devops-node-api/interfaces/BuildInterfaces'); +import args = require("../../lib/arguments"); +import trace = require('../../lib/trace'); +import taskAgentContracts = require("azure-devops-node-api/interfaces/TaskAgentInterfaces"); +import agentClient = require("azure-devops-node-api/TaskAgentApiBase"); +import CoreContracts = require("azure-devops-node-api/interfaces/CoreInterfaces"); + +export function getCommand(args: string[]): ShowEndpoint { + return new ShowEndpoint(args); +} +export interface EndpointArguments extends CoreArguments { + id: args.StringArgument, +} +export class ShowEndpoint extends TfCommand { + protected serverCommand = true; + protected description = "Get a list of build definitions"; + protected setCommandArgs(): void { + super.setCommandArgs(); + this.registerCommandArgument("id", "Endpoint ID", "Endpoint Guid Identifier.", args.StringArgument, null); + } + + protected getHelpArgs(): string[] { + return ["project","id"]; + } + + public exec(): Promise { + var coreapi = this.webApi.getCoreApi(this.connection.getCollectionUrl()) + trace.debug("Searching for Service Endpoints ..."); + return this.webApi.getTaskAgentApi(this.connection.getCollectionUrl()).then((agentapi: agentClient.ITaskAgentApiBase) => { + return this.commandArgs.project.val().then((project) => { + return this.commandArgs.id.val().then((id) =>{ + return coreapi.then((api) => { + return api. getProject(project).then((projectObj) =>{ + return api.getConnectedServiceDetails(projectObj.id,id).then((endpoint) => { + return endpoint; + }); + }); + }); + }); + }); + }); + } + + public friendlyOutput(data: CoreContracts.WebApiConnectedServiceDetails): void { + if (!data) { + throw new Error('no endpoints supplied'); + } + + trace.println(); + trace.info('id : %s', data.connectedServiceMetaData.id); + trace.info('name : %s', data.connectedServiceMetaData.friendlyName); + trace.info('type : %s', data.connectedServiceMetaData.kind); + trace.info('description : %s', data.connectedServiceMetaData.description); + trace.info('auth scheme : %s', JSON.stringify(data.credentialsXml)); + trace.info('created By : %s', data.connectedServiceMetaData.authenticatedBy.displayName); + } +} diff --git a/app/exec/extension/_lib/utils.ts b/app/exec/extension/_lib/utils.ts index 1dc886e7..6ea8e213 100644 --- a/app/exec/extension/_lib/utils.ts +++ b/app/exec/extension/_lib/utils.ts @@ -4,7 +4,7 @@ import path = require("path"); import xml = require("xml2js"); export function removeMetaKeys(obj: any): any { - return _.omitBy(obj, (v, k) => _.startsWith(k, "__meta_")); + return _.pickBy(obj,(v,k)=> !_.startsWith(k,"__meta_")); } export function cleanAssetPath(assetPath: string, root: string = ".") { diff --git a/app/exec/extension/create.ts b/app/exec/extension/create.ts index 7d1a3ee6..5cc7427a 100644 --- a/app/exec/extension/create.ts +++ b/app/exec/extension/create.ts @@ -3,7 +3,7 @@ import { VsixManifestBuilder } from "./_lib/vsix-manifest-builder"; import { MergeSettings, PackageSettings } from "./_lib/interfaces"; import { VsixWriter } from "./_lib/vsix-writer"; import { TfCommand } from "../../lib/tfcommand"; -import colors = require("colors"); +import colors = require('colors'); import extBase = require("./default"); import trace = require("../../lib/trace"); diff --git a/app/exec/reset.ts b/app/exec/reset.ts index 5e5cedb9..81c28f3a 100644 --- a/app/exec/reset.ts +++ b/app/exec/reset.ts @@ -4,7 +4,6 @@ import { EOL as eol } from "os"; import args = require("../lib/arguments"); import common = require("../lib/common"); import path = require("path"); - import trace = require("../lib/trace"); export function getCommand(args: string[]): Reset { diff --git a/app/exec/workitem/default.ts b/app/exec/workitem/default.ts index 0be55da4..4fb6c9c2 100644 --- a/app/exec/workitem/default.ts +++ b/app/exec/workitem/default.ts @@ -3,8 +3,8 @@ import args = require("../../lib/arguments"); import vssCoreContracts = require("azure-devops-node-api/interfaces/common/VSSInterfaces"); import witContracts = require("azure-devops-node-api/interfaces/WorkItemTrackingInterfaces"); import trace = require("../../lib/trace"); -import { EOL as eol } from "os"; -import _ = require("lodash"); +import { EOL as eol } from 'os'; +import _ = require('lodash'); export class WorkItemValuesJsonArgument extends args.JsonArgument {} diff --git a/app/exec/workitem/links.ts b/app/exec/workitem/links.ts new file mode 100644 index 00000000..a2641acd --- /dev/null +++ b/app/exec/workitem/links.ts @@ -0,0 +1,41 @@ +import { EOL as eol } from "os"; +import { TfCommand } from "../../lib/tfcommand"; +import args = require("../../lib/arguments"); +import trace = require("../../lib/trace"); +import witBase = require("./default"); +import witClient = require("azure-devops-node-api/WorkItemTrackingApi"); +import witContracts = require("azure-devops-node-api/interfaces/WorkItemTrackingInterfaces"); +import { WorkItemExpand } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces"; + +export function getCommand(args: string[]): WorkItemLinks { + return new WorkItemLinks(args); +} + +export class WorkItemLinks extends witBase.WorkItemBase { + protected description = "Show Work Item links."; + protected serverCommand = true; + + protected getHelpArgs(): string[] { + return ["workItemId"]; + } + + public exec(): Promise { + var ids = []; + var witapi = this.webApi.getWorkItemTrackingApi(); + return this.commandArgs.workItemId.val().then((workItemId) => { + ids[0] = workItemId; + return witapi.then((api) => { return api.getWorkItems(ids, null, null, WorkItemExpand.All) }); + }); + } + + public friendlyOutput(workItems: witContracts.WorkItem[]): void { + workItems.forEach((wi) => { + wi.relations.forEach((link) =>{ + trace.println(); + trace.info("%s: [%s] %s", link.rel, link.url, link.attributes["name"] ? "- " + link.attributes["name"] : "") + }) + trace.println(); + }) + return //witBase.friendlyOutput(workItems); + } +} \ No newline at end of file diff --git a/artifactory-deploy.sh b/artifactory-deploy.sh new file mode 100755 index 00000000..fb9d8078 --- /dev/null +++ b/artifactory-deploy.sh @@ -0,0 +1,101 @@ +#!/bin/bash +set -e #exit at every exit code different from "0" + +export VERSION=$(cat package.json|grep version|sed s/\"version\"\:\ \"//g|sed s/\"\,//g|sed s/\ //g) +echo $VERSION > ./version.txt +mkdir -p tfx-cli +cp -r node_modules tfx-cli/ +cp -r _build tfx-cli/ +zip -r tfx-cli.zip ./tfx-cli/ +rm -rf ./tfx-cli/ +touch ./upload.log + +# based on https://github.com/JFrogDev/project-examples/blob/master/bash-example/deploy-file.sh + +usage () { + ME=$(basename "$0") + cat>&2< + FAIL=false + eval $1 && FAIL=true + if $FAIL ; then + echo "FAIL: $2">&2 + exit -1 + fi +} + + +while getopts ":g:a:d:f:e:u:p:h" opt; do + case $opt in + g) + GROUP=$OPTARG;; + a) + ARTIFACT=$OPTARG;; + d) + BASEURL=$OPTARG;; + f) + FILE=$OPTARG;; + e) + EXT=$OPTARG;; + u) + ARTIFACTORY_USER=$OPTARG;; + p) + ARTIFACTORY_PASSWD=$OPTARG;; + h) + usage + exit 0;; + \?) + fail_if "true" "Invalid option: -$OPTARG" + ;; + :) + fail_if "true" "Option -$OPTARG requires an argument" + ;; + esac +done + +fail_if '[[ -z "$GROUP" ]]' 'missing GROUP' +fail_if '[[ -z "$ARTIFACT" ]]' 'missing ARTIFACT' +fail_if '[[ -z "$VERSION" ]]' 'missing VERSION' +fail_if '[[ -z "$BASEURL" ]]' 'missing BASEURL' +fail_if '[[ -z "$FILE" ]]' 'missing FILE' +fail_if '[[ -z "$EXT" ]]' 'missing EXT' +fail_if '[[ -z "$ARTIFACTORY_USER" ]]' 'missing ARTIFACTORY_USER' +fail_if '[[ -z "$ARTIFACTORY_PASSWD" ]]' 'missing ARTIFACTORY_PASSWD' + + +md5Value="`md5sum "$FILE"`" +md5Value="${md5Value:0:32}" +sha1Value="`sha1sum "$FILE"`" +sha1Value="${sha1Value:0:40}" + +uploadUrl="$BASEURL/${GROUP//[.]//}/$ARTIFACT/"$VERSION"/${ARTIFACT}-${VERSION}.${EXT}" + +printf "File: %s\nVersion: %s\nMD5: %s\nSHA1: %s\nUpload URL: %s\n" "$FILE" "$VERSION" "$md5Value" "$sha1Value" "$uploadUrl" + +STATUSCODE=$(curl --progress-bar -i -X PUT -u $ARTIFACTORY_USER:$ARTIFACTORY_PASSWD \ + -H "X-Checksum-Md5: $md5Value" \ + -H "X-Checksum-Sha1: $sha1Value" \ + -T "$FILE" \ + --output ./upload.log --write-out "%{http_code}" \ + "$uploadUrl"||:) +## comment test +fail_if '[[ "$STATUSCODE" -ne "201" ]]' "Upload failed: http status $STATUSCODE" + +echo "Upload successfull!" +# generic comment to check CI \ No newline at end of file diff --git a/create_package.sh b/create_package.sh new file mode 100755 index 00000000..2b401ea9 --- /dev/null +++ b/create_package.sh @@ -0,0 +1,8 @@ +#!/bin/bash +export version=$(cat package.json|grep version|sed s/\"version\"\:\ \"//g|sed s/\"\,//g|sed s/\ //g) +echo $version > ./version.txt +mkdir -p tfx-cli +cp -r node_modules tfx-cli/ +cp -r _build tfx-cli/ +zip -r tfx-cli-$version.zip ./tfx-cli/ +rm -rf ./tfx-cli/ diff --git a/docs/builds.md b/docs/builds.md index edd8c128..a84f16bd 100644 --- a/docs/builds.md +++ b/docs/builds.md @@ -15,7 +15,18 @@ OR --definition-name - The name of the build definition to build against. ``` -### Example +```txt +Additional optional Parameters + --parameters - Build process Parameters JSON file / string. + --priority - Queue a build with priority 1 [High] - 5 [Low] default = 3 [Normal]). + --version - the source version for the queued build. + --shelveset - the shelveset to queue in the build. + --demands - Demands string [semi-colon separator] for Queued Build [key / key -equals value]. + --wait - wait for the triggered build + --timeout - Maximum time to wait for the build to complete (in seconds). +``` + +####Example ```bash ~$ tfx build queue --project MyProject --definition-name TestDefinition Copyright Microsoft Corporation @@ -51,6 +62,36 @@ status : NotStarted queue time : Fri Aug 21 2015 15:07:49 GMT-0400 (Eastern Daylight Time) ``` +## Details + +####Options +```txt + --project, -p Project name. + --build-id Identifies a particular Build. +``` + +####Example +```bash +$tfx build details --project MyProject --build-id MyBuildID + +Copyright Microsoft Corporation + +id : MyBuildID +number (name) : MyBuildNumber +definition name : MyDefinition +definition id : MyDefinitionID +requested by : Owner +status : InProgress +result : unknown +queue time : 2017-04-04T10:09:20.367Z +start time : 2017-04-04T10:09:27.084Z +finish time : in progress +quality : +reason : Manual +version : Changeset / Commit ID +API URL : https://MyServerURL/tfs/MyProject/BuildAPI_Guid/_apis/build/Builds/BuildID +``` + ## List Queries for a list of builds. diff --git a/docs/contributions.md b/docs/contributions.md index 65a59ef5..c673543d 100644 --- a/docs/contributions.md +++ b/docs/contributions.md @@ -39,7 +39,7 @@ tfx-cli@0.1.8 /usr/local/lib/node_modules/tfx-cli ├── q@1.4.1 ├── validator@3.43.0 ├── shelljs@0.5.1 -├── vso-node-api@0.3.0 +├── azure-devops-node-api@0.3.0 ├── read@1.0.6 (mute-stream@0.0.5) └── archiver@0.14.4 (buffer-crc32@0.2.5, lazystream@0.1.0, async@0.9.2, readable-stream@1.0.33, tar-stream@1.1.5, lodash@3.2.0, zip-stream@0.5.2, glob@4.3.5) ``` diff --git a/docs/definitions.md b/docs/definitions.md new file mode 100644 index 00000000..8e7fa0dd --- /dev/null +++ b/docs/definitions.md @@ -0,0 +1,159 @@ +# Build definitions tasks + +You can queue, show, and list builds using tfx. + +## Queue Status + +Queues a build for a given project with a given definition. + +####Options +```txt +--project - Required. The name of the project to queue a build for. +AND +--definition-id - The id of the build definition to build against. +AND +--status - desired queue status (Enabled / Disabled / Paused). +``` + +####Example +```bash +~$ tfx build definitions queuestatus --project MyProject --definition-id 123 --status "paused" +build definition TestDefinition (current status is: Disabled) +setting definition TestDefinition to Paused + +Build Definition TestDefinition Paused successfully! +``` + +## definition + +Shows information for a given build. + +####Options +```txt +--project - Required. The name of the project to queue a build for. +--id - Required. The id of the build to show. +``` + +####Example +```bash +$ tfx build definition --project MyProject --id 1 +Copyright Microsoft Corporation + + +name : TestDefinition +id : 1 +revision : 123 +Created Date : dd-mm-yyyy +Queue Status : Enabled +type : Build +url : https://:/MyProject>//_apis/build/Definitions/1 +``` + +## List + +Queries for a list of builds. + +####Options +```txt +--project - Required. The name of the project to queue a build for. +``` + +####Example +```bash +~$ tfx build definitions list + +Copyright Microsoft Corporation + +... + +id : 1 +name : TestDefinition +type : Build + +id : 2 +name : XamlTestDefinition +type : Xaml + +``` +## export + +export a build definition to Json file. + +####Options +```txt +--project Project name. +--definition-id Identifies a build definition. +--definition-path Local path to a Build Definition Json (default file name is --.json). +--overwrite Overwrite existing Build Definition Json. +--revision Get specific definition revision. + +``` + +####Example +```bash +~$ tfx build definitions export --project MyProject --definition-id 1 + +Copyright Microsoft Corporation +Build Definition 1 exported successfully + +``` +## update + +update a build definition from Json file. + +####Options +```txt +--project - Required, Project name. +--definition-id - Required, Identifies a build definition. +--definition-path - Required, Local path to a Build Definition. + +``` +####Example +```bash +~$ tfx build definitions update --project MyProject --definition-id 1 --definition-path ./TestDefinition-1-123.json +Copyright Microsoft Corporation + +id : 1 +name : TestDefinition +revision : 124 + +``` + +## create + +create a new Build definition from Json file. + +####Options +```txt +--project - Required, Project name. +--definition-path - Required, Local path to a Build Definition. +--name - Required, Name of the Build Definition. + +``` +####Example +```bash +~$ tfx build definitions create --project MyProject --definition-path ./TestDefinition-1-123.json --name NewBuildDefinition +Copyright Microsoft Corporation + +id : 2 +name : NewBuildDefinition +type : Build + +``` +## delete + +delete a build definition. + +####Options +```txt +--project - Required, Project name. +--definition-id - Required, Identifies a build definition. +``` + +####Example +```bash +~$ tfx build definitions delete --project MyProject --definition-id 2 +Copyright Microsoft Corporation + +Build Definition 2 deleted successfully! +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bfc4782e..0997bcc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tfx-cli", - "version": "0.7.8", + "version": "0.7.82", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -205,9 +205,9 @@ } }, "buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.0.tgz", - "integrity": "sha512-nUJyfChH7PMJy75eRDCCKtszSEFokUNXC1hNVSe+o+VdcgvDPLs20k3v8UXI8ruRYAJiYtyRea8mYyqPxoHWDw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -751,7 +751,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minimist-options": { @@ -765,7 +765,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -773,15 +773,15 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, "mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=" + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "ncp": { "version": "2.0.0", @@ -1184,9 +1184,9 @@ } }, "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", + "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", "dev": true }, "underscore": { @@ -1249,7 +1249,7 @@ }, "validator": { "version": "3.43.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz", + "resolved": "http://registry.npmjs.org/validator/-/validator-3.43.0.tgz", "integrity": "sha1-lkZLmS1BloM9l6GUv0Cxn/VLrgU=" }, "walkdir": { @@ -1272,7 +1272,7 @@ }, "winston": { "version": "0.8.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-0.8.3.tgz", + "resolved": "http://registry.npmjs.org/winston/-/winston-0.8.3.tgz", "integrity": "sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=", "requires": { "async": "0.2.x", diff --git a/package.json b/package.json index 513f981a..55ad27a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tfx-cli", - "version": "0.7.8", + "version": "0.7.82", "description": "CLI for Azure DevOps Services and Team Foundation Server", "repository": { "type": "git", diff --git a/tfx-cli.cmd b/tfx-cli.cmd new file mode 100644 index 00000000..de33a722 --- /dev/null +++ b/tfx-cli.cmd @@ -0,0 +1,6 @@ +@SETLOCAL + +@SET PATHEXT=%PATHEXT:;.JS;=;% + + +"C:\Program Files\nodejs\node.exe" "_build\tfx-cli.js" %* \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 883d3e4f..7dd15d06 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es6", "moduleResolution": "node", "declaration": false, "sourceMap": true, @@ -16,7 +16,5 @@ "app/app.ts", "app/exec/**/*.ts", "typings/**/*.d.ts" - /*, - "node_modules/@types/q/index.d.ts" // We augment the definitions with a file under typings, so we need an explicit reference here.*/ ] } \ No newline at end of file diff --git a/tsd.json b/tsd.json index 61d6ed4a..315352e3 100644 --- a/tsd.json +++ b/tsd.json @@ -6,28 +6,28 @@ "bundle": "typings/tsd.d.ts", "installed": { "node/node.d.ts": { - "commit": "f0aa5507070dc74859b636bd2dac37f3e8cab8d1" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "q/Q.d.ts": { "commit": "066819c65ff561ca109955e0b725c253e243f0a2" }, "colors/colors.d.ts": { - "commit": "b83eea746f8583a6c9956c19d8b553f2b66ce577" + "commit": "a11d013885ea072e6dba017ec5181eaf1ee152db" }, "glob/glob.d.ts": { - "commit": "066819c65ff561ca109955e0b725c253e243f0a2" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "xml2js/xml2js.d.ts": { - "commit": "066819c65ff561ca109955e0b725c253e243f0a2" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "mocha/mocha.d.ts": { "commit": "9cf502e3c1a8532f92ee0114e46509175e790938" }, "minimatch/minimatch.d.ts": { - "commit": "066819c65ff561ca109955e0b725c253e243f0a2" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "lodash/lodash.d.ts": { - "commit": "066819c65ff561ca109955e0b725c253e243f0a2" + "commit": "a11d013885ea072e6dba017ec5181eaf1ee152db" }, "tmp/tmp.d.ts": { "commit": "066819c65ff561ca109955e0b725c253e243f0a2" @@ -36,7 +36,7 @@ "commit": "230b346dad577c91af8db9f0a1db26697d2dd5bd" }, "jszip/jszip.d.ts": { - "commit": "e18e130e2188348070f98d92b98c0ea481c4a086" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "request/request.d.ts": { "commit": "43b6bf88758852b9ab713a9b011487f047f94f4e" @@ -44,29 +44,44 @@ "form-data/form-data.d.ts": { "commit": "43b6bf88758852b9ab713a9b011487f047f94f4e" }, - "vso-node-api/vso-node-api.d.ts": { + "azure-devops-node-api/azure-devops-node-api.d.ts": { "commit": "b9c534a52a50547c61c5f80519aaed285eb3e8b8" }, "mkdirp/mkdirp.d.ts": { - "commit": "76ed26e95e91f1c91f2d11819c44b55b617e859a" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "minimist/minimist.d.ts": { "commit": "eb59a40d3c2f3257e34ec2ede181046230814a41" }, "shelljs/shelljs.d.ts": { - "commit": "eb59a40d3c2f3257e34ec2ede181046230814a41" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "node-uuid/node-uuid.d.ts": { - "commit": "eb59a40d3c2f3257e34ec2ede181046230814a41" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "validator/validator.d.ts": { - "commit": "eb59a40d3c2f3257e34ec2ede181046230814a41" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "archiver/archiver.d.ts": { - "commit": "eb59a40d3c2f3257e34ec2ede181046230814a41" + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" }, "read/read.d.ts": { "commit": "0dd29bf8253536ae24e61c109524e924ea510046" + }, + "requirejs/require.d.ts": { + "commit": "a11d013885ea072e6dba017ec5181eaf1ee152db" + }, + "copy-paste/copy-paste.d.ts": { + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" + }, + "winreg/winreg.d.ts": { + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" + }, + "node-uuid/node-uuid-cjs.d.ts": { + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" + }, + "node-uuid/node-uuid-base.d.ts": { + "commit": "f9351fd6d2154b3822444c54e687f1b102827eac" } } }