添加自定义管道任务扩展

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

本指南逐步讲解如何创建、测试和发布自定义生成或发布任务作为 Azure DevOps 扩展。 使用自定义管道任务,可将 Azure DevOps 扩展到专为团队工作流定制的专用功能,从简单的实用工具扩展到与外部系统的复杂集成。

了解如何执行以下任务:

  • 设置开发环境和项目结构
  • 使用 TypeScript 和 Azure Pipelines 任务库创建任务逻辑
  • 使用模拟框架实现全面的单元测试
  • 打包扩展以供分发
  • 发布到 Visual Studio 市场
  • 为扩展维护设置自动化 CI/CD 管道

有关 Azure Pipelines 的详细信息,请参阅 什么是 Azure Pipelines?

注意

本文介绍基于代理的扩展中的代理任务。 有关服务器任务和基于服务器的扩展的信息,请参阅 服务器任务创作

先决条件

在开始之前,请确保满足以下要求:

组件 要求 说明
Azure DevOps 组织 必选 如果没有组织,请创建组织
文本编辑器 建议 用于 IntelliSense 和调试支持的 Visual Studio Code
Node.js 必选 安装 最新版本 (建议Node.js 20 或更高版本)
TypeScript 编译器 必选 安装 最新版本 (版本 4.6.3 或更高版本)
Azure DevOps CLI (tfx-cli) 必选 安装到 npm i -g tfx-cli 包扩展
Azure DevOps 扩展 SDK 必选 安装 azure-devops-extension-sdk
测试框架 必选 Mocha 用于单元测试(在安装期间安装)

项目结构

home为项目创建目录。 完成本教程后,扩展应具有以下结构:

|--- README.md    
|--- images                        
    |--- extension-icon.png  
|--- buildandreleasetask            // Task scripts location
    |--- task.json                  // Task definition
    |--- index.ts                   // Main task logic
    |--- package.json               // Node.js dependencies
    |--- tests/                     // Unit tests
        |--- _suite.ts
        |--- success.ts
        |--- failure.ts
|--- vss-extension.json             // Extension manifest

重要

开发计算机必须运行 最新版本的 Node.js ,以确保与生产环境的兼容性。 task.json更新文件以使用 Node 20:

"execution": {
    "Node20_1": {
      "target": "index.js"
    }
}

1.创建自定义任务

本部分将指导你创建自定义任务的基本结构和实现。 此步骤中的所有文件都应在 buildandreleasetask 项目 home 目录内的文件夹中创建。

注意

本演练将 Windows 与 PowerShell 配合使用。 这些步骤适用于所有平台,但环境变量语法不同。 在 Mac 或 Linux 上,替换为 $env:<var>=<val>export <var>=<val>

设置任务基架

创建基本项目结构并安装所需的依赖项:

  1. 若要初始化 Node.js 项目,请打开 PowerShell,转到文件夹 buildandreleasetask ,然后运行:

    npm init --yes
    

    该文件 package.json 使用默认设置创建。 该 --yes 标志会自动接受所有默认选项。

    提示

    Azure Pipelines 代理要求任务文件夹包含节点模块。 复制到 node_modules 文件夹 buildandreleasetask 。 若要管理 VSIX 文件大小(50 MB 限制),请考虑运行 npm install --production 或在 npm prune --production 打包之前。

  2. 安装 Azure Pipelines 任务库:

    npm install azure-pipelines-task-lib --save
    
  3. 安装 TypeScript 类型定义:

    npm install @types/node --save-dev
    npm install @types/q --save-dev
    
  4. 设置版本控制排除项

    echo node_modules > .gitignore
    

    每次生成过程都应运行 npm install 以重新生成node_modules。

  5. 安装测试依赖项:

    npm install mocha --save-dev -g
    npm install sync-request --save-dev
    npm install @types/mocha --save-dev
    
  6. 安装 TypeScript 编译器:

    npm install typescript@4.6.3 -g --save-dev
    

    注意

    全局安装 TypeScript 以确保 tsc 命令可用。 如果没有它,则默认使用 TypeScript 2.3.4。

  7. 配置 TypeScript 编译:

    tsc --init --target es2022
    

    该文件 tsconfig.json 使用 ES2022 目标设置创建。

实现任务逻辑

完成基架后,创建定义功能和元数据的核心任务文件:

  1. 创建任务定义文件:在buildandreleasetask文件夹中创建task.json。 此文件介绍 Azure Pipelines 系统的任务、定义输入、执行设置和 UI 演示文稿。

    {
     "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
     "id": "{{taskguid}}",
     "name": "{{taskname}}",
     "friendlyName": "{{taskfriendlyname}}",
     "description": "{{taskdescription}}",
     "helpMarkDown": "",
     "category": "Utility",
     "author": "{{taskauthor}}",
     "version": {
         "Major": 0,
         "Minor": 1,
         "Patch": 0
     },
     "instanceNameFormat": "Echo $(samplestring)",
     "inputs": [
         {
             "name": "samplestring",
             "type": "string",
             "label": "Sample String",
             "defaultValue": "",
             "required": true,
             "helpMarkDown": "A sample string"
         }
     ],
     "execution": {
         "Node20_1": {
             "target": "index.js"
         }
     }
     }
    

    注意

    替换为 {{placeholders}} 任务的实际信息。 taskguid 必须独一无二。 使用 PowerShell 生成一个: (New-Guid).Guid

  2. 若要实现任务逻辑,请使用任务的主要功能创建 index.ts

    import tl = require('azure-pipelines-task-lib/task');
    
     async function run() {
         try {
             const inputString: string | undefined = tl.getInput('samplestring', true);
             if (inputString == 'bad') {
                 tl.setResult(tl.TaskResult.Failed, 'Bad input was given');
                 return;
             }
             console.log('Hello', inputString);
         }
         catch (err: any) {
             tl.setResult(tl.TaskResult.Failed, err.message);
         }
     }
    
     run();
    
  3. 将 TypeScript 编译为 JavaScript:

    tsc
    

    index.js从 TypeScript 源创建该文件。

了解 task.json 组件

该文件 task.json 是任务定义的核心。 以下是关键属性:

属性 说明 示例:
id 任务的唯一 GUID 标识符 使用 (New-Guid).Guid
name 不带空格的任务名称(在内部使用) MyCustomTask
friendlyName UI 中显示的显示名称 My Custom Task
description 任务功能的详细说明 Performs custom operations on files
author 发布者或作者名称 My Company
instanceNameFormat 任务在管道步骤中的显示方式 Process $(inputFile)
inputs 输入参数数组 请参阅以下输入类型
execution 执行环境规范 Node20_1PowerShell3
restrictions 命令和变量的安全限制 建议用于新任务

安全限制

对于生产任务,请添加安全限制以限制命令使用情况和变量访问:

"restrictions": {
  "commands": {
    "mode": "restricted"
  },
  "settableVariables": {
    "allowed": ["variable1", "test*"]
  }
}

受限模式 仅允许以下命令:

  • logdetaillogissuecompletesetprogress
  • setsecretsetvariabledebugsettaskvariable
  • prependpathpublish

变量允许列表 控制可通过 setvariableprependpath设置哪些变量。 支持基本正则表达式模式。

注意

此功能需要 代理版本 2.182.1 或更高版本。

输入类型和示例

任务参数的常见输入类型:

"inputs": [
    {
        "name": "stringInput",
        "type": "string",
        "label": "Text Input",
        "defaultValue": "",
        "required": true,
        "helpMarkDown": "Enter a text value"
    },
    {
        "name": "boolInput",
        "type": "boolean",
        "label": "Enable Feature",
        "defaultValue": "false",
        "required": false
    },
    {
        "name": "picklistInput",
        "type": "pickList",
        "label": "Select Option",
        "options": {
            "option1": "First Option",
            "option2": "Second Option"
        },
        "defaultValue": "option1"
    },
    {
        "name": "fileInput",
        "type": "filePath",
        "label": "Input File",
        "required": true,
        "helpMarkDown": "Path to the input file"
    }
]

在本地测试任务

在打包之前,请测试任务以确保其正常工作:

  1. 缺少输入的测试(应失败):

    node index.js
    

    预期输出:

    ##vso[task.debug]agent.workFolder=undefined
    ##vso[task.debug]loading inputs and endpoints
    ##vso[task.debug]loaded 0
    ##vso[task.debug]task result: Failed
    ##vso[task.issue type=error;]Input required: samplestring
    ##vso[task.complete result=Failed;]Input required: samplestring
    
  2. 使用有效输入进行测试(应成功):

    $env:INPUT_SAMPLESTRING="World"
    node index.js
    

    预期输出:

    ##vso[task.debug]agent.workFolder=undefined
    ##vso[task.debug]loading inputs and endpoints
    ##vso[task.debug]loading INPUT_SAMPLESTRING
    ##vso[task.debug]loaded 1
    ##vso[task.debug]samplestring=World
    Hello World
    
  3. 测试错误处理:

    $env:INPUT_SAMPLESTRING="bad"
    node index.js
    

    此作应触发代码中的错误处理路径。

    提示

    有关任务运行程序和 Node.js 版本的信息,请参阅 Node 运行程序更新指南

有关详细信息,请参阅 生成/发布任务参考

2. 实现全面的单元测试

彻底测试任务可确保可靠性,并帮助在部署到生产管道之前捕获问题。

安装测试依赖项

安装所需的测试工具:

npm install mocha --save-dev -g
npm install sync-request --save-dev
npm install @types/mocha --save-dev

创建测试

  1. 在任务目录中创建包含tests_suite.ts文件的文件夹:

     import * as path from 'path';
     import * as assert from 'assert';
     import * as ttm from 'azure-pipelines-task-lib/mock-test';
    
     describe('Sample task tests', function () {
    
         before( function() {
             // Setup before tests
         });
    
         after(() => {
             // Cleanup after tests
         });
    
         it('should succeed with simple inputs', function(done: Mocha.Done) {
             // Success test implementation
         });
    
         it('should fail if tool returns 1', function(done: Mocha.Done) {
             // Failure test implementation
         });    
       });
    

    提示

    测试文件夹应位于任务文件夹中(例如 buildandreleasetask)。 如果遇到同步请求错误,请在任务文件夹中安装它: npm i --save-dev sync-request

  2. 在测试目录中创建 success.ts 以模拟成功的任务执行:

     import ma = require('azure-pipelines-task-lib/mock-answer');
     import tmrm = require('azure-pipelines-task-lib/mock-run');
     import path = require('path');
    
     let taskPath = path.join(__dirname, '..', 'index.js');
     let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath);
    
     // Set valid input for success scenario
     tmr.setInput('samplestring', 'human');
    
     tmr.run();
    
  3. 将成功测试添加到 _suite.ts 文件:

     it('should succeed with simple inputs', function(done: Mocha.Done) {
         this.timeout(1000);
    
         let tp: string = path.join(__dirname, 'success.js');
         let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
    
         tr.runAsync().then(() => {
             console.log(tr.succeeded);
             assert.equal(tr.succeeded, true, 'should have succeeded');
             assert.equal(tr.warningIssues.length, 0, "should have no warnings");
             assert.equal(tr.errorIssues.length, 0, "should have no errors");
             console.log(tr.stdout);
             assert.equal(tr.stdout.indexOf('Hello human') >= 0, true, "should display Hello human");
             done();
         }).catch((error) => {
             done(error); // Ensure the test case fails if there's an error
         });
     });
    
  4. 在测试目录中创建 failure.ts 以测试错误处理:

    import ma = require('azure-pipelines-task-lib/mock-answer');
    import tmrm = require('azure-pipelines-task-lib/mock-run');
    import path = require('path');
    
    let taskPath = path.join(__dirname, '..', 'index.js');
    let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath);
    
    // Set invalid input to trigger failure
    tmr.setInput('samplestring', 'bad');
    
    tmr.run();
    
  5. 将失败测试添加到 _suite.ts 文件:

     it('should fail if tool returns 1', function(done: Mocha.Done) {
         this.timeout(1000);
    
         const tp = path.join(__dirname, 'failure.js');
         const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
    
         tr.runAsync().then(() => {
             console.log(tr.succeeded);
             assert.equal(tr.succeeded, false, 'should have failed');
             assert.equal(tr.warningIssues.length, 0, 'should have no warnings');
             assert.equal(tr.errorIssues.length, 1, 'should have 1 error issue');
             assert.equal(tr.errorIssues[0], 'Bad input was given', 'error issue output');
             assert.equal(tr.stdout.indexOf('Hello bad'), -1, 'Should not display Hello bad');
             done();
         });
     });
    

运行您的测试

执行测试套件:

# Compile TypeScript
tsc

# Run tests
mocha tests/_suite.js

这两个测试都应通过。 对于详细输出(类似于生成控制台输出),请设置跟踪环境变量:

$env:TASK_TEST_TRACE=1
mocha tests/_suite.js

测试覆盖率最佳做法

  • 测试所有输入组合:有效输入、输入无效、缺少所需的输入
  • 测试错误方案:网络故障、文件系统错误、权限问题
  • 模拟外部依赖项:不要依赖单元测试中的外部服务
  • 验证输出:检查控制台输出、任务结果和生成的项目
  • 性能测试:考虑为处理大型文件的任务添加测试

安全最佳做法

  • 输入验证:始终验证和清理输入
  • 机密处理:用于 setSecret 敏感数据
  • 命令限制:对生产任务实施命令限制
  • 最小权限:仅请求必要的权限
  • 常规更新:使依赖项和 Node.js 版本保持最新

在本地测试任务并实现全面的单元测试后,将其打包到 Azure DevOps 的扩展中。

安装打包工具

安装跨平台命令行接口(tfx-cli):

npm install -g tfx-cli

创建扩展清单

扩展清单 (vss-extension.json) 包含有关扩展的所有信息,包括对任务文件夹和映像的引用。

  1. 使用文件创建图像文件夹extension-icon.png

  2. 在扩展的根目录中创建 vss-extension.json (不在任务文件夹中):

    {
     "manifestVersion": 1,
     "id": "my-custom-tasks",
     "name": "My Custom Tasks",
     "version": "1.0.0",
     "publisher": "your-publisher-id",
     "targets": [
         {
             "id": "Microsoft.VisualStudio.Services"
         }
     ],
     "description": "Custom build and release tasks for Azure DevOps",
     "categories": [
         "Azure Pipelines"
     ],
     "icons": {
         "default": "images/extension-icon.png"
     },
     "files": [
         {
             "path": "MyCustomTask"
         }
     ],
     "contributions": [
         {
             "id": "my-custom-task",
             "type": "ms.vss-distributed-task.task",
             "targets": [
                 "ms.vss-distributed-task.tasks"
             ],
             "properties": {
                 "name": "MyCustomTask"
             }
         }
     ]
    }
    

关键清单属性

属性 说明
publisher 市场发布者标识符
contributions.id 扩展内的唯一标识符
contributions.properties.name 必须与任务文件夹名称匹配
files.path 相对于清单的任务文件夹的路径

注意

发布者 值更改为发布者名称。 有关创建发布者的信息,请参阅 “创建发布者”。

打包扩展

将扩展名打包到 .vsix 文件中:

tfx extension create --manifest-globs vss-extension.json

版本管理

  • 扩展版本:递增每个更新的版本vss-extension.json
  • 任务版本:递增每个任务更新的版本task.json
  • 自动递增:用于 --rev-version 自动递增修补程序版本
tfx extension create --manifest-globs vss-extension.json --rev-version

重要

必须更新任务版本和扩展版本,以便更改在 Azure DevOps 中生效。

版本控制策略

遵循任务更新的语义版本控制原则:

  • 主要版本:对输入/输出的重大更改
  • 次要版本:新功能,向后兼容
  • 修补程序版本:仅 Bug 修复

更新过程:

  1. 更新 task.json 版本
  2. 更新 vss-extension.json 版本
  3. 在测试组织中全面测试
  4. 发布和监视问题

发布到 Visual Studio Marketplace

1.创建发布者

  1. 登录到 Visual Studio Marketplace 发布门户
  2. 如果系统提示,请创建新的发布服务器:
    • 发布者标识符:在扩展清单中使用(例如) mycompany-myteam
    • 显示名称:市场中显示的公共名称(例如 My Team
  3. 查看并接受 市场发布者协议

2.上传扩展

Web 接口方法:

  1. 选择 “上传新扩展”
  2. 选择打包 .vsix 的文件
  3. 选择“上传”

命令行方法:

tfx extension publish --manifest-globs vss-extension.json --share-with yourOrganization

3.共享扩展

  1. 在市场中右键单击扩展
  2. 选择共享
  3. 输入组织名称
  4. 根据需要添加更多组织

重要

必须验证发布者才能公开共享扩展。 有关详细信息,请参阅 包/发布/安装

4.安装到组织

共享后,将扩展安装到 Azure DevOps 组织:

  1. 导航到 组织设置>扩展
  2. 浏览扩展
  3. 选择“ 免费获取 并安装”

3.打包并发布扩展

验证扩展

安装后,验证任务是否正常工作:

  1. 创建或编辑管道。
  2. 添加自定义任务:
    • 在管道编辑器中选择 “添加任务
    • 按名称搜索自定义任务
    • 将其添加到管道
  3. 配置任务参数:
    • 设置所需的输入
    • 配置可选设置
  4. 运行管道以测试功能
  5. 监视执行:
    • 检查任务日志以正确执行
    • 验证预期输出
    • 确保没有错误或警告

4.使用 CI/CD 自动发布扩展

若要有效维护自定义任务,请创建用于处理测试、打包和发布的自动化生成和发布管道。

自动化的先决条件

  • Azure DevOps 扩展任务:免费安装扩展
  • 变量组:使用以下变量创建 管道库变量组
    • publisherId:市场发布者 ID
    • extensionId:来自 vss-extension.json 的扩展 ID
    • extensionName:来自 vss-extension.json 的扩展名称
    • artifactName:VSIX 项目的名称
  • 服务连接:使用管道访问权限创建市场服务连接

完成 CI/CD 管道

创建一个 YAML 管道,其中包含用于测试、打包和发布的综合阶段:

trigger: 
- main

pool:
  vmImage: "ubuntu-latest"

variables:
  - group: extension-variables # Your variable group name

stages:
  - stage: Test_and_validate
    displayName: 'Run Tests and Validate Code'
    jobs:
      - job: RunTests
        displayName: 'Execute unit tests'
        steps:
          - task: TfxInstaller@4
            displayName: 'Install TFX CLI'
            inputs:
              version: "v0.x"
          
          - task: Npm@1
            displayName: 'Install task dependencies'
            inputs:
              command: 'install'
              workingDir: '/MyCustomTask' # Update to your task directory
          
          - task: Bash@3
            displayName: 'Compile TypeScript'
            inputs:
              targetType: "inline"
              script: |
                cd MyCustomTask # Update to your task directory
                tsc
          
          - task: Npm@1
            displayName: 'Run unit tests'
            inputs:
              command: 'custom'
              workingDir: '/MyCustomTask' # Update to your task directory
              customCommand: 'test' # Ensure this script exists in package.json
          
          - task: PublishTestResults@2
            displayName: 'Publish test results'
            inputs:
              testResultsFormat: 'JUnit'
              testResultsFiles: '**/test-results.xml'
              searchFolder: '$(System.DefaultWorkingDirectory)'

  - stage: Package_extension
    displayName: 'Package Extension'
    dependsOn: Test_and_validate
    condition: succeeded()
    jobs:
      - job: PackageExtension
        displayName: 'Create VSIX package'
        steps:
          - task: TfxInstaller@4
            displayName: 'Install TFX CLI'
            inputs:
              version: "v0.x"
          
          - task: Npm@1
            displayName: 'Install dependencies'
            inputs:
              command: 'install'
              workingDir: '/MyCustomTask'
          
          - task: Bash@3
            displayName: 'Compile TypeScript'
            inputs:
              targetType: "inline"
              script: |
                cd MyCustomTask
                tsc
          
          - task: QueryAzureDevOpsExtensionVersion@4
            name: QueryVersion
            displayName: 'Query current extension version'
            inputs:
              connectTo: 'VsTeam'
              connectedServiceName: 'marketplace-connection'
              publisherId: '$(publisherId)'
              extensionId: '$(extensionId)'
              versionAction: 'Patch'
          
          - task: PackageAzureDevOpsExtension@4
            displayName: 'Package extension'
            inputs:
              rootFolder: '$(System.DefaultWorkingDirectory)'
              publisherId: '$(publisherId)'
              extensionId: '$(extensionId)'
              extensionName: '$(extensionName)'
              extensionVersion: '$(QueryVersion.Extension.Version)'
              updateTasksVersion: true
              updateTasksVersionType: 'patch'
              extensionVisibility: 'private'
              extensionPricing: 'free'
          
          - task: PublishBuildArtifacts@1
            displayName: 'Publish VSIX artifact'
            inputs:
              PathtoPublish: '$(System.DefaultWorkingDirectory)/*.vsix'
              ArtifactName: '$(artifactName)'
              publishLocation: 'Container'

  - stage: Publish_to_marketplace
    displayName: 'Publish to Marketplace'
    dependsOn: Package_extension
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: PublishExtension
        displayName: 'Deploy to marketplace'
        environment: 'marketplace-production'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: TfxInstaller@4
                  displayName: 'Install TFX CLI'
                  inputs:
                    version: "v0.x"
                
                - task: PublishAzureDevOpsExtension@4
                  displayName: 'Publish to marketplace'
                  inputs:
                    connectTo: 'VsTeam'
                    connectedServiceName: 'marketplace-connection'
                    fileType: 'vsix'
                    vsixFile: '$(Pipeline.Workspace)/$(artifactName)/*.vsix'
                    publisherId: '$(publisherId)'
                    extensionId: '$(extensionId)'
                    extensionName: '$(extensionName)'
                    updateTasksVersion: false
                    extensionVisibility: 'private'
                    extensionPricing: 'free'

配置用于测试的 package.json

将测试脚本添加到:package.json

{
  "scripts": {
    "test": "mocha tests/_suite.js --reporter xunit --reporter-option output=test-results.xml",
    "test-verbose": "cross-env TASK_TEST_TRACE=1 npm test"
  }
}

管道阶段细分

阶段 1:测试和验证

  • 目的:确保代码质量和功能
  • :安装依赖项、编译 TypeScript、运行单元测试、发布结果
  • 验证:所有测试都必须通过才能继续

阶段 2:包扩展

  • 目的:创建可部署的 VSIX 包
  • :查询当前版本、增量版本、包扩展、发布项目
  • 版本控制:自动处理版本增量

阶段 3:发布到市场

  • 目的:部署到 Visual Studio Marketplace
  • 条件:仅在成功打包后在主分支上运行
  • 环境:将部署环境用于审批入口

CI/CD 最佳做法

  • 分支保护:仅从主/发布分支发布
  • 环境入口:将部署环境用于生产版本
  • 版本管理:自动执行版本增量以避免冲突
  • 测试覆盖率:在打包之前确保全面的测试覆盖率
  • 安全性:使用服务连接而不是硬编码凭据
  • 监视:为失败的部署设置警报

对于经典生成管道,请按照以下步骤设置扩展打包和发布:

  1. 添加 Bash 任务以将 TypeScript 编译到 JavaScript 中。

  2. 若要查询现有版本,请使用以下输入添加 查询扩展版本 任务:

    • 连接到:Visual Studio Marketplace
    • Visual Studio Marketplace(服务连接):服务连接
    • 发布者 ID:Visual Studio Marketplace 发布者的 ID
    • 扩展 ID:vss-extension.json 文件中扩展的 ID
    • 版本升级:补丁
    • 输出变量: Task.Extension.Version
  3. 若要基于清单 Json 打包扩展,请使用以下输入添加 包扩展 任务:

    • 根清单文件夹:指向包含清单文件的根目录。 例如,$(System.DefaultWorkingDirectory) 是根目录。
    • 清单文件:vss-extension.json
    • 发布者 ID:Visual Studio Marketplace 发布者的 ID
    • 扩展 ID:vss-extension.json 文件中扩展的 ID
    • 扩展名:vss-extension.json 文件中扩展的名称
    • 扩展版本:$(Task.Extension.Version)
    • 替代任务版本:已选中 (true)
    • 替代类型:仅替换补丁 (1.0.r)
    • 扩展可见性:如果扩展仍在开发中,请将该值设置为 私有。 若要将扩展发布到公共,请将值设置为 公共
  4. 若要复制到已发布的文件,请使用以下输入添加 “复制文件 ”任务:

    • 内容:所有要复制以便发布为工件的文件。
    • 目标文件夹:文件复制到的文件夹
      • 例如:$(Build.ArtifactStagingDirectory)
  5. 添加发布生成项目以发布要在其他作业或管道中使用的项目。 使用以下输入:

    • 发布路径:包含要发布的文件的文件夹的路径
      • 例如:$(Build.ArtifactStagingDirectory)
    • 文物名称:分配给该文物的名称
    • 工件发布路径:选择 Azure Pipelines 以便在后续作业中使用该工件。

阶段 3:下载生成产物并发布扩展程序

  1. 要将 tfx-cli 安装到生成代理上,请添加将 Node CLI 用于 Azure DevOps (tfx-cli)

  2. 若要将项目下载到新作业,请使用以下输入添加 “下载生成项目 ”任务:

    • 下载以下作业生成的工件:如果要在新作业中从同一管道下载工件,请选择当前生成。 如果要在新管道上下载,请选择特定生成
    • 下载类型:选择 特定项目 以下载已发布的所有文件。
    • 项目名称:已发布的项目名称
    • 目标目录:应下载文件的文件夹
  3. 若要获取 发布扩展 任务,请使用以下输入:

    • 连接到:Visual Studio Marketplace
    • Visual Studio Marketplace 连接:ServiceConnection
    • 输入文件类型:VSIX 文件
    • VSIX 文件: /Publisher.*.vsix
    • 发布者 ID:Visual Studio Marketplace 发布者的 ID
    • 扩展 ID:vss-extension.json 文件中扩展的 ID
    • 扩展名:vss-extension.json 文件中扩展的名称
    • 扩展可见性:私有或公共

可选:安装和测试扩展

发布扩展后,需要在 Azure DevOps 组织中安装它。

将扩展安装到组织

在几个步骤中安装共享扩展:

  1. 转到 “组织”设置 ,然后选择“ 扩展”。

  2. 在“ 与我共享的扩展 ”部分找到扩展:

    • 选择扩展链接
    • 选择“ 免费获取”“安装”
  3. 检查扩展是否显示在 “已安装 的扩展”列表中:

    • 确认它在管道任务库中可用

注意

如果未看到“ 扩展 ”选项卡,请确保位于组织管理级别(https://dev.azure.com/{organization}/_admin)而不是项目级别。

端到端测试

安装后,执行全面的测试:

  1. 创建测试管道:

    • 将自定义任务添加到新管道
    • 配置所有输入参数
    • 使用各种输入组合进行测试
  2. 验证功能:

    • 运行管道并监视执行
    • 检查任务输出和日志
    • 使用无效输入验证错误处理
  3. 测试性能:

    • 使用大型输入文件进行测试(如果适用)
    • 监视资源使用情况
    • 验证超时行为

常见问题

问:如何处理任务取消?

答:管道代理向任务进程发送 SIGINTSIGTERM 发出信号。 虽然 任务库 不提供显式取消处理,但任务可以实现信号处理程序。 有关详细信息,请参阅 代理作业取消

问:如何从组织中删除任务?

答: 不支持自动删除 ,因为它会中断现有管道。 相反:

  1. 弃用任务将任务标记为已弃用
  2. 版本管理颠簸任务版本
  3. 通信:通知用户弃用时间线

问:如何将任务升级到最新的 Node.js 版本?

答:升级到 最新的 Node 版本 ,以提高性能和安全性。 有关迁移指南,请参阅 将任务升级到 Node 20

通过在以下项中包含多个执行部分task.json来支持多个 Node 版本

"execution": {
  "Node20_1": {
    "target": "index.js"
  },
  "Node10": {
    "target": "index.js"
  }
}

具有 Node 20 的代理使用首选版本,而较旧的代理则回退到 Node 10。

要升级你的任务,请执行以下操作:

  • 若要确保代码按预期方式运行,请在各种 Node 运行程序版本上测试任务。

  • 在任务的执行部分中,将NodeNode10更新为Node16Node20

  • 若要支持较旧的服务器版本,应保留目标 Node/Node10 。 旧版 Azure DevOps Server 可能不包含最新的 Node 运行程序版本。

  • 可以选择共享目标中定义的入口点,或者将目标优化到使用的 Node 版本。

    "execution": {
       "Node10": {
         "target": "bash10.js",
         "argumentFormat": ""
       },
       "Node16": {
         "target": "bash16.js",
         "argumentFormat": ""
       },
       "Node20_1": {
         "target": "bash20.js",
         "argumentFormat": ""
       }
    }
    

重要

如果你没有在自定义任务中添加对 Node 20 运行器的支持,则任务会在从 pipelines-agent-*发布源安装的代理上失败。