From 4f3bb311c67292b5b99d54b6787658fd63999872 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Thu, 31 Jul 2025 13:56:31 +1000 Subject: [PATCH 1/7] feat: update to node20 --- .github/workflows/build.yml | 6 +++--- README.md | 4 +++- package.json | 8 ++++---- source/tasks/AwaitTask/AwaitTaskV6/task.json | 6 +++--- .../tasks/BuildInformation/BuildInformationV6/task.json | 6 +++--- .../CreateOctopusRelease/CreateOctopusReleaseV6/task.json | 6 +++--- source/tasks/Deploy/DeployV6/task.json | 6 +++--- source/tasks/DeployTenant/TenantedDeployV6/task.json | 6 +++--- source/tasks/OctoInstaller/OctoInstallerV6/task.json | 6 +++--- source/tasks/PackNuGet/PackNuGetV6/task.json | 6 +++--- source/tasks/PackZip/PackZipV6/task.json | 6 +++--- source/tasks/Push/PushV6/task.json | 6 +++--- source/tasks/RunRunbook/RunRunbookV6/task.json | 6 +++--- .../CreateOctopusRelease/CreateOctopusReleaseV3/index.ts | 4 ---- .../CreateOctopusRelease/CreateOctopusReleaseV4/index.ts | 7 ------- .../OctopusMetadata/OctopusMetadataV4/index.ts | 3 --- source/tasksLegacy/Promote/PromoteV3/index.ts | 3 --- source/tasksLegacy/Promote/PromoteV4/index.ts | 4 ---- source/tasksLegacy/Push/PushV3/index.ts | 2 -- source/tasksLegacy/Push/PushV4/index.ts | 2 -- source/tasksLegacy/Utils/tool.ts | 6 +++--- 21 files changed, 43 insertions(+), 66 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b2b8ad3..9e8c5afe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ on: required: true env: - PACKAGE_VERSION: 6.0.${{ github.run_number }} + PACKAGE_VERSION: 6.1.${{ github.run_number }} OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }} OCTOPUS_API_KEY: ${{ secrets.INTEGRATIONS_API_KEY }} @@ -66,7 +66,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "20" cache: "npm" - name: Test @@ -96,7 +96,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "20" cache: "npm" - name: Install Go diff --git a/README.md b/README.md index 048b62de..8282f168 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ Microsoft TFS/ADO web extensions are powered by Node.js under the hood. Simply o ### Prerequisites -* Node.js 10.15.3 (LTS) (`choco install nodejs` or `brew install node@10` or [web](https://nodejs.org)) +* Node.js + * Pre-v6: 10.15.3 (LTS) (`choco install nodejs --version="10.15.3"` or `brew install node@10` or [web](https://nodejs.org)) + * v6: 20.19.4 (LTS) (`choco install nodejs --version="20.19.4"` or `brew install node@20` or [web](https://nodejs.org)) * NPM: 5.6.0+ (`npm install npm@latest -g`) * TFX (`npm install tfx-cli -g`) * Install golang (`choco install golang` or `brew install go` or [web](https://golang.org)) diff --git a/package.json b/package.json index d0f0fc40..335dec7b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@types/express": "^4.17.13", "@types/glob": "7.2.0", "@types/jest": "28.1.5", - "@types/node": "^18.0.4", + "@types/node": "^20.3.1", "@types/ramda": "0.28.15", "@types/test-console": "^2.0.0", "@types/uuid": "8.3.4", @@ -34,14 +34,14 @@ "test-console": "^2.0.0", "ts-jest": "28.0.5", "ts-node": "^10.9.0", - "typescript": "^4.7.4", + "typescript": "5.1.6", "yargs": "^17.5.1" }, "dependencies": { "@octopusdeploy/api-client": "^3.7.0", "azure-devops-node-api": "11.2.0", - "azure-pipelines-task-lib": "3.3.1", - "azure-pipelines-tool-lib": "1.3.2", + "azure-pipelines-task-lib": "^4.13.0", + "azure-pipelines-tool-lib": "^2.0.7", "command-line-args": "^5.2.1", "fp-ts": "1.19.5", "glob": "7.2.0", diff --git a/source/tasks/AwaitTask/AwaitTaskV6/task.json b/source/tasks/AwaitTask/AwaitTaskV6/task.json index 35cbf363..5ca6145c 100644 --- a/source/tasks/AwaitTask/AwaitTaskV6/task.json +++ b/source/tasks/AwaitTask/AwaitTaskV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -87,7 +87,7 @@ ], "instanceNameFormat": "Await Octopus Deploy Task", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/BuildInformation/BuildInformationV6/task.json b/source/tasks/BuildInformation/BuildInformationV6/task.json index 7b370cf7..6270c39f 100644 --- a/source/tasks/BuildInformation/BuildInformationV6/task.json +++ b/source/tasks/BuildInformation/BuildInformationV6/task.json @@ -11,11 +11,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -65,7 +65,7 @@ ], "instanceNameFormat": "Push Package Build Information to Octopus", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json index 312c8d72..d70bc41e 100644 --- a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [ ], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "versionControl", @@ -147,7 +147,7 @@ ], "instanceNameFormat": "Create Octopus Release", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/Deploy/DeployV6/task.json b/source/tasks/Deploy/DeployV6/task.json index 21643ec9..c3fff930 100644 --- a/source/tasks/Deploy/DeployV6/task.json +++ b/source/tasks/Deploy/DeployV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "advanced", @@ -99,7 +99,7 @@ ], "instanceNameFormat": "Deploy Octopus Release", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/DeployTenant/TenantedDeployV6/task.json b/source/tasks/DeployTenant/TenantedDeployV6/task.json index bbfdff08..1ef0cc60 100644 --- a/source/tasks/DeployTenant/TenantedDeployV6/task.json +++ b/source/tasks/DeployTenant/TenantedDeployV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "advanced", @@ -115,7 +115,7 @@ ], "instanceNameFormat": "Deploy Octopus Release Tenants", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/OctoInstaller/OctoInstallerV6/task.json b/source/tasks/OctoInstaller/OctoInstallerV6/task.json index b90d03d6..34f7d541 100644 --- a/source/tasks/OctoInstaller/OctoInstallerV6/task.json +++ b/source/tasks/OctoInstaller/OctoInstallerV6/task.json @@ -16,12 +16,12 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "satisfies": ["octopus"], "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "advanced", @@ -40,7 +40,7 @@ ], "instanceNameFormat": "Install Octopus CLI tool version $(version)", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/PackNuGet/PackNuGetV6/task.json b/source/tasks/PackNuGet/PackNuGetV6/task.json index 775bca67..25fc9632 100644 --- a/source/tasks/PackNuGet/PackNuGetV6/task.json +++ b/source/tasks/PackNuGet/PackNuGetV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "advanced", @@ -124,7 +124,7 @@ ], "instanceNameFormat": "Package NuGet $(PackageId)", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/PackZip/PackZipV6/task.json b/source/tasks/PackZip/PackZipV6/task.json index 0e2b0296..b87e2f87 100644 --- a/source/tasks/PackZip/PackZipV6/task.json +++ b/source/tasks/PackZip/PackZipV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "groups": [ { "name": "advanced", @@ -84,7 +84,7 @@ ], "instanceNameFormat": "Package Zip $(PackageId)", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/Push/PushV6/task.json b/source/tasks/Push/PushV6/task.json index 6957b352..219a1a7f 100644 --- a/source/tasks/Push/PushV6/task.json +++ b/source/tasks/Push/PushV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -58,7 +58,7 @@ ], "instanceNameFormat": "Push Packages to Octopus", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasks/RunRunbook/RunRunbookV6/task.json b/source/tasks/RunRunbook/RunRunbookV6/task.json index 93091225..66bab114 100644 --- a/source/tasks/RunRunbook/RunRunbookV6/task.json +++ b/source/tasks/RunRunbook/RunRunbookV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 0, + "Minor": 1, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "2.206.1", + "minimumAgentVersion": "3.232.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -104,7 +104,7 @@ ], "instanceNameFormat": "Run Octopus Runbook", "execution": { - "Node16": { + "Node20_1": { "target": "index.js" } } diff --git a/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/index.ts b/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/index.ts index ebab652b..53b579b6 100644 --- a/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/index.ts +++ b/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/index.ts @@ -40,12 +40,9 @@ async function run() { }, environmentVariables.defaultWorkingDirectory); const configure = [ - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), argumentEnquote("project", project), - // @ts-expect-error argumentIfSet(argumentEnquote, "releaseNumber", releaseNumber), - // @ts-expect-error argumentIfSet(argumentEnquote, "channel", channel), connectionArguments(octoConnection), flag("enableServiceMessages", true), @@ -54,7 +51,6 @@ async function run() { multiArgument(argumentEnquote, "tenant", deployForTenants), multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), argumentEnquote("releaseNotesFile", releaseNotesFile), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(octoConnection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/index.ts b/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/index.ts index b9f2fc0a..54e91070 100644 --- a/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/index.ts +++ b/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/index.ts @@ -32,20 +32,14 @@ async function run() { const octo = await getOrInstallOctoCommandRunner("create-release"); const configure = [ - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), - // @ts-expect-error argumentEnquote("project", project), - // @ts-expect-error argumentIfSet(argumentEnquote, "releaseNumber", releaseNumber), - // @ts-expect-error argumentIfSet(argumentEnquote, "channel", channel), connectionArguments(connection), flag("enableServiceMessages", true), multiArgument(argumentEnquote, "deployTo", deployToEnvironments), - // @ts-expect-error argumentIfSet(argumentEnquote, "gitRef", gitRef), - // @ts-expect-error argumentIfSet(argumentEnquote, "gitCommit", gitCommit), flag("progress", deployToEnvironments.length > 0 && deploymentProgress), multiArgument(argumentEnquote, "tenant", deployForTenants), @@ -63,7 +57,6 @@ async function run() { configure.push(argumentEnquote("releaseNotesFile", releaseNotesFile)); } - // @ts-expect-error configure.push(includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments)); const code: number = await octo diff --git a/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/index.ts b/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/index.ts index 973ac51a..c5166f17 100644 --- a/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/index.ts +++ b/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/index.ts @@ -69,14 +69,11 @@ async function run() { const connection = getDefaultOctopusConnectionDetailsOrThrow(); const configure: Array<(tool: ToolRunner) => ToolRunner> = [ connectionArguments(connection), - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), multiArgument(argumentEnquote, "package-id", packageIds), - // @ts-expect-error argument("version", packageVersion), argumentEnquote("file", buildInformationFile), argument("overwrite-mode", overwriteMode), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/Promote/PromoteV3/index.ts b/source/tasksLegacy/Promote/PromoteV3/index.ts index 53b5eb44..859ad9dd 100644 --- a/source/tasksLegacy/Promote/PromoteV3/index.ts +++ b/source/tasksLegacy/Promote/PromoteV3/index.ts @@ -24,17 +24,14 @@ async function run() { const octo = await getOrInstallOctoCommandRunner("promote-release"); const configure = [ - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), argumentEnquote("project", project), connectionArguments(connection), - // @ts-expect-error argumentEnquote("from", from), multiArgument(argumentEnquote, "to", to), multiArgument(argumentEnquote, "tenant", deploymentForTenants), multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), flag("progress", showProgress), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/Promote/PromoteV4/index.ts b/source/tasksLegacy/Promote/PromoteV4/index.ts index 672fc0bd..f14e6a7a 100644 --- a/source/tasksLegacy/Promote/PromoteV4/index.ts +++ b/source/tasksLegacy/Promote/PromoteV4/index.ts @@ -23,18 +23,14 @@ async function run() { const octo = await getOrInstallOctoCommandRunner("promote-release"); const configure = [ - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), - // @ts-expect-error argumentEnquote("project", project), connectionArguments(connection), - // @ts-expect-error argumentEnquote("from", from), multiArgument(argumentEnquote, "to", to), multiArgument(argumentEnquote, "tenant", deployForTenants), multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), flag("progress", showProgress), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/Push/PushV3/index.ts b/source/tasksLegacy/Push/PushV3/index.ts index e002e3a0..22e2f561 100644 --- a/source/tasksLegacy/Push/PushV3/index.ts +++ b/source/tasksLegacy/Push/PushV3/index.ts @@ -22,11 +22,9 @@ async function run() { const configure = [ connectionArguments(connection), - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), multiArgument(argumentEnquote, "package", matchedPackages), flag("replace-existing", replace), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/Push/PushV4/index.ts b/source/tasksLegacy/Push/PushV4/index.ts index bd8d0671..9c718d05 100644 --- a/source/tasksLegacy/Push/PushV4/index.ts +++ b/source/tasksLegacy/Push/PushV4/index.ts @@ -24,11 +24,9 @@ async function run() { const configure = [ connectionArguments(connection), - // @ts-expect-error argumentIfSet(argumentEnquote, "space", space), multiArgument(argumentEnquote, "package", matchedPackages), argument("overwrite-mode", overwriteMode), - // @ts-expect-error includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), ]; diff --git a/source/tasksLegacy/Utils/tool.ts b/source/tasksLegacy/Utils/tool.ts index e892e3e7..51633390 100644 --- a/source/tasksLegacy/Utils/tool.ts +++ b/source/tasksLegacy/Utils/tool.ts @@ -99,9 +99,9 @@ export const assertOctoVersionAcceptsIds = async function (): Promise { const [, major, minor, patch] = outputLastLine.trim().match(/^(\d+)\.(\d+)\.(\d+)\b/) || [0, 0, 0, 0]; const compatible = `${major}.${minor}.${patch}` == "1.0.0" || // allow dev versions - major > 6 || - (major == 6 && minor > 10) || - (major == 6 && minor == 10 && patch >= 0); + Number(major) > 6 || + (major === 6 && Number(minor) > 10) || + (major === 6 && minor === 10 && Number(patch) >= 0); if (!compatible) { throw new Error("The Octopus CLI tool is too old to run this task. Please use version 6.10.0 or newer, or downgrade the task to version 3.*."); } From 6a07e88606558a0cfa7bfff7236cb6674e543309 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Thu, 31 Jul 2025 13:59:34 +1000 Subject: [PATCH 2/7] chore: update lock file --- package-lock.json | 600 +++++++++++++++------------------------------- 1 file changed, 187 insertions(+), 413 deletions(-) diff --git a/package-lock.json b/package-lock.json index b49d3acc..b5302c84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,8 @@ "dependencies": { "@octopusdeploy/api-client": "^3.7.0", "azure-devops-node-api": "11.2.0", - "azure-pipelines-task-lib": "3.3.1", - "azure-pipelines-tool-lib": "1.3.2", + "azure-pipelines-task-lib": "^4.13.0", + "azure-pipelines-tool-lib": "^2.0.7", "command-line-args": "^5.2.1", "fp-ts": "1.19.5", "glob": "7.2.0", @@ -26,7 +26,7 @@ "@types/express": "^4.17.13", "@types/glob": "7.2.0", "@types/jest": "28.1.5", - "@types/node": "^18.0.4", + "@types/node": "^20.3.1", "@types/ramda": "0.28.15", "@types/test-console": "^2.0.0", "@types/uuid": "8.3.4", @@ -47,7 +47,7 @@ "test-console": "^2.0.0", "ts-jest": "28.0.5", "ts-node": "^10.9.0", - "typescript": "^4.7.4", + "typescript": "5.1.6", "yargs": "^17.5.1" } }, @@ -1296,14 +1296,6 @@ "integrity": "sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==", "dev": true }, - "node_modules/@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -1336,14 +1328,6 @@ "@types/range-parser": "*" } }, - "node_modules/@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -1442,9 +1426,13 @@ "integrity": "sha512-a2yhRIADupQfOFM75v7GfcQQLUxU705+i/xcZ3N/3PK3Xdo31SUfuCUByWPGOHB1e38m7MxTx/D8FPVsJXZKJw==" }, "node_modules/@types/node": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.4.tgz", - "integrity": "sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==" + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/prettier": { "version": "2.6.3", @@ -1460,7 +1448,8 @@ "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true }, "node_modules/@types/ramda": { "version": "0.28.15", @@ -1766,6 +1755,17 @@ "node": ">=6.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==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1970,11 +1970,6 @@ "node": ">=8" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -2007,16 +2002,16 @@ } }, "node_modules/azure-pipelines-task-lib": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-3.3.1.tgz", - "integrity": "sha512-56ZAr4MHIoa24VNVuwPL4iUQ5MKaigPoYXkBG8E8fiVmh8yZdatUo25meNoQwg77vDY22F63Q44UzXoMWmy7ag==", + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.3.tgz", + "integrity": "sha512-UxfH5pk3uOHTi9TtLtdDyugQVkFES5A836ZEePjcs3jYyxm3EJ6IlFYq6gbfd6mNBhrM9fxG2u/MFYIJ+Z0cxQ==", "dependencies": { + "adm-zip": "^0.5.10", "minimatch": "3.0.5", - "mockery": "^1.7.0", + "nodejs-file-downloader": "^4.11.1", "q": "^1.5.1", - "semver": "^5.1.0", + "semver": "^5.7.2", "shelljs": "^0.8.5", - "sync-request": "6.1.0", "uuid": "^3.0.1" } }, @@ -2032,9 +2027,9 @@ } }, "node_modules/azure-pipelines-task-lib/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": { "semver": "bin/semver" } @@ -2049,13 +2044,13 @@ } }, "node_modules/azure-pipelines-tool-lib": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/azure-pipelines-tool-lib/-/azure-pipelines-tool-lib-1.3.2.tgz", - "integrity": "sha512-PtYcd3E2ouwZhLuaOpWA00FYoLjRuJs1V8mNu3u6lBnqeYd4jh/8VL/of6nchm8f2NM6Div+EEnbOcmWvcptPg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/azure-pipelines-tool-lib/-/azure-pipelines-tool-lib-2.0.8.tgz", + "integrity": "sha512-yCFxJfZeNPUDCi7dbmiqVvq5lFpZdqB9kzr/wB9sZuE0RvUEhBF51gtzdR9cI5+NOsfkAVWwQJVWvdGQR5I3Wg==", "dependencies": { "@types/semver": "^5.3.0", "@types/uuid": "^3.4.5", - "azure-pipelines-task-lib": "^3.1.10", + "azure-pipelines-task-lib": "^4.1.0", "semver": "^5.7.0", "semver-compare": "^1.0.0", "typed-rest-client": "^1.8.6", @@ -2356,7 +2351,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/bytes": { "version": "3.1.2", @@ -2426,11 +2422,6 @@ } ] }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2558,47 +2549,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2653,7 +2603,8 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "node_modules/crc-32": { "version": "1.2.2", @@ -2704,7 +2655,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -3997,14 +3947,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "engines": { - "node": ">=4" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4179,20 +4121,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4209,19 +4137,18 @@ "node": ">= 0.8" } }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "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==", "dependencies": { - "@types/node": "^10.0.3" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4427,7 +4354,8 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -5497,16 +5425,10 @@ "node": ">=10" } }, - "node_modules/mockery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", - "integrity": "sha512-gUQA33ayi0tuAhr/rJNZPr7Q7uvlBt4gyJPbi0CDcAfIzIrDu1YgGMFgmAu3stJqBpK57m7+RxUbcS+pt59fKQ==" - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -5535,6 +5457,17 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "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==", + "dependencies": { + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5679,11 +5612,6 @@ "node": ">=6" } }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -5862,15 +5790,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "dependencies": { - "asap": "~2.0.6" - } + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "node_modules/prompts": { "version": "2.4.2", @@ -6220,6 +6141,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "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==", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6569,27 +6498,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dependencies": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dependencies": { - "get-port": "^3.1.0" - } - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -6668,45 +6576,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dependencies": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/then-request/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" - }, - "node_modules/then-request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6743,6 +6612,14 @@ "node": ">=0.6" } }, + "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==", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, "node_modules/ts-jest": { "version": "28.0.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.5.tgz", @@ -6925,22 +6802,17 @@ "underscore": "^1.12.1" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/typical": { @@ -6956,6 +6828,12 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" }, + "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 + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7005,10 +6883,16 @@ "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" }, + "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==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/utils-merge": { "version": "1.0.1", @@ -8230,14 +8114,6 @@ "integrity": "sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==", "dev": true }, - "@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "requires": { - "@types/node": "*" - } - }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -8270,14 +8146,6 @@ "@types/range-parser": "*" } }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "requires": { - "@types/node": "*" - } - }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -8376,9 +8244,13 @@ "integrity": "sha512-a2yhRIADupQfOFM75v7GfcQQLUxU705+i/xcZ3N/3PK3Xdo31SUfuCUByWPGOHB1e38m7MxTx/D8FPVsJXZKJw==" }, "@types/node": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.4.tgz", - "integrity": "sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==" + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "dev": true, + "requires": { + "undici-types": "~6.21.0" + } }, "@types/prettier": { "version": "2.6.3", @@ -8394,7 +8266,8 @@ "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true }, "@types/ramda": { "version": "0.28.15", @@ -8597,6 +8470,14 @@ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" }, + "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==", + "requires": { + "debug": "4" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -8759,11 +8640,6 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -8795,16 +8671,16 @@ } }, "azure-pipelines-task-lib": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-3.3.1.tgz", - "integrity": "sha512-56ZAr4MHIoa24VNVuwPL4iUQ5MKaigPoYXkBG8E8fiVmh8yZdatUo25meNoQwg77vDY22F63Q44UzXoMWmy7ag==", + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.3.tgz", + "integrity": "sha512-UxfH5pk3uOHTi9TtLtdDyugQVkFES5A836ZEePjcs3jYyxm3EJ6IlFYq6gbfd6mNBhrM9fxG2u/MFYIJ+Z0cxQ==", "requires": { + "adm-zip": "^0.5.10", "minimatch": "3.0.5", - "mockery": "^1.7.0", + "nodejs-file-downloader": "^4.11.1", "q": "^1.5.1", - "semver": "^5.1.0", + "semver": "^5.7.2", "shelljs": "^0.8.5", - "sync-request": "6.1.0", "uuid": "^3.0.1" }, "dependencies": { @@ -8817,9 +8693,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "uuid": { "version": "3.4.0", @@ -8829,13 +8705,13 @@ } }, "azure-pipelines-tool-lib": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/azure-pipelines-tool-lib/-/azure-pipelines-tool-lib-1.3.2.tgz", - "integrity": "sha512-PtYcd3E2ouwZhLuaOpWA00FYoLjRuJs1V8mNu3u6lBnqeYd4jh/8VL/of6nchm8f2NM6Div+EEnbOcmWvcptPg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/azure-pipelines-tool-lib/-/azure-pipelines-tool-lib-2.0.8.tgz", + "integrity": "sha512-yCFxJfZeNPUDCi7dbmiqVvq5lFpZdqB9kzr/wB9sZuE0RvUEhBF51gtzdR9cI5+NOsfkAVWwQJVWvdGQR5I3Wg==", "requires": { "@types/semver": "^5.3.0", "@types/uuid": "^3.4.5", - "azure-pipelines-task-lib": "^3.1.10", + "azure-pipelines-task-lib": "^4.1.0", "semver": "^5.7.0", "semver-compare": "^1.0.0", "typed-rest-client": "^1.8.6", @@ -9055,7 +8931,8 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "bytes": { "version": "3.1.2", @@ -9099,11 +8976,6 @@ "integrity": "sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -9206,46 +9078,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -9293,7 +9125,8 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "crc-32": { "version": "1.2.2", @@ -9332,7 +9165,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -10206,11 +10038,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==" - }, "get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -10323,17 +10150,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -10347,19 +10163,13 @@ "toidentifier": "1.0.1" } }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "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==", "requires": { - "@types/node": "^10.0.3" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - } + "agent-base": "6", + "debug": "4" } }, "human-signals": { @@ -10496,7 +10306,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true }, "isexe": { "version": "2.0.0", @@ -11332,16 +11143,10 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, - "mockery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", - "integrity": "sha512-gUQA33ayi0tuAhr/rJNZPr7Q7uvlBt4gyJPbi0CDcAfIzIrDu1YgGMFgmAu3stJqBpK57m7+RxUbcS+pt59fKQ==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "natural-compare": { "version": "1.4.0", @@ -11367,6 +11172,17 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "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==", + "requires": { + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -11471,11 +11287,6 @@ "callsites": "^3.0.0" } }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" - }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11599,15 +11410,8 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "requires": { - "asap": "~2.0.6" - } + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "prompts": { "version": "2.4.2", @@ -11839,6 +11643,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "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==", + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -12115,24 +11927,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "requires": { - "get-port": "^3.1.0" - } - }, "tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -12195,41 +11989,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12257,6 +12016,14 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "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==", + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, "ts-jest": { "version": "28.0.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.5.tgz", @@ -12369,15 +12136,10 @@ "underscore": "^1.12.1" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true }, "typical": { @@ -12390,6 +12152,12 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" }, + "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 + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -12420,10 +12188,16 @@ "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" }, + "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==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "utils-merge": { "version": "1.0.1", From c1bf604d760394a48a80a2fdee54d70943de8768 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Fri, 1 Aug 2025 10:09:20 +1000 Subject: [PATCH 3/7] feat: introduce v7 steps for node20 --- source/tasks/AwaitTask/AwaitTaskV7/icon.png | Bin 0 -> 842 bytes source/tasks/AwaitTask/AwaitTaskV7/icon.svg | 9 + source/tasks/AwaitTask/AwaitTaskV7/index.ts | 26 +++ .../AwaitTask/AwaitTaskV7/input-parameters.ts | 76 +++++++ source/tasks/AwaitTask/AwaitTaskV7/task.json | 94 ++++++++ source/tasks/AwaitTask/AwaitTaskV7/waiter.ts | 207 ++++++++++++++++++ .../BuildInformationV7/buildInformation.ts | 21 ++ .../BuildInformationV7/icon.png | Bin 0 -> 760 bytes .../BuildInformationV7/icon.svg | 4 + .../BuildInformationV7/index.ts | 28 +++ .../inputCommandBuilder.test.ts | 88 ++++++++ .../BuildInformationV7/inputCommandBuilder.ts | 49 +++++ .../BuildInformationV7/overwriteMode.test.ts | 24 ++ .../BuildInformationV7/overwriteMode.ts | 28 +++ .../BuildInformationV7/task.json | 72 ++++++ .../BuildInformationV7/vsts.ts | 141 ++++++++++++ .../CreateOctopusReleaseV7/createRelease.ts | 30 +++ .../CreateOctopusReleaseV7/icon.png | Bin 0 -> 1005 bytes .../CreateOctopusReleaseV7/icon.svg | 7 + .../CreateOctopusReleaseV7/index.ts | 26 +++ .../inputCommandBuilder.test.ts | 86 ++++++++ .../inputCommandBuilder.ts | 88 ++++++++ .../CreateOctopusReleaseV7/release.ts | 48 ++++ .../CreateOctopusReleaseV7/task.json | 154 +++++++++++++ .../tasks/Deploy/DeployV7/createDeployment.ts | 49 +++++ source/tasks/Deploy/DeployV7/deploy.ts | 63 ++++++ source/tasks/Deploy/DeployV7/icon.png | Bin 0 -> 962 bytes source/tasks/Deploy/DeployV7/icon.svg | 5 + source/tasks/Deploy/DeployV7/index.ts | 26 +++ .../DeployV7/inputCommandBuilder.test.ts | 98 +++++++++ .../Deploy/DeployV7/inputCommandBuilder.ts | 68 ++++++ source/tasks/Deploy/DeployV7/task.json | 106 +++++++++ .../TenantedDeployV7/createDeployment.ts | 50 +++++ .../DeployTenant/TenantedDeployV7/deploy.ts | 64 ++++++ .../DeployTenant/TenantedDeployV7/icon.png | Bin 0 -> 962 bytes .../DeployTenant/TenantedDeployV7/icon.svg | 5 + .../DeployTenant/TenantedDeployV7/index.ts | 26 +++ .../inputCommandBuilder.test.ts | 106 +++++++++ .../TenantedDeployV7/inputCommandBuilder.ts | 74 +++++++ .../DeployTenant/TenantedDeployV7/task.json | 122 +++++++++++ .../downloadEndpointRetriever.test.ts | 124 +++++++++++ .../downloadEndpointRetriever.ts | 152 +++++++++++++ .../OctoInstaller/OctoInstallerV7/icon.png | Bin 0 -> 817 bytes .../OctoInstaller/OctoInstallerV7/icon.svg | 4 + .../OctoInstaller/OctoInstallerV7/index.ts | 35 +++ .../OctoInstallerV7/installer.ts | 56 +++++ .../octopusCLIVersionResolver.test.ts | 67 ++++++ .../octopusCLIVersionResolver.ts | 72 ++++++ .../OctoInstaller/OctoInstallerV7/task.json | 47 ++++ .../PackNuGet/PackNuGetV7/create-package.ts | 38 ++++ source/tasks/PackNuGet/PackNuGetV7/icon.png | Bin 0 -> 849 bytes source/tasks/PackNuGet/PackNuGetV7/icon.svg | 6 + source/tasks/PackNuGet/PackNuGetV7/index.ts | 42 ++++ .../PackNuGet/PackNuGetV7/input-parameters.ts | 33 +++ source/tasks/PackNuGet/PackNuGetV7/task.json | 131 +++++++++++ .../tasks/PackZip/PackZipV7/create-package.ts | 23 ++ source/tasks/PackZip/PackZipV7/icon.png | Bin 0 -> 849 bytes source/tasks/PackZip/PackZipV7/icon.svg | 6 + source/tasks/PackZip/PackZipV7/index.ts | 42 ++++ .../PackZip/PackZipV7/input-parameters.ts | 23 ++ source/tasks/PackZip/PackZipV7/task.json | 91 ++++++++ source/tasks/Push/PushV7/icon.png | Bin 0 -> 760 bytes source/tasks/Push/PushV7/icon.svg | 4 + source/tasks/Push/PushV7/index.ts | 43 ++++ source/tasks/Push/PushV7/push.ts | 42 ++++ source/tasks/Push/PushV7/task.json | 65 ++++++ source/tasks/RunRunbook/RunRunbookV7/icon.png | Bin 0 -> 886 bytes source/tasks/RunRunbook/RunRunbookV7/icon.svg | 4 + source/tasks/RunRunbook/RunRunbookV7/index.ts | 26 +++ .../RunRunbookV7/inputCommandBuilder.test.ts | 62 ++++++ .../RunRunbookV7/inputCommandBuilder.ts | 74 +++++++ .../RunRunbook/RunRunbookV7/runRunbook.ts | 70 ++++++ .../RunRunbook/RunRunbookV7/runbookRun.ts | 29 +++ .../tasks/RunRunbook/RunRunbookV7/task.json | 111 ++++++++++ 74 files changed, 3690 insertions(+) create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/icon.png create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/icon.svg create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/index.ts create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/input-parameters.ts create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/task.json create mode 100644 source/tasks/AwaitTask/AwaitTaskV7/waiter.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/buildInformation.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/icon.png create mode 100644 source/tasks/BuildInformation/BuildInformationV7/icon.svg create mode 100644 source/tasks/BuildInformation/BuildInformationV7/index.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.test.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/overwriteMode.test.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/overwriteMode.ts create mode 100644 source/tasks/BuildInformation/BuildInformationV7/task.json create mode 100644 source/tasks/BuildInformation/BuildInformationV7/vsts.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/createRelease.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.png create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.svg create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/index.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.test.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/release.ts create mode 100644 source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/task.json create mode 100644 source/tasks/Deploy/DeployV7/createDeployment.ts create mode 100644 source/tasks/Deploy/DeployV7/deploy.ts create mode 100644 source/tasks/Deploy/DeployV7/icon.png create mode 100644 source/tasks/Deploy/DeployV7/icon.svg create mode 100644 source/tasks/Deploy/DeployV7/index.ts create mode 100644 source/tasks/Deploy/DeployV7/inputCommandBuilder.test.ts create mode 100644 source/tasks/Deploy/DeployV7/inputCommandBuilder.ts create mode 100644 source/tasks/Deploy/DeployV7/task.json create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/createDeployment.ts create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/deploy.ts create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/icon.png create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/icon.svg create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/index.ts create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.test.ts create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.ts create mode 100644 source/tasks/DeployTenant/TenantedDeployV7/task.json create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.test.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/icon.png create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/icon.svg create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/index.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/installer.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.test.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.ts create mode 100644 source/tasks/OctoInstaller/OctoInstallerV7/task.json create mode 100644 source/tasks/PackNuGet/PackNuGetV7/create-package.ts create mode 100644 source/tasks/PackNuGet/PackNuGetV7/icon.png create mode 100644 source/tasks/PackNuGet/PackNuGetV7/icon.svg create mode 100644 source/tasks/PackNuGet/PackNuGetV7/index.ts create mode 100644 source/tasks/PackNuGet/PackNuGetV7/input-parameters.ts create mode 100644 source/tasks/PackNuGet/PackNuGetV7/task.json create mode 100644 source/tasks/PackZip/PackZipV7/create-package.ts create mode 100644 source/tasks/PackZip/PackZipV7/icon.png create mode 100644 source/tasks/PackZip/PackZipV7/icon.svg create mode 100644 source/tasks/PackZip/PackZipV7/index.ts create mode 100644 source/tasks/PackZip/PackZipV7/input-parameters.ts create mode 100644 source/tasks/PackZip/PackZipV7/task.json create mode 100644 source/tasks/Push/PushV7/icon.png create mode 100644 source/tasks/Push/PushV7/icon.svg create mode 100644 source/tasks/Push/PushV7/index.ts create mode 100644 source/tasks/Push/PushV7/push.ts create mode 100644 source/tasks/Push/PushV7/task.json create mode 100644 source/tasks/RunRunbook/RunRunbookV7/icon.png create mode 100644 source/tasks/RunRunbook/RunRunbookV7/icon.svg create mode 100644 source/tasks/RunRunbook/RunRunbookV7/index.ts create mode 100644 source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.test.ts create mode 100644 source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.ts create mode 100644 source/tasks/RunRunbook/RunRunbookV7/runRunbook.ts create mode 100644 source/tasks/RunRunbook/RunRunbookV7/runbookRun.ts create mode 100644 source/tasks/RunRunbook/RunRunbookV7/task.json diff --git a/source/tasks/AwaitTask/AwaitTaskV7/icon.png b/source/tasks/AwaitTask/AwaitTaskV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1daf69d2d018b11e8399a47acd0f2e9b957e426 GIT binary patch literal 842 zcmV-Q1GW5#P);tRFq0XS zjt(Zy3=q(5_9WS0EdON;6!wKW0@jDL1qw{)9G+Wnyk}ljFs<30QX8ROkL4XdYmcw%6-?TJnH^GxxjW zhJpWbPX#=38y@R&6*=xy6^%S_e!cU*JRnqJ#FPg~1(zX_HG*}xIxG*(-*Rj@%sJ$N zNfq({XN|3RV6s_o9#Kaxzz30~_~11VU{+TtNT5p|SZl@uyidE4XhU0&$yG{6@AZxc zboqV2G_I4<2izdO1EL>xfDtalD0I{$;wMsiXf!+ED)N)S|#jIz2HksY__+4G8qTb`k>?siM;3fwDuknt@NxZ<| z4hYwhSMrL0cH;=B9m4sCNxlCDJac`xJdKu<(Y(*eEwgPwCsL7PQ-ktwk-I<7i7xeg z3Hq)$ literal 0 HcmV?d00001 diff --git a/source/tasks/AwaitTask/AwaitTaskV7/icon.svg b/source/tasks/AwaitTask/AwaitTaskV7/icon.svg new file mode 100644 index 00000000..8b1a306e --- /dev/null +++ b/source/tasks/AwaitTask/AwaitTaskV7/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/source/tasks/AwaitTask/AwaitTaskV7/index.ts b/source/tasks/AwaitTask/AwaitTaskV7/index.ts new file mode 100644 index 00000000..50922d3d --- /dev/null +++ b/source/tasks/AwaitTask/AwaitTaskV7/index.ts @@ -0,0 +1,26 @@ +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; +import { Waiter } from "./waiter"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); + +new Waiter(connection, task, logger).run(); diff --git a/source/tasks/AwaitTask/AwaitTaskV7/input-parameters.ts b/source/tasks/AwaitTask/AwaitTaskV7/input-parameters.ts new file mode 100644 index 00000000..9bea618d --- /dev/null +++ b/source/tasks/AwaitTask/AwaitTaskV7/input-parameters.ts @@ -0,0 +1,76 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { WaitExecutionResult } from "./waiter"; + +export interface InputParameters { + space: string; + step: string; + tasks: WaitExecutionResult[]; + pollingInterval: number; + timeout: number; + showProgress: boolean; + cancelOnTimeout: boolean; +} + +export function getInputParameters(logger: Logger, task: TaskWrapper): InputParameters { + const space = task.getInput("Space"); + if (!space) { + throw new Error("Failed to successfully build parameters: space name is required."); + } + + const step = task.getInput("Step"); + if (!step) { + throw new Error("Failed to successfully build parameters: step name is required."); + } + + const taskJson = task.getOutputVariable(step, "server_tasks"); + if (taskJson === undefined) { + throw new Error(`Failed to successfully build parameters: cannot find '${step}.server_tasks' variable from execution step`); + } + const tasks = JSON.parse(taskJson); + if (!Array.isArray(tasks)) { + throw new Error(`Failed to successfully build parameters: '${step}.server_tasks' variable from execution step is not an array`); + } + + let pollingInterval = 10; + const pollingIntervalField = task.getInput("PollingInterval"); + if (pollingIntervalField) { + pollingInterval = +pollingIntervalField; + } + + let timeoutSeconds = 600; + const timeoutField = task.getInput("TimeoutAfter"); + if (timeoutField) { + timeoutSeconds = +timeoutField; + } + + const showProgress = task.getBoolean("ShowProgress") ?? false; + if (showProgress && tasks.length > 1) { + throw new Error("Failed to successfully build parameters: ShowProgress can only be enabled when waiting for a single task"); + } + + const cancelOnTimeout = task.getBoolean("CancelOnTimeout") ?? false; + + const parameters: InputParameters = { + space: task.getInput("Space") || "", + step: step, + tasks: tasks, + showProgress: showProgress, + pollingInterval: pollingInterval, + timeout: timeoutSeconds, + cancelOnTimeout: cancelOnTimeout + }; + + const errors: string[] = []; + if (parameters.space === "") { + errors.push("The Octopus space name is required."); + } + + if (errors.length > 0) { + throw new Error("Failed to successfully build parameters.\n" + errors.join("\n")); + } + + logger.debug?.(`Tasks: \n${JSON.stringify(parameters, null, 2)}`); + + return parameters; +} diff --git a/source/tasks/AwaitTask/AwaitTaskV7/task.json b/source/tasks/AwaitTask/AwaitTaskV7/task.json new file mode 100644 index 00000000..6e333361 --- /dev/null +++ b/source/tasks/AwaitTask/AwaitTaskV7/task.json @@ -0,0 +1,94 @@ +{ + "id": "38df691d-23eb-48d4-8638-61764f48bacb", + "name": "OctopusAwaitTask", + "friendlyName": "Await Octopus Task Completion", + "description": "Await the completion of a execution task", + "helpMarkDown": "set-by-pack.ps1", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "Step", + "type": "string", + "label": "Step", + "defaultValue": "", + "required": true, + "helpMarkDown": "The name of the step that queued the deployment/runbook run. You will need to set a output variable reference name on the source step and use that name here." + }, + { + "name": "PollingInterval", + "type": "int", + "label": "Polling Interval", + "defaultValue": "10", + "required": false, + "helpMarkDown": "How frequently, in seconds, to check the status. (Default: 10s)" + }, + { + "name": "TimeoutAfter", + "type": "int", + "label": "Timeout After", + "defaultValue": "600", + "required": false, + "helpMarkDown": "Duration, in seconds, to allow for completion before timing out. (Default: 600s)" + }, + { + "name": "ShowProgress", + "type": "boolean", + "label": "Show Progress", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Log Octopus task outputs to Azure DevOps output. (Default: false)" + }, + { + "name": "CancelOnTimeout", + "type": "boolean", + "label": "Cancel Task on Timeout", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Cancel the Octopus task and mark this task as failed if the timeout is reached. (Default: false)" + } + ], + "outputVariables": [ + { + "name": "completed_successfully", + "description": "Whether the task(s) completed successfully or not. This will only be true if all tasks succeeded, and false if any tasks failed." + }, + { + "name": "server_task_results", + "description": "JSON representation of all tasks results. { \"serverTaskId\": , \"tenantName\": , \"environmentName\": , \"successful\": }" + } + ], + "instanceNameFormat": "Await Octopus Deploy Task", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/AwaitTask/AwaitTaskV7/waiter.ts b/source/tasks/AwaitTask/AwaitTaskV7/waiter.ts new file mode 100644 index 00000000..501512a4 --- /dev/null +++ b/source/tasks/AwaitTask/AwaitTaskV7/waiter.ts @@ -0,0 +1,207 @@ +import { ActivityElement, ActivityLogEntryCategory, ActivityStatus, Client, Logger, ServerTaskWaiter, SpaceRepository, SpaceServerTaskRepository, TaskState } from "@octopusdeploy/api-client"; +import { getDeepLink, OctoServerConnectionDetails } from "tasks/Utils/connection"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { getInputParameters, InputParameters } from "./input-parameters"; +import { ExecutionResult } from "../../Utils/executionResult"; +import { getClient } from "../../Utils/client"; + +export interface WaitExecutionResult extends ExecutionResult { + successful: boolean; +} + +export class Waiter { + constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} + + public async run() { + try { + const inputParameters = getInputParameters(this.logger, this.task); + + const client = await getClient(this.connection, this.logger, "task", "wait", 6); + + const waitExecutionResults = inputParameters.showProgress + ? await this.waitWithProgress(client, inputParameters) + : await this.waitWithoutProgress(client, inputParameters); + + const url = this.connection.url; + const spaceId = await this.getSpaceId(client, inputParameters.space); + let failedDeploymentsCount = 0; + waitExecutionResults.map((r) => { + const link = getDeepLink(url, `${spaceId}/tasks/${r.serverTaskId}`); + const context = this.getContext(r); + if (r.successful) { + this.logger.info?.(`Succeeded: ${link}`); + } else { + this.logger.warn?.(`Failed: ${link}`); + failedDeploymentsCount++; + } + this.task.setOutputVariable(`${context}.completed_successfully`, r.successful.toString()); + }); + + if (failedDeploymentsCount > 0) { + this.task.setFailure(`${failedDeploymentsCount} ${failedDeploymentsCount == 1 ? "task" : "tasks"} failed.`); + this.task.setOutputVariable("completed_successfully", "false"); + } else { + this.task.setSuccess("All tasks completed successfully"); + this.task.setOutputVariable("completed_successfully", "true"); + } + + this.task.setOutputVariable("server_task_results", JSON.stringify(waitExecutionResults)); + + } catch (error) { + if (error instanceof Error && error.message.includes("Timeout reached") && error.message.includes("cancelled")) { + this.task.setFailure(error.message); + this.task.setOutputVariable("completed_successfully", "false"); + return; + } + + this.task.setFailure(`Failed to wait for tasks: ${error}`); + this.task.setOutputVariable("completed_successfully", "false"); + } + } + + async getSpaceId(client: Client, spaceName: string): Promise { + const spaceRepository = new SpaceRepository(client); + const spaceList = await spaceRepository.list({ partialName: spaceName }); + const matches = spaceList.Items.filter((s) => s.Name.localeCompare(spaceName) === 0); + return matches.length > 0 ? matches[0].Id : undefined; + } + + getContext(result: WaitExecutionResult): string { + return result.tenantName ? result.tenantName.replace(" ", "_") : result.environmentName.replace(" ", "_"); + } + + async waitWithoutProgress(client: Client, inputParameters: InputParameters): Promise { + const waiter = new ServerTaskWaiter(client, inputParameters.space); + const taskIds = inputParameters.tasks.map((t) => t.serverTaskId); + const lookup = new Map(inputParameters.tasks.map((t) => [t.serverTaskId, t])); + + const results: WaitExecutionResult[] = []; + + await waiter.waitForServerTasksToComplete(taskIds, inputParameters.pollingInterval * 1000, inputParameters.timeout * 1000, (t) => { + const taskResult = lookup.get(t.Id); + if (!taskResult) return; + const context = this.getProgressContext(taskResult); + + if (!t.IsCompleted) { + this.logger.info?.(`${taskResult.type}${context} is '${t.State}'`); + return; + } + + this.logger.info?.(`${taskResult.type}${context} ${t.State === TaskState.Success ? "completed successfully" : "did not complete successfully"}`); + taskResult.successful = t.State == TaskState.Success; + results.push(taskResult); + }, + inputParameters.cancelOnTimeout); + + return results; + } + + async waitWithProgress(client: Client, inputParameters: InputParameters): Promise { + const waiter = new ServerTaskWaiter(client, inputParameters.space); + const taskIds = inputParameters.tasks.map((t) => t.serverTaskId); + const taskLookup = new Map(inputParameters.tasks.map((t) => [t.serverTaskId, t])); + + const taskRepository = new SpaceServerTaskRepository(client, inputParameters.space); + const loggedChildTaskIds: string[] = []; + const lastTaskUpdate: { [taskId: string]: string } = {}; + + const results: WaitExecutionResult[] = []; + + const promises: Promise[] = []; + await waiter.waitForServerTasksToComplete(taskIds, inputParameters.pollingInterval * 1000, inputParameters.timeout * 1000, (t) => { + const taskResult = taskLookup.get(t.Id); + if (!taskResult) return; + + const taskUpdate = `${taskResult.type}${this.getProgressContext(taskResult)} is '${t.State}'`; + if (loggedChildTaskIds.length == 0 && lastTaskUpdate[taskResult.serverTaskId] !== taskUpdate) { + // Log top level updates until we have details, don't log them again + this.logger.info?.(taskUpdate); + lastTaskUpdate[taskResult.serverTaskId] = taskUpdate; + } + + // Log details of the task + const promise = this.printTaskDetails(taskRepository, taskResult, loggedChildTaskIds, results); + promises.push(promise); + }, + inputParameters.cancelOnTimeout); + + await Promise.all(promises); + return results; + } + + async printTaskDetails(repository: SpaceServerTaskRepository, task: WaitExecutionResult, loggedChildTaskIds: string[], results: WaitExecutionResult[]): Promise { + try { + this.logger.debug?.(`Fetching details on ${task.serverTaskId}`); + const taskDetails = await repository.getDetails(task.serverTaskId); + this.logger.debug?.(`Fetched details on ${task.serverTaskId}: ${JSON.stringify(taskDetails)}`); + + const activities = taskDetails.ActivityLogs.flatMap((parentActivity) => parentActivity.Children.filter(isComplete).filter((activity) => !loggedChildTaskIds.includes(activity.Id))); + + for (const activity of activities) { + this.logWithStatus(`\t${activity.Status}: ${activity.Name}`, activity.Status); + + if (activity.Started && activity.Ended) { + const startTime = new Date(activity.Started); + const endTime = new Date(activity.Ended); + const duration = (endTime.getTime() - startTime.getTime()) / 1000; + this.logger.info?.(`\t\t\t---------------------------------`); + this.logger.info?.(`\t\t\tStarted: \t${activity.Started}\n\t\t\tEnded: \t${activity.Ended}\n\t\t\tDuration:\t${duration.toFixed(1)}s`); + this.logger.info?.(`\t\t\t---------------------------------`); + } + + activity.Children.filter(isComplete) + .flatMap((child) => child.LogElements) + .forEach((log) => { + this.logWithCategory(`\t\t${log.OccurredAt}: ${log.MessageText}`, log.Category); + log.Detail && this.logger.debug?.(log.Detail); + }); + + loggedChildTaskIds.push(activity.Id); + } + if (!taskDetails.Task.IsCompleted) return; + + const message = taskDetails.Task.State === TaskState.Success ? "completed successfully" : "did not complete successfully"; + this.logger.info?.(`${task.type}${this.getProgressContext(task)} ${message}`); + task.successful = taskDetails.Task.State === TaskState.Success; + results.push(task); + } catch (e) { + const error = e instanceof Error ? e : undefined; + this.logger.error?.(`Failed to fetch details on ${task}: ${e}`, error); + } + } + + getProgressContext(task: WaitExecutionResult): string { + return `${task.environmentName ? ` to environment '${task.environmentName}'` : ""}${task.tenantName ? ` for tenant '${task.tenantName}'` : ""}`; + } + + logWithCategory(message: string, category?: ActivityLogEntryCategory) { + switch (category) { + case "Error": + case "Fatal": + this.logger.error?.(message, undefined); + break; + case "Warning": + this.logger.warn?.(message); + break; + default: + this.logger.info?.(message); + } + } + + logWithStatus(message: string, status?: ActivityStatus) { + switch (status) { + case "Failed": + this.logger.error?.(message, undefined); + break; + case "SuccessWithWarning": + this.logger.warn?.(message); + break; + default: + this.logger.info?.(message); + } + } +} + +function isComplete(element: ActivityElement) { + return element.Status != "Pending" && element.Status != "Running"; +} diff --git a/source/tasks/BuildInformation/BuildInformationV7/buildInformation.ts b/source/tasks/BuildInformation/BuildInformationV7/buildInformation.ts new file mode 100644 index 00000000..640c2dcd --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/buildInformation.ts @@ -0,0 +1,21 @@ +import { OctoServerConnectionDetails } from "../../Utils/connection"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import { BuildInformationRepository, Logger } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { getOverwriteMode } from "./overwriteMode"; +import { IVstsHelper } from "./vsts"; +import { getClient } from "../../Utils/client"; + +export class BuildInformation { + constructor(readonly connection: OctoServerConnectionDetails, readonly logger: Logger, readonly task: TaskWrapper, readonly vsts: IVstsHelper) {} + + public async run() { + const command = await createCommandFromInputs(this.logger, this.task, this.vsts); + const client = await getClient(this.connection, this.logger, "build-information", "push", 6) + + const overwriteMode = await getOverwriteMode(this.logger, this.task); + this.logger.debug?.(`Build Information:\n${JSON.stringify(command, null, 2)}`); + const repository = new BuildInformationRepository(client, command.spaceName); + await repository.push(command, overwriteMode); + } +} diff --git a/source/tasks/BuildInformation/BuildInformationV7/icon.png b/source/tasks/BuildInformation/BuildInformationV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..55087619c14789e8ebef4e6d34fae1299867f5fc GIT binary patch literal 760 zcmV9FL&t~D)eELa5&6Yt3N@=RfN zy=lsy!3POYfnxBv5j4D`_gX?Y8h=%wal#4%V?ZyaGnk|CIuwr?OarZTISqzugzFAu zz7E|JFCfH#2~#+6@NQhl$u4&e$vO?#oMtnDNl-yFO@wB19rWCF(lg z@ofhJ_}xWy>8)(?0aHdQFE~peuov#1gi9|^C}tN5T|&$3R~K9850~`dI&2(b)Tvq zR=1Pb_>f{8KlVf5JJfe^4=<8ykn#cN(^4xbrAx6E)e8x-ga4`lcN*m4=%H+%i$@*` z8YM>0#pdf+ghltMWBFeT^0HR)qS!~=6{j|b5WNANA7WF5gW`>$8sKI$81hH>>}aYv&&iG6q;CfG#)y0000_=h% literal 0 HcmV?d00001 diff --git a/source/tasks/BuildInformation/BuildInformationV7/icon.svg b/source/tasks/BuildInformation/BuildInformationV7/icon.svg new file mode 100644 index 00000000..9b9c1c04 --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/tasks/BuildInformation/BuildInformationV7/index.ts b/source/tasks/BuildInformation/BuildInformationV7/index.ts new file mode 100644 index 00000000..8e22177e --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/index.ts @@ -0,0 +1,28 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { getDefaultOctopusConnectionDetailsOrThrow } from "tasks/Utils/connection"; +import * as tasks from "azure-pipelines-task-lib/task"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { BuildInformation } from "./buildInformation"; +import { IVstsHelper, VstsHelper } from "./vsts"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); +const vsts: IVstsHelper = new VstsHelper(logger); + +new BuildInformation(connection, logger, task, vsts).run(); diff --git a/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.test.ts b/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.test.ts new file mode 100644 index 00000000..26dea624 --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.test.ts @@ -0,0 +1,88 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import { VstsParameters, IVstsHelper } from "./vsts"; + +class MockVsts implements IVstsHelper { + getVsts(_logger: Logger): Promise { + const vsts: VstsParameters = { + branch: "/refs/head/main", + environment: { + projectId: "projectId", + projectName: "projectName", + buildNumber: "buildNumber", + buildId: 1234, + buildName: "buildName", + buildRepositoryName: "buildRepositoryName", + releaseName: "releaseName", + releaseUri: "releaseUri", + releaseId: "releaseId", + teamCollectionUri: "http://teamcollectionuri/", + defaultWorkingDirectory: "defaultWorkingDirectory", + buildRepositoryProvider: "buildRepositoryProvider", + buildRepositoryUri: "buildRepositoryUri", + buildSourceVersion: "buildSourceVersion", + agentBuildDirectory: "agentBuildDirectory", + }, + vcsType: "vcsType", + commits: [{ Comment: "commit comment", Id: "commitId" }], + }; + + return new Promise((resolve) => resolve(vsts)); + } +} + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + let vsts: MockVsts; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + vsts = new MockVsts(); + }); + + test("all regular fields supplied", async () => { + task.addVariableString("Space", "Default"); + task.addVariableString("PackageVersion", "1.2.3"); + task.addVariableString("PackageIds", "Package1\nPackage2"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("Replace", "true"); + + const command = await createCommandFromInputs(logger, task, vsts); + expect(command.Packages.length).toBe(2); + expect(command.Packages[0].Id).toBe("Package1"); + expect(command.Packages[0].Version).toBe("1.2.3"); + expect(command.Packages[1].Id).toBe("Package2"); + expect(command.Packages[1].Version).toBe("1.2.3"); + expect(command.Branch).toBe("/refs/head/main"); + expect(command.BuildEnvironment).toBe("Azure DevOps"); + expect(command.spaceName).toBe("Default"); + expect(command.BuildNumber).toBe("buildNumber"); + expect(command.BuildUrl).toBe("http://teamcollectionuri/projectName/_build/results?buildId=1234"); + expect(command.Commits.length).toBe(1); + expect(command.Commits[0].Id).toBe("commitId"); + expect(command.VcsCommitNumber).toBe("buildSourceVersion"); + expect(command.VcsRoot).toBe("buildRepositoryUri"); + expect(command.VcsType).toBe("vcsType"); + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); + + test("missing parameters", async () => { + const t = async () => { + await createCommandFromInputs(logger, task, vsts); + }; + await expect(t).rejects.toThrow("Failed to successfully build parameters:\nspace name is required\nmust specify at least one package name"); + }); + + test("missing package version", async () => { + const t = async () => { + task.addVariableString("Space", "Default"); + task.addVariableString("PackageIds", "Package1"); + await createCommandFromInputs(logger, task, vsts); + }; + await expect(t).rejects.toThrow("Failed to successfully build parameters:\nmust specify a package version number, in SemVer format"); + }); +}); diff --git a/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.ts b/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.ts new file mode 100644 index 00000000..eabaa2e4 --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/inputCommandBuilder.ts @@ -0,0 +1,49 @@ +import { CreateOctopusBuildInformationCommand, Logger, PackageIdentity } from "@octopusdeploy/api-client"; +import { getLineSeparatedItems } from "../../Utils/inputs"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { IVstsHelper } from "./vsts"; + +export async function createCommandFromInputs(logger: Logger, task: TaskWrapper, vstsHelper: IVstsHelper): Promise { + const vsts = await vstsHelper.getVsts(logger); + const inputPackages = getLineSeparatedItems(task.getInput("PackageIds") || "") || []; + logger.debug?.(`PackageIds: ${inputPackages}`); + const packages: PackageIdentity[] = []; + for (const packageId of inputPackages) { + packages.push({ + Id: packageId, + Version: task.getInput("PackageVersion") || "", + }); + } + + const command: CreateOctopusBuildInformationCommand = { + spaceName: task.getInput("Space") || "", + BuildEnvironment: "Azure DevOps", + BuildNumber: vsts.environment.buildNumber, + BuildUrl: vsts.environment.teamCollectionUri.replace(/\/$/, "") + "/" + vsts.environment.projectName + "/_build/results?buildId=" + vsts.environment.buildId, + Branch: vsts.branch || "", + VcsType: vsts.vcsType, + VcsRoot: vsts.environment.buildRepositoryUri, + VcsCommitNumber: vsts.environment.buildSourceVersion, + Commits: vsts.commits, + Packages: packages, + }; + + const errors: string[] = []; + if (!command.spaceName) { + errors.push("space name is required"); + } + + if (!command.Packages || command.Packages.length === 0) { + errors.push("must specify at least one package name"); + } else { + if (!command.Packages[0].Version || command.Packages[0].Version === "") { + errors.push("must specify a package version number, in SemVer format"); + } + } + + if (errors.length > 0) { + throw new Error(`Failed to successfully build parameters:\n${errors.join("\n")}`); + } + + return command; +} diff --git a/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.test.ts b/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.test.ts new file mode 100644 index 00000000..3e051611 --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.test.ts @@ -0,0 +1,24 @@ +import { Logger, OverwriteMode } from "@octopusdeploy/api-client"; +import { getOverwriteMode } from "./overwriteMode"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + }); + + test("second run", () => { + task.addVariableString("system.jobAttempt", "2"); + const overwriteMode = getOverwriteMode(logger, task); + expect(overwriteMode).toBe(OverwriteMode.IgnoreIfExists); + }); + + test("user provided", () => { + task.addVariableString("Replace", "true"); + const overwriteMode = getOverwriteMode(logger, task); + expect(overwriteMode).toBe(OverwriteMode.OverwriteExisting); + }); +}); diff --git a/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.ts b/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.ts new file mode 100644 index 00000000..6f3b5318 --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/overwriteMode.ts @@ -0,0 +1,28 @@ +import { Logger, OverwriteMode } from "@octopusdeploy/api-client"; +import { ReplaceOverwriteMode } from "../../Utils/inputs"; +import { TaskWrapper } from "../../Utils/taskInput"; + +export function getOverwriteMode(logger: Logger, task: TaskWrapper): OverwriteMode { + const isRetry = parseInt(task.getVariable("system.jobAttempt") || "0") > 1; + const overwriteMode: ReplaceOverwriteMode = + (ReplaceOverwriteMode as any)[task.getInput("Replace", false) || ""] || // eslint-disable-line @typescript-eslint/no-explicit-any + (isRetry ? ReplaceOverwriteMode.IgnoreIfExists : ReplaceOverwriteMode.false); + + let apiOverwriteMode: OverwriteMode; + switch (overwriteMode) { + case ReplaceOverwriteMode.true: + apiOverwriteMode = OverwriteMode.OverwriteExisting; + break; + case ReplaceOverwriteMode.IgnoreIfExists: + apiOverwriteMode = OverwriteMode.IgnoreIfExists; + break; + case ReplaceOverwriteMode.false: + apiOverwriteMode = OverwriteMode.FailIfExists; + break; + default: + apiOverwriteMode = OverwriteMode.FailIfExists; + break; + } + logger.debug?.(`Overwrite mode: ${apiOverwriteMode}`); + return apiOverwriteMode; +} diff --git a/source/tasks/BuildInformation/BuildInformationV7/task.json b/source/tasks/BuildInformation/BuildInformationV7/task.json new file mode 100644 index 00000000..baa6c22d --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/task.json @@ -0,0 +1,72 @@ +{ + "id": "559b81c9-efc1-40f3-9058-71ab1810d837", + "name": "OctopusBuildInformation", + "friendlyName": "Push Package Build Information to Octopus", + "description": "Collect information related to the build, including work items from commit messages, and push to your Octopus Deploy Server.", + "helpMarkDown": "set-by-pack.ps1", + "category": "Package", + "visibility": [ + "Build" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "PackageIds", + "type": "multiLine", + "label": "Package IDs", + "defaultValue": "", + "required": true, + "helpMarkDown": "Newline-separated package IDs; e.g.\nMyCompany.MyApp\nMyCompany.MyApp2" + }, + { + "name": "PackageVersion", + "type": "string", + "label": "Package Version", + "defaultValue": "", + "required": true, + "helpMarkDown": "The version of the package; must be a valid [SemVer](http://semver.org/) version." + }, + { + "name": "Replace", + "type": "pickList", + "label": "Overwrite Mode", + "defaultValue": "false", + "required": true, + "helpMarkDown": "Normally, if the same package build information already exists on the server, the server will reject the package build information push. This is a good practice as it ensures build information isn't accidentally overwritten or ignored. Use this setting to override this behavior.", + "options": { + "false": "Fail if exists", + "true": "Overwrite existing", + "IgnoreIfExists": "Ignore if exists" + } + } + ], + "instanceNameFormat": "Push Package Build Information to Octopus", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/BuildInformation/BuildInformationV7/vsts.ts b/source/tasks/BuildInformation/BuildInformationV7/vsts.ts new file mode 100644 index 00000000..20c8fd6c --- /dev/null +++ b/source/tasks/BuildInformation/BuildInformationV7/vsts.ts @@ -0,0 +1,141 @@ +import { IOctopusBuildInformationCommit, Logger } from "@octopusdeploy/api-client"; +import * as vsts from "azure-devops-node-api"; +import * as tasks from "azure-pipelines-task-lib/task"; +import os from "os"; + +export interface VstsParameters { + branch: string; + environment: VstsEnvironmentVariables; + vcsType: string; + commits: IOctopusBuildInformationCommit[]; +} + +export interface IVstsHelper { + getVsts(logger: Logger): Promise; +} + +export interface ReleaseEnvironmentVariables { + releaseName: string; + releaseId: string; + releaseUri: string; +} + +export interface BuildEnvironmentVariables { + buildNumber: string; + buildId: number; + buildName: string; + buildRepositoryName: string; + buildRepositoryProvider: string; + buildRepositoryUri: string; + buildSourceVersion: string; +} + +export interface AgentEnvironmentVariables { + agentBuildDirectory: string; +} + +export interface SystemEnvironmentVariables { + projectName: string; + projectId: string; + teamCollectionUri: string; + defaultWorkingDirectory: string; +} + +export type VstsEnvironmentVariables = ReleaseEnvironmentVariables & BuildEnvironmentVariables & AgentEnvironmentVariables & SystemEnvironmentVariables; + +export class VstsHelper implements IVstsHelper { + constructor(readonly logger: Logger) {} + async getVsts(): Promise { + const environment = this.getVstsEnvironmentVariables(); + const vstsConnection = this.createVstsConnection(environment); + const branch = await this.getBuildBranch(vstsConnection, environment); + const commits = await this.getBuildChanges(vstsConnection, environment, this.logger); + + const vsts: VstsParameters = { + branch: branch || "", + environment: environment, + vcsType: await this.getVcsTypeFromProvider(environment.buildRepositoryProvider), + commits: commits, + }; + + return vsts; + } + + private getVstsEnvironmentVariables(): VstsEnvironmentVariables { + return { + projectId: process.env["SYSTEM_TEAMPROJECTID"] || "", + projectName: process.env["SYSTEM_TEAMPROJECT"] || "", + buildNumber: process.env["BUILD_BUILDNUMBER"] || "", + buildId: Number(process.env["BUILD_BUILDID"]), + buildName: process.env["BUILD_DEFINITIONNAME"] || "", + buildRepositoryName: process.env["BUILD_REPOSITORY_NAME"] || "", + releaseName: process.env["RELEASE_RELEASENAME"] || "", + releaseUri: process.env["RELEASE_RELEASEWEBURL"] || "", + releaseId: process.env["RELEASE_RELEASEID"] || "", + teamCollectionUri: process.env["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"] || "", + defaultWorkingDirectory: process.env["SYSTEM_DEFAULTWORKINGDIRECTORY"] || "", + buildRepositoryProvider: process.env["BUILD_REPOSITORY_PROVIDER"] || "", + buildRepositoryUri: process.env["BUILD_REPOSITORY_URI"] || "", + buildSourceVersion: process.env["BUILD_SOURCEVERSION"] || "", + agentBuildDirectory: process.env["AGENT_BUILDDIRECTORY"] || "", + }; + } + + private getVcsTypeFromProvider(buildRepositoryProvider: string): string { + switch (buildRepositoryProvider) { + case "TfsGit": + case "GitHub": + return "Git"; + case "TfsVersionControl": + return "TFVC"; + default: + return buildRepositoryProvider; + } + } + + private createVstsConnection(environment: SystemEnvironmentVariables): vsts.WebApi { + const vstsAuthorization = tasks.getEndpointAuthorization("SystemVssConnection", true); + const token = vstsAuthorization?.parameters["AccessToken"] || ""; + const authHandler = vsts.getPersonalAccessTokenHandler(token); + return new vsts.WebApi(environment.teamCollectionUri, authHandler); + } + + private async getBuildBranch(client: vsts.WebApi, environment: VstsEnvironmentVariables): Promise { + const api = await client.getBuildApi(); + const build = await api.getBuild(environment.projectName, environment.buildId); + return build.sourceBranch; + } + + private async getBuildChanges(client: vsts.WebApi, environment: VstsEnvironmentVariables, logger: Logger): Promise { + const api = await client.getBuildApi(); + const gitApi = await client.getGitApi(); + + const changes = await api.getBuildChanges(environment.projectName, environment.buildId, undefined, 100000); + + if (environment.buildRepositoryProvider === "TfsGit") { + const promises = changes.map(async (x) => { + if (x.messageTruncated && x.id) { + const segments = x.location?.split("/"); + if (segments && segments.length >= 3) { + const repositoryId = segments[segments.length - 3]; + + try { + const commit = await gitApi.getCommit(x.id, repositoryId); + x.message = commit.comment; + } catch (error: unknown) { + if (error instanceof Error) { + logger.warn?.(`Using a truncated commit message for commit ${x.id}, because an error occurred while fetching the full message.${os.EOL}${error.message}`); + } + } + } + } + + return x; + }); + + await Promise.all(promises); + } + + return changes.map((change) => ({ Id: change.id || "", Comment: change.message || "" })); + } +} diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/createRelease.ts b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/createRelease.ts new file mode 100644 index 00000000..c912ecd2 --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/createRelease.ts @@ -0,0 +1,30 @@ +import os from "os"; +import { Client, CreateReleaseCommandV1, Logger, ReleaseRepository } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; + +// Returns the release number that was actually created in Octopus +export async function createReleaseFromInputs(client: Client, command: CreateReleaseCommandV1, task: TaskWrapper, logger: Logger): Promise { + logger.info?.("🐙 Creating a release in Octopus Deploy..."); + + try { + const repository = new ReleaseRepository(client, command.spaceName); + const response = await repository.create(command); + + if (command.IgnoreIfAlreadyExists) { + client.info(`🎉 Release ${response.ReleaseVersion} is ready for deployment!`); + } else { + client.info(`🎉 Release ${response.ReleaseVersion} created successfully!`); + } + + task.setOutputVariable("release_number", response.ReleaseVersion); + + return response.ReleaseVersion; + } catch (error: unknown) { + if (error instanceof Error) { + task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); + } else { + task.setFailure(`"Failed to execute command. ${error}`, true); + } + throw error; + } +} diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.png b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9a019843699bb5927160e412ef11d97c650c788a GIT binary patch literal 1005 zcmVk%*P0oINIyrmw^~=ep zUh@Tn3=|?pFC7tB8ZspGA(sd+}&(9FfzTgnm`3x3I+q8Kod}tT@(m}TIVqZX&3_XQYP)fUP+)f zLCT^;=)fn2Hur^e=j_66t|I|@3>XGVrDqn`GNegoA`0=lCr4G|zWsjR?zTLnD3D3% zx_MurDtWv>eF(A=XBJlnz5aV3M^4y*OE zwG@nwv~?UNM<;V;7gvwuFSiP#9DZ^GhCs6uDz(OnIkehMRtn1A8z6#g?CWbo6C*YZ zfu>YUN+aDSt9nAAc@9buw~PCBJq6#@VIv67D!NtGUAW}h02+W(vtE^2bq*AUC~x9Q zxEXV4CDO9FgAYa2nNQcotj~EDuD+3Uiys&8Xv{jTj#-t&zyv63dJ!Romgj5mgzjyW zuv0qbTV2A{G1)8DMuov-#;wz2U;><4Ih~TiEVUPWsXFA-va`CHYx2Sb0)Nr2QU!y+6|6;DjVmK>K5m_56tu4+ZHXgV2q8eXDux8)?8wnkLNAKE zwj)8r1;`BenUngEC98Aap?Qa)IU$8+mY%2v590OBS-Npyy`bXcS5e;d!kXG75-ISr zr4xz;?&6VkX3~j|iA45?HRWB$W2?gD`-p<|@Z5Us-920FH)wtT4+(^ZgEh-4xpwmZ b_~-ZoVZ&v~B~N@700000NkvXXu0mjfE}gsH literal 0 HcmV?d00001 diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.svg b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.svg new file mode 100644 index 00000000..8fcdc79e --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/index.ts b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/index.ts new file mode 100644 index 00000000..8ee1440d --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/index.ts @@ -0,0 +1,26 @@ +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; +import { Release } from "./release"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); + +new Release(connection, task, logger).run(); diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.test.ts b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.test.ts new file mode 100644 index 00000000..f1f97814 --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.test.ts @@ -0,0 +1,86 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; +import * as path from "path"; +import fs from "fs"; +import os from "os"; + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + }); + + test("all regular fields supplied", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("Channel", "Beta"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DefaultPackageVersion", "1.0.1"); + task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0"); + task.addVariableString("GitRef", "main"); + task.addVariableBoolean("IgnoreIfAlreadyExists", true); + + const command = createCommandFromInputs(logger, task); + expect(command.spaceName).toBe("Default"); + expect(command.ProjectName).toBe("Awesome project"); + expect(command.ChannelName).toBe("Beta"); + expect(command.ReleaseVersion).toBe("1.0.0"); + expect(command.PackageVersion).toBe("1.0.1"); + expect(command.Packages).toStrictEqual(["Step1:Foo:1.0.0", "Bar:2.0.0"]); + expect(command.GitRef).toBe("main"); + expect(command.IgnoreIfAlreadyExists).toBe(true); + + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); + + test("packages in additional fields", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0"); + task.addVariableString("AdditionalArguments", "--package Baz:2.5.0"); + + const command = createCommandFromInputs(logger, task); + expect(command.Packages).toStrictEqual(["Baz:2.5.0", "Step1:Foo:1.0.0", "Bar:2.0.0"]); + }); + + test("release notes file", async () => { + const tempOutDir = await fs.mkdtempSync(path.join(os.tmpdir(), "octopus_")); + const notesPath = path.join(tempOutDir, "notes.txt"); + + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("ReleaseNotesFile", notesPath); + + fs.writeFileSync(notesPath, "this is a release note"); + const command = createCommandFromInputs(logger, task); + expect(command.ReleaseNotes).toBe("this is a release note"); + }); + + test("specifying both release notes and release notes file causes error", async () => { + const tempOutDir = await fs.mkdtempSync(path.join(os.tmpdir(), "octopus_")); + const notesPath = path.join(tempOutDir, "notes.txt"); + + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("ReleaseNotes", "inline release notes"); + task.addVariableString("ReleaseNotesFile", notesPath); + + fs.writeFileSync(notesPath, "this is a release note"); + expect(() => createCommandFromInputs(logger, task)).toThrowError("cannot specify ReleaseNotes and ReleaseNotesFile"); + }); + + test("duplicate variable name, variables field takes precedence", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0"); + task.addVariableString("AdditionalArguments", "--package Bar:2.0.0"); + + const command = createCommandFromInputs(logger, task); + expect(command.Packages).toStrictEqual(["Bar:2.0.0", "Step1:Foo:1.0.0"]); + }); +}); diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.ts b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.ts new file mode 100644 index 00000000..a6c82c3e --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/inputCommandBuilder.ts @@ -0,0 +1,88 @@ +import commandLineArgs from "command-line-args"; +import shlex from "shlex"; +import { getLineSeparatedItems } from "../../Utils/inputs"; +import { CreateReleaseCommandV1, Logger } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { isNullOrWhitespace } from "../../../tasksLegacy/Utils/inputs"; +import fs from "fs"; + +export function createCommandFromInputs(logger: Logger, task: TaskWrapper): CreateReleaseCommandV1 { + const packages: string[] = []; + let defaultPackageVersion: string | undefined = undefined; + + const additionalArguments = task.getInput("AdditionalArguments"); + logger.debug?.("AdditionalArguments:" + additionalArguments); + if (additionalArguments) { + logger.warn?.("Additional arguments are no longer supported and will be removed in future versions. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields."); + const optionDefs = [ + { name: "package", type: String, multiple: true }, + { name: "defaultPackageVersion", type: String }, + { name: "packageVersion", type: String }, + ]; + const splitArgs = shlex.split(additionalArguments); + const options = commandLineArgs(optionDefs, { argv: splitArgs }); + logger.debug?.(JSON.stringify(options)); + for (const pkg of options.package) { + packages.push(pkg.trim()); + } + + // defaultPackageVersion and packageVersion both represent the default package version + if (options.defaultPackageVersion) { + defaultPackageVersion = options.defaultPackageVersion; + } + if (options.packageVersion) { + defaultPackageVersion = options.packageVersion; + } + } + + const packagesField = task.getInput("Packages"); + logger.debug?.("Packages:" + packagesField); + if (packagesField) { + const packagesFieldData = getLineSeparatedItems(packagesField).map((p) => p.trim()) || undefined; + if (packagesFieldData) { + for (const packageLine of packagesFieldData) { + const trimmedPackageLine = packageLine.trim(); + if (packages.indexOf(trimmedPackageLine) < 0) { + packages.push(trimmedPackageLine); + } + } + } + } + + const defaultPackageVersionField = task.getInput("DefaultPackageVersion"); + if (defaultPackageVersionField) { + defaultPackageVersion = defaultPackageVersionField; + } + + const command: CreateReleaseCommandV1 = { + spaceName: task.getInput("Space", true) || "", + ProjectName: task.getInput("Project", true) || "", + ReleaseVersion: task.getInput("ReleaseNumber"), + ChannelName: task.getInput("Channel"), + PackageVersion: defaultPackageVersion, + Packages: packages.length > 0 ? packages : undefined, + ReleaseNotes: task.getInput("ReleaseNotes"), + GitRef: task.getInput("GitRef"), + GitCommit: task.getInput("GitCommit"), + IgnoreIfAlreadyExists: task.getBoolean("IgnoreIfAlreadyExists") || undefined, + }; + + const releaseNotesFilePath = task.getInput("ReleaseNotesFile"); + + if (command.ReleaseNotes && releaseNotesFilePath) { + const message = "cannot specify ReleaseNotes and ReleaseNotesFile"; + task.setFailure(message); + throw new Error(message); + } + + if (releaseNotesFilePath) { + const releaseNotesFile = releaseNotesFilePath; + if (!isNullOrWhitespace(releaseNotesFile) && fs.existsSync(releaseNotesFile) && fs.lstatSync(releaseNotesFile).isFile()) { + command.ReleaseNotes = fs.readFileSync(releaseNotesFile).toString(); + } + } + + logger.debug?.(JSON.stringify(command)); + + return command; +} diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/release.ts b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/release.ts new file mode 100644 index 00000000..6b5bd80a --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/release.ts @@ -0,0 +1,48 @@ +import { Client, CreateReleaseCommandV1, Logger, Project, ProjectRepository, resolveSpaceId } from "@octopusdeploy/api-client"; +import { getDeepLink, OctoServerConnectionDetails } from "../../Utils/connection"; +import { createReleaseFromInputs } from "./createRelease"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import path from "path"; +import { getVstsEnvironmentVariables } from "../../../tasksLegacy/Utils/environment"; +import { v4 as uuidv4 } from "uuid"; +import * as tasks from "azure-pipelines-task-lib"; +import { getClient } from "../../Utils/client"; + +export class Release { + constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} + + public async run() { + try { + const command = createCommandFromInputs(this.logger, this.task); + const client = await getClient(this.connection, this.logger, "release", "create", 6); + const version = await createReleaseFromInputs(client, command, this.task, this.logger); + + await this.tryCreateSummary(client, command, version); + + this.task.setSuccess("Release creation succeeded."); + } catch (error: unknown) { + if (error instanceof Error) { + this.task.setFailure(`"Failed to successfully create release. ${error.message}${os.EOL}${error.stack}`, true); + } else { + this.task.setFailure(`"Failed to successfully create release. ${error}`, true); + } + throw error; + } + } + + private async tryCreateSummary(client: Client, command: CreateReleaseCommandV1, version: string) { + const spaceId = await resolveSpaceId(client, command.spaceName); + const projectRepo = new ProjectRepository(client, command.spaceName); + const projects = await projectRepo.list({ partialName: command.ProjectName }); + const matchedProjects = projects.Items.filter((p: Project) => p.Name.localeCompare(command.ProjectName) === 0); + if (matchedProjects.length === 1) { + const link = getDeepLink(this.connection.url, `${spaceId}/projects/${matchedProjects[0].Id}/deployments/releases/${version}`); + const markdown = `[Release ${version} created for '${matchedProjects[0].Name}'](${link})`; + const markdownFile = path.join(getVstsEnvironmentVariables().defaultWorkingDirectory, `${uuidv4()}.md`); + tasks.writeFile(markdownFile, markdown); + tasks.addAttachment("Distributedtask.Core.Summary", "Octopus Create Release", markdownFile); + } + } +} diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/task.json b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/task.json new file mode 100644 index 00000000..f92a116b --- /dev/null +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV7/task.json @@ -0,0 +1,154 @@ +{ + "id": "4E131B60-5532-4362-95B6-7C67D9841B4F", + "name": "OctopusCreateRelease", + "friendlyName": "Create Octopus Release", + "description": "Create a Release in Octopus Deploy", + "helpMarkDown": "set-by-pack.ps1", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [ ], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "versionControl", + "displayName": "Version Control", + "isExpanded": false + }, + { + "name": "additional", + "displayName": "Additional Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "Project", + "type": "string", + "label": "Project", + "defaultValue": "", + "required": true, + "helpMarkDown": "The project within Octopus. This must be the name of the project, not the id." + }, + { + "name": "ReleaseNumber", + "type": "string", + "label": "Release Number", + "defaultValue": "", + "required": false, + "helpMarkDown": "The number to use for this release. You can leave this blank if the release number is calculated by Octopus." + }, + { + "name": "Channel", + "type": "string", + "label": "Channel", + "defaultValue": "", + "required": false, + "helpMarkDown": "The [channel](https://g.octopushq.com/Channels) to use for the release. This must be the name of the channel, not the id." + }, + { + "name": "DefaultPackageVersion", + "type": "string", + "label": "Default Package Version", + "defaultValue": "", + "required": false, + "helpMarkDown": "Set this to provide a default package version to use for all packages on all steps. Can be used in conjunction with the Packages field, which can be used to override versions for specific packages." + }, + { + "name": "Packages", + "type": "multiLine", + "label": "Packages", + "defaultValue": "", + "required": false, + "helpMarkDown": "A multi-line list of version numbers to use for a package in the release. Format: StepName:Version or PackageID:Version or StepName:PackageName:Version. StepName, PackageID, and PackageName can be replaced with an asterisk ('*'). An asterisk will be assumed for StepName, PackageID, or PackageName if they are omitted." + }, + { + "name": "ReleaseNotes", + "type": "string", + "label": "Release Notes", + "defaultValue": "", + "required": false, + "helpMarkDown": "Octopus Release notes. This field supports markdown. To include newlines, you can use HTML linebreaks. Can only specify this if 'ReleaseNotesFile' is not supplied." + }, + { + "name": "ReleaseNotesFile", + "type": "string", + "label": "Release Notes File", + "defaultValue": "", + "required": false, + "helpMarkDown": "Octopus Release notes file. Path to a file that contains the release notes. Supports markdown. Can only specify this if 'ReleaseNotes' is not supplied." + }, + { + "name": "GitRef", + "type": "string", + "label": "Git Reference", + "defaultValue": "", + "required": false, + "helpMarkDown": "Git branch reference to use when creating the release for version controlled Projects.", + "groupName": "versionControl" + }, + { + "name": "GitCommit", + "type": "string", + "label": "Git Commit", + "defaultValue": "", + "required": false, + "helpMarkDown": "Git commit to use when creating the release for version controlled Projects. Use in conjunction with the gitRef parameter to select any previous commit.", + "groupName": "versionControl" + }, + { + "name": "IgnoreIfAlreadyExists", + "type": "boolean", + "label": "Ignore Existing Release", + "defaultValue": "", + "required": false, + "helpMarkDown": "If enabled will not attempt to create a new release if there is already one with the same version number", + "groupName": "additional" + }, + { + "name": "AdditionalArguments", + "type": "string", + "label": "Additional Arguments", + "defaultValue": "", + "required": false, + "helpMarkDown": "Additional arguments are no longer supported. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields.", + "groupName": "additional" + } + ], + "OutputVariables": [ + { + "name": "release_number", + "description": "The Octopus Deploy release number assigned to the Release." + } + ], + "instanceNameFormat": "Create Octopus Release", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/Deploy/DeployV7/createDeployment.ts b/source/tasks/Deploy/DeployV7/createDeployment.ts new file mode 100644 index 00000000..0ab07801 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/createDeployment.ts @@ -0,0 +1,49 @@ +import { Client, CreateDeploymentUntenantedCommandV1, DeploymentRepository, EnvironmentRepository, Logger } from "@octopusdeploy/api-client"; +import os from "os"; +import { TaskWrapper } from "../../Utils/taskInput"; +import { ExecutionResult } from "../../Utils/executionResult"; + +export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentUntenantedCommandV1, task: TaskWrapper, logger: Logger): Promise { + logger.info?.("🐙 Deploying a release in Octopus Deploy..."); + + try { + const deploymentRepository = new DeploymentRepository(client, command.spaceName); + const response = await deploymentRepository.create(command); + + client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`); + + if (response.DeploymentServerTasks.length === 0) { + throw new Error("Expected at least one deployment to be queued."); + } + if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) { + throw new Error("Server task id was not deserialized correctly."); + } + + const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId); + + const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length }); + + const envIds = deployments.Items.map((d) => d.EnvironmentId); + const envRepository = new EnvironmentRepository(client, command.spaceName); + const environments = await envRepository.list({ ids: envIds, take: envIds.length }); + + const results = response.DeploymentServerTasks.map((x) => { + return { + serverTaskId: x.ServerTaskId, + environmentName: environments.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].EnvironmentId)[0].Name, + type: "Deployment", + } as ExecutionResult; + }); + + task.setOutputVariable("server_tasks", JSON.stringify(results)); + + return results; + } catch (error: unknown) { + if (error instanceof Error) { + task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); + } else { + task.setFailure(`"Failed to execute command. ${error}`, true); + } + throw error; + } +} diff --git a/source/tasks/Deploy/DeployV7/deploy.ts b/source/tasks/Deploy/DeployV7/deploy.ts new file mode 100644 index 00000000..314c267d --- /dev/null +++ b/source/tasks/Deploy/DeployV7/deploy.ts @@ -0,0 +1,63 @@ +import { Client, CreateDeploymentUntenantedCommandV1, Logger, resolveSpaceId, ServerTask, SpaceServerTaskRepository } from "@octopusdeploy/api-client"; +import { getDeepLink, OctoServerConnectionDetails } from "../../Utils/connection"; +import { createDeploymentFromInputs } from "./createDeployment"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { getClient } from "../../Utils/client"; +import path from "path"; +import { getVstsEnvironmentVariables } from "../../../tasksLegacy/Utils/environment"; +import { v4 as uuidv4 } from "uuid"; +import { ExecutionResult } from "../../Utils/executionResult"; +import * as tasks from "azure-pipelines-task-lib"; + +export class Deploy { + constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} + + public async run() { + try { + const command = createCommandFromInputs(this.logger, this.task); + const client = await getClient(this.connection, this.logger, "release", "deploy", 6); + + const results = await createDeploymentFromInputs(client, command, this.task, this.logger); + await this.tryCreateSummary(client, command, results); + + this.task.setSuccess("Deployment succeeded."); + } catch (error: unknown) { + if (error instanceof Error) { + this.task.setFailure(`"Failed to successfully deploy release. ${error.message}${os.EOL}${error.stack}`, true); + } else { + this.task.setFailure(`"Failed to successfully deploy release. ${error}`, true); + } + throw error; + } + } + + private async tryCreateSummary(client: Client, command: CreateDeploymentUntenantedCommandV1, results: ExecutionResult[]) { + if (results.length === 0) { + return; + } + + const spaceId = await resolveSpaceId(client, command.spaceName); + const taskRepo = new SpaceServerTaskRepository(client, command.spaceName); + const allTasks = await taskRepo.getByIds<{ DeploymentId: string }>(results.map((t) => t.serverTaskId)); + const taskLookup = new Map>(); + allTasks.forEach(function (t) { + taskLookup.set(t.Id, t); + }); + + const url = this.connection.url; + let markdown = `${results[0].type} tasks\n\n`; + results.forEach(function (result) { + const task = taskLookup.get(result.serverTaskId); + if (task != null) { + const link = getDeepLink(url, `${spaceId}/deployments/${task.Arguments.DeploymentId}`); + markdown += `[${result.environmentName}](${link})\n`; + } + }); + + const markdownFile = path.join(getVstsEnvironmentVariables().defaultWorkingDirectory, `${uuidv4()}.md`); + tasks.writeFile(markdownFile, markdown); + tasks.addAttachment("Distributedtask.Core.Summary", "Octopus Deploy", markdownFile); + } +} diff --git a/source/tasks/Deploy/DeployV7/icon.png b/source/tasks/Deploy/DeployV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1da59821ad363bed76631aeb239b609aad92851a GIT binary patch literal 962 zcmV;z13mnSP)|2dvD&p_ssw|xMm3a2kd+ygLBxLyq9dq;;Brq zMt~zls_N8h?l2o^{G((2Q@k%VXn60JiUET}GT630)y`jKZXyDyBP3m-uJ8 z5IYzn1Zck(K-al7fM@BpR^73S7;u}p=KCT6B*+&j$-B=6&P@+X?agspmP0DN#Rw44 z)p!n^POuz+CfzF^F@b!;~OXQSdQNy%u!7)cn)S7pKs8?1@G~ zuSZNii{*3Kal85(Pe%n~c7FMU{3;GBSqQo^r5(-p$_MpMD^IH9_k0EvkO;IjSw2#x z2(2(OztfHvf_v&P%Yk#N>zz~e$p7{xYk!a$`Tmgz kL4jSIJFE?wzrnTRFYOt1l=o1aHvj+t07*qoM6N<$f({(C#sB~S literal 0 HcmV?d00001 diff --git a/source/tasks/Deploy/DeployV7/icon.svg b/source/tasks/Deploy/DeployV7/icon.svg new file mode 100644 index 00000000..f750c8e8 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/tasks/Deploy/DeployV7/index.ts b/source/tasks/Deploy/DeployV7/index.ts new file mode 100644 index 00000000..b683ec69 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/index.ts @@ -0,0 +1,26 @@ +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { Deploy } from "./deploy"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); + +new Deploy(connection, task, logger).run(); diff --git a/source/tasks/Deploy/DeployV7/inputCommandBuilder.test.ts b/source/tasks/Deploy/DeployV7/inputCommandBuilder.test.ts new file mode 100644 index 00000000..240ea628 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/inputCommandBuilder.test.ts @@ -0,0 +1,98 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + }); + + test("all regular fields supplied", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("Environments", "dev, test"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + + const command = createCommandFromInputs(logger, task); + expect(command.EnvironmentNames).toStrictEqual(["dev", "test"]); + expect(command.ProjectName).toBe("Awesome project"); + expect(command.ReleaseVersion).toBe("1.0.0"); + expect(command.spaceName).toBe("Default"); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); + + test("variables in additional fields", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("AdditionalArguments", "-v var3=value3 --variable var4=value4"); + task.addVariableString("Environments", "test"); + task.addVariableString("Project", "project 1"); + task.addVariableString("ReleaseNumber", "1.2.3"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2", var3: "value3", var4: "value4" }); + }); + + test("missing space", () => { + const t = () => { + task.addVariableString("Environments", "test"); + createCommandFromInputs(logger, task); + }; + expect(t).toThrowError("Input required: Space"); + }); + + test("duplicate variable name, variables field takes precedence", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("AdditionalArguments", "-v var1=value3"); + task.addVariableString("Environments", "test"); + task.addVariableString("Project", "project 1"); + task.addVariableString("ReleaseNumber", "1.2.3"); + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + }); + + test("handles escaped colons in variable names", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "Test\\:Variable: Testing3"); + task.addVariableString("Environments", "test"); + task.addVariableString("Project", "Test project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DeployForTenants", "Tenant 1"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ "Test:Variable": "Testing3" }); + }); + + test("handles multiple variables with escaped and unescaped colons", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "Long\\:Variable\\:Name: Value123\nTest\\:Variable: Value: With: Colons"); + task.addVariableString("Environments", "test"); + task.addVariableString("Project", "Test project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DeployForTenants", "Tenant 1"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ + "Long:Variable:Name": "Value123", + "Test:Variable": "Value: With: Colons" + }); + }); + + test("multiline environments", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Environments", "dev, test\nprod"); + task.addVariableString("Project", "project 1"); + task.addVariableString("ReleaseNumber", "1.2.3"); + const command = createCommandFromInputs(logger, task); + expect(command.EnvironmentNames).toStrictEqual(["dev", "test", "prod"]); + }); +}); diff --git a/source/tasks/Deploy/DeployV7/inputCommandBuilder.ts b/source/tasks/Deploy/DeployV7/inputCommandBuilder.ts new file mode 100644 index 00000000..1c27c6c6 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/inputCommandBuilder.ts @@ -0,0 +1,68 @@ +import commandLineArgs from "command-line-args"; +import shlex from "shlex"; +import { getLineSeparatedItems, parseVariableString } from "../../Utils/inputs"; +import { CreateDeploymentUntenantedCommandV1, Logger, PromptedVariableValues } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; + +export function createCommandFromInputs(logger: Logger, task: TaskWrapper): CreateDeploymentUntenantedCommandV1 { + const variablesMap: PromptedVariableValues | undefined = {}; + + const additionalArguments = task.getInput("AdditionalArguments"); + logger.debug?.("AdditionalArguments:" + additionalArguments); + if (additionalArguments) { + logger.warn?.("Additional arguments are no longer supported and will be removed in future versions. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields."); + const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }]; + const splitArgs = shlex.split(additionalArguments); + const options = commandLineArgs(optionDefs, { argv: splitArgs }); + logger.debug?.(JSON.stringify(options)); + for (const variable of options.variable) { + const variableMap = variable.split("=").map((x: string) => x.trim()); + variablesMap[variableMap[0]] = variableMap[1]; + } + } + + const variablesField = task.getInput("Variables"); + logger.debug?.("Variables: " + variablesField); + if (variablesField) { + const variables = getLineSeparatedItems(variablesField).map((p) => p.trim()) || undefined; + if (variables) { + for (const variable of variables) { + const [name, value] = parseVariableString(variable); + variablesMap[name] = value; + } + } + } + + const environmentsField = task.getInput("Environments", true); + let environments: string[] = []; + + if (environmentsField) { + const lines = getLineSeparatedItems(environmentsField); + lines.forEach((l) => { + environments = environments.concat(l.split(",").map((e: string) => e.trim())); + }); + } + logger.debug?.("Environments:" + environmentsField); + + const command: CreateDeploymentUntenantedCommandV1 = { + spaceName: task.getInput("Space", true) || "", + ProjectName: task.getInput("Project", true) || "", + ReleaseVersion: task.getInput("ReleaseNumber", true) || "", + EnvironmentNames: environments, + UseGuidedFailure: task.getBoolean("UseGuidedFailure") || undefined, + Variables: variablesMap || undefined, + }; + + const errors: string[] = []; + if (command.spaceName === "") { + errors.push("The Octopus space name is required."); + } + + if (errors.length > 0) { + throw new Error("Failed to successfully build parameters.\n" + errors.join("\n")); + } + + logger.debug?.(JSON.stringify(command)); + + return command; +} diff --git a/source/tasks/Deploy/DeployV7/task.json b/source/tasks/Deploy/DeployV7/task.json new file mode 100644 index 00000000..0be3eab1 --- /dev/null +++ b/source/tasks/Deploy/DeployV7/task.json @@ -0,0 +1,106 @@ +{ + "id": "8ca1d96a-151d-44b7-bc4f-9251e2ea6971", + "name": "OctopusDeployRelease", + "friendlyName": "Deploy Octopus Release", + "description": "Deploy an Octopus Deploy Release", + "helpMarkDown": "set-by-pack.ps1", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "Project", + "type": "string", + "label": "Project", + "defaultValue": "", + "required": true, + "helpMarkDown": "The project within Octopus. This must be the name of the project, not the id." + }, + { + "name": "ReleaseNumber", + "type": "string", + "label": "Release Number", + "defaultValue": "", + "required": true, + "helpMarkDown": "The number of the release to deploy." + }, + { + "name": "Environments", + "type": "multiLine", + "label": "Deploy to Environments", + "defaultValue": "", + "required": true, + "helpMarkDown": "List of environments to deploy to, one environment per line. (Note: multiple on a line, separated by commas, is supported to ease migration from earlier versions of the step but this format should be considered deprecated)" + }, + { + "name": "Variables", + "type": "multiLine", + "label": "Values for prompted variables", + "defaultValue": "", + "required": false, + "helpMarkDown": "Variable values to pass to the the deployment, use syntax `variable: value`" + }, + { + "name": "UseGuidedFailure", + "type": "boolean", + "label": "Use guided failure", + "defaultValue": "False", + "required": false, + "helpMarkDown": "Whether to use guided failure mode if errors occur during the deployment." + }, + { + "name": "AdditionalArguments", + "type": "string", + "label": "Additional Arguments (deprecated)", + "defaultValue": "", + "required": false, + "helpMarkDown": "Additional arguments are no longer supported. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields.", + "groupName": "advanced" + } + ], + "OutputVariables": [ + { + "name": "server_tasks", + "description": "List of server tasks representing the deployment server tasks." + } + ], + "instanceNameFormat": "Deploy Octopus Release", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/DeployTenant/TenantedDeployV7/createDeployment.ts b/source/tasks/DeployTenant/TenantedDeployV7/createDeployment.ts new file mode 100644 index 00000000..cd7d99c3 --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/createDeployment.ts @@ -0,0 +1,50 @@ +import { Client, CreateDeploymentTenantedCommandV1, DeploymentRepository, Logger, TenantRepository } from "@octopusdeploy/api-client"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { ExecutionResult } from "../../Utils/executionResult"; + +export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentTenantedCommandV1, task: TaskWrapper, logger: Logger): Promise { + logger.info?.("🐙 Deploying a release in Octopus Deploy..."); + + try { + const deploymentRepository = new DeploymentRepository(client, command.spaceName); + const response = await deploymentRepository.createTenanted(command); + + client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`); + + if (response.DeploymentServerTasks.length === 0) { + throw new Error("Expected at least one deployment to be queued."); + } + if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) { + throw new Error("Server task id was not deserialized correctly."); + } + + const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId); + + const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length }); + + const tenantIds = deployments.Items.map((d) => d.TenantId || ""); + const tenantRepository = new TenantRepository(client, command.spaceName); + const tenants = await tenantRepository.list({ ids: tenantIds, take: tenantIds.length }); + + const results = response.DeploymentServerTasks.map((x) => { + return { + serverTaskId: x.ServerTaskId, + environmentName: command.EnvironmentName, + tenantName: tenants.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].TenantId)[0].Name, + type: "Deployment", + } as ExecutionResult; + }); + + task.setOutputVariable("server_tasks", JSON.stringify(results)); + + return results; + } catch (error: unknown) { + if (error instanceof Error) { + task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); + } else { + task.setFailure(`"Failed to execute command. ${error}`, true); + } + throw error; + } +} diff --git a/source/tasks/DeployTenant/TenantedDeployV7/deploy.ts b/source/tasks/DeployTenant/TenantedDeployV7/deploy.ts new file mode 100644 index 00000000..ab9fa919 --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/deploy.ts @@ -0,0 +1,64 @@ +import { CreateDeploymentTenantedCommandV1, Logger, Client, resolveSpaceId, SpaceServerTaskRepository, ServerTask } from "@octopusdeploy/api-client"; +import { getDeepLink, OctoServerConnectionDetails } from "../../Utils/connection"; +import { createDeploymentFromInputs } from "./createDeployment"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { getClient } from "../../Utils/client"; +import { ExecutionResult } from "../../Utils/executionResult"; +import path from "path"; +import { getVstsEnvironmentVariables } from "../../../tasksLegacy/Utils/environment"; +import { v4 as uuidv4 } from "uuid"; +import * as tasks from "azure-pipelines-task-lib"; + +export class Deploy { + constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} + + public async run() { + try { + const command = createCommandFromInputs(this.logger, this.task); + const client = await getClient(this.connection, this.logger, "release", "deploy-tenanted", 6); + + const results = await createDeploymentFromInputs(client, command, this.task, this.logger); + + await this.tryCreateSummary(client, command, results); + + this.task.setSuccess("Deployment succeeded."); + } catch (error: unknown) { + if (error instanceof Error) { + this.task.setFailure(`"Failed to successfully deploy release. ${error.message}${os.EOL}${error.stack}`, true); + } else { + this.task.setFailure(`"Failed to successfully deploy release. ${error}`, true); + } + throw error; + } + } + + private async tryCreateSummary(client: Client, command: CreateDeploymentTenantedCommandV1, results: ExecutionResult[]) { + if (results.length === 0) { + return; + } + + const spaceId = await resolveSpaceId(client, command.spaceName); + const taskRepo = new SpaceServerTaskRepository(client, command.spaceName); + const allTasks = await taskRepo.getByIds<{ DeploymentId: string }>(results.map((t) => t.serverTaskId)); + const taskLookup = new Map>(); + allTasks.forEach(function (t) { + taskLookup.set(t.Id, t); + }); + + const url = this.connection.url; + let markdown = `${results[0].type} tasks for '${results[0].environmentName}' environment\n\n`; + results.forEach(function (result) { + const task = taskLookup.get(result.serverTaskId); + if (task != null) { + const link = getDeepLink(url, `${spaceId}/deployments/${task.Arguments.DeploymentId}`); + markdown += `[${result.tenantName}](${link})\n`; + } + }); + + const markdownFile = path.join(getVstsEnvironmentVariables().defaultWorkingDirectory, `${uuidv4()}.md`); + tasks.writeFile(markdownFile, markdown); + tasks.addAttachment("Distributedtask.Core.Summary", "Octopus Deploy Tenants", markdownFile); + } +} diff --git a/source/tasks/DeployTenant/TenantedDeployV7/icon.png b/source/tasks/DeployTenant/TenantedDeployV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1da59821ad363bed76631aeb239b609aad92851a GIT binary patch literal 962 zcmV;z13mnSP)|2dvD&p_ssw|xMm3a2kd+ygLBxLyq9dq;;Brq zMt~zls_N8h?l2o^{G((2Q@k%VXn60JiUET}GT630)y`jKZXyDyBP3m-uJ8 z5IYzn1Zck(K-al7fM@BpR^73S7;u}p=KCT6B*+&j$-B=6&P@+X?agspmP0DN#Rw44 z)p!n^POuz+CfzF^F@b!;~OXQSdQNy%u!7)cn)S7pKs8?1@G~ zuSZNii{*3Kal85(Pe%n~c7FMU{3;GBSqQo^r5(-p$_MpMD^IH9_k0EvkO;IjSw2#x z2(2(OztfHvf_v&P%Yk#N>zz~e$p7{xYk!a$`Tmgz kL4jSIJFE?wzrnTRFYOt1l=o1aHvj+t07*qoM6N<$f({(C#sB~S literal 0 HcmV?d00001 diff --git a/source/tasks/DeployTenant/TenantedDeployV7/icon.svg b/source/tasks/DeployTenant/TenantedDeployV7/icon.svg new file mode 100644 index 00000000..f750c8e8 --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/tasks/DeployTenant/TenantedDeployV7/index.ts b/source/tasks/DeployTenant/TenantedDeployV7/index.ts new file mode 100644 index 00000000..b683ec69 --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/index.ts @@ -0,0 +1,26 @@ +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { Deploy } from "./deploy"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); + +new Deploy(connection, task, logger).run(); diff --git a/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.test.ts b/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.test.ts new file mode 100644 index 00000000..b69f3573 --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.test.ts @@ -0,0 +1,106 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + }); + + test("all regular fields supplied", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("Environment", "dev"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DeployForTenants", "Tenant 1\nTenant 2"); + task.addVariableString("DeployForTenantTags", "tag set 1/tag 1\ntag set 1/tag 2"); + + const command = createCommandFromInputs(logger, task); + expect(command.EnvironmentName).toBe("dev"); + expect(command.ProjectName).toBe("Awesome project"); + expect(command.ReleaseVersion).toBe("1.0.0"); + expect(command.spaceName).toBe("Default"); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + expect(command.Tenants).toStrictEqual(["Tenant 1", "Tenant 2"]); + expect(command.TenantTags).toStrictEqual(["tag set 1/tag 1", "tag set 1/tag 2"]); + + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); + + test("variables in additional fields", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("AdditionalArguments", "-v var3=value3 --variable var4=value4"); + task.addVariableString("DeployForTenants", "Tenant 1"); + task.addVariableString("Project", "project 1"); + task.addVariableString("Environment", "test"); + task.addVariableString("ReleaseNumber", "1.2.3"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2", var3: "value3", var4: "value4" }); + }); + + test("missing space", () => { + const t = () => { + createCommandFromInputs(logger, task); + }; + expect(t).toThrowError("Failed to successfully build parameters: space name is required."); + }); + + test("duplicate variable name, variables field takes precedence", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("AdditionalArguments", "-v var1=value3"); + task.addVariableString("DeployForTenants", "Tenant 1"); + task.addVariableString("Project", "project 1"); + task.addVariableString("Environment", "test"); + task.addVariableString("ReleaseNumber", "1.2.3"); + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + }); + + test("handles escaped colons in variable names", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "Test\\:Variable: Testing3"); + task.addVariableString("Environment", "dev"); + task.addVariableString("Project", "Test project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DeployForTenants", "Tenant 1"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ "Test:Variable": "Testing3" }); + }); + + test("handles multiple variables with escaped and unescaped colons", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Variables", "Long\\:Variable\\:Name: Value123\nTest\\:Variable: Value: With: Colons"); + task.addVariableString("Environment", "dev"); + task.addVariableString("Project", "Test project"); + task.addVariableString("ReleaseNumber", "1.0.0"); + task.addVariableString("DeployForTenants", "Tenant 1"); + + const command = createCommandFromInputs(logger, task); + expect(command.Variables).toStrictEqual({ + "Long:Variable:Name": "Value123", + "Test:Variable": "Value: With: Colons" + }); + }); + + test("validate tenants and tags", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "project 1"); + task.addVariableString("ReleaseNumber", "1.2.3"); + task.addVariableString("Environment", "test"); + const t = () => { + createCommandFromInputs(logger, task); + }; + + expect(t).toThrowError("Failed to successfully build parameters.\nMust provide at least one tenant or tenant tag."); + }); +}); diff --git a/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.ts b/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.ts new file mode 100644 index 00000000..7a78701c --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/inputCommandBuilder.ts @@ -0,0 +1,74 @@ +import commandLineArgs from "command-line-args"; +import shlex from "shlex"; +import { getLineSeparatedItems, parseVariableString } from "../../Utils/inputs"; +import { CreateDeploymentTenantedCommandV1, Logger, PromptedVariableValues } from "@octopusdeploy/api-client"; +import { TaskWrapper } from "tasks/Utils/taskInput"; + +export function createCommandFromInputs(logger: Logger, task: TaskWrapper): CreateDeploymentTenantedCommandV1 { + const space = task.getInput("Space"); + if (!space) { + throw new Error("Failed to successfully build parameters: space name is required."); + } + + const variablesMap: PromptedVariableValues | undefined = {}; + + const additionalArguments = task.getInput("AdditionalArguments"); + logger.debug?.("AdditionalArguments:" + additionalArguments); + if (additionalArguments) { + logger.warn?.("Additional arguments are no longer supported and will be removed in future versions. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields."); + const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }]; + const splitArgs = shlex.split(additionalArguments); + const options = commandLineArgs(optionDefs, { argv: splitArgs }); + logger.debug?.(JSON.stringify(options)); + for (const variable of options.variable) { + const variableMap = variable.split("=").map((x: string) => x.trim()); + variablesMap[variableMap[0]] = variableMap[1]; + } + } + + const variablesField = task.getInput("Variables"); + logger.debug?.("Variables: " + variablesField); + if (variablesField) { + const variables = getLineSeparatedItems(variablesField).map((p) => p.trim()) || undefined; + if (variables) { + for (const variable of variables) { + const [name, value] = parseVariableString(variable); + variablesMap[name] = value; + } + } + } + + const tenantsField = task.getInput("DeployForTenants"); + logger.debug?.("Tenants: " + tenantsField); + const tagsField = task.getInput("DeployForTenantTags"); + logger.debug?.("Tenant Tags: " + tagsField); + const tags = getLineSeparatedItems(tagsField || "")?.map((t: string) => t.trim()) || []; + + const command: CreateDeploymentTenantedCommandV1 = { + spaceName: task.getInput("Space") || "", + ProjectName: task.getInput("Project", true) || "", + ReleaseVersion: task.getInput("ReleaseNumber", true) || "", + EnvironmentName: task.getInput("Environment", true) || "", + Tenants: getLineSeparatedItems(tenantsField || "")?.map((t: string) => t.trim()) || [], + TenantTags: tags, + UseGuidedFailure: task.getBoolean("UseGuidedFailure") || undefined, + Variables: variablesMap || undefined, + }; + + const errors: string[] = []; + if (command.spaceName === "") { + errors.push("The Octopus space name is required."); + } + + if (command.TenantTags.length === 0 && command.Tenants.length === 0) { + errors.push("Must provide at least one tenant or tenant tag."); + } + + if (errors.length > 0) { + throw new Error("Failed to successfully build parameters.\n" + errors.join("\n")); + } + + logger.debug?.(JSON.stringify(command)); + + return command; +} diff --git a/source/tasks/DeployTenant/TenantedDeployV7/task.json b/source/tasks/DeployTenant/TenantedDeployV7/task.json new file mode 100644 index 00000000..c91c713f --- /dev/null +++ b/source/tasks/DeployTenant/TenantedDeployV7/task.json @@ -0,0 +1,122 @@ +{ + "id": "a847e2d1-5435-4d52-a774-6d300953e85f", + "name": "OctopusDeployReleaseTenanted", + "friendlyName": "Deploy Octopus Release to Tenants", + "description": "Deploy an Octopus Deploy Release to Tenants", + "helpMarkDown": "set-by-pack.ps1", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "Project", + "type": "string", + "label": "Project", + "defaultValue": "", + "required": true, + "helpMarkDown": "The project within Octopus. This must be the name of the project, not the id." + }, + { + "name": "ReleaseNumber", + "type": "string", + "label": "Release Number", + "defaultValue": "", + "required": true, + "helpMarkDown": "The number of the release to deploy." + }, + { + "name": "Environment", + "type": "string", + "label": "Deploy to Environment", + "defaultValue": "", + "required": true, + "helpMarkDown": "The " + }, + { + "name": "DeployForTenants", + "type": "multiline", + "label": "Tenant(s)", + "defaultValue": "", + "required": false, + "helpMarkDown": "Deploy the release for this list of tenants. Wildcard '*' will deploy to all tenants currently able to deploy to the above provided environment." + }, + { + "name": "DeployForTenantTags", + "type": "multiLine", + "label": "Tenant tag(s)", + "defaultValue": "", + "required": false, + "helpMarkDown": "Deploy the release for tenants who match these tags and are ready to deploy to the provided environment." + }, + { + "name": "Variables", + "type": "multiLine", + "label": "Values for prompted variables", + "defaultValue": "", + "required": false, + "helpMarkDown": "Variable values to pass to the the deployment, use syntax `variable: value`" + }, + { + "name": "UseGuidedFailure", + "type": "boolean", + "label": "Use guided failure", + "defaultValue": "False", + "required": false, + "helpMarkDown": "Whether to use guided failure mode if errors occur during the deployment." + }, + { + "name": "AdditionalArguments", + "type": "string", + "label": "Additional Arguments (deprecated)", + "defaultValue": "", + "required": false, + "helpMarkDown": "Additional arguments are no longer supported. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields.", + "groupName": "advanced" + } + ], + "OutputVariables": [ + { + "name": "server_tasks", + "description": "List of server tasks representing the deployment server tasks." + } + ], + "instanceNameFormat": "Deploy Octopus Release Tenants", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.test.ts b/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.test.ts new file mode 100644 index 00000000..36e92a63 --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.test.ts @@ -0,0 +1,124 @@ +import { DownloadEndpointRetriever } from "./downloadEndpointRetriever"; +import { mkdtemp, rm } from "fs/promises"; +import * as path from "path"; +import os from "os"; +import express from "express"; +import { Server } from "http"; +import { AddressInfo } from "net"; +import { Logger } from "@octopusdeploy/api-client"; + +describe("OctopusInstaller", () => { + let tempOutDir: string; + let releasesUrl: string; + let server: Server; + + const msgs: string[] = []; + const logger: Logger = { + debug: (message) => { + msgs.push(message); + }, + info: (message) => { + msgs.push(message); + }, + warn: (message) => { + msgs.push(message); + }, + error: (message, err) => { + if (err !== undefined) { + msgs.push(err.message); + } else { + msgs.push(message); + } + }, + }; + + jest.setTimeout(100000); + + beforeEach(async () => { + tempOutDir = await mkdtemp(path.join(os.tmpdir(), "octopus_")); + process.env["AGENT_TOOLSDIRECTORY"] = tempOutDir; + process.env["AGENT_TEMPDIRECTORY"] = tempOutDir; + + const app = express(); + + app.get("/OctopusDeploy/cli/main/releases.json", (_, res) => { + const latestToolsPayload = `[ + { + "tag_name": "v7.4.1", + "assets": [ + { + "name": "octopus_7.4.1_windows_amd64.zip", + "browser_download_url": "http://localhost:${address.port}/OctopusDeploy/cli/releases/download/v7.4.1/octopus_7.4.1_windows_amd64.zip" + } + ] + }, + { + "tag_name": "v8.0.0", + "assets": [ + { + "name": "octopus_8.0.0_windows_amd64.zip", + "browser_download_url": "http://localhost:${address.port}/OctopusDeploy/cli/releases/download/v8.0.0/octopus_8.0.0_windows_amd64.zip" + } + ] + }, + { + "tag_name": "v8.2.0", + "assets": [ + { + "name": "octopus_8.2.0_windows_amd64.zip", + "browser_download_url": "http://localhost:${address.port}/OctopusDeploy/cli/releases/download/v8.2.0/octopus_8.2.0_windows_amd64.zip" + } + ] + } + ]`; + + res.send(latestToolsPayload); + }); + + app.get("/OctopusDeploy/cli/releases/download/v7.4.1/octopus_7.4.1_windows_amd64.zip", (_, res) => { + res.sendStatus(200); + }); + + app.get("/OctopusDeploy/cli/releases/download/v8.0.0/octopus_8.0.0_windows_amd64.zip", (_, res) => { + res.sendStatus(200); + }); + + app.get("/OctopusDeploy/cli/releases/download/v8.2.0/octopus_8.2.0_windows_amd64.zip", (_, res) => { + res.sendStatus(200); + }); + + server = await new Promise((resolve) => { + const r = app.listen(() => { + resolve(r); + }); + }); + + const address = server.address() as AddressInfo; + releasesUrl = `http://localhost:${address.port}/OctopusDeploy/cli/main/releases.json`; + }); + + afterEach(async () => { + await new Promise((resolve) => { + server.close(() => { + resolve(); + }); + }); + + await rm(tempOutDir, { recursive: true }); + }); + + test("Installs specific version", async () => { + const result = await new DownloadEndpointRetriever(releasesUrl, "win32", "amd64", logger).getEndpoint("8.0.0"); + expect(result.version).toBe("8.0.0"); + }); + + test("Installs wildcard version", async () => { + const result = await new DownloadEndpointRetriever(releasesUrl, "win32", "amd64", logger).getEndpoint("7.*"); + expect(result.version).toBe("7.4.1"); + }); + + test("Installs latest of latest", async () => { + const result = await new DownloadEndpointRetriever(releasesUrl, "win32", "amd64", logger).getEndpoint("*"); + expect(result.version).toBe("8.2.0"); + }); +}); diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.ts b/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.ts new file mode 100644 index 00000000..a7b5515e --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/downloadEndpointRetriever.ts @@ -0,0 +1,152 @@ +import * as tasks from "azure-pipelines-task-lib/task"; +import * as TypedRestClient from "typed-rest-client"; +import { IProxyConfiguration } from "typed-rest-client/Interfaces"; +import { OctopusCLIVersionResolver } from "./octopusCLIVersionResolver"; +import { Logger } from "@octopusdeploy/api-client"; + +const downloadsRegEx = + /^.*_(?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)_(?linux|macOS|windows)_(?arm64|amd64).(?tar.gz|zip)$/i; + +type DownloadOption = { + version: string; + location: string; + extension: string; + platform?: string; + architecture?: string; +}; + +export interface Endpoint { + downloadUrl: string; + version: string; + architecture: string; +} + +interface VersionsResponse { + versions: string[]; + downloads: DownloadOption[]; +} + +interface GitHubRelease { + tag_name: string; + assets: GitHubReleaseAsset[]; +} + +interface GitHubReleaseAsset { + version: string; + name: string; + browser_download_url: string; +} + +export class DownloadEndpointRetriever { + constructor(readonly releasesUrl: string, readonly osPlatform: string, readonly osArch: string, readonly logger: Logger) {} + + public async getEndpoint(versionSpec: string): Promise { + this.logger.debug?.(`Attempting to contact ${this.releasesUrl} to find Octopus CLI tool version ${versionSpec}`); + + const versionsResponse: VersionsResponse | null = await this.getVersions(); + if (versionsResponse === null) { + throw Error(`Unable to get versions...`); + } + + const version = new OctopusCLIVersionResolver(versionsResponse.versions).getVersion(versionSpec); + if (version === null) { + throw Error(`The version specified (${version}) is not available to download.`); + } + + this.logger.debug?.(`Attempting to find Octopus CLI version ${version}`); + + let platform = "linux"; + switch (this.osPlatform) { + case "darwin": + platform = "macOS"; + break; + case "win32": + platform = "windows"; + break; + } + + let arch = "amd64"; + switch (this.osArch) { + case "arm": + case "arm64": + arch = "arm64"; + break; + } + + this.logger.debug?.(`Attempting download for platform '${platform}' and architecture ${this.osArch}`); + + let downloadUrl: string | undefined; + + for (const download of versionsResponse.downloads) { + if (download.version === version && download.platform === platform && download.architecture === arch) { + downloadUrl = download.location; + } + } + + if (downloadUrl === undefined || downloadUrl === null) { + throw Error(`Failed to resolve endpoint URL to download: ${downloadUrl}`); + } + + this.logger.debug?.(`Checking status of download url '${downloadUrl}'`); + + const http = this.restClient(); + const statusCode = (await http.client.head(downloadUrl)).message.statusCode; + if (statusCode !== 200) { + throw Error(`Octopus CLI version not found: ${version}`); + } + + this.logger.info?.(`✓ Octopus CLI version found: ${version}`); + return { downloadUrl, version, architecture: arch }; + } + + async getVersions(): Promise { + const githubReleasesClient = this.restClient(); + + const releasesResponse = (await githubReleasesClient.get(this.releasesUrl)).result; + if (releasesResponse === null) { + return null; + } + + const ext: string = this.osPlatform === "win32" ? "zip" : "tar.gz"; + + const downloads = releasesResponse.flatMap((v) => + v.assets + .filter((a) => downloadsRegEx.test(a.name)) + .map((a) => { + const matches = downloadsRegEx.exec(a.name); + + return { + version: matches?.groups?.version || v.tag_name.slice(1), + location: a.browser_download_url, + extension: matches?.groups?.extension || `.${ext}`, + platform: matches?.groups?.platform || undefined, + architecture: matches?.groups?.architecture || undefined, + }; + }) + ); + const versions = downloads.map((d) => d.version); + return { + versions, + downloads, + }; + } + + private restClient() { + const proxyConfiguration = tasks.getHttpProxyConfiguration(this.releasesUrl); + let proxySettings: IProxyConfiguration | undefined = undefined; + + if (proxyConfiguration) { + this.logger.debug?.( + "Using agent configured proxy. If this command should not be sent via the agent's proxy, you might need to add or modify the agent's .proxybypass file. See https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/proxy#specify-proxy-bypass-urls." + ); + proxySettings = { + proxyUrl: proxyConfiguration.proxyUrl, + proxyUsername: proxyConfiguration.proxyUsername, + proxyPassword: proxyConfiguration.proxyPassword, + proxyBypassHosts: proxyConfiguration.proxyBypassHosts, + }; + } + + return new TypedRestClient.RestClient("OctoTFS/Tasks", this.releasesUrl, undefined, { proxy: proxySettings }); + } +} diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/icon.png b/source/tasks/OctoInstaller/OctoInstallerV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..295e6e85702eb5ffd68b8d7c00f6a7440c3dd7be GIT binary patch literal 817 zcmV-11J3-3P)(*1K~#7F?N&`v z6G0Sy-IFSyRBAkdvr=$jh8rX+T#^G&j3*FQF4H+dQRM;1$mWO#~G82W9iAY5VD{TwgX%Jv?I8TyTKxV2-RY(YyGR`RqVYH_*4&X+#DIz zjH#v(LF9p$lS_eHgb1$aC9ty<1B#W-)uNvSWWUWwm6u&!t>~H`iiXVdo53&vLgoYa zoo%3T*n^zm(5lM3`o2C?KrBMw&9}miQ)3S%2VWofK?JghL%t~pqR8nPEI{Da@xhMA z1!?NnRT#1y3r&2aDiu)dJi`n=D*(s^`FmJ(3fpXexT`XOw z=X}XIN=Pr%!uzHOhvyyF@@of1#cW3N@h8(q>dcj3=rmySms*HD|FJsBQL4z$%-*?r v89ODL)^D)>&vlRraiu$U;`fR_;Lh;}EeSZj&%90m00000NkvXXu0mjfxE^r- literal 0 HcmV?d00001 diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/icon.svg b/source/tasks/OctoInstaller/OctoInstallerV7/icon.svg new file mode 100644 index 00000000..f1a574a2 --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/index.ts b/source/tasks/OctoInstaller/OctoInstallerV7/index.ts new file mode 100644 index 00000000..3ef53562 --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/index.ts @@ -0,0 +1,35 @@ +import * as tasks from "azure-pipelines-task-lib/task"; +import { Logger } from "@octopusdeploy/api-client"; +import { Installer } from "./installer"; +import os from "os"; + +async function run() { + try { + const version = tasks.getInput("octopusVersion", true) || ""; + + const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, + }; + + new Installer("https://raw.githubusercontent.com/OctopusDeploy/cli/main/releases.json", os.platform(), os.arch(), logger).run(version); + } catch (error: unknown) { + if (error instanceof Error) { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); + } else { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); + } + } +} + +run(); diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/installer.ts b/source/tasks/OctoInstaller/OctoInstallerV7/installer.ts new file mode 100644 index 00000000..1ed24bcc --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/installer.ts @@ -0,0 +1,56 @@ +import * as tools from "azure-pipelines-tool-lib"; +import * as tasks from "azure-pipelines-task-lib"; +import path from "path"; +import { executeWithSetResult } from "../../Utils/octopusTasks"; +import { DownloadEndpointRetriever, Endpoint } from "./downloadEndpointRetriever"; +import { Logger } from "@octopusdeploy/api-client"; + +const TOOL_NAME = "octopus"; + +export class Installer { + constructor(readonly releasesUrl: string, readonly osPlatform: string, readonly osArch: string, readonly logger: Logger) {} + + public async run(versionSpec: string) { + await executeWithSetResult( + async () => { + const endpoint = await new DownloadEndpointRetriever(this.releasesUrl, this.osPlatform, this.osArch, this.logger).getEndpoint(versionSpec); + let toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); + + if (!toolPath) { + toolPath = await this.installTool(endpoint); + toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); + } + + tools.prependPath(toolPath); + }, + `Installed octopus v${versionSpec}.`, + `Failed to install octopus v${versionSpec}.` + ); + } + + private async installTool(endpoint: Endpoint): Promise { + if (!endpoint.downloadUrl) { + throw Error(`Failed to download Octopus CLI tool version ${endpoint.version}.`); + } + + const downloadPath = await tools.downloadTool(endpoint.downloadUrl); + + // + // Extract + // + let extPath: string; + if (this.osPlatform == "win32") { + extPath = tasks.getVariable("Agent.TempDirectory") || ""; + if (!extPath) { + throw new Error("Expected Agent.TempDirectory to be set"); + } + + extPath = path.join(extPath, "n"); // use as short a path as possible due to nested node_modules folders + extPath = await tools.extractZip(downloadPath, extPath); + } else { + extPath = await tools.extractTar(downloadPath); + } + + return await tools.cacheDir(extPath, TOOL_NAME, endpoint.version); + } +} diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.test.ts b/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.test.ts new file mode 100644 index 00000000..eb33e38f --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.test.ts @@ -0,0 +1,67 @@ +import { OctopusCLIVersionResolver } from "./octopusCLIVersionResolver"; + +describe("OctopusCLIVersionFetcher tests", () => { + test("Gets latest", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); + + const version = fetcher.getVersion("*"); + + expect(version).toBe("2.1.0"); + }); + + test("Fixed returns fixed version", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); + + const version = fetcher.getVersion("1.0.0"); + + expect(version).toBe("1.0.0"); + }); + + test("When version no exists", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); + + expect(() => fetcher.getVersion("5.0.0")).toThrow(); + }); + + test("Get latest minor", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); + + const version = fetcher.getVersion("2.*"); + + expect(version).toBe("2.1.0"); + }); + + test("Get latest patch", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0"]); + + const version = fetcher.getVersion("1.0.*"); + + expect(version).toBe("1.0.3"); + }); + + test("When version spec if invalid", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); + + expect(() => fetcher.getVersion("*.*")).toThrow(); + + expect(() => fetcher.getVersion("*.2")).toThrow(); + + expect(() => fetcher.getVersion("sdfs")).toThrow(); + }); + + test("Get latest major", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); + + const version = fetcher.getVersion("2"); + + expect(version).toBe("2.1.0"); + }); + + test("Get latest not pre-release", () => { + const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0", "4.0.0-pre"]); + + const version = fetcher.getVersion("*"); + + expect(version).toBe("3.0.0"); + }); +}); diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.ts b/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.ts new file mode 100644 index 00000000..9cca5ca6 --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/octopusCLIVersionResolver.ts @@ -0,0 +1,72 @@ +import { maxSatisfying, valid } from "semver"; + +export class OctopusCLIVersionResolver { + constructor(readonly versions: string[]) {} + + getVersion(versionSpec: string): string { + if (versionSpec === "*") { + const version = maxSatisfying(this.versions, versionSpec); + if (!version) { + throw new Error(`A version satisfying '${versionSpec}' could not be found.`); + } + + return version; + } + + if (valid(versionSpec) === null) { + const parts = versionSpec.split("."); + if (parts.length > 3) { + throw new Error(`The '${versionSpec}' is an invalid version, a version needs to be a maximum of three parts.`); + } + + if (parts.length === 1) { + const majorVersion = parts[0]; + if (Number.isNaN(Number.parseInt(majorVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its major part.`); + } + } + + if (parts.length === 2) { + const majorVersion = parts[0]; + const minorVersion = parts[1]; + + // the major version number must be a number + if (Number.isNaN(Number.parseInt(majorVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`); + } + + if (minorVersion !== "*" && Number.isNaN(Number.parseInt(minorVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its minor part.`); + } + } + + if (parts.length === 3) { + const majorVersion = parts[0]; + const minorVersion = parts[1]; + const patchVersion = parts[2]; + + // the major version number must be a number + if (Number.isNaN(Number.parseInt(majorVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`); + } + + // the minor version number must be a number + if (Number.isNaN(Number.parseInt(minorVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number for its minor part.`); + } + + if (patchVersion !== "*" && Number.isNaN(Number.parseInt(patchVersion))) { + throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its patch part.`); + } + } + } + + const version = maxSatisfying(this.versions, versionSpec); + + if (!version) { + throw new Error(`A version satisfying '${versionSpec}' could not be found.`); + } + + return version; + } +} diff --git a/source/tasks/OctoInstaller/OctoInstallerV7/task.json b/source/tasks/OctoInstaller/OctoInstallerV7/task.json new file mode 100644 index 00000000..eeee4f13 --- /dev/null +++ b/source/tasks/OctoInstaller/OctoInstallerV7/task.json @@ -0,0 +1,47 @@ +{ + "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3", + "name": "OctoInstaller", + "friendlyName": "Octopus CLI Installer", + "description": "Install a specific version of the Octopus CLI (Go)", + "helpMarkDown": "Install a specific version of the Octopus CLI (Go)", + "category": "Tool", + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "satisfies": ["octopus"], + "demands": [], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "octopusVersion", + "type": "string", + "label": "Octopus CLI Version", + "required": true, + "helpMarkDown": "Specify version of Octopus CLI to install.
Versions can be given in the following formats
  • `1.*` => Install latest in major version.
  • `7.3.*` => Install latest in major and minor version.
  • `8.0.1` => Install exact version.
  • `*` => Install whatever is latest.

  • Find the value of `version` for installing Octopus CLI, from the [this link](https://raw.githubusercontent.com/OctopusDeploy/cli/main/releases.json)." + } + ], + "instanceNameFormat": "Install Octopus CLI tool version $(version)", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} diff --git a/source/tasks/PackNuGet/PackNuGetV7/create-package.ts b/source/tasks/PackNuGet/PackNuGetV7/create-package.ts new file mode 100644 index 00000000..4760a8d1 --- /dev/null +++ b/source/tasks/PackNuGet/PackNuGetV7/create-package.ts @@ -0,0 +1,38 @@ +import { Logger, NuGetPackageBuilder, NuGetPackArgs } from "@octopusdeploy/api-client"; +import path from "path"; +import fs from "fs"; +import { InputParameters } from "./input-parameters"; +import { isNullOrWhitespace } from "../../../tasksLegacy/Utils/inputs"; + +type createPackageResult = { + filePath: string; + filename: string; +}; + +export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise { + const builder = new NuGetPackageBuilder(); + const inputs: NuGetPackArgs = { + packageId: parameters.packageId, + version: parameters.packageVersion, + outputFolder: parameters.outputPath, + basePath: parameters.sourcePath, + inputFilePatterns: parameters.include, + overwrite: parameters.overwrite, + logger, + }; + + inputs.nuspecArgs = { + title: parameters.nuGetTitle, + description: parameters.nuGetDescription, + authors: parameters.nuGetAuthors, + releaseNotes: parameters.nuGetReleaseNotes, + }; + + if (!isNullOrWhitespace(parameters.nuGetReleaseNotesFile) && fs.existsSync(parameters.nuGetReleaseNotesFile) && fs.lstatSync(parameters.nuGetReleaseNotesFile).isFile()) { + inputs.nuspecArgs.releaseNotes = fs.readFileSync(parameters.nuGetReleaseNotesFile).toString(); + } + + const packageFilename = await builder.pack(inputs); + + return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename }; +} diff --git a/source/tasks/PackNuGet/PackNuGetV7/icon.png b/source/tasks/PackNuGet/PackNuGetV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..65e99099c863501697a27239ff6665c148fb9137 GIT binary patch literal 849 zcmV-X1FrmuP)_t`PqKPn6|>;R|5DfGkj6T`yLuJh*%TSv>5@ zh5UiBkSZzJ8M~9s;>IKnxE3CGRmmhh-P1kOJ>3m(gKGw+2@jtvuOsSenyyjrA9{N- z`JY{QzD_~``s6sllq0PmiJocslK zJ5BRFH=rTNl`%X85i|D+nFT^Ka{_TcPsa2V#_Xz7(Wt6eup1D<&m=3=)IvOld?Qw9 zXuW(?ZH_s$@V>Z%CZ|! zXl|Uy88?v;W=o%2SyZLf?2rM-%GW9i&JIXXEknd2uI)+L9b{Y%!m84}k5G(&o+fGd zIuO+Q-ml4vDudhhc3+g`XDU+KDP9i465N^!&wUtP#SHzW&!)xgkOMe#^Z=UG4M1VRo#Hs zkBuFv3(&G_RM?7&S_asF0R9qD0#afrt{3^$F##EA({-)IvcL`I@-a0X7F+>Q>ZR<* z$|vQ~rY>sh(z1v$asjIICEf*G0i5?a`%!i~=qqorkNeYs0`UkgdII`~m&rHfmAg?Q z@;|)sDI?C4AvU!?M)x=Yw`P^OHEQW}o&7%s<381ZFY&D2IF_e}oZZLLS)y6rBdtzsc#y9d~4hIxeJiuil-=2a)eNY5joGr8aAfB*2Dg9XE@9 bgKNi6+y@+Og?ElI00000NkvXXu0mjfw0(Du literal 0 HcmV?d00001 diff --git a/source/tasks/PackNuGet/PackNuGetV7/icon.svg b/source/tasks/PackNuGet/PackNuGetV7/icon.svg new file mode 100644 index 00000000..69e50017 --- /dev/null +++ b/source/tasks/PackNuGet/PackNuGetV7/icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/source/tasks/PackNuGet/PackNuGetV7/index.ts b/source/tasks/PackNuGet/PackNuGetV7/index.ts new file mode 100644 index 00000000..bb22cd92 --- /dev/null +++ b/source/tasks/PackNuGet/PackNuGetV7/index.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import * as tasks from "azure-pipelines-task-lib/task"; +import { Logger } from "@octopusdeploy/api-client"; +import { getInputs } from "./input-parameters"; +import os from "os"; +import { createPackageFromInputs } from "./create-package"; + +async function run() { + try { + const parameters = getInputs(); + + const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, + }; + + const result = await createPackageFromInputs(parameters, logger); + + tasks.setVariable("package_file_path", result.filePath); + tasks.setVariable("package_filename", result.filename); + + tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded"); + } catch (error: unknown) { + if (error instanceof Error) { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); + } else { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); + } + } +} + +run(); diff --git a/source/tasks/PackNuGet/PackNuGetV7/input-parameters.ts b/source/tasks/PackNuGet/PackNuGetV7/input-parameters.ts new file mode 100644 index 00000000..475182dc --- /dev/null +++ b/source/tasks/PackNuGet/PackNuGetV7/input-parameters.ts @@ -0,0 +1,33 @@ +import * as tasks from "azure-pipelines-task-lib/task"; +import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs"; +import { getLineSeparatedItems } from "../../Utils/inputs"; + +export interface InputParameters { + packageId: string; + packageVersion: string; + outputPath: string; + sourcePath: string; + include: string[]; + nuGetDescription: string; + nuGetAuthors: string[]; + nuGetTitle?: string; + nuGetReleaseNotes?: string; + nuGetReleaseNotesFile?: string; + overwrite?: boolean; +} + +export const getInputs = (): InputParameters => { + return { + packageId: tasks.getInput("PackageId", true) || "", + packageVersion: tasks.getInput("PackageVersion", true) || "", + outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".", + sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".", + include: getLineSeparatedItems(tasks.getInput("Include") || "**"), + nuGetDescription: tasks.getInput("NuGetDescription", true) || "", + nuGetAuthors: getLineSeparatedItems(tasks.getInput("NuGetAuthors", true) || ""), + nuGetTitle: tasks.getInput("NuGetTitle"), + nuGetReleaseNotes: tasks.getInput("NuGetReleaseNotes"), + nuGetReleaseNotesFile: tasks.getInput("NuGetReleaseNotesFile", false), + overwrite: tasks.getBoolInput("Overwrite"), + }; +}; diff --git a/source/tasks/PackNuGet/PackNuGetV7/task.json b/source/tasks/PackNuGet/PackNuGetV7/task.json new file mode 100644 index 00000000..4159e4ee --- /dev/null +++ b/source/tasks/PackNuGet/PackNuGetV7/task.json @@ -0,0 +1,131 @@ +{ + "id": "72e7a1b6-19bc-48e6-8d20-a81f201d65a3", + "name": "OctopusPackNuGet", + "friendlyName": "Package Application for Octopus - NuGet", + "description": "Package your application into a NuGet file.", + "helpMarkDown": "set-by-pack.ps1", + "category": "Package", + "visibility": ["Build", "Release"], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "PackageId", + "type": "string", + "label": "Package ID", + "defaultValue": "", + "required": true, + "helpMarkDown": "The ID of the package. e.g. MyCompany.App" + }, + { + "name": "PackageVersion", + "type": "string", + "label": "Package Version", + "defaultValue": "", + "required": true, + "helpMarkDown": "The version of the package; must be a valid [SemVer](http://semver.org/) version." + }, + { + "name": "SourcePath", + "type": "filePath", + "label": "Source Path", + "defaultValue": "", + "required": false, + "helpMarkDown": "The folder containing the files and folders to package. Defaults to working directory." + }, + { + "name": "OutputPath", + "type": "filePath", + "label": "Output Path", + "defaultValue": "", + "required": false, + "helpMarkDown": "The directory into which the generated package will be written. Defaults to working directory." + }, + { + "name": "Include", + "type": "multiLine", + "label": "Include", + "defaultValue": "", + "required": false, + "helpMarkDown": "File patterns to include, relative to the root path. e.g. /bin/*.dll. Defaults to '**' if not specified." + }, + { + "name": "NuGetDescription", + "type": "string", + "label": "Description", + "defaultValue": "", + "required": true, + "helpMarkDown": "A description to add to the NuGet package metadata." + }, + { + "name": "NuGetAuthors", + "type": "multiLine", + "label": "Authors", + "defaultValue": "", + "required": true, + "helpMarkDown": "Authors to add to the NuGet package metadata." + }, + { + "name": "NuGetTitle", + "type": "string", + "label": "Title", + "defaultValue": "", + "required": false, + "helpMarkDown": "A title to add to the NuGet package metadata." + }, + { + "name": "NuGetReleaseNotes", + "type": "string", + "label": "Release Notes", + "defaultValue": "", + "required": false, + "helpMarkDown": "Release notes to add to the NuGet package metadata." + }, + { + "name": "NuGetReleaseNotesFile", + "type": "filePath", + "label": "Release Notes File", + "defaultValue": "", + "required": false, + "helpMarkDown": "A file containing release notes to add to the NuGet package metadata. Overrides `NuGetReleaseNotes`." + }, + { + "name": "Overwrite", + "type": "boolean", + "label": "Overwrite", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Allow an existing package of the same ID and version to be overwritten.", + "groupName": "advanced" + } + ], + "OutputVariables": [ + { + "name": "package_file_path", + "description": "The full path to the package file that was created." + }, + { + "name": "package_filename", + "description": "The filename of the package that was created." + } + ], + "instanceNameFormat": "Package NuGet $(PackageId)", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} diff --git a/source/tasks/PackZip/PackZipV7/create-package.ts b/source/tasks/PackZip/PackZipV7/create-package.ts new file mode 100644 index 00000000..ade00cbe --- /dev/null +++ b/source/tasks/PackZip/PackZipV7/create-package.ts @@ -0,0 +1,23 @@ +import { Logger, ZipPackageBuilder } from "@octopusdeploy/api-client"; +import path from "path"; +import { InputParameters } from "./input-parameters"; + +type createPackageResult = { + filePath: string; + filename: string; +}; + +export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise { + const builder = new ZipPackageBuilder(); + const packageFilename = await builder.pack({ + packageId: parameters.packageId, + version: parameters.packageVersion, + outputFolder: parameters.outputPath, + basePath: parameters.sourcePath, + inputFilePatterns: parameters.include, + overwrite: parameters.overwrite, + logger, + }); + + return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename }; +} diff --git a/source/tasks/PackZip/PackZipV7/icon.png b/source/tasks/PackZip/PackZipV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..65e99099c863501697a27239ff6665c148fb9137 GIT binary patch literal 849 zcmV-X1FrmuP)_t`PqKPn6|>;R|5DfGkj6T`yLuJh*%TSv>5@ zh5UiBkSZzJ8M~9s;>IKnxE3CGRmmhh-P1kOJ>3m(gKGw+2@jtvuOsSenyyjrA9{N- z`JY{QzD_~``s6sllq0PmiJocslK zJ5BRFH=rTNl`%X85i|D+nFT^Ka{_TcPsa2V#_Xz7(Wt6eup1D<&m=3=)IvOld?Qw9 zXuW(?ZH_s$@V>Z%CZ|! zXl|Uy88?v;W=o%2SyZLf?2rM-%GW9i&JIXXEknd2uI)+L9b{Y%!m84}k5G(&o+fGd zIuO+Q-ml4vDudhhc3+g`XDU+KDP9i465N^!&wUtP#SHzW&!)xgkOMe#^Z=UG4M1VRo#Hs zkBuFv3(&G_RM?7&S_asF0R9qD0#afrt{3^$F##EA({-)IvcL`I@-a0X7F+>Q>ZR<* z$|vQ~rY>sh(z1v$asjIICEf*G0i5?a`%!i~=qqorkNeYs0`UkgdII`~m&rHfmAg?Q z@;|)sDI?C4AvU!?M)x=Yw`P^OHEQW}o&7%s<381ZFY&D2IF_e}oZZLLS)y6rBdtzsc#y9d~4hIxeJiuil-=2a)eNY5joGr8aAfB*2Dg9XE@9 bgKNi6+y@+Og?ElI00000NkvXXu0mjfw0(Du literal 0 HcmV?d00001 diff --git a/source/tasks/PackZip/PackZipV7/icon.svg b/source/tasks/PackZip/PackZipV7/icon.svg new file mode 100644 index 00000000..69e50017 --- /dev/null +++ b/source/tasks/PackZip/PackZipV7/icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/source/tasks/PackZip/PackZipV7/index.ts b/source/tasks/PackZip/PackZipV7/index.ts new file mode 100644 index 00000000..bb22cd92 --- /dev/null +++ b/source/tasks/PackZip/PackZipV7/index.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import * as tasks from "azure-pipelines-task-lib/task"; +import { Logger } from "@octopusdeploy/api-client"; +import { getInputs } from "./input-parameters"; +import os from "os"; +import { createPackageFromInputs } from "./create-package"; + +async function run() { + try { + const parameters = getInputs(); + + const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, + }; + + const result = await createPackageFromInputs(parameters, logger); + + tasks.setVariable("package_file_path", result.filePath); + tasks.setVariable("package_filename", result.filename); + + tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded"); + } catch (error: unknown) { + if (error instanceof Error) { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); + } else { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); + } + } +} + +run(); diff --git a/source/tasks/PackZip/PackZipV7/input-parameters.ts b/source/tasks/PackZip/PackZipV7/input-parameters.ts new file mode 100644 index 00000000..cc11f5a0 --- /dev/null +++ b/source/tasks/PackZip/PackZipV7/input-parameters.ts @@ -0,0 +1,23 @@ +import * as tasks from "azure-pipelines-task-lib/task"; +import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs"; +import { getLineSeparatedItems } from "../../Utils/inputs"; + +export interface InputParameters { + packageId: string; + packageVersion: string; + outputPath: string; + sourcePath: string; + include: string[]; + overwrite?: boolean; +} + +export const getInputs = (): InputParameters => { + return { + packageId: tasks.getInput("PackageId", true) || "", + packageVersion: tasks.getInput("PackageVersion", true) || "", + outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".", + sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".", + include: getLineSeparatedItems(tasks.getInput("Include") || "**"), + overwrite: tasks.getBoolInput("Overwrite"), + }; +}; diff --git a/source/tasks/PackZip/PackZipV7/task.json b/source/tasks/PackZip/PackZipV7/task.json new file mode 100644 index 00000000..8eaad2c4 --- /dev/null +++ b/source/tasks/PackZip/PackZipV7/task.json @@ -0,0 +1,91 @@ +{ + "id": "3f248d80-a755-498d-863c-f936c5821318", + "name": "OctopusPackZip", + "friendlyName": "Package Application for Octopus - Zip", + "description": "Package your application into a Zip file.", + "helpMarkDown": "set-by-pack.ps1", + "category": "Package", + "visibility": ["Build", "Release"], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced Options", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "PackageId", + "type": "string", + "label": "Package ID", + "defaultValue": "", + "required": true, + "helpMarkDown": "The ID of the package. e.g. MyCompany.App" + }, + { + "name": "PackageVersion", + "type": "string", + "label": "Package Version", + "defaultValue": "", + "required": false, + "helpMarkDown": "The version of the package; must be a valid [SemVer](http://semver.org/) version." + }, + { + "name": "SourcePath", + "type": "filePath", + "label": "Source Path", + "defaultValue": "", + "required": false, + "helpMarkDown": "The folder containing the files and folders to package. Defaults to working directory." + }, + { + "name": "OutputPath", + "type": "filePath", + "label": "Output Path", + "defaultValue": "", + "required": false, + "helpMarkDown": "The directory into which the generated package will be written. Defaults to working directory." + }, + { + "name": "Include", + "type": "multiLine", + "label": "Include", + "defaultValue": "", + "required": false, + "helpMarkDown": "File patterns to include, relative to the root path. e.g. /bin/*.dll" + }, + { + "name": "Overwrite", + "type": "boolean", + "label": "Overwrite", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Allow an existing package of the same ID and version to be overwritten.", + "groupName": "advanced" + } + ], + "OutputVariables": [ + { + "name": "package_file_path", + "description": "The full path to the package file that was created." + }, + { + "name": "package_filename", + "description": "The filename of the package that was created." + } + ], + "instanceNameFormat": "Package Zip $(PackageId)", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} diff --git a/source/tasks/Push/PushV7/icon.png b/source/tasks/Push/PushV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..55087619c14789e8ebef4e6d34fae1299867f5fc GIT binary patch literal 760 zcmV9FL&t~D)eELa5&6Yt3N@=RfN zy=lsy!3POYfnxBv5j4D`_gX?Y8h=%wal#4%V?ZyaGnk|CIuwr?OarZTISqzugzFAu zz7E|JFCfH#2~#+6@NQhl$u4&e$vO?#oMtnDNl-yFO@wB19rWCF(lg z@ofhJ_}xWy>8)(?0aHdQFE~peuov#1gi9|^C}tN5T|&$3R~K9850~`dI&2(b)Tvq zR=1Pb_>f{8KlVf5JJfe^4=<8ykn#cN(^4xbrAx6E)e8x-ga4`lcN*m4=%H+%i$@*` z8YM>0#pdf+ghltMWBFeT^0HR)qS!~=6{j|b5WNANA7WF5gW`>$8sKI$81hH>>}aYv&&iG6q;CfG#)y0000_=h% literal 0 HcmV?d00001 diff --git a/source/tasks/Push/PushV7/icon.svg b/source/tasks/Push/PushV7/icon.svg new file mode 100644 index 00000000..9b9c1c04 --- /dev/null +++ b/source/tasks/Push/PushV7/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/tasks/Push/PushV7/index.ts b/source/tasks/Push/PushV7/index.ts new file mode 100644 index 00000000..6aac5bec --- /dev/null +++ b/source/tasks/Push/PushV7/index.ts @@ -0,0 +1,43 @@ +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs"; +import { Push } from "./push"; +import os from "os"; +import { getClient } from "../../Utils/client"; + +async function run() { + try { + const spaceName = getRequiredInput("Space"); + const packages = getLineSeparatedItems(tasks.getInput("Packages", true) || ""); + const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || ""); + + const connection = getDefaultOctopusConnectionDetailsOrThrow(); + + const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, + }; + + const client = await getClient(connection, logger, "package", "push", 6); + await new Push(client).run(spaceName, packages, overwriteMode); + } catch (error: unknown) { + if (error instanceof Error) { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error.message}${os.EOL}${error.stack}`, true); + } else { + tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error}`, true); + } + } +} + +run(); diff --git a/source/tasks/Push/PushV7/push.ts b/source/tasks/Push/PushV7/push.ts new file mode 100644 index 00000000..7e38dfe6 --- /dev/null +++ b/source/tasks/Push/PushV7/push.ts @@ -0,0 +1,42 @@ +import { ReplaceOverwriteMode } from "../../Utils/inputs"; +import { Client, OverwriteMode, PackageRepository } from "@octopusdeploy/api-client"; +import glob from "glob"; + +export class Push { + constructor(readonly client: Client) {} + + public async run(spaceName: string, packages: string[], overwriteMode: ReplaceOverwriteMode) { + const matchedPackages = await this.resolveGlobs(packages); + + let mappedOverwriteMode = OverwriteMode.FailIfExists; + if (overwriteMode === ReplaceOverwriteMode.true) { + mappedOverwriteMode = OverwriteMode.OverwriteExisting; + } else if (overwriteMode === ReplaceOverwriteMode.IgnoreIfExists) { + mappedOverwriteMode = OverwriteMode.IgnoreIfExists; + } + + const repository = new PackageRepository(this.client, spaceName); + await repository.push(matchedPackages, mappedOverwriteMode); + + return packages; + } + + private resolveGlobs = async (globs: string[]): Promise => { + const globResults = await Promise.all(globs.map(this.pGlobNoNull)); + const results = ([] as string[]).concat(...globResults); + + return results; + }; + + private pGlobNoNull = (pattern: string): Promise => { + return new Promise((resolve, reject) => { + glob(pattern, { nonull: true }, (err, matches) => { + if (err) { + reject(err); + return; + } + resolve(matches); + }); + }); + }; +} diff --git a/source/tasks/Push/PushV7/task.json b/source/tasks/Push/PushV7/task.json new file mode 100644 index 00000000..523a8e89 --- /dev/null +++ b/source/tasks/Push/PushV7/task.json @@ -0,0 +1,65 @@ +{ + "id": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2", + "name": "OctopusPush", + "friendlyName": "Push Package(s) to Octopus", + "description": "Push your NuGet or Zip package to your Octopus Deploy Server. **v6 of this step requires Octopus 2022.3+**", + "helpMarkDown": "set-by-pack.ps1", + "category": "Package", + "visibility": [ + "Build", + "Release" + ], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space Name", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space name within Octopus." + }, + { + "name": "Packages", + "type": "multiLine", + "label": "Packages", + "defaultValue": "", + "required": true, + "helpMarkDown": "Package files to push. To push multiple packages, enter on multiple lines." + }, + { + "name": "Replace", + "type": "pickList", + "label": "Overwrite Mode", + "defaultValue": "false", + "required": true, + "helpMarkDown": "Normally, if the same package already exists on the server, the server will reject the package push. This is a good practice as it ensures a package isn't accidentally overwritten or ignored. Use this setting to override this behavior.", + "options": { + "false": "Fail if exists", + "true": "Overwrite existing", + "IgnoreIfExists": "Ignore if exists" + } + } + ], + "instanceNameFormat": "Push Packages to Octopus", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} \ No newline at end of file diff --git a/source/tasks/RunRunbook/RunRunbookV7/icon.png b/source/tasks/RunRunbook/RunRunbookV7/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3b34013ff2a43a8907cd2798577a739eff05332c GIT binary patch literal 886 zcmV-+1Bv{JP)27tqd-v_`0bJpdfq4_&BY#d(vcLi!j6%_*wHUR+ zPVczgPk45Ixys4|)h8GYc*^ChSjd-!$L?D3zBS z-m!{_tU!wL=5!b``CLB#bub39ax%{kIRw34GM=#*sg0J?*eGwGOc)MU>eib-oK zkASJ{khnYlxPpiq`3qvioLY=F!`KP-oCFT_F5kR0-jPE}K3HY9}(C%+2to;b?G_t-K zygA>IHxiXI`i34Ngj<`hRtv$d^4vdvHSAb$z1RtgoiqyO{R-3Z+?eL#ZrzqGpvCcz z@K$ZtRbi-p)}bRM*7<_saz-;El2*PMhAR9x`)#mtffzQ1agRT)xUMIF;B1+)sTjpl%Ah^vtv$K`yhz~dg z-wbx(%m{&Kt*Rmd7XXU993X`a + + + diff --git a/source/tasks/RunRunbook/RunRunbookV7/index.ts b/source/tasks/RunRunbook/RunRunbookV7/index.ts new file mode 100644 index 00000000..77852885 --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/index.ts @@ -0,0 +1,26 @@ +import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; +import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; +import { Logger } from "@octopusdeploy/api-client"; +import * as tasks from "azure-pipelines-task-lib/task"; +import { RunbookRun } from "./runbookRun"; + +const connection = getDefaultOctopusConnectionDetailsOrThrow(); + +const logger: Logger = { + debug: (message) => { + tasks.debug(message); + }, + info: (message) => console.log(message), + warn: (message) => tasks.warning(message), + error: (message, err) => { + if (err !== undefined) { + tasks.error(err.message); + } else { + tasks.error(message); + } + }, +}; + +const task: TaskWrapper = new ConcreteTaskWrapper(); + +new RunbookRun(connection, task, logger).run(); diff --git a/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.test.ts b/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.test.ts new file mode 100644 index 00000000..aeb6e80f --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.test.ts @@ -0,0 +1,62 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { createCommandFromInputs, CreateGitRunbookRunCommandV1, isCreateGitRunbookRunCommand } from "./inputCommandBuilder"; +import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; + +describe("getInputCommand", () => { + let logger: Logger; + let task: MockTaskWrapper; + beforeEach(() => { + logger = {}; + task = new MockTaskWrapper(); + }); + + test("all regular fields supplied", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("Runbook", "A runbook"); + task.addVariableString("Environments", "dev\nStaging"); + task.addVariableString("Tenants", "Tenant 1\nTenant 2"); + task.addVariableString("TenantTags", "tag set 1/tag 1\ntag set 1/tag 2"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + + const command = createCommandFromInputs(logger, task); + expect(isCreateGitRunbookRunCommand(command)).toBe(false); + expect(command.spaceName).toBe("Default"); + expect(command.ProjectName).toBe("Awesome project"); + expect(command.RunbookName).toBe("A runbook"); + expect(command.EnvironmentNames).toStrictEqual(["dev", "Staging"]); + expect(command.Tenants).toStrictEqual(["Tenant 1", "Tenant 2"]); + expect(command.TenantTags).toStrictEqual(["tag set 1/tag 1", "tag set 1/tag 2"]); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); + + test("when gitRef is supplied, the command contains the ref plus all regular fields supplied", () => { + task.addVariableString("Space", "Default"); + task.addVariableString("Project", "Awesome project"); + task.addVariableString("Runbook", "A runbook"); + task.addVariableString("Environments", "dev\nStaging"); + task.addVariableString("Tenants", "Tenant 1\nTenant 2"); + task.addVariableString("TenantTags", "tag set 1/tag 1\ntag set 1/tag 2"); + task.addVariableString("Variables", "var1: value1\nvar2: value2"); + task.addVariableString("GitRef", "some-ref"); + + const command = createCommandFromInputs(logger, task); + expect(isCreateGitRunbookRunCommand(command)).toBe(true); + expect(command.spaceName).toBe("Default"); + expect(command.ProjectName).toBe("Awesome project"); + expect(command.RunbookName).toBe("A runbook"); + expect(command.EnvironmentNames).toStrictEqual(["dev", "Staging"]); + expect(command.Tenants).toStrictEqual(["Tenant 1", "Tenant 2"]); + expect(command.TenantTags).toStrictEqual(["tag set 1/tag 1", "tag set 1/tag 2"]); + expect(command.Variables).toStrictEqual({ var1: "value1", var2: "value2" }); + expect((command as CreateGitRunbookRunCommandV1).GitRef).toBe("some-ref"); + + expect(task.lastResult).toBeUndefined(); + expect(task.lastResultMessage).toBeUndefined(); + expect(task.lastResultDone).toBeUndefined(); + }); +}); diff --git a/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.ts b/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.ts new file mode 100644 index 00000000..0835d872 --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/inputCommandBuilder.ts @@ -0,0 +1,74 @@ +import { getLineSeparatedItems } from "../../Utils/inputs"; +import { CreateRunbookRunCommandV1, Logger, PromptedVariableValues } from "@octopusdeploy/api-client"; +import { RunGitRunbookCommand } from "@octopusdeploy/api-client/dist/features/projects/runbooks/runs/RunGitRunbookCommand"; +import { TaskWrapper } from "tasks/Utils/taskInput"; + +// The api-client doesn't have a type for this command that we can differentiate from CreateRunbookRunCommandV1 +// so we'll wrap it to make things easier. +export type CreateGitRunbookRunCommandV1 = RunGitRunbookCommand & { + GitRef: string; +}; + +export function isCreateGitRunbookRunCommand(command: CreateRunbookRunCommandV1 | CreateGitRunbookRunCommandV1): command is CreateGitRunbookRunCommandV1 { + return (command as CreateGitRunbookRunCommandV1).GitRef !== undefined; +} + +export function createCommandFromInputs(logger: Logger, task: TaskWrapper): CreateRunbookRunCommandV1 | CreateGitRunbookRunCommandV1 { + const variablesMap: PromptedVariableValues | undefined = {}; + + const variablesField = task.getInput("Variables"); + logger.debug?.("Variables: " + variablesField); + if (variablesField) { + const variables = getLineSeparatedItems(variablesField).map((p) => p.trim()) || undefined; + if (variables) { + for (const variable of variables) { + const variableMap = variable.split(":").map((x) => x.trim()); + variablesMap[variableMap[0]] = variableMap[1]; + } + } + } + + const environmentsField = task.getInput("Environments", true); + logger.debug?.("Environments: " + environmentsField); + const tenantsField = task.getInput("Tenants"); + logger.debug?.("Tenants: " + tenantsField); + const tagsField = task.getInput("TenantTags"); + logger.debug?.("Tenant Tags: " + tagsField); + const tags = getLineSeparatedItems(tagsField || "")?.map((t: string) => t.trim()) || []; + + const gitRef = task.getInput("GitRef"); + logger.debug?.("GitRef: " + gitRef); + + if (gitRef) { + const command: CreateGitRunbookRunCommandV1 = { + spaceName: task.getInput("Space") || "", + ProjectName: task.getInput("Project", true) || "", + RunbookName: task.getInput("Runbook", true) || "", + EnvironmentNames: getLineSeparatedItems(environmentsField || "")?.map((t: string) => t.trim()) || [], + Tenants: getLineSeparatedItems(tenantsField || "")?.map((t: string) => t.trim()) || [], + TenantTags: tags, + UseGuidedFailure: task.getBoolean("UseGuidedFailure") || undefined, + Variables: variablesMap || undefined, + GitRef: gitRef, + }; + + logger.debug?.(JSON.stringify(command)); + + return command; + } + + const command: CreateRunbookRunCommandV1 = { + spaceName: task.getInput("Space") || "", + ProjectName: task.getInput("Project", true) || "", + RunbookName: task.getInput("Runbook", true) || "", + EnvironmentNames: getLineSeparatedItems(environmentsField || "")?.map((t: string) => t.trim()) || [], + Tenants: getLineSeparatedItems(tenantsField || "")?.map((t: string) => t.trim()) || [], + TenantTags: tags, + UseGuidedFailure: task.getBoolean("UseGuidedFailure") || undefined, + Variables: variablesMap || undefined, + }; + + logger.debug?.(JSON.stringify(command)); + + return command; +} diff --git a/source/tasks/RunRunbook/RunRunbookV7/runRunbook.ts b/source/tasks/RunRunbook/RunRunbookV7/runRunbook.ts new file mode 100644 index 00000000..564e4082 --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/runRunbook.ts @@ -0,0 +1,70 @@ +import { Client, CreateRunbookRunCommandV1, RunbookRunRepository, Logger, TenantRepository, EnvironmentRepository, CreateRunbookRunResponseV1 } from "@octopusdeploy/api-client"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { ExecutionResult } from "../../Utils/executionResult"; +import { CreateGitRunbookRunCommandV1, isCreateGitRunbookRunCommand } from "./inputCommandBuilder"; +import { RunGitRunbookResponse } from "@octopusdeploy/api-client/dist/features/projects/runbooks/runs/RunGitRunbookResponse"; + +export async function createRunbookRunFromInputs(client: Client, command: CreateRunbookRunCommandV1 | CreateGitRunbookRunCommandV1, task: TaskWrapper, logger: Logger): Promise { + logger.info?.("🐙 Running a Runbook in Octopus Deploy..."); + + try { + const repository = new RunbookRunRepository(client, command.spaceName); + + const response = await createRunbookRun(repository, command); + + logger.info?.(`🎉 ${response.RunbookRunServerTasks.length} Run${response.RunbookRunServerTasks.length > 1 ? "s" : ""} queued successfully!`); + + if (response.RunbookRunServerTasks.length === 0) { + throw new Error("Expected at least one run to be queued."); + } + if (response.RunbookRunServerTasks[0].ServerTaskId === null || response.RunbookRunServerTasks[0].ServerTaskId === undefined) { + throw new Error("Server task id was not deserialized correctly."); + } + + const runbookRunIds = response.RunbookRunServerTasks.map((x) => x.RunbookRunId); + + const runs = await repository.list({ ids: runbookRunIds, take: runbookRunIds.length }); + + const envIds = runs.Items.map((d) => d.EnvironmentId || ""); + logger.debug?.(`Environment Ids: ${envIds.join(", ")}`); + const envRepository = new EnvironmentRepository(client, command.spaceName); + const envs = await envRepository.list({ ids: envIds, take: envIds.length }); + + const tenantIds = runs.Items.map((d) => d.TenantId || ""); + logger.debug?.(`Tenant Ids: ${tenantIds.join(", ")}`); + const tenantRepository = new TenantRepository(client, command.spaceName); + const tenants = await tenantRepository.list({ ids: tenantIds, take: tenantIds.length }); + + const results = response.RunbookRunServerTasks.map((x) => { + const filteredTenants = tenants.Items.filter((e) => e.Id === runs.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].TenantId); + const tenantName = filteredTenants.length > 0 ? filteredTenants[0].Name : null; + return { + serverTaskId: x.ServerTaskId, + environmentName: envs.Items.filter((e) => e.Id === runs.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].EnvironmentId)[0].Name, + tenantName: tenantName, + type: "Runbook run", + } as ExecutionResult; + }); + + const tasksJson = JSON.stringify(results); + logger.debug?.(`server_tasks: ${tasksJson}`); + task.setOutputVariable("server_tasks", tasksJson); + + return results; + } catch (error: unknown) { + if (error instanceof Error) { + task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); + } else { + task.setFailure(`"Failed to execute command. ${error}`, true); + } + throw error; + } +} + +async function createRunbookRun(repository: RunbookRunRepository, command: CreateRunbookRunCommandV1 | CreateGitRunbookRunCommandV1): Promise { + if (isCreateGitRunbookRunCommand(command)) { + return await repository.createGit(command, command.GitRef); + } + return await repository.create(command); +} diff --git a/source/tasks/RunRunbook/RunRunbookV7/runbookRun.ts b/source/tasks/RunRunbook/RunRunbookV7/runbookRun.ts new file mode 100644 index 00000000..16dbd379 --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/runbookRun.ts @@ -0,0 +1,29 @@ +import { Logger } from "@octopusdeploy/api-client"; +import { OctoServerConnectionDetails } from "../../Utils/connection"; +import { createRunbookRunFromInputs } from "./runRunbook"; +import { createCommandFromInputs } from "./inputCommandBuilder"; +import os from "os"; +import { TaskWrapper } from "tasks/Utils/taskInput"; +import { getClient } from "../../Utils/client"; + +export class RunbookRun { + constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} + + public async run() { + try { + const command = createCommandFromInputs(this.logger, this.task); + const client = await getClient(this.connection, this.logger, "runbook", "run", 6); + + createRunbookRunFromInputs(client, command, this.task, this.logger); + + this.task.setSuccess("Runbook run succeeded."); + } catch (error: unknown) { + if (error instanceof Error) { + this.task.setFailure(`"Failed to successfully run runbook. ${error.message}${os.EOL}${error.stack}`, true); + } else { + this.task.setFailure(`"Failed to successfully run runbook. ${error}`, true); + } + throw error; + } + } +} diff --git a/source/tasks/RunRunbook/RunRunbookV7/task.json b/source/tasks/RunRunbook/RunRunbookV7/task.json new file mode 100644 index 00000000..b0a9b7a5 --- /dev/null +++ b/source/tasks/RunRunbook/RunRunbookV7/task.json @@ -0,0 +1,111 @@ +{ + "id": "5a2273e0-aa4f-4502-bcba-6817835e2bbd", + "name": "OctopusRunRunbook", + "friendlyName": "Run Octopus Runbook", + "description": "Run an Octopus Deploy Runbook", + "helpMarkDown": "set-by-pack.ps1", + "category": "Deploy", + "visibility": ["Build", "Release"], + "author": "Octopus Deploy", + "version": { + "Major": 7, + "Minor": 0, + "Patch": 0 + }, + "demands": [], + "minimumAgentVersion": "3.232.1", + "inputs": [ + { + "name": "OctoConnectedServiceName", + "type": "connectedService:OctopusEndpoint", + "label": "Octopus Deploy Server", + "defaultValue": "", + "required": true, + "helpMarkDown": "Octopus Deploy server connection" + }, + { + "name": "Space", + "type": "string", + "label": "Space", + "defaultValue": "", + "required": true, + "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." + }, + { + "name": "Project", + "type": "string", + "label": "Project", + "defaultValue": "", + "required": true, + "helpMarkDown": "The project within Octopus. This must be the name of the project, not the id." + }, + { + "name": "Runbook", + "type": "string", + "label": "Runbook", + "defaultValue": "", + "required": true, + "helpMarkDown": "The name of the runbook to run." + }, + { + "name": "Environments", + "type": "multiLine", + "label": "Environment(s)", + "defaultValue": "", + "required": true, + "helpMarkDown": "The environment names to run the runbook for." + }, + { + "name": "GitRef", + "type": "string", + "label": "Git Reference", + "defaultValue": "", + "required": false, + "helpMarkDown": "The Git reference to run the runbook for. Only applies when runbooks are stored in a Git repository for config-as-code enabled projects." + }, + { + "name": "Tenants", + "type": "multiLine", + "label": "Tenant(s)", + "defaultValue": "", + "required": false, + "helpMarkDown": "The tenant names to run the runbook for." + }, + { + "name": "TenantTags", + "type": "multiLine", + "label": "Tenant tag(s)", + "defaultValue": "", + "required": false, + "helpMarkDown": "Run for all tenants with the given tag(s)." + }, + { + "name": "Variables", + "type": "multiLine", + "label": "Values for prompted variables", + "defaultValue": "", + "required": false, + "helpMarkDown": "Variable values to pass to the run, use syntax `variable: value`." + }, + { + "name": "UseGuidedFailure", + "type": "boolean", + "label": "Use guided failure", + "defaultValue": "False", + "required": false, + "helpMarkDown": "Whether to use guided failure mode if errors occur during the run." + } + ], + "OutputVariables": [ + { + "name": "server_tasks", + "description": "A list of objects, containing `ServerTaskId`, `EnvironmentName` and `TenantName`, for each queued run." + } + ], + "instanceNameFormat": "Run Octopus Runbook", + "execution": { + "Node20_1": { + "target": "index.js" + } + } +} From 24b1921cb95caed8dcba5557679375d44446cdeb Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Fri, 1 Aug 2025 10:11:04 +1000 Subject: [PATCH 4/7] chore: bump extension version to 7 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e8c5afe..bb13cb7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ on: required: true env: - PACKAGE_VERSION: 6.1.${{ github.run_number }} + PACKAGE_VERSION: 7.0.${{ github.run_number }} OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }} OCTOPUS_API_KEY: ${{ secrets.INTEGRATIONS_API_KEY }} @@ -36,7 +36,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "20" cache: "npm" - name: Build From c552cb335d296c5337fef32f0cecaac6fb2e08a9 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Fri, 1 Aug 2025 10:46:20 +1000 Subject: [PATCH 5/7] chore: use environment files rather than set-output --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb13cb7a..a3ef4a31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: run: | npm ci npm run build -- --extensionVersion $PACKAGE_VERSION - echo "::set-output name=package_version::$PACKAGE_VERSION" + echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT - uses: actions/upload-artifact@v4 with: name: dist @@ -135,7 +135,7 @@ jobs: echo "::debug::${{github.event_name}}" OUTPUT_FILE="release_notes.txt" jq --raw-output '.release.body' ${{ github.event_path }} | sed 's#\r# #g' > $OUTPUT_FILE - echo "::set-output name=release-note-file::$OUTPUT_FILE" + echo "release-note-file=$OUTPUT_FILE" >> $GITHUB_OUTPUT - name: Create a release in Octopus Deploy 🐙 uses: OctopusDeploy/create-release-action@v3 From d78a5e83849f40d0f6cdb864b712b5d8c3da860a Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Fri, 1 Aug 2025 10:53:57 +1000 Subject: [PATCH 6/7] chore: revert v6 changes --- source/tasks/AwaitTask/AwaitTaskV6/task.json | 6 +++--- source/tasks/BuildInformation/BuildInformationV6/task.json | 6 +++--- .../CreateOctopusRelease/CreateOctopusReleaseV6/task.json | 6 +++--- source/tasks/Deploy/DeployV6/task.json | 6 +++--- source/tasks/DeployTenant/TenantedDeployV6/task.json | 6 +++--- source/tasks/OctoInstaller/OctoInstallerV6/task.json | 6 +++--- source/tasks/PackNuGet/PackNuGetV6/task.json | 6 +++--- source/tasks/PackZip/PackZipV6/task.json | 6 +++--- source/tasks/Push/PushV6/task.json | 6 +++--- source/tasks/RunRunbook/RunRunbookV6/task.json | 6 +++--- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/source/tasks/AwaitTask/AwaitTaskV6/task.json b/source/tasks/AwaitTask/AwaitTaskV6/task.json index 5ca6145c..35cbf363 100644 --- a/source/tasks/AwaitTask/AwaitTaskV6/task.json +++ b/source/tasks/AwaitTask/AwaitTaskV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -87,7 +87,7 @@ ], "instanceNameFormat": "Await Octopus Deploy Task", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/BuildInformation/BuildInformationV6/task.json b/source/tasks/BuildInformation/BuildInformationV6/task.json index 6270c39f..7b370cf7 100644 --- a/source/tasks/BuildInformation/BuildInformationV6/task.json +++ b/source/tasks/BuildInformation/BuildInformationV6/task.json @@ -11,11 +11,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -65,7 +65,7 @@ ], "instanceNameFormat": "Push Package Build Information to Octopus", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json index d70bc41e..312c8d72 100644 --- a/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json +++ b/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [ ], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "versionControl", @@ -147,7 +147,7 @@ ], "instanceNameFormat": "Create Octopus Release", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/Deploy/DeployV6/task.json b/source/tasks/Deploy/DeployV6/task.json index c3fff930..21643ec9 100644 --- a/source/tasks/Deploy/DeployV6/task.json +++ b/source/tasks/Deploy/DeployV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "advanced", @@ -99,7 +99,7 @@ ], "instanceNameFormat": "Deploy Octopus Release", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/DeployTenant/TenantedDeployV6/task.json b/source/tasks/DeployTenant/TenantedDeployV6/task.json index 1ef0cc60..bbfdff08 100644 --- a/source/tasks/DeployTenant/TenantedDeployV6/task.json +++ b/source/tasks/DeployTenant/TenantedDeployV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "advanced", @@ -115,7 +115,7 @@ ], "instanceNameFormat": "Deploy Octopus Release Tenants", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/OctoInstaller/OctoInstallerV6/task.json b/source/tasks/OctoInstaller/OctoInstallerV6/task.json index 34f7d541..b90d03d6 100644 --- a/source/tasks/OctoInstaller/OctoInstallerV6/task.json +++ b/source/tasks/OctoInstaller/OctoInstallerV6/task.json @@ -16,12 +16,12 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "satisfies": ["octopus"], "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "advanced", @@ -40,7 +40,7 @@ ], "instanceNameFormat": "Install Octopus CLI tool version $(version)", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/PackNuGet/PackNuGetV6/task.json b/source/tasks/PackNuGet/PackNuGetV6/task.json index 25fc9632..775bca67 100644 --- a/source/tasks/PackNuGet/PackNuGetV6/task.json +++ b/source/tasks/PackNuGet/PackNuGetV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "advanced", @@ -124,7 +124,7 @@ ], "instanceNameFormat": "Package NuGet $(PackageId)", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/PackZip/PackZipV6/task.json b/source/tasks/PackZip/PackZipV6/task.json index b87e2f87..0e2b0296 100644 --- a/source/tasks/PackZip/PackZipV6/task.json +++ b/source/tasks/PackZip/PackZipV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "groups": [ { "name": "advanced", @@ -84,7 +84,7 @@ ], "instanceNameFormat": "Package Zip $(PackageId)", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/Push/PushV6/task.json b/source/tasks/Push/PushV6/task.json index 219a1a7f..6957b352 100644 --- a/source/tasks/Push/PushV6/task.json +++ b/source/tasks/Push/PushV6/task.json @@ -12,11 +12,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -58,7 +58,7 @@ ], "instanceNameFormat": "Push Packages to Octopus", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } diff --git a/source/tasks/RunRunbook/RunRunbookV6/task.json b/source/tasks/RunRunbook/RunRunbookV6/task.json index 66bab114..93091225 100644 --- a/source/tasks/RunRunbook/RunRunbookV6/task.json +++ b/source/tasks/RunRunbook/RunRunbookV6/task.json @@ -9,11 +9,11 @@ "author": "Octopus Deploy", "version": { "Major": 6, - "Minor": 1, + "Minor": 0, "Patch": 0 }, "demands": [], - "minimumAgentVersion": "3.232.1", + "minimumAgentVersion": "2.206.1", "inputs": [ { "name": "OctoConnectedServiceName", @@ -104,7 +104,7 @@ ], "instanceNameFormat": "Run Octopus Runbook", "execution": { - "Node20_1": { + "Node16": { "target": "index.js" } } From ac6b73b9fa38c33f1c4f8f593b81119a31a19265 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Mon, 4 Aug 2025 10:19:09 +1000 Subject: [PATCH 7/7] chore: update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8282f168..78380198 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ Microsoft TFS/ADO web extensions are powered by Node.js under the hood. Simply o ### Prerequisites -* Node.js - * Pre-v6: 10.15.3 (LTS) (`choco install nodejs --version="10.15.3"` or `brew install node@10` or [web](https://nodejs.org)) - * v6: 20.19.4 (LTS) (`choco install nodejs --version="20.19.4"` or `brew install node@20` or [web](https://nodejs.org)) +* Node.js (`choco install nodejs --version="20.19.4"` or `nvm install 20` or [web](https://nodejs.org)) + * Pre-v6 tasks: 10.15.3 (EOL) + * v6 tasks: 16.20.2 (EOL) + * v7 tasks: 20.19.4 (LTS) * NPM: 5.6.0+ (`npm install npm@latest -g`) * TFX (`npm install tfx-cli -g`) * Install golang (`choco install golang` or `brew install go` or [web](https://golang.org))