Compare commits
12 Commits
a9a27461ff
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 783f147fb0 | |||
| 77ba71f7ec | |||
| 91dd2f1574 | |||
| 11c967aeb4 | |||
| 520b125ac1 | |||
| f4ac753dae | |||
| ef756810cc | |||
| 54d7e612b6 | |||
| b58b01cdb1 | |||
| 2c2981d791 | |||
| 8e9224cff9 | |||
| e801a45066 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ task/AzureFederatedAuth/dist/
|
||||
build/
|
||||
AGENTS.md
|
||||
.DS_Store
|
||||
.env
|
||||
|
||||
@@ -21,6 +21,8 @@ For administrator-facing installation and usage guidance, see `overview.md`.
|
||||
- Downloads a blob to a local file path using the selected AzureRM service connection.
|
||||
- `PutBlob@1`
|
||||
- Uploads a local file as a blob using the selected AzureRM service connection.
|
||||
- `SetupGitHubRelease@1`
|
||||
- Downloads and installs a binary from the latest GitHub release and prepends it to PATH.
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -29,6 +31,7 @@ For administrator-facing installation and usage guidance, see `overview.md`.
|
||||
- `task/ListBlobs` - task implementation and manifest
|
||||
- `task/GetBlob` - task implementation and manifest
|
||||
- `task/PutBlob` - task implementation and manifest
|
||||
- `task/SetupGitHubRelease` - task implementation and manifest
|
||||
- `shared` - local npm package with shared OIDC/devops/blob helpers
|
||||
- `scripts/build.sh` - builds tasks and packages the extension
|
||||
- `examples/azure-pipelines-smoke.yml` - smoke pipeline example
|
||||
@@ -77,6 +80,10 @@ Publishing requires a Visual Studio Marketplace publisher and sharing the publis
|
||||
|
||||
Slawomir Koszewski
|
||||
|
||||
## AI Generated Content
|
||||
|
||||
Parts of this repository were generated with the assistance of AI. The author has reviewed and edited the AI-generated content to ensure accuracy and clarity.
|
||||
|
||||
## License
|
||||
|
||||
MIT. See `LICENSE`.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
@@ -11,6 +11,7 @@ Azure DevOps extension with pipeline tasks for federated auth and blob/state ope
|
||||
- `ListBlobs@1` - lists blobs in a container (optional prefix) using the selected AzureRM service connection.
|
||||
- `GetBlob@1` - downloads a blob to a local file path using the selected AzureRM service connection.
|
||||
- `PutBlob@1` - uploads a local file as a blob using the selected AzureRM service connection.
|
||||
- `SetupGitHubRelease@1` - downloads and installs a binary from the latest GitHub release and prepends it to PATH.
|
||||
|
||||
Implementation note: task shared helpers are packaged locally during build and bundled with the extension (no external package registry access required at runtime).
|
||||
|
||||
@@ -45,6 +46,10 @@ https://github.com/skoszewski/ado-sk-toolkit-extension.git
|
||||
|
||||
Slawomir Koszewski
|
||||
|
||||
## AI Generated Content
|
||||
|
||||
Parts of this extension's source code were generated with the assistance of AI. The author has reviewed and edited the AI-generated content to ensure accuracy and clarity.
|
||||
|
||||
## License
|
||||
|
||||
MIT. See `LICENSE`.
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 3 ]]; then
|
||||
echo "Usage: $0 <vsix-path> <publisher-id> <org1> [org2] [org3] ..."
|
||||
echo "Requires environment variable AZDO_PAT to be set."
|
||||
exit 1
|
||||
fi
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
set -a
|
||||
source "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
|
||||
if [[ -z "${AZDO_PAT:-}" ]]; then
|
||||
echo "AZDO_PAT is not set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VSIX_PATH="$1"
|
||||
PUBLISHER_ID="$2"
|
||||
shift 2
|
||||
VSIX_PATH="$SCRIPT_DIR/build/${PUBLISHER_ID}.${EXTENSION_ID}-${EXTENSION_VERSION}.vsix"
|
||||
|
||||
if [[ ! -f "$VSIX_PATH" ]]; then
|
||||
echo "VSIX file not found at path: $VSIX_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for ORG in "$@"; do
|
||||
echo "Publishing to organization: $ORG"
|
||||
npx tfx-cli extension publish \
|
||||
tfx extension publish \
|
||||
--vsix "$VSIX_PATH" \
|
||||
--publisher "$PUBLISHER_ID" \
|
||||
--token "$AZDO_PAT" \
|
||||
--share-with "$ORG"
|
||||
done
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
import {
|
||||
buildOidcUrl,
|
||||
exchangeOidcForScopedToken,
|
||||
getServiceConnectionMetadata,
|
||||
requestOidcToken
|
||||
} from './oidc';
|
||||
import { requireVariable } from './devops-helpers';
|
||||
|
||||
export const STORAGE_SCOPE = 'https://storage.azure.com/.default';
|
||||
|
||||
export async function requestStorageAccessToken(
|
||||
endpointId: string
|
||||
): Promise<string> {
|
||||
const oidcBaseUrl = requireVariable('System.OidcRequestUri');
|
||||
const systemAccessToken = requireVariable('System.AccessToken');
|
||||
const oidcBaseUrl = tl.getVariable('System.OidcRequestUri');
|
||||
const systemAccessToken = tl.getVariable('System.AccessToken');
|
||||
|
||||
if (oidcBaseUrl === undefined) {
|
||||
throw new Error('Missing required pipeline variable: System.OidcRequestUri.');
|
||||
}
|
||||
|
||||
if (systemAccessToken === undefined) {
|
||||
throw new Error('Missing required pipeline variable: System.AccessToken.');
|
||||
}
|
||||
|
||||
const metadata = getServiceConnectionMetadata(endpointId);
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
type TaskLibBridge = {
|
||||
getInput: (name: string, required?: boolean) => string | undefined;
|
||||
getVariable: (name: string) => string | undefined;
|
||||
};
|
||||
|
||||
function getTaskLibBridge(): TaskLibBridge {
|
||||
return require('azure-pipelines-task-lib/task') as TaskLibBridge;
|
||||
}
|
||||
|
||||
export function requireInput(name: string): string {
|
||||
const taskLib = getTaskLibBridge();
|
||||
const value = taskLib.getInput(name, true);
|
||||
if (!value) {
|
||||
throw new Error(`Task input ${name} is required.`);
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export function requireVariable(name: string): string {
|
||||
const taskLib = getTaskLibBridge();
|
||||
const value = taskLib.getVariable(name);
|
||||
if (!value) {
|
||||
throw new Error(`Missing required pipeline variable: ${name}.`);
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './devops-helpers';
|
||||
export * from './oidc';
|
||||
export * from './blob';
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
|
||||
export type ServiceConnectionMetadata = {
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
@@ -11,40 +13,25 @@ export type TokenResponse = {
|
||||
|
||||
export const CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
|
||||
|
||||
type TaskLibEndpointBridge = {
|
||||
getEndpointAuthorizationParameter: (
|
||||
endpointId: string,
|
||||
key: string,
|
||||
optional: boolean
|
||||
) => string | undefined;
|
||||
getEndpointDataParameter: (endpointId: string, key: string, optional: boolean) => string | undefined;
|
||||
};
|
||||
|
||||
type OidcResponse = {
|
||||
oidcToken?: string;
|
||||
};
|
||||
|
||||
function getTaskLibEndpointBridge(): TaskLibEndpointBridge {
|
||||
return require('azure-pipelines-task-lib/task') as TaskLibEndpointBridge;
|
||||
}
|
||||
|
||||
export function getServiceConnectionMetadata(endpointId: string): ServiceConnectionMetadata {
|
||||
const taskLib = getTaskLibEndpointBridge();
|
||||
|
||||
const tenantId =
|
||||
taskLib.getEndpointAuthorizationParameter(endpointId, 'tenantid', true) ||
|
||||
taskLib.getEndpointDataParameter(endpointId, 'tenantid', true);
|
||||
tl.getEndpointAuthorizationParameter(endpointId, 'tenantid', true) ||
|
||||
tl.getEndpointDataParameter(endpointId, 'tenantid', true);
|
||||
|
||||
const clientId =
|
||||
taskLib.getEndpointAuthorizationParameter(endpointId, 'serviceprincipalid', true) ||
|
||||
taskLib.getEndpointAuthorizationParameter(endpointId, 'clientid', true) ||
|
||||
taskLib.getEndpointDataParameter(endpointId, 'serviceprincipalid', true);
|
||||
tl.getEndpointAuthorizationParameter(endpointId, 'serviceprincipalid', true) ||
|
||||
tl.getEndpointAuthorizationParameter(endpointId, 'clientid', true) ||
|
||||
tl.getEndpointDataParameter(endpointId, 'serviceprincipalid', true);
|
||||
|
||||
if (!tenantId) {
|
||||
if (tenantId === undefined) {
|
||||
throw new Error('Could not resolve tenant ID from the selected AzureRM service connection.');
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
if (clientId === undefined) {
|
||||
throw new Error('Could not resolve client ID from the selected AzureRM service connection.');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "azure-federated-auth-task",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,21 +4,27 @@ import {
|
||||
buildOidcUrl,
|
||||
exchangeOidcForScopedToken,
|
||||
getServiceConnectionMetadata,
|
||||
requestOidcToken,
|
||||
requireInput,
|
||||
requireVariable
|
||||
requestOidcToken
|
||||
} from '@skoszewski/ado-sk-toolkit-shared';
|
||||
|
||||
const AZDO_APP_SCOPE = '499b84ac-1321-427f-aa17-267ca6975798/.default';
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const endpointId = requireInput('serviceConnectionARM');
|
||||
const endpointId = tl.getInputRequired('serviceConnectionARM');
|
||||
const setGitAccessToken = tl.getBoolInput('setGitAccessToken', false);
|
||||
const printTokenHashes = tl.getBoolInput('printTokenHashes', false);
|
||||
|
||||
const oidcBaseUrl = requireVariable('System.OidcRequestUri');
|
||||
const accessToken = requireVariable('System.AccessToken');
|
||||
const oidcBaseUrl = tl.getVariable('System.OidcRequestUri');
|
||||
const accessToken = tl.getVariable('System.AccessToken');
|
||||
|
||||
if (oidcBaseUrl === undefined) {
|
||||
throw new Error('Missing required pipeline variable: System.OidcRequestUri.');
|
||||
}
|
||||
|
||||
if (accessToken === undefined) {
|
||||
throw new Error('Missing required pipeline variable: System.AccessToken.');
|
||||
}
|
||||
|
||||
console.log('Requesting OIDC token for ARM authentication...');
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 9
|
||||
"Patch": 10
|
||||
},
|
||||
"instanceNameFormat": "Configure federated auth: $(serviceConnectionARM)",
|
||||
"inputs": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "copy-blob-task",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
import {
|
||||
buildBlobUrl,
|
||||
requestStorageAccessToken,
|
||||
requireInput
|
||||
requestStorageAccessToken
|
||||
} from '@skoszewski/ado-sk-toolkit-shared';
|
||||
|
||||
async function copyBlob(
|
||||
@@ -38,12 +37,12 @@ async function copyBlob(
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const endpointId = requireInput('serviceConnectionARM');
|
||||
const srcStorageAccountName = requireInput('srcStorageAccountName');
|
||||
const dstStorageAccountName = requireInput('dstStorageAccountName');
|
||||
const srcContainerName = requireInput('srcContainerName');
|
||||
const dstContainerNameInput = tl.getInput('dstContainerName', false)?.trim() || '';
|
||||
const blobName = requireInput('blobName');
|
||||
const endpointId = tl.getInputRequired('serviceConnectionARM');
|
||||
const srcStorageAccountName = tl.getInputRequired('srcStorageAccountName');
|
||||
const dstStorageAccountName = tl.getInputRequired('dstStorageAccountName');
|
||||
const srcContainerName = tl.getInputRequired('srcContainerName');
|
||||
const dstContainerNameInput = tl.getInput('dstContainerName', false) || '';
|
||||
const blobName = tl.getInputRequired('blobName');
|
||||
|
||||
console.log('Requesting storage access token from Microsoft Entra ID...');
|
||||
const accessToken = await requestStorageAccessToken(endpointId);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 2
|
||||
"Patch": 3
|
||||
},
|
||||
"instanceNameFormat": "Copy blob: $(srcStorageAccountName)/$(srcContainerName)/$(blobName)",
|
||||
"inputs": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "get-blob-task",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -3,8 +3,7 @@ import * as path from 'node:path';
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
import {
|
||||
buildBlobUrl,
|
||||
requestStorageAccessToken,
|
||||
requireInput
|
||||
requestStorageAccessToken
|
||||
} from '@skoszewski/ado-sk-toolkit-shared';
|
||||
|
||||
async function downloadBlob(blobUrl: string, bearerToken: string): Promise<Buffer> {
|
||||
@@ -28,11 +27,11 @@ async function downloadBlob(blobUrl: string, bearerToken: string): Promise<Buffe
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const endpointId = requireInput('serviceConnectionARM');
|
||||
const storageAccountName = requireInput('storageAccountName');
|
||||
const containerName = requireInput('containerName');
|
||||
const blobName = requireInput('blobName');
|
||||
const destinationPath = requireInput('destinationPath');
|
||||
const endpointId = tl.getInputRequired('serviceConnectionARM');
|
||||
const storageAccountName = tl.getInputRequired('storageAccountName');
|
||||
const containerName = tl.getInputRequired('containerName');
|
||||
const blobName = tl.getInputRequired('blobName');
|
||||
const destinationPath = tl.getInputRequired('destinationPath');
|
||||
|
||||
console.log('Requesting storage access token from Microsoft Entra ID...');
|
||||
const accessToken = await requestStorageAccessToken(endpointId);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 0
|
||||
"Patch": 1
|
||||
},
|
||||
"instanceNameFormat": "Get blob: $(storageAccountName)/$(containerName)/$(blobName)",
|
||||
"inputs": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "list-blobs-task",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
import {
|
||||
requestStorageAccessToken,
|
||||
requireInput
|
||||
requestStorageAccessToken
|
||||
} from '@skoszewski/ado-sk-toolkit-shared';
|
||||
|
||||
function decodeXmlValue(value: string): string {
|
||||
@@ -60,11 +59,11 @@ async function listBlobs(listUrl: string, bearerToken: string): Promise<string[]
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const endpointId = requireInput('serviceConnectionARM');
|
||||
const storageAccountName = requireInput('storageAccountName');
|
||||
const containerName = requireInput('containerName');
|
||||
const prefix = tl.getInput('prefix', false)?.trim() || '';
|
||||
const maxResultsRaw = tl.getInput('maxResults', false)?.trim() || '1000';
|
||||
const endpointId = tl.getInputRequired('serviceConnectionARM');
|
||||
const storageAccountName = tl.getInputRequired('storageAccountName');
|
||||
const containerName = tl.getInputRequired('containerName');
|
||||
const prefix = tl.getInput('prefix', false) || '';
|
||||
const maxResultsRaw = tl.getInput('maxResults', false) || '1000';
|
||||
const maxResults = Number.parseInt(maxResultsRaw, 10);
|
||||
|
||||
if (!Number.isInteger(maxResults) || maxResults <= 0) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 1
|
||||
"Patch": 2
|
||||
},
|
||||
"instanceNameFormat": "List blobs: $(storageAccountName)/$(containerName)",
|
||||
"inputs": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "put-blob-task",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,8 +2,7 @@ import * as fs from 'node:fs/promises';
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
import {
|
||||
buildBlobUrl,
|
||||
requestStorageAccessToken,
|
||||
requireInput
|
||||
requestStorageAccessToken
|
||||
} from '@skoszewski/ado-sk-toolkit-shared';
|
||||
|
||||
async function uploadBlob(
|
||||
@@ -35,12 +34,12 @@ async function uploadBlob(
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const endpointId = requireInput('serviceConnectionARM');
|
||||
const storageAccountName = requireInput('storageAccountName');
|
||||
const containerName = requireInput('containerName');
|
||||
const blobName = requireInput('blobName');
|
||||
const sourcePath = requireInput('sourcePath');
|
||||
const contentType = tl.getInput('contentType', false)?.trim() || 'application/octet-stream';
|
||||
const endpointId = tl.getInputRequired('serviceConnectionARM');
|
||||
const storageAccountName = tl.getInputRequired('storageAccountName');
|
||||
const containerName = tl.getInputRequired('containerName');
|
||||
const blobName = tl.getInputRequired('blobName');
|
||||
const sourcePath = tl.getInputRequired('sourcePath');
|
||||
const contentType = tl.getInput('contentType', false) || 'application/octet-stream';
|
||||
|
||||
console.log('Requesting storage access token from Microsoft Entra ID...');
|
||||
const accessToken = await requestStorageAccessToken(endpointId);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 0
|
||||
"Patch": 1
|
||||
},
|
||||
"instanceNameFormat": "Put blob: $(storageAccountName)/$(containerName)/$(blobName)",
|
||||
"inputs": [
|
||||
|
||||
680
task/SetupGitHubRelease/package-lock.json
generated
Normal file
680
task/SetupGitHubRelease/package-lock.json
generated
Normal file
@@ -0,0 +1,680 @@
|
||||
{
|
||||
"name": "setup-github-release-task",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "setup-github-release-task",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"azure-pipelines-task-lib": "^5.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.30",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
"run-parallel": "^1.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.stat": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.walk": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
"fastq": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
|
||||
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/azure-pipelines-task-lib": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-5.2.6.tgz",
|
||||
"integrity": "sha512-YWEJFAY+Imk5nWwPd5ao6h/J8BgW2Dtzt+M5QiUWDV5cB10n4zywQ5Dulj2OcO1B9tGfdV5KDKVnQRKfJ72YmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.10",
|
||||
"minimatch": "3.0.5",
|
||||
"nodejs-file-downloader": "^4.11.1",
|
||||
"q": "^1.5.1",
|
||||
"semver": "^5.7.2",
|
||||
"shelljs": "^0.10.0",
|
||||
"uuid": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.0",
|
||||
"human-signals": "^2.1.0",
|
||||
"is-stream": "^2.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^4.0.1",
|
||||
"onetime": "^5.1.2",
|
||||
"signal-exit": "^3.0.3",
|
||||
"strip-final-newline": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.2",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodejs-file-downloader": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/nodejs-file-downloader/-/nodejs-file-downloader-4.13.0.tgz",
|
||||
"integrity": "sha512-nI2fKnmJWWFZF6SgMPe1iBodKhfpztLKJTtCtNYGhm/9QXmWa/Pk9Sv00qHgzEvNLe1x7hjGDRor7gcm/ChaIQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"sanitize-filename": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
|
||||
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
"integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
|
||||
"deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.0",
|
||||
"teleport": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sanitize-filename": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
|
||||
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
|
||||
"license": "WTFPL OR ISC",
|
||||
"dependencies": {
|
||||
"truncate-utf8-bytes": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shelljs": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz",
|
||||
"integrity": "sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"execa": "^5.1.1",
|
||||
"fast-glob": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
|
||||
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
|
||||
"license": "WTFPL",
|
||||
"dependencies": {
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
|
||||
"license": "(WTFPL OR MIT)"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
task/SetupGitHubRelease/package.json
Normal file
23
task/SetupGitHubRelease/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "setup-github-release-task",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"author": "Slawomir Koszewski",
|
||||
"license": "MIT",
|
||||
"description": "Azure DevOps task to download and install the latest release asset from GitHub.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"azure-pipelines-task-lib": "^5.2.6"
|
||||
},
|
||||
"overrides": {
|
||||
"minimatch": "3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.30",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
430
task/SetupGitHubRelease/src/index.ts
Normal file
430
task/SetupGitHubRelease/src/index.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as fsp from 'node:fs/promises';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import * as tl from 'azure-pipelines-task-lib/task';
|
||||
|
||||
type ReleaseAsset = {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
};
|
||||
|
||||
type ReleaseInfo = {
|
||||
tag_name: string;
|
||||
assets: ReleaseAsset[];
|
||||
};
|
||||
|
||||
type PlatformInfo = {
|
||||
system: string;
|
||||
arch: string;
|
||||
systemPattern: string;
|
||||
archPattern: string;
|
||||
};
|
||||
|
||||
type MatchOptions = {
|
||||
fileName?: string;
|
||||
fileType?: string;
|
||||
};
|
||||
|
||||
const systemPatterns: Record<string, string> = {
|
||||
linux: 'linux',
|
||||
darwin: '(darwin|macos|mac|osx)',
|
||||
win32: '(windows|win)'
|
||||
};
|
||||
|
||||
const archPatterns: Record<string, string> = {
|
||||
x64: '(x86_64|x64|amd64)',
|
||||
arm64: '(aarch64|arm64)'
|
||||
};
|
||||
|
||||
function getPlatformInfo(): PlatformInfo {
|
||||
const system = os.platform();
|
||||
const arch = os.arch();
|
||||
|
||||
return {
|
||||
system,
|
||||
arch,
|
||||
systemPattern: systemPatterns[system] || system,
|
||||
archPattern: archPatterns[arch] || arch
|
||||
};
|
||||
}
|
||||
|
||||
function getExtensionPattern(fileType: string): string {
|
||||
if (fileType === 'archive') {
|
||||
return '\\.(zip|tar\\.gz|tar|tgz|7z)';
|
||||
}
|
||||
|
||||
if (fileType === 'package') {
|
||||
return '\\.(deb|rpm|pkg)';
|
||||
}
|
||||
|
||||
return fileType;
|
||||
}
|
||||
|
||||
function getMatchingAsset(assets: ReleaseAsset[], platform: PlatformInfo, options: MatchOptions): ReleaseAsset {
|
||||
const fileName = options.fileName;
|
||||
const extPattern = getExtensionPattern(options.fileType || 'archive');
|
||||
|
||||
if (!fileName) {
|
||||
const pattern = `${platform.systemPattern}[_-]${platform.archPattern}.*${extPattern}$`;
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
const matches = assets.filter((asset) => regex.test(asset.name));
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`No assets matched the default criteria: ${pattern}`);
|
||||
}
|
||||
|
||||
if (matches.length > 1) {
|
||||
throw new Error(`Multiple assets matched the default criteria: ${matches.map((asset) => asset.name).join(', ')}`);
|
||||
}
|
||||
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
if (fileName.startsWith('~')) {
|
||||
let pattern = fileName.substring(1);
|
||||
const hasSystem = pattern.includes('{{SYSTEM}}');
|
||||
const hasArch = pattern.includes('{{ARCH}}');
|
||||
const hasExt = pattern.includes('{{EXT_PATTERN}}');
|
||||
const hasEnd = pattern.endsWith('$');
|
||||
|
||||
if (!hasSystem && !hasArch && !hasExt && !hasEnd) {
|
||||
pattern += '.*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$';
|
||||
} else if (hasSystem && hasArch && !hasExt && !hasEnd) {
|
||||
pattern += '.*{{EXT_PATTERN}}$';
|
||||
}
|
||||
|
||||
const finalPattern = pattern
|
||||
.replace(/{{SYSTEM}}/g, platform.systemPattern)
|
||||
.replace(/{{ARCH}}/g, platform.archPattern)
|
||||
.replace(/{{EXT_PATTERN}}/g, extPattern);
|
||||
|
||||
const regex = new RegExp(finalPattern, 'i');
|
||||
const matches = assets.filter((asset) => regex.test(asset.name));
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`No assets matched the regex: ${finalPattern}`);
|
||||
}
|
||||
|
||||
if (matches.length > 1) {
|
||||
throw new Error(`Multiple assets matched the criteria: ${matches.map((asset) => asset.name).join(', ')}`);
|
||||
}
|
||||
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
const exact = assets.find((asset) => asset.name === fileName);
|
||||
if (!exact) {
|
||||
throw new Error(`No asset found matching the exact name: ${fileName}`);
|
||||
}
|
||||
|
||||
return exact;
|
||||
}
|
||||
|
||||
async function fetchLatestRelease(repository: string, token?: string): Promise<ReleaseInfo> {
|
||||
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'setup-github-release-ado-task'
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`Failed to fetch latest release for ${repository}: ${response.status} ${response.statusText}. ${body}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as ReleaseInfo;
|
||||
}
|
||||
|
||||
async function downloadAsset(url: string, destinationPath: string, token?: string): Promise<void> {
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'setup-github-release-ado-task'
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`Failed to download asset from ${url}: ${response.status} ${response.statusText}. ${body}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
await fsp.writeFile(destinationPath, Buffer.from(arrayBuffer));
|
||||
}
|
||||
|
||||
async function extractAsset(filePath: string, destinationDirectory: string): Promise<void> {
|
||||
const lowerName = path.basename(filePath).toLowerCase();
|
||||
|
||||
await fsp.mkdir(destinationDirectory, { recursive: true });
|
||||
|
||||
if (lowerName.endsWith('.tar.gz') || lowerName.endsWith('.tgz') || lowerName.endsWith('.tar')) {
|
||||
const result = spawnSync('tar', ['-xf', filePath, '-C', destinationDirectory]);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`tar failed with status ${result.status}: ${result.stderr.toString()}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lowerName.endsWith('.zip')) {
|
||||
if (process.platform === 'win32') {
|
||||
const tarResult = spawnSync('tar', ['-xf', filePath, '-C', destinationDirectory]);
|
||||
if (tarResult.status === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const escapedFilePath = filePath.replace(/'/g, "''");
|
||||
const escapedDestinationDirectory = destinationDirectory.replace(/'/g, "''");
|
||||
const command = `Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFilePath}', '${escapedDestinationDirectory}')`;
|
||||
|
||||
for (const shell of ['pwsh', 'powershell']) {
|
||||
const result = spawnSync(shell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command]);
|
||||
if (result.status === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('ZIP extraction failed on Windows.');
|
||||
}
|
||||
|
||||
const unzipResult = spawnSync('unzip', ['-q', filePath, '-d', destinationDirectory]);
|
||||
if (unzipResult.status !== 0) {
|
||||
throw new Error(`unzip failed with status ${unzipResult.status}: ${unzipResult.stderr.toString()}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lowerName.endsWith('.7z')) {
|
||||
const sevenZipResult = spawnSync('7z', ['x', filePath, `-o${destinationDirectory}`, '-y']);
|
||||
if (sevenZipResult.status !== 0) {
|
||||
throw new Error(`7z failed with status ${sevenZipResult.status}: ${sevenZipResult.stderr.toString()}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lowerName.endsWith('.pkg') || lowerName.endsWith('.xar')) {
|
||||
const xarResult = spawnSync('xar', ['-xf', filePath], { cwd: destinationDirectory });
|
||||
if (xarResult.status !== 0) {
|
||||
throw new Error(`xar failed with status ${xarResult.status}: ${xarResult.stderr.toString()}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const destinationPath = path.join(destinationDirectory, path.basename(filePath));
|
||||
await fsp.copyFile(filePath, destinationPath);
|
||||
}
|
||||
|
||||
function findBinary(directory: string, pattern: string | RegExp, debug: boolean): string | undefined {
|
||||
const items = fs.readdirSync(directory);
|
||||
|
||||
if (debug) {
|
||||
tl.debug(`Searching for binary in ${directory}`);
|
||||
for (const item of items) {
|
||||
tl.debug(`- ${item}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(directory, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
const nested = findBinary(fullPath, pattern, debug);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let match = false;
|
||||
if (pattern instanceof RegExp) {
|
||||
match = pattern.test(item);
|
||||
} else {
|
||||
match = item === pattern;
|
||||
if (!match && process.platform === 'win32' && !pattern.toLowerCase().endsWith('.exe')) {
|
||||
match = item.toLowerCase() === `${pattern.toLowerCase()}.exe`;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function copyDirectory(sourceDirectory: string, destinationDirectory: string): Promise<void> {
|
||||
await fsp.mkdir(destinationDirectory, { recursive: true });
|
||||
const entries = await fsp.readdir(sourceDirectory, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const sourcePath = path.join(sourceDirectory, entry.name);
|
||||
const destinationPath = path.join(destinationDirectory, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await copyDirectory(sourcePath, destinationPath);
|
||||
} else if (entry.isSymbolicLink()) {
|
||||
const linkTarget = await fsp.readlink(sourcePath);
|
||||
await fsp.symlink(linkTarget, destinationPath);
|
||||
} else {
|
||||
await fsp.copyFile(sourcePath, destinationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getToolsRoot(): string {
|
||||
const toolsDirectory = tl.getVariable('Agent.ToolsDirectory');
|
||||
if (toolsDirectory !== undefined) {
|
||||
return toolsDirectory;
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.ado-sk-tools');
|
||||
}
|
||||
|
||||
async function findAnyCachedVersion(toolName: string, arch: string): Promise<{ version: string; toolDirectory: string } | undefined> {
|
||||
const archRoot = path.join(getToolsRoot(), toolName);
|
||||
if (!fs.existsSync(archRoot)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const entries = await fsp.readdir(archRoot, { withFileTypes: true });
|
||||
const versions = entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
|
||||
for (let index = versions.length - 1; index >= 0; index -= 1) {
|
||||
const version = versions[index];
|
||||
const candidate = path.join(archRoot, version, arch);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return {
|
||||
version,
|
||||
toolDirectory: candidate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSpecificCacheDirectory(toolName: string, version: string, arch: string): string {
|
||||
return path.join(getToolsRoot(), toolName, version, arch);
|
||||
}
|
||||
|
||||
async function cacheTool(sourceDirectory: string, toolName: string, version: string, arch: string): Promise<string> {
|
||||
const destinationDirectory = getSpecificCacheDirectory(toolName, version, arch);
|
||||
await fsp.rm(destinationDirectory, { recursive: true, force: true });
|
||||
await copyDirectory(sourceDirectory, destinationDirectory);
|
||||
return destinationDirectory;
|
||||
}
|
||||
|
||||
function setExecutable(filePath: string): void {
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(filePath, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const repository = tl.getInputRequired('repository');
|
||||
const fileNameInput = tl.getInput('fileName', false) || '';
|
||||
const binaryInput = tl.getInput('binaryName', false) || '';
|
||||
const fileType = tl.getInput('fileType', false) || 'archive';
|
||||
const updateCache = (tl.getInput('updateCache', false) || 'false').toLowerCase();
|
||||
const debug = tl.getBoolInput('debug', false);
|
||||
const token = tl.getInput('token', false) || process.env.GITHUB_TOKEN;
|
||||
|
||||
if (!/^[^/\s]+\/[^/\s]+$/.test(repository)) {
|
||||
throw new Error('Input repository must be in owner/repo format.');
|
||||
}
|
||||
|
||||
if (!['false', 'true', 'always'].includes(updateCache)) {
|
||||
throw new Error('Input updateCache must be one of: false, true, always.');
|
||||
}
|
||||
|
||||
const platformInfo = getPlatformInfo();
|
||||
const toolName = repository.split('/').pop() || repository;
|
||||
|
||||
if (updateCache === 'false') {
|
||||
const cached = await findAnyCachedVersion(toolName, platformInfo.arch);
|
||||
if (cached) {
|
||||
tl.debug(`Using cached ${toolName} version ${cached.version}`);
|
||||
tl.prependPath(cached.toolDirectory);
|
||||
tl.setResult(tl.TaskResult.Succeeded, `Using cached ${toolName} version ${cached.version}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tl.debug(`Fetching latest release for ${repository}`);
|
||||
const release = await fetchLatestRelease(repository, token);
|
||||
const asset = getMatchingAsset(release.assets, platformInfo, {
|
||||
fileName: fileNameInput,
|
||||
fileType
|
||||
});
|
||||
|
||||
const version = release.tag_name.replace(/^v/, '');
|
||||
const binaryName = binaryInput || toolName;
|
||||
|
||||
if (updateCache !== 'always') {
|
||||
const cachedDirectory = getSpecificCacheDirectory(toolName, version, platformInfo.arch);
|
||||
if (fs.existsSync(cachedDirectory)) {
|
||||
tl.debug(`Using cached ${toolName} version ${version}`);
|
||||
tl.prependPath(cachedDirectory);
|
||||
tl.setResult(tl.TaskResult.Succeeded, `Using cached ${toolName} version ${version}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'setup-github-release-'));
|
||||
const downloadPath = path.join(tempRoot, asset.name);
|
||||
|
||||
tl.debug(`Downloading asset ${asset.name}`);
|
||||
await downloadAsset(asset.browser_download_url, downloadPath, token);
|
||||
|
||||
let extractionRoot = path.join(tempRoot, 'extract');
|
||||
const lowerName = asset.name.toLowerCase();
|
||||
|
||||
if (
|
||||
/\.(tar\.gz|tar|tgz)$/i.test(lowerName) ||
|
||||
/\.zip$/i.test(lowerName) ||
|
||||
/\.7z$/i.test(lowerName) ||
|
||||
/\.(xar|pkg)$/i.test(lowerName)
|
||||
) {
|
||||
await extractAsset(downloadPath, extractionRoot);
|
||||
} else {
|
||||
extractionRoot = path.join(tempRoot, 'bin');
|
||||
await fsp.mkdir(extractionRoot, { recursive: true });
|
||||
const destinationPath = path.join(extractionRoot, asset.name);
|
||||
await fsp.rename(downloadPath, destinationPath);
|
||||
setExecutable(destinationPath);
|
||||
}
|
||||
|
||||
const binaryPattern = binaryName.startsWith('~')
|
||||
? new RegExp(binaryName.substring(1), 'i')
|
||||
: binaryName;
|
||||
|
||||
const binaryPath = findBinary(extractionRoot, binaryPattern, debug);
|
||||
if (!binaryPath) {
|
||||
throw new Error(`Could not find binary ${binaryName} in extracted asset.`);
|
||||
}
|
||||
|
||||
setExecutable(binaryPath);
|
||||
|
||||
const binaryDirectory = path.dirname(binaryPath);
|
||||
const cachedDirectory = await cacheTool(binaryDirectory, toolName, version, platformInfo.arch);
|
||||
|
||||
tl.prependPath(cachedDirectory);
|
||||
tl.setResult(tl.TaskResult.Succeeded, `Installed ${toolName} ${version} from ${repository}.`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
tl.setResult(tl.TaskResult.Failed, message);
|
||||
}
|
||||
}
|
||||
|
||||
void run();
|
||||
85
task/SetupGitHubRelease/task.json
Normal file
85
task/SetupGitHubRelease/task.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
|
||||
"id": "950311ab-f037-4f4f-a875-8a4251e2dbd5",
|
||||
"name": "SetupGitHubRelease",
|
||||
"friendlyName": "Setup GitHub Release",
|
||||
"description": "Downloads and installs a binary from the latest GitHub release and adds it to PATH.",
|
||||
"helpMarkDown": "Matches release assets by platform and architecture, downloads, extracts, locates the binary, caches it, and prepends its directory to PATH.",
|
||||
"category": "Utility",
|
||||
"author": "Slawomir Koszewski",
|
||||
"version": {
|
||||
"Major": 1,
|
||||
"Minor": 0,
|
||||
"Patch": 0
|
||||
},
|
||||
"instanceNameFormat": "Setup GitHub release from $(repository)",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "repository",
|
||||
"type": "string",
|
||||
"label": "Repository",
|
||||
"defaultValue": "",
|
||||
"required": true,
|
||||
"helpMarkDown": "GitHub repository in owner/repo format."
|
||||
},
|
||||
{
|
||||
"name": "fileName",
|
||||
"type": "string",
|
||||
"label": "File Name",
|
||||
"defaultValue": "",
|
||||
"required": false,
|
||||
"helpMarkDown": "Asset name or regex prefixed with ~."
|
||||
},
|
||||
{
|
||||
"name": "binaryName",
|
||||
"type": "string",
|
||||
"label": "Binary Name",
|
||||
"defaultValue": "",
|
||||
"required": false,
|
||||
"helpMarkDown": "Binary name or regex prefixed with ~. Defaults to repository name."
|
||||
},
|
||||
{
|
||||
"name": "fileType",
|
||||
"type": "string",
|
||||
"label": "File Type",
|
||||
"defaultValue": "archive",
|
||||
"required": false,
|
||||
"helpMarkDown": "archive, package, or custom regex pattern for file extension matching."
|
||||
},
|
||||
{
|
||||
"name": "updateCache",
|
||||
"type": "pickList",
|
||||
"label": "Update Cache",
|
||||
"defaultValue": "false",
|
||||
"required": false,
|
||||
"helpMarkDown": "false: use any cached version. true: use current latest release and cache if needed. always: always download latest and refresh cache.",
|
||||
"options": {
|
||||
"false": "false",
|
||||
"true": "true",
|
||||
"always": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"type": "boolean",
|
||||
"label": "Debug",
|
||||
"defaultValue": "false",
|
||||
"required": false,
|
||||
"helpMarkDown": "Logs discovered files while searching for the binary."
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "string",
|
||||
"label": "GitHub Token",
|
||||
"defaultValue": "",
|
||||
"required": false,
|
||||
"helpMarkDown": "Optional GitHub token for private repos or higher API limits. If empty, uses GITHUB_TOKEN if available."
|
||||
}
|
||||
],
|
||||
"execution": {
|
||||
"Node20_1": {
|
||||
"target": "dist/SetupGitHubRelease/src/index.js"
|
||||
}
|
||||
},
|
||||
"minimumAgentVersion": "3.225.0"
|
||||
}
|
||||
14
task/SetupGitHubRelease/tsconfig.json
Normal file
14
task/SetupGitHubRelease/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".."
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifestVersion": 1,
|
||||
"id": "sk-azure-devops-toolkit",
|
||||
"name": "SK Azure DevOps Toolkit",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.1",
|
||||
"publisher": "skoszewski-lab",
|
||||
"targets": [
|
||||
{
|
||||
@@ -38,6 +38,9 @@
|
||||
{
|
||||
"path": "task/PutBlob"
|
||||
},
|
||||
{
|
||||
"path": "task/SetupGitHubRelease"
|
||||
},
|
||||
{
|
||||
"path": "images",
|
||||
"addressable": true
|
||||
@@ -97,6 +100,16 @@
|
||||
"properties": {
|
||||
"name": "task/PutBlob"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "289ace53-1fe2-45c7-abf2-a8e8fb57552d",
|
||||
"type": "ms.vss-distributed-task.task",
|
||||
"targets": [
|
||||
"ms.vss-distributed-task.tasks"
|
||||
],
|
||||
"properties": {
|
||||
"name": "task/SetupGitHubRelease"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user