Skip to content
Merged
3 changes: 2 additions & 1 deletion .vscode/cspell-devops-ext.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ codepaths
LASTEXITCODE
tmrm
toolrunner
TOOLSDIRECTORY
tsbuildinfo
vsix
vsix
25 changes: 25 additions & 0 deletions eng/pipelines/release-azuredevops.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Continuous deployment trigger
trigger:
branches:
include:
- main
paths:
include:
- ext/azuredevops
- eng/pipelines/release-azuredevops.yml

pr:
paths:
include:
- ext/azuredevops
- eng/pipelines/release-azuredevops.yml

extends:
template: /eng/pipelines/templates/stages/1es-redirect.yml
parameters:
stages:
- template: /eng/pipelines/templates/stages/azuredevops-build-and-test.yml

- template: /eng/pipelines/templates/stages/azuredevops-sign.yml

- template: /eng/pipelines/templates/stages/azuredevops-publish-manual.yml
72 changes: 72 additions & 0 deletions eng/pipelines/templates/jobs/azuredevops-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
parameters:
- name: NameSuffix
type: string
- name: Pool
type: string
- name: ImageKey
type: string
default: image
- name: OSVmImage
type: string
- name: OS
type: string
- name: Variables
type: object

jobs:
- job: Build_${{ parameters.NameSuffix }}
displayName: Build ${{ parameters.NameSuffix }}

pool:
name: ${{ parameters.Pool }}
${{ parameters.ImageKey }}: ${{ parameters.OSVmImage }}
os: ${{ parameters.OS }}

variables:
# TODO: What version of Node to use for Azure DevOps extension build?
NodeVersion: 20.x
${{ insert }}: ${{ parameters.Variables }}

steps:
- checkout: self

- task: NodeTool@0
inputs:
versionSpec: $(NodeVersion)

- pwsh: |
npm i -g npm tfx-cli
npm ci
displayName: Install build tools and dependencies
workingDirectory: ext/azuredevops/setupAzd

# TODO: If private build, update vss-extension.json and other JSONs to
# set a dev version

- task: PowerShell@2
inputs:
targetType: 'filePath'
filePath: '$(Build.SourcesDirectory)/ext/azuredevops/ci-test.ps1'
workingDirectory: '$(Build.SourcesDirectory)/ext/azuredevops'
displayName: Test

- task: PowerShell@2
inputs:
targetType: 'filePath'
filePath: '$(Build.SourcesDirectory)/ext/azuredevops/ci-package.ps1'
workingDirectory: '$(Build.SourcesDirectory)/ext/azuredevops'
displayName: Package Extension

- pwsh: |
Copy-Item `
-Path "$(Build.SourcesDirectory)/ext/azuredevops/*.vsix" `
-Destination "$(Build.ArtifactStagingDirectory)/"
condition: and(succeeded(), eq('true', variables['UploadArtifact']))
displayName: Copy VSIX to staging directory

templateContext:
outputs:
- output: pipelineArtifact
path: $(Build.ArtifactStagingDirectory)
condition: and(succeeded(), eq('true', variables['UploadArtifact']))
artifact: vsix
2 changes: 1 addition & 1 deletion eng/pipelines/templates/stages/1es-redirect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resources:
- repository: azure-sdk-build-tools
type: git
name: internal/azure-sdk-build-tools
ref: refs/tags/azure-sdk-build-tools_20251112.1
ref: refs/tags/azure-sdk-build-tools_20260112.5

parameters:
- name: stages
Expand Down
45 changes: 45 additions & 0 deletions eng/pipelines/templates/stages/azuredevops-build-and-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
parameters:
- name: BuildMatrix
type: object
default:
Windows:
Pool: $(WINDOWSPOOL)
ImageKey: image
OSVmImage: $(WINDOWSVMIMAGE)
OS: windows
Variables: {}

Linux:
Pool: $(LINUXPOOL)
ImageKey: image
OSVmImage: $(LINUXVMIMAGE)
OS: linux
Variables:
UploadArtifact: 'true'
Codeql.Enabled: true
Codeql.SkipTaskAutoInjection: false
Codeql.BuildIdentifier: azuredevops_linux

Mac:
Pool: Azure Pipelines
ImageKey: vmImage
OSVmImage: $(MACVMIMAGE)
OS: macOS
Variables: {}

stages:
- stage: BuildAndTest
variables:
- template: /eng/pipelines/templates/variables/globals.yml
- template: /eng/pipelines/templates/variables/image.yml

jobs:
- ${{ each build in parameters.BuildMatrix }}:
- template: /eng/pipelines/templates/jobs/azuredevops-build.yml
parameters:
NameSuffix: ${{ build.key }}
Pool: ${{ build.value.Pool }}
ImageKey: ${{ build.value.ImageKey }}
OSVmImage: ${{ build.value.OSVmImage }}
OS: ${{ build.value.OS }}
Variables: ${{ build.value.Variables }}
64 changes: 64 additions & 0 deletions eng/pipelines/templates/stages/azuredevops-publish-manual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
stages:
- stage: PublishManual
dependsOn: Sign
condition: >-
and(
succeeded(),
ne(variables['Skip.Release'], 'true'),
or(
eq('Manual', variables['BuildReasonOverride']),
and(
eq('', variables['BuildReasonOverride']),
eq(variables['Build.Reason'], 'Manual')
)
)
)

variables:
- template: /eng/pipelines/templates/variables/globals.yml
- template: /eng/pipelines/templates/variables/image.yml

jobs:
- deployment: Publish_Release
environment: package-publish
pool:
name: azsdk-pool
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pool name 'azsdk-pool' is hard-coded here, while other pipeline files use '$(LINUXPOOL)' from the globals template. For consistency and easier maintenance across the pipeline, consider using the variable reference '$(LINUXPOOL)' instead.

Suggested change
name: azsdk-pool
name: $(LINUXPOOL)

Copilot uses AI. Check for mistakes.
image: ubuntu-24.04
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image version 'ubuntu-24.04' is hard-coded here, whereas the vscode-publish-manual.yml uses '$(LINUXVMIMAGE)' from the image template variables. For consistency and easier maintenance, consider using the variable reference pattern instead of hard-coding the image version.

Suggested change
image: ubuntu-24.04
image: $(LINUXVMIMAGE)

Copilot uses AI. Check for mistakes.
os: linux

templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
artifactName: signed
targetPath: signed

strategy:
runOnce:
deploy:
steps:
- pwsh: |
$files = Get-ChildItem -Path "$(Build.SourcesDirectory)/signed/*.vsix"
if ($files -is [Array]) {
if ($files.Count -gt 1) {
Write-Host "More than one VSIX file found in signed artifact."
exit 1
}
$vsixFile = $files[0].FullName
} else {
$vsixFile = $files.FullName
}

Write-Host "##vso[task.setvariable variable=VSIX_FILE]$vsixFile"
displayName: Get VSIX Filename

- task: 1ES.PublishAzureDevOpsExtension@1
displayName: Publish Extension
inputs:
targetPath: "$(Build.SourcesDirectory)/signed"
connectedServiceNameAzureRM: azure-sdk-vsmarketplace
fileType: vsix
vsixFile: "$(VSIX_FILE)"
useV5: true
validateExtension: false
54 changes: 54 additions & 0 deletions eng/pipelines/templates/stages/azuredevops-sign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
stages:
- stage: Sign
dependsOn: BuildAndTest
variables:
- template: /eng/pipelines/templates/variables/globals.yml
- template: /eng/pipelines/templates/variables/image.yml

jobs:
- job: Sign
${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual') }}:
displayName: Sign
${{ else }}:
displayName: SKIP Signing

pool:
name: $(WINDOWSPOOL)
image: $(WINDOWSVMIMAGE)
os: windows

steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: vsix
path: vsix

- ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual') }}:
- template: pipelines/steps/azd-azdo-extension-signing.yml@azure-sdk-build-tools
parameters:
Path: $(Build.SourcesDirectory)\vsix
Pattern: '*.vsix'

- ${{ else }}:
- pwsh: Write-Host "Skipping signing. Build reason - $(Build.Reason)"
displayName: Signing process skipped for non-release build

- pwsh: |
New-Item -ItemType Directory -Path signed
Copy-Item vsix/*.vsix signed/
displayName: Copy signing outputs
condition: always()

templateContext:
outputs:
- output: pipelineArtifact
condition: succeeded()
displayName: Publish Signed Artifacts
artifact: signed
path: signed/

- output: pipelineArtifact
condition: failed()
displayName: Publish failed Signed Artifacts
artifact: signed-FailedAttempt$(System.JobAttempt)
path: signed/
23 changes: 23 additions & 0 deletions ext/azuredevops/ci-package.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env pwsh

$originalLocation = Get-Location

try {
Set-Location $PSScriptRoot

Write-Host "Running tsc build"
npm --prefix $PSScriptRoot/setupAzd/ run build
if ($LASTEXITCODE) {
Write-Host "Build failed"
exit $LASTEXITCODE
}
Write-Host "Building Azure DevOps extension package"
tfx extension create --manifest-globs vss-extension.json
if ($LASTEXITCODE) {
Write-Host "Packaging failed"
exit $LASTEXITCODE
}

} finally {
Set-Location $originalLocation
}
2 changes: 2 additions & 0 deletions ext/azuredevops/ci-test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env pwsh
npm --prefix $PSScriptRoot/setupAzd/ test
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script does not check the exit code of the npm test command. While the default PowerShell behavior will fail the pipeline task if npm exits with a non-zero code, it's a best practice to explicitly check $LASTEXITCODE for consistency with the ci-package.ps1 script pattern. Consider adding error handling similar to ci-package.ps1.

Suggested change
npm --prefix $PSScriptRoot/setupAzd/ test
npm --prefix $PSScriptRoot/setupAzd/ test
if ($LASTEXITCODE -ne 0) {
Write-Error "npm test failed with exit code $LASTEXITCODE."
exit $LASTEXITCODE
}

Copilot uses AI. Check for mistakes.
15 changes: 11 additions & 4 deletions ext/azuredevops/setupAzd/tests/_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import * as ttm from 'azure-pipelines-task-lib/mock-test';
describe('Setup azd task tests', function () {

before(function() {
// Setup before tests
// Disable tool download for tests to prevent actual network calls
process.env.AGENT_TOOLSDIRECTORY = '/tmp/test-tools';
process.env.RUNNER_TOOL_CACHE = '/tmp/test-tools';
});

after(() => {
// Cleanup after tests
delete process.env.AGENT_TOOLSDIRECTORY;
delete process.env.RUNNER_TOOL_CACHE;
});

it('should succeed with default version (latest)', function(done: Mocha.Done) {
this.timeout(5000);
// Increased timeout for macOS where tool cache operations can take longer
this.timeout(30000);

const tp: string = path.join(__dirname, 'success.js');
const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
Expand All @@ -30,7 +35,8 @@ describe('Setup azd task tests', function () {
});

it('should succeed with specific version', function(done: Mocha.Done) {
this.timeout(5000);
// Increased timeout for macOS compatibility
this.timeout(30000);

const tp: string = path.join(__dirname, 'successVersion.js');
const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
Expand All @@ -47,7 +53,8 @@ describe('Setup azd task tests', function () {
});

it('should fail with invalid version', function(done: Mocha.Done) {
this.timeout(5000);
// Increased timeout for macOS compatibility
this.timeout(30000);

const tp: string = path.join(__dirname, 'invalidVersion.js');
const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
Expand Down
Loading