调试 Azure DevOps 任务扩展 (TypeScript)

Debugging a Azure DevOps Task Extension (TypeScript)

我在 PowerShell 开发了所有任务扩展,现在我开始将我的第一个扩展翻译成 TypeScript。扩展是一个小任务,应该 运行 在构建或发布管道中。该任务应该部署到 Azure DevOps Server 2020.1(本地)。


准备

教程

系统设置

- Visual Studio Code
- Node (v14.15.4)
- TypeScript (Version 4.1.3)
- ts-node (v9.1.1)
- mocha (8.2.0)
- ts-mocha (8.0.0)
- azure-pipelines-task-lib (2.12.0)

Launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "args": ["task/index.ts", "--Template", "Custom"],
      "internalConsoleOptions": "openOnSessionStart",
      "name": "Run TypeScript",
      "request": "launch",
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
      "skipFiles": ["<node_internals>/**"],
      "type": "pwa-node"
    }
  ]
}

启动命令: node.exe --nolazy -r ts-node/register/transpile-only task/index.ts --Template Custom


问题

在运行时,当tl.getInput函数执行到true时,调试立即停止,没有任何反应(无错误,无输出)。

App.ts:

import tl = require("azure-pipelines-task-lib/task");
export const App = {
  Param: {
      Test: "Here",
      Template: tl.getInput("Template", true),
  }
}

Index.ts(入口点):

import { App } from "./app";

function run() {
  console.log("Hello");
  console.log(App.Param.Test);
}

run();

输出(什么都没有):


Index.ts(修改):

import { App } from "./app";

function run() {
  console.log("Hello");
  // console.log(App.Param.Test);
}

run();

输出(修改):

Hello

显然它停止了,因为所需的变量 Template 没有传递给应用程序。


问题

很明显,运行宁 azure-pipelines-task-lib 没有 Azure DevOps 环境 运行 进入问题。但我希望可以在本地模拟所需的管道变量和 运行 这个库。 如果使用 azure-pipelines-task-lib 意味着你必须部署扩展和 运行它在管道中进行测试,用它来开发任务有点复杂,或者?


编辑 1:

我发现关于 vsts-task-lib. In azure-pipelines-tasks/docs/debugging.md is manual to debug that library. The author of Debugging TypeScript Tasks in VS Code 的已弃用存储库描述了一个示例 launch.json 配置,我针对我的用例对其进行了修改:

{
      "name": "Launch tar.gz",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/dist/task/index.js",
      "stopOnEntry": false,
      "args": [],
      "cwd": "${workspaceRoot}/task",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "runtimeExecutable": null,
      "runtimeArgs": ["--nolazy"],
      "env": {
        "NODE_ENV": "development",
        "INPUT_Separator": ";",
        "BUILD_SOURCESDIRECTORY": "C:\agents\latest\_work\21\s"
      },
      "sourceMaps": true,
      "outFiles": ["${workspaceRoot}/dist"]
    }

我可以确认可以启动调试,tl.getInput("Separator") 将 return ;

is there a way to debug an azure devops task extension?

是的,根据 Step 1 in the article "Add a custom pipelines task extension", after installing all the required libraries and dependencies and adding all the required task implementation files, you can compile and run the taskPowerShell 或其他 shell。默认情况下,任务是 运行 调试模式。请参阅我在下面分享的示例。

is it possible to pass parameter and load them via tl.getInput?

当然,您可以将tl.getInput的值作为参数传递。请参阅我在下面分享的示例。

is there a state of the art or a complete guideline how to develop azure devops task extension?

目前,关于DevOps扩展的Microsoft Docs是我们开发DevOps扩展的最佳指南。

按照你的情况,我也在我这边测试,下面是我使用的主要源码:

  • task.json
{
    "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
    "id": "dc7322d8-6c98-4be7-91c9-dcbf7f4df7dd",
    "name": "buildAndReleaseTask",
    "friendlyName": "Build and Release task",
    "description": "Test create a Build and Release task.",
    "helpMarkDown": "",
    "category": "Utility",
    "author": "Bright Ran",
    "version": {
        "Major": 0,
        "Minor": 1,
        "Patch": 0
    },
    "instanceNameFormat": "Echo $(UserName)",
    "inputs": [
        {
            "name": "UserName",
            "type": "string",
            "label": "User name",
            "defaultValue": "",
            "required": true,
            "helpMarkDown": "An user name"
        }
    ],
    "execution": {
        "Node10": {
            "target": "index.js"
        }
    }
}
  • App.ts(和你的差不多)
import tl = require("azure-pipelines-task-lib/task");
export const App = {
  Param: {
      Here: "Here",
      UserName: tl.getInput("UserName", true),
  }
}
  • index.ts(和你的差不多)
import { App } from "./App";

function run() {
  console.log("Hello,", App.Param.UserName);
  console.log("Look", App.Param.Here);
}

run();
  • 编译结果和运行任务。
tsc
$env:INPUT_USERNAME="xxxx"
node index.js

从结果可以看出两个参数都可以正常传递

Debugging TypeScript Tasks in VS Code 的帮助下,我能够做以下事情:

  • 使用 tl.getInputimport tl = require("azure-pipelines-task-lib/task")
  • 读取输入参数
  • 使用 tl.getVariableimport tl = require("azure-pipelines-task-lib/task")
  • 读取环境变量
  • 使用 new azdev.WebApiimport * as azdev from "azure-devops-node-api"
  • 连接到 Azure DevOps 服务器
  • 使用来自 import * as ba from "azure-devops-node-api/BuildApi"
  • getBuildApi 进行构建 api 请求
  • 运行 并直接使用 TypeScript 调试应用程序而无需 JavaScript 翻译

launch.json

{
  "name": "Run TypeScript",
  "type": "pwa-node",
  "request": "launch",
  "internalConsoleOptions": "openOnSessionStart",
  "stopOnEntry": false,
  // path to your ts file
  "args": ["index.ts"],
  "cwd": "${workspaceRoot}/task",
  "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
  "env": {
    "NODE_ENV": "development",
    // param (enter your input params here!)
    "INPUT_WebhookUrl": "MyVariables",
    "INPUT_Template": "Empty",
    "INPUT_Json": "{\"text\":\"I am a test message\",\"attachments\":[{\"text\":\"And here’s an attachment!\"}]}",
    "INPUT_Separator": ";",
    // env
    "AGENT_JOBSTATUS": "Succeeded",
    "AGENT_NAME": "MyAgent",
    "BUILD_BUILDID": "5",
    "BUILD_BUILDNUMBER": "20210108.1",
    "BUILD_REASON": "Scheduled",
    "BUILD_REPOSITORY_NAME": "MyRepo",
    "BUILD_SOURCEBRANCHNAME": "master",
    "BUILD_SOURCEVERSION": "122a24f",
    "BUILDCONFIGURATION": "Debug",
    "BUILDPLATFORM": "Any CPU",
    "SYSTEM_ACCESSTOKEN": "",
    "SYSTEM_DEFINITIONNAME": "MyDefinitionName",
    "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://myurl.de/mycollection/",
    "SYSTEM_TEAMPROJECT": "PSItraffic",
    // debug
    "DEBUG_PAT": "my debug pat"
  },
  "skipFiles": ["<node_internals>/**"]
}

用例

读取参数和环境:app.ts

import tl = require("azure-pipelines-task-lib/task");

export const App = {
  // ------------------------------------------------------------ param
  Param: {
    WebhookUrl: tl.getDelimitedInput("WebhookUrl", "\n", true),
    Template: tl.getInput("Template", true)
  },
  // ------------------------------------------------------------ env
  Env: {
    Agent: {
      Jobstatus: getVariable("AGENT_JOBSTATUS"),
      Name: getVariable("AGENT_NAME"),
    },

    ...

    System: {
      AccessToken: getVariable("SYSTEM_ACCESSTOKEN"),
      DefinitionName: getVariable("SYSTEM_DEFINITIONNAME"),
      TeamFoundationServerUri: getVariable("SYSTEM_TEAMFOUNDATIONSERVERURI"),
      TeamProject: getVariable("SYSTEM_TEAMPROJECT"),
    },
  // ------------------------------------------------------------ debug
  Debug: {
    Pat: getVariable("DEBUG_PAT"),
  },
}

function getVariable(name: string): string {
  // get variable
  let v = tl.getVariable(name);
  if (v === undefined) return "";
  return v;
}

连接到 azure devops 服务器:rest.ts

import { App } from "./app";
import * as azdev from "azure-devops-node-api";
import * as ba from "azure-devops-node-api/BuildApi";


export class Rest {
  static AuthHanlder: IRequestHandler = Rest.Auth();
  static Connection: azdev.WebApi = new azdev.WebApi(App.Env.System.TeamFoundationServerUri, Rest.AuthHanlder);

  static Auth(): IRequestHandler {
    // auth
    if (App.Env.System.AccessToken === "") return azdev.getPersonalAccessTokenHandler(App.Debug.Pat);
    // no sure if this works on production
    return azdev.getBearerHandler(App.Env.System.AccessToken);
  }
}