├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
└── workflows
│ ├── build.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .octopus
├── deployment_process.ocl
├── deployment_settings.ocl
└── schema_version.ocl
├── .prettierignore
├── .prettierrc
├── LICENSE.txt
├── README.md
├── embed-octo.cmd
├── embed-octo.ps1
├── esbuild.mjs
├── img
├── ps-addpowershellstep.png
├── ps-configurepowershellstep.png
├── tfsbuild-addbuildstep.png
├── tfsbuild-configurebuildstep.png
├── tfsbuild-connectedservice1.png
├── tfsbuild-connectedservice2.png
└── tfsbuild-releasenotes.png
├── jest.config.ts
├── localBuild.cmd
├── localbuild.sh
├── pack.cmd
├── pack.ps1
├── package-lock.json
├── package.json
├── publish.ps1
├── source
├── extension-icon.LocalTest.png
├── extension-icon.Test.png
├── extension-icon.png
├── extension-manifest.LocalTest.json
├── extension-manifest.Production.json
├── extension-manifest.Test.json
├── extension-manifest.json
├── img
│ ├── catalog-image.jpg
│ ├── create-package-options.png
│ ├── create-release-options.png
│ ├── deploy-release-options.png
│ ├── multiple-widget-preview.jpg
│ ├── octopus-add-tasks-deploy.png
│ ├── octopus-add-tasks-package.png
│ ├── octopus-alltasks.png
│ ├── octopus_create-release-04.png
│ ├── octopus_deploy-02.png
│ ├── octopus_installer.png
│ ├── octopus_package-03.png
│ ├── octopus_promote-05.png
│ ├── octopus_push-01.png
│ ├── preview-image.jpg
│ ├── promote-release-options.png
│ ├── push-metadata-options.png
│ ├── service-connection.png
│ ├── tfsbuild-addbuildstep.png
│ ├── tfsbuild-connectedservice1.png
│ ├── tfsbuild-connectedservice2.png
│ ├── vstsbuild-octopusendpoint-2.png
│ └── widget-icon.jpg
├── tasks
│ ├── AwaitTask
│ │ └── AwaitTaskV6
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── input-parameters.ts
│ │ │ ├── task.json
│ │ │ └── waiter.ts
│ ├── BuildInformation
│ │ └── BuildInformationV6
│ │ │ ├── buildInformation.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── inputCommandBuilder.test.ts
│ │ │ ├── inputCommandBuilder.ts
│ │ │ ├── overwriteMode.test.ts
│ │ │ ├── overwriteMode.ts
│ │ │ ├── task.json
│ │ │ └── vsts.ts
│ ├── CreateOctopusRelease
│ │ ├── CreateOctopusReleaseV5
│ │ │ ├── createRelease.test.ts
│ │ │ ├── createRelease.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── CreateOctopusReleaseV6
│ │ │ ├── createRelease.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── inputCommandBuilder.test.ts
│ │ │ ├── inputCommandBuilder.ts
│ │ │ ├── release.ts
│ │ │ └── task.json
│ ├── Deploy
│ │ ├── DeployV5
│ │ │ ├── deploy.test.ts
│ │ │ ├── deploy.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── DeployV6
│ │ │ ├── createDeployment.ts
│ │ │ ├── deploy.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── inputCommandBuilder.test.ts
│ │ │ ├── inputCommandBuilder.ts
│ │ │ └── task.json
│ ├── DeployTenant
│ │ └── TenantedDeployV6
│ │ │ ├── createDeployment.ts
│ │ │ ├── deploy.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── inputCommandBuilder.test.ts
│ │ │ ├── inputCommandBuilder.ts
│ │ │ └── task.json
│ ├── OctoCli
│ │ └── OctoCliV5
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── octoCli.test.ts
│ │ │ ├── octoCli.ts
│ │ │ └── task.json
│ ├── OctoInstaller
│ │ ├── OctoInstallerV5
│ │ │ ├── downloadEndpointRetriever.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── installer.test.ts
│ │ │ ├── installer.ts
│ │ │ ├── octopusCLIVersionFetcher.test.ts
│ │ │ ├── octopusCLIVersionFetcher.ts
│ │ │ └── task.json
│ │ └── OctoInstallerV6
│ │ │ ├── downloadEndpointRetriever.test.ts
│ │ │ ├── downloadEndpointRetriever.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── installer.ts
│ │ │ ├── octopusCLIVersionResolver.test.ts
│ │ │ ├── octopusCLIVersionResolver.ts
│ │ │ └── task.json
│ ├── OctopusMetadata
│ │ └── OctopusMetadataV5
│ │ │ ├── buildInformation.test.ts
│ │ │ ├── buildInformation.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── PackNuGet
│ │ └── PackNuGetV6
│ │ │ ├── create-package.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── input-parameters.ts
│ │ │ └── task.json
│ ├── PackZip
│ │ └── PackZipV6
│ │ │ ├── create-package.ts
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── input-parameters.ts
│ │ │ └── task.json
│ ├── Promote
│ │ └── PromoteV5
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── promote.test.ts
│ │ │ ├── promote.ts
│ │ │ └── task.json
│ ├── Push
│ │ ├── PushV5
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── push.test.ts
│ │ │ ├── push.ts
│ │ │ └── task.json
│ │ └── PushV6
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── push.ts
│ │ │ └── task.json
│ ├── RunRunbook
│ │ └── RunRunbookV6
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ ├── inputCommandBuilder.test.ts
│ │ │ ├── inputCommandBuilder.ts
│ │ │ ├── runRunbook.ts
│ │ │ ├── runbookRun.ts
│ │ │ └── task.json
│ └── Utils
│ │ ├── MockTaskWrapper.ts
│ │ ├── client.ts
│ │ ├── command-line-args.test.ts
│ │ ├── connection.test.ts
│ │ ├── connection.ts
│ │ ├── executionResult.ts
│ │ ├── inputs.ts
│ │ ├── octopusTasks.ts
│ │ ├── pluginInformation.ts
│ │ ├── taskInput.ts
│ │ ├── testing.ts
│ │ └── tool.ts
├── tasksLegacy
│ ├── CreateOctopusRelease
│ │ ├── CreateOctopusReleaseV3
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── CreateOctopusReleaseV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── Deploy
│ │ ├── DeployV3
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── DeployV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── OctoCli
│ │ └── OctoCliV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── OctoInstaller
│ │ └── OctoInstallerV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── OctopusMetadata
│ │ └── OctopusMetadataV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── Pack
│ │ └── PackV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── Promote
│ │ ├── PromoteV3
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── PromoteV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ ├── Push
│ │ ├── PushV3
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ │ └── PushV4
│ │ │ ├── icon.png
│ │ │ ├── icon.svg
│ │ │ ├── index.ts
│ │ │ └── task.json
│ └── Utils
│ │ ├── OctoApiKeyHandler.ts
│ │ ├── connection.ts
│ │ ├── environment.ts
│ │ ├── inputs.ts
│ │ ├── install.ts
│ │ └── tool.ts
├── vsts.md
└── widgets
│ └── ProjectStatus
│ ├── configuration.html
│ ├── js
│ ├── configuration.js
│ ├── moment.min.js
│ └── octo-status.js
│ └── octo-status.html
├── task-ids.json
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | dist/**/*
3 | source/widgets/**/*
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "node": true
5 | },
6 | "parser": "@typescript-eslint/parser",
7 | "plugins": ["@typescript-eslint", "prettier"],
8 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:prettier/recommended"]
9 | }
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Confused? https://help.github.com/articles/dealing-with-line-endings/
2 | # Set the default behavior, in case people don't have core.autocrlf set.
3 | * text=auto
4 |
5 | *.doc diff=astextplain
6 | *.DOC diff=astextplain
7 | *.docx diff=astextplain
8 | *.DOCX diff=astextplain
9 | *.dot diff=astextplain
10 | *.DOT diff=astextplain
11 | *.pdf diff=astextplain
12 | *.PDF diff=astextplain
13 | *.rtf diff=astextplain
14 | *.RTF diff=astextplain
15 |
16 | *.bmp binary
17 | *.gif binary
18 | *.jpg binary
19 | *.png binary
20 |
21 | *.ascx text
22 | *.cmd text
23 | *.coffee text
24 | *.config text
25 | *.cs text diff=csharp
26 | *.css text
27 | *.less text
28 | *.cshtml text
29 | *.htm text
30 | *.html text
31 | *.htm text
32 | *.js text
33 | *.json text
34 | *.msbuild text
35 | *.resx text
36 | *.ruleset text
37 | *.Stylecop text
38 | *.targets text
39 | *.tt text
40 | *.txt text
41 | *.vb text
42 | *.vbhtml text
43 | *.xml text
44 | *.xunit text
45 |
46 | *.csproj text merge=union
47 | *.vbproj text merge=union
48 |
49 | *.sln text eol=crlf merge=union
50 |
51 | *.approved.* binary
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '18 1 * * 0'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v2
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v1
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v1
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v1
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | modules
3 | Artifacts
4 |
5 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
6 | [Bb]in/
7 | [Oo]bj/
8 |
9 | # mstest test results
10 | TestResults
11 | tests
12 |
13 | ## Ignore Visual Studio temporary files, build results, and
14 | ## files generated by popular Visual Studio add-ons.
15 |
16 | # User-specific files
17 | *.suo
18 | *.user
19 | *.sln.docstates
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Rr]elease/
24 | x64/
25 | *_i.c
26 | *_p.c
27 | *.ilk
28 | *.meta
29 | *.obj
30 | *.pch
31 | *.pdb
32 | *.pgc
33 | *.pgd
34 | *.rsp
35 | *.sbr
36 | *.tlb
37 | *.tli
38 | *.tlh
39 | *.tmp
40 | *.log
41 | *.vspscc
42 | *.vssscc
43 | .builds
44 |
45 | # Visual C++ cache files
46 | ipch/
47 | *.aps
48 | *.ncb
49 | *.opensdf
50 | *.sdf
51 |
52 | # Visual Studio profiler
53 | *.psess
54 | *.vsp
55 | *.vspx
56 |
57 | # Guidance Automation Toolkit
58 | *.gpState
59 |
60 | # ReSharper is a .NET coding add-in
61 | _ReSharper*
62 |
63 | # NCrunch
64 | *.ncrunch*
65 | .*crunch*.local.xml
66 |
67 | # Installshield output folder
68 | [Ee]xpress
69 |
70 | # DocProject is a documentation generator add-in
71 | DocProject/buildhelp/
72 | DocProject/Help/*.HxT
73 | DocProject/Help/*.HxC
74 | DocProject/Help/*.hhc
75 | DocProject/Help/*.hhk
76 | DocProject/Help/*.hhp
77 | DocProject/Help/Html2
78 | DocProject/Help/html
79 |
80 | # Click-Once directory
81 | publish
82 |
83 | # Publish Web Output
84 | *.Publish.xml
85 |
86 | # NuGet Packages Directory
87 | packages
88 |
89 | # Windows Azure Build Output
90 | csx
91 | *.build.csdef
92 |
93 | # Windows Store app package directory
94 | AppPackages/
95 |
96 | # Others
97 | [Bb]in
98 | [Oo]bj
99 | sql
100 | TestResults
101 | [Tt]est[Rr]esult*
102 | *.Cache
103 | ClientBin
104 | [Ss]tyle[Cc]op.*
105 | ~$*
106 | *.dbmdl
107 | Generated_Code #added for RIA/Silverlight projects
108 | *.swp
109 | .idea
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 | dist/
118 | build/Artifacts/
119 | build/Temp/
120 | dist/
121 | .rpt2_cache/
122 |
123 | *.received.*
124 | *.DS_Store
125 |
126 | *.iml
127 |
128 | modules/*
129 | reports/*
130 | .taskkey
131 |
--------------------------------------------------------------------------------
/.octopus/deployment_settings.ocl:
--------------------------------------------------------------------------------
1 | connectivity_policy {
2 | allow_deployments_to_no_targets = true
3 | }
4 |
5 | versioning_strategy {
6 | donor_package {
7 | package = "OctoTFS.vsix"
8 | step = "push-to-azure-marketplace"
9 | }
10 | }
--------------------------------------------------------------------------------
/.octopus/schema_version.ocl:
--------------------------------------------------------------------------------
1 | version = 9
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/task.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "printWidth": 250,
4 | "tabWidth": 4,
5 | "trailingComma": "es5",
6 | "endOfLine": "auto",
7 | "overrides": [
8 | {
9 | "files": "*.yml",
10 | "options": {
11 | "tabWidth": 2
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) Octopus Deploy and contributors. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/embed-octo.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal
3 | cd "%~dp0"
4 | powershell -NoProfile -ExecutionPolicy Unrestricted .\embed-octo.ps1 %*
--------------------------------------------------------------------------------
/esbuild.mjs:
--------------------------------------------------------------------------------
1 | import { build } from "esbuild";
2 | import copyStaticFiles from "esbuild-copy-static-files";
3 | import glob from "glob";
4 | import { sep } from "path";
5 | import { statSync } from "fs";
6 | import os from "os";
7 | import yargs from "yargs";
8 |
9 | let __dirname = new URL(".", import.meta.url).pathname;
10 |
11 | if (os.platform() === "win32") {
12 | if (__dirname.startsWith("/")) {
13 | __dirname = __dirname.substring(1);
14 | }
15 | }
16 |
17 | function entryPoints() {
18 | function doReplacements(entries, source) {
19 | const matches = glob.sync(`${__dirname}source/${source}/**/index.ts`);
20 |
21 | for (let match of matches) {
22 | const key = match.replace(`${__dirname}source/${source}/`, "tasks/").replace(".ts", "");
23 |
24 | entries[key] = match;
25 | }
26 | }
27 |
28 | const entries = {};
29 | doReplacements(entries, "tasks");
30 | doReplacements(entries, "tasksLegacy");
31 |
32 | return entries;
33 | }
34 |
35 | const argv = yargs(process.argv).argv;
36 |
37 | function noTSFiles(src) {
38 | if (src.endsWith(".ts")) {
39 | return false;
40 | }
41 |
42 | return true;
43 | }
44 |
45 | function noFolders(src) {
46 | const isDirectory = statSync(src).isDirectory();
47 |
48 | if (src.endsWith(`${sep}source`)) {
49 | return true;
50 | }
51 |
52 | return !isDirectory;
53 | }
54 |
55 | const bundleAsMuchAsWeCan = {
56 | name: "my-special-bundle",
57 | setup(build) {
58 | build.onResolve({ filter: /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/ }, (args) => {
59 | console.log(args.path);
60 | console.log(`args.resolveDir=${args.resolveDir}`);
61 | if (args.path.startsWith("azure-pipelines-tool-lib") || args.path.startsWith("azure-pipelines-task-lib")) return { path: args.path, external: true };
62 | });
63 | },
64 | };
65 |
66 | build({
67 | entryPoints: entryPoints(),
68 | bundle: true,
69 | target: "es2018",
70 | platform: "node",
71 | outdir: "dist",
72 | metafile: true,
73 | minify: true,
74 | plugins: [
75 | copyStaticFiles({ src: "./source/img", dest: "dist/img" }),
76 | copyStaticFiles({ src: "./source", dest: "dist", recursive: false, filter: noFolders }),
77 | copyStaticFiles({ src: "./source/widgets", dest: "dist/widgets" }),
78 | copyStaticFiles({ src: "./node_modules/vss-web-extension-sdk/lib", dest: "dist/widgets/ProjectStatus/lib" }),
79 | copyStaticFiles({ src: "./source/tasks", dest: "dist/tasks", filter: noTSFiles }),
80 | copyStaticFiles({ src: "./source/tasksLegacy", dest: "dist/tasks", filter: noTSFiles }),
81 | bundleAsMuchAsWeCan,
82 | ],
83 | logLimit: 0,
84 | logLevel: "info",
85 | define: { "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), "process.env.EXTENSION_VERSION": JSON.stringify(argv.extensionVersion) },
86 | }).catch(() => process.exit(1));
87 |
--------------------------------------------------------------------------------
/img/ps-addpowershellstep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/ps-addpowershellstep.png
--------------------------------------------------------------------------------
/img/ps-configurepowershellstep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/ps-configurepowershellstep.png
--------------------------------------------------------------------------------
/img/tfsbuild-addbuildstep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-addbuildstep.png
--------------------------------------------------------------------------------
/img/tfsbuild-configurebuildstep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-configurebuildstep.png
--------------------------------------------------------------------------------
/img/tfsbuild-connectedservice1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-connectedservice1.png
--------------------------------------------------------------------------------
/img/tfsbuild-connectedservice2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-connectedservice2.png
--------------------------------------------------------------------------------
/img/tfsbuild-releasenotes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-releasenotes.png
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "@jest/types";
2 |
3 | const config: Config.InitialOptions = {
4 | maxWorkers: 4,
5 | verbose: true,
6 | preset: "ts-jest/presets/js-with-ts",
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/localBuild.cmd:
--------------------------------------------------------------------------------
1 | rm -r dist
2 | call npm run build -- --extensionVersion %*
3 | rm -r modules
4 |
5 | setlocal
6 | cd "%~dp0"
7 | powershell -NoProfile -ExecutionPolicy Unrestricted .\pack.ps1 LocalTest %* -setupTaskDependencies
8 |
--------------------------------------------------------------------------------
/localbuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm -r dist
3 | npm run build -- --extensionVersion $1
4 | rm -r modules
5 | mkdir dist
6 | pwsh -NoProfile -ExecutionPolicy Unrestricted ./pack.ps1 LocalTest $1 -setupTaskDependencies
--------------------------------------------------------------------------------
/pack.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal
3 | cd "%~dp0"
4 | powershell -NoProfile -ExecutionPolicy Unrestricted .\pack.ps1 %*
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "test": "jest --ci --reporters=default --reporters=jest-junit",
5 | "build": "tsc --noEmit && node esbuild.mjs",
6 | "lint:fix": "eslint . --fix",
7 | "lint": "eslint ."
8 | },
9 | "devDependencies": {
10 | "@tsconfig/node10": "^1.0.9",
11 | "@types/archiver": "^5.3.1",
12 | "@types/command-line-args": "^5.2.0",
13 | "@types/express": "^4.17.13",
14 | "@types/glob": "7.2.0",
15 | "@types/jest": "28.1.5",
16 | "@types/node": "^18.0.4",
17 | "@types/ramda": "0.28.15",
18 | "@types/test-console": "^2.0.0",
19 | "@types/uuid": "8.3.4",
20 | "@types/yargs": "^17.0.10",
21 | "@typescript-eslint/eslint-plugin": "^5.30.6",
22 | "@typescript-eslint/parser": "^5.30.6",
23 | "archiver": "^5.3.1",
24 | "esbuild": "^0.14.49",
25 | "esbuild-copy-static-files": "^0.1.0",
26 | "eslint": "^8.19.0",
27 | "eslint-config-prettier": "^8.5.0",
28 | "eslint-plugin-prettier": "^4.2.1",
29 | "express": "^4.18.1",
30 | "jest": "^28.1.3",
31 | "jest-junit": "^14.0.0",
32 | "prettier": "2.7.1",
33 | "rimraf": "3.0.2",
34 | "test-console": "^2.0.0",
35 | "ts-jest": "28.0.5",
36 | "ts-node": "^10.9.0",
37 | "typescript": "^4.7.4",
38 | "yargs": "^17.5.1"
39 | },
40 | "dependencies": {
41 | "@octopusdeploy/api-client": "^3.5.1",
42 | "azure-devops-node-api": "11.2.0",
43 | "azure-pipelines-task-lib": "3.3.1",
44 | "azure-pipelines-tool-lib": "1.3.2",
45 | "command-line-args": "^5.2.1",
46 | "fp-ts": "1.19.5",
47 | "glob": "7.2.0",
48 | "ramda": "0.28.0",
49 | "semver": "^7.3.7",
50 | "shlex": "^2.1.2",
51 | "typed-rest-client": "1.8.9",
52 | "uuid": "8.3.2",
53 | "vss-web-extension-sdk": "5.141.0"
54 | },
55 | "jest-junit": {
56 | "outputDirectory": "reports",
57 | "outputName": "jest-junit.xml",
58 | "ancestorSeparator": " › ",
59 | "uniqueOutputName": "false",
60 | "suiteNameTemplate": "{filepath}",
61 | "classNameTemplate": "{classname}",
62 | "titleTemplate": "{title}"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/source/extension-icon.LocalTest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.LocalTest.png
--------------------------------------------------------------------------------
/source/extension-icon.Test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.Test.png
--------------------------------------------------------------------------------
/source/extension-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.png
--------------------------------------------------------------------------------
/source/extension-manifest.LocalTest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "octopus-deploy-build-release-tasks-localtest",
3 | "name": "[LocalTest] Octopus Deploy Integration",
4 | "version": "set-by-pack.ps1",
5 | "publisher": "octopusdeploy",
6 | "public": false,
7 | "branding": {
8 | "color": "#434a54",
9 | "theme": "dark"
10 | }
11 | }
--------------------------------------------------------------------------------
/source/extension-manifest.Production.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "octopus-deploy-build-release-tasks",
3 | "name": "Octopus Deploy Integration",
4 | "version": "set-by-pack.ps1",
5 | "publisher": "octopusdeploy",
6 | "public": true,
7 | "branding": {
8 | "color": "#eee",
9 | "theme": "light"
10 | }
11 | }
--------------------------------------------------------------------------------
/source/extension-manifest.Test.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "octopus-deploy-build-release-tasks-test",
3 | "name": "[Test] Octopus Deploy Integration",
4 | "version": "set-by-pack.ps1",
5 | "publisher": "octopusdeploy",
6 | "public": false,
7 | "branding": {
8 | "color": "#434a54",
9 | "theme": "dark"
10 | }
11 | }
--------------------------------------------------------------------------------
/source/img/catalog-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/catalog-image.jpg
--------------------------------------------------------------------------------
/source/img/create-package-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/create-package-options.png
--------------------------------------------------------------------------------
/source/img/create-release-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/create-release-options.png
--------------------------------------------------------------------------------
/source/img/deploy-release-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/deploy-release-options.png
--------------------------------------------------------------------------------
/source/img/multiple-widget-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/multiple-widget-preview.jpg
--------------------------------------------------------------------------------
/source/img/octopus-add-tasks-deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-add-tasks-deploy.png
--------------------------------------------------------------------------------
/source/img/octopus-add-tasks-package.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-add-tasks-package.png
--------------------------------------------------------------------------------
/source/img/octopus-alltasks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-alltasks.png
--------------------------------------------------------------------------------
/source/img/octopus_create-release-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_create-release-04.png
--------------------------------------------------------------------------------
/source/img/octopus_deploy-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_deploy-02.png
--------------------------------------------------------------------------------
/source/img/octopus_installer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_installer.png
--------------------------------------------------------------------------------
/source/img/octopus_package-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_package-03.png
--------------------------------------------------------------------------------
/source/img/octopus_promote-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_promote-05.png
--------------------------------------------------------------------------------
/source/img/octopus_push-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_push-01.png
--------------------------------------------------------------------------------
/source/img/preview-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/preview-image.jpg
--------------------------------------------------------------------------------
/source/img/promote-release-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/promote-release-options.png
--------------------------------------------------------------------------------
/source/img/push-metadata-options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/push-metadata-options.png
--------------------------------------------------------------------------------
/source/img/service-connection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/service-connection.png
--------------------------------------------------------------------------------
/source/img/tfsbuild-addbuildstep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-addbuildstep.png
--------------------------------------------------------------------------------
/source/img/tfsbuild-connectedservice1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-connectedservice1.png
--------------------------------------------------------------------------------
/source/img/tfsbuild-connectedservice2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-connectedservice2.png
--------------------------------------------------------------------------------
/source/img/vstsbuild-octopusendpoint-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/vstsbuild-octopusendpoint-2.png
--------------------------------------------------------------------------------
/source/img/widget-icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/widget-icon.jpg
--------------------------------------------------------------------------------
/source/tasks/AwaitTask/AwaitTaskV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/AwaitTask/AwaitTaskV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/AwaitTask/AwaitTaskV6/index.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
3 | import { Logger } from "@octopusdeploy/api-client";
4 | import * as tasks from "azure-pipelines-task-lib/task";
5 | import { Waiter } from "./waiter";
6 |
7 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
8 |
9 | const logger: Logger = {
10 | debug: (message) => {
11 | tasks.debug(message);
12 | },
13 | info: (message) => console.log(message),
14 | warn: (message) => tasks.warning(message),
15 | error: (message, err) => {
16 | if (err !== undefined) {
17 | tasks.error(err.message);
18 | } else {
19 | tasks.error(message);
20 | }
21 | },
22 | };
23 |
24 | const task: TaskWrapper = new ConcreteTaskWrapper();
25 |
26 | new Waiter(connection, task, logger).run();
27 |
--------------------------------------------------------------------------------
/source/tasks/AwaitTask/AwaitTaskV6/input-parameters.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from "@octopusdeploy/api-client";
2 | import { TaskWrapper } from "tasks/Utils/taskInput";
3 | import { WaitExecutionResult } from "./waiter";
4 |
5 | export interface InputParameters {
6 | space: string;
7 | step: string;
8 | tasks: WaitExecutionResult[];
9 | pollingInterval: number;
10 | timeout: number;
11 | showProgress: boolean;
12 | }
13 |
14 | export function getInputParameters(logger: Logger, task: TaskWrapper): InputParameters {
15 | const space = task.getInput("Space");
16 | if (!space) {
17 | throw new Error("Failed to successfully build parameters: space name is required.");
18 | }
19 |
20 | const step = task.getInput("Step");
21 | if (!step) {
22 | throw new Error("Failed to successfully build parameters: step name is required.");
23 | }
24 |
25 | const taskJson = task.getOutputVariable(step, "server_tasks");
26 | if (taskJson === undefined) {
27 | throw new Error(`Failed to successfully build parameters: cannot find '${step}.server_tasks' variable from execution step`);
28 | }
29 | const tasks = JSON.parse(taskJson);
30 | if (!Array.isArray(tasks)) {
31 | throw new Error(`Failed to successfully build parameters: '${step}.server_tasks' variable from execution step is not an array`);
32 | }
33 |
34 | let pollingInterval = 10;
35 | const pollingIntervalField = task.getInput("PollingInterval");
36 | if (pollingIntervalField) {
37 | pollingInterval = +pollingIntervalField;
38 | }
39 |
40 | let timeoutSeconds = 600;
41 | const timeoutField = task.getInput("TimeoutAfter");
42 | if (timeoutField) {
43 | timeoutSeconds = +timeoutField;
44 | }
45 |
46 | const showProgress = task.getBoolean("ShowProgress") ?? false;
47 | if (showProgress && tasks.length > 1) {
48 | throw new Error("Failed to successfully build parameters: ShowProgress can only be enabled when waiting for a single task");
49 | }
50 |
51 | const parameters: InputParameters = {
52 | space: task.getInput("Space") || "",
53 | step: step,
54 | tasks: tasks,
55 | showProgress: showProgress,
56 | pollingInterval: pollingInterval,
57 | timeout: timeoutSeconds,
58 | };
59 |
60 | const errors: string[] = [];
61 | if (parameters.space === "") {
62 | errors.push("The Octopus space name is required.");
63 | }
64 |
65 | if (errors.length > 0) {
66 | throw new Error("Failed to successfully build parameters.\n" + errors.join("\n"));
67 | }
68 |
69 | logger.debug?.(`Tasks: \n${JSON.stringify(parameters, null, 2)}`);
70 |
71 | return parameters;
72 | }
73 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/buildInformation.ts:
--------------------------------------------------------------------------------
1 | import { OctoServerConnectionDetails } from "../../Utils/connection";
2 | import { createCommandFromInputs } from "./inputCommandBuilder";
3 | import { BuildInformationRepository, Logger } from "@octopusdeploy/api-client";
4 | import { TaskWrapper } from "tasks/Utils/taskInput";
5 | import { getOverwriteMode } from "./overwriteMode";
6 | import { IVstsHelper } from "./vsts";
7 | import { getClient } from "../../Utils/client";
8 |
9 | export class BuildInformation {
10 | constructor(readonly connection: OctoServerConnectionDetails, readonly logger: Logger, readonly task: TaskWrapper, readonly vsts: IVstsHelper) {}
11 |
12 | public async run() {
13 | const command = await createCommandFromInputs(this.logger, this.task, this.vsts);
14 | const client = await getClient(this.connection, this.logger, "build-information", "push", 6)
15 |
16 | const overwriteMode = await getOverwriteMode(this.logger, this.task);
17 | this.logger.debug?.(`Build Information:\n${JSON.stringify(command, null, 2)}`);
18 | const repository = new BuildInformationRepository(client, command.spaceName);
19 | await repository.push(command, overwriteMode);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/BuildInformation/BuildInformationV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/index.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from "@octopusdeploy/api-client";
2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "tasks/Utils/connection";
3 | import * as tasks from "azure-pipelines-task-lib/task";
4 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
5 | import { BuildInformation } from "./buildInformation";
6 | import { IVstsHelper, VstsHelper } from "./vsts";
7 |
8 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
9 |
10 | const logger: Logger = {
11 | debug: (message) => {
12 | tasks.debug(message);
13 | },
14 | info: (message) => console.log(message),
15 | warn: (message) => tasks.warning(message),
16 | error: (message, err) => {
17 | if (err !== undefined) {
18 | tasks.error(err.message);
19 | } else {
20 | tasks.error(message);
21 | }
22 | },
23 | };
24 |
25 | const task: TaskWrapper = new ConcreteTaskWrapper();
26 | const vsts: IVstsHelper = new VstsHelper(logger);
27 |
28 | new BuildInformation(connection, logger, task, vsts).run();
29 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/inputCommandBuilder.ts:
--------------------------------------------------------------------------------
1 | import { CreateOctopusBuildInformationCommand, Logger, PackageIdentity } from "@octopusdeploy/api-client";
2 | import { getLineSeparatedItems } from "../../Utils/inputs";
3 | import { TaskWrapper } from "tasks/Utils/taskInput";
4 | import { IVstsHelper } from "./vsts";
5 |
6 | export async function createCommandFromInputs(logger: Logger, task: TaskWrapper, vstsHelper: IVstsHelper): Promise {
7 | const vsts = await vstsHelper.getVsts(logger);
8 | const inputPackages = getLineSeparatedItems(task.getInput("PackageIds") || "") || [];
9 | logger.debug?.(`PackageIds: ${inputPackages}`);
10 | const packages: PackageIdentity[] = [];
11 | for (const packageId of inputPackages) {
12 | packages.push({
13 | Id: packageId,
14 | Version: task.getInput("PackageVersion") || "",
15 | });
16 | }
17 |
18 | const command: CreateOctopusBuildInformationCommand = {
19 | spaceName: task.getInput("Space") || "",
20 | BuildEnvironment: "Azure DevOps",
21 | BuildNumber: vsts.environment.buildNumber,
22 | BuildUrl: vsts.environment.teamCollectionUri.replace(/\/$/, "") + "/" + vsts.environment.projectName + "/_build/results?buildId=" + vsts.environment.buildId,
23 | Branch: vsts.branch || "",
24 | VcsType: vsts.vcsType,
25 | VcsRoot: vsts.environment.buildRepositoryUri,
26 | VcsCommitNumber: vsts.environment.buildSourceVersion,
27 | Commits: vsts.commits,
28 | Packages: packages,
29 | };
30 |
31 | const errors: string[] = [];
32 | if (!command.spaceName) {
33 | errors.push("space name is required");
34 | }
35 |
36 | if (!command.Packages || command.Packages.length === 0) {
37 | errors.push("must specify at least one package name");
38 | } else {
39 | if (!command.Packages[0].Version || command.Packages[0].Version === "") {
40 | errors.push("must specify a package version number, in SemVer format");
41 | }
42 | }
43 |
44 | if (errors.length > 0) {
45 | throw new Error(`Failed to successfully build parameters:\n${errors.join("\n")}`);
46 | }
47 |
48 | return command;
49 | }
50 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/overwriteMode.test.ts:
--------------------------------------------------------------------------------
1 | import { Logger, OverwriteMode } from "@octopusdeploy/api-client";
2 | import { getOverwriteMode } from "./overwriteMode";
3 | import { MockTaskWrapper } from "../../Utils/MockTaskWrapper";
4 |
5 | describe("getInputCommand", () => {
6 | let logger: Logger;
7 | let task: MockTaskWrapper;
8 | beforeEach(() => {
9 | logger = {};
10 | task = new MockTaskWrapper();
11 | });
12 |
13 | test("second run", () => {
14 | task.addVariableString("system.jobAttempt", "2");
15 | const overwriteMode = getOverwriteMode(logger, task);
16 | expect(overwriteMode).toBe(OverwriteMode.IgnoreIfExists);
17 | });
18 |
19 | test("user provided", () => {
20 | task.addVariableString("Replace", "true");
21 | const overwriteMode = getOverwriteMode(logger, task);
22 | expect(overwriteMode).toBe(OverwriteMode.OverwriteExisting);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/overwriteMode.ts:
--------------------------------------------------------------------------------
1 | import { Logger, OverwriteMode } from "@octopusdeploy/api-client";
2 | import { ReplaceOverwriteMode } from "../../Utils/inputs";
3 | import { TaskWrapper } from "../../Utils/taskInput";
4 |
5 | export function getOverwriteMode(logger: Logger, task: TaskWrapper): OverwriteMode {
6 | const isRetry = parseInt(task.getVariable("system.jobAttempt") || "0") > 1;
7 | const overwriteMode: ReplaceOverwriteMode =
8 | (ReplaceOverwriteMode as any)[task.getInput("Replace", false) || ""] || // eslint-disable-line @typescript-eslint/no-explicit-any
9 | (isRetry ? ReplaceOverwriteMode.IgnoreIfExists : ReplaceOverwriteMode.false);
10 |
11 | let apiOverwriteMode: OverwriteMode;
12 | switch (overwriteMode) {
13 | case ReplaceOverwriteMode.true:
14 | apiOverwriteMode = OverwriteMode.OverwriteExisting;
15 | break;
16 | case ReplaceOverwriteMode.IgnoreIfExists:
17 | apiOverwriteMode = OverwriteMode.IgnoreIfExists;
18 | break;
19 | case ReplaceOverwriteMode.false:
20 | apiOverwriteMode = OverwriteMode.FailIfExists;
21 | break;
22 | default:
23 | apiOverwriteMode = OverwriteMode.FailIfExists;
24 | break;
25 | }
26 | logger.debug?.(`Overwrite mode: ${apiOverwriteMode}`);
27 | return apiOverwriteMode;
28 | }
29 |
--------------------------------------------------------------------------------
/source/tasks/BuildInformation/BuildInformationV6/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "559b81c9-efc1-40f3-9058-71ab1810d837",
3 | "name": "OctopusBuildInformation",
4 | "friendlyName": "Push Package Build Information to Octopus",
5 | "description": "Collect information related to the build, including work items from commit messages, and push to your Octopus Deploy Server.",
6 | "helpMarkDown": "set-by-pack.ps1",
7 | "category": "Package",
8 | "visibility": [
9 | "Build"
10 | ],
11 | "author": "Octopus Deploy",
12 | "version": {
13 | "Major": 6,
14 | "Minor": 0,
15 | "Patch": 0
16 | },
17 | "demands": [],
18 | "minimumAgentVersion": "2.206.1",
19 | "inputs": [
20 | {
21 | "name": "OctoConnectedServiceName",
22 | "type": "connectedService:OctopusEndpoint",
23 | "label": "Octopus Deploy Server",
24 | "defaultValue": "",
25 | "required": true,
26 | "helpMarkDown": "Octopus Deploy server connection"
27 | },
28 | {
29 | "name": "Space",
30 | "type": "string",
31 | "label": "Space",
32 | "defaultValue": "",
33 | "required": true,
34 | "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id."
35 | },
36 | {
37 | "name": "PackageIds",
38 | "type": "multiLine",
39 | "label": "Package IDs",
40 | "defaultValue": "",
41 | "required": true,
42 | "helpMarkDown": "Newline-separated package IDs; e.g.\nMyCompany.MyApp\nMyCompany.MyApp2"
43 | },
44 | {
45 | "name": "PackageVersion",
46 | "type": "string",
47 | "label": "Package Version",
48 | "defaultValue": "",
49 | "required": true,
50 | "helpMarkDown": "The version of the package; must be a valid [SemVer](http://semver.org/) version."
51 | },
52 | {
53 | "name": "Replace",
54 | "type": "pickList",
55 | "label": "Overwrite Mode",
56 | "defaultValue": "false",
57 | "required": true,
58 | "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.",
59 | "options": {
60 | "false": "Fail if exists",
61 | "true": "Overwrite existing",
62 | "IgnoreIfExists": "Ignore if exists"
63 | }
64 | }
65 | ],
66 | "instanceNameFormat": "Push Package Build Information to Octopus",
67 | "execution": {
68 | "Node16": {
69 | "target": "index.js"
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/createRelease.test.ts:
--------------------------------------------------------------------------------
1 | import { CreateRelease } from "./createRelease";
2 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing";
3 |
4 | describe("Create Release", () => {
5 | test("Create a minimum release", async () => {
6 | const output = await executeCommand(() => new CreateRelease(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run("my space", "my project"));
7 |
8 | expect(output).toContain("create-release --space my space --project my project --enableServiceMessages --server http://octopus.com --apiKey myapikey");
9 | });
10 |
11 | test("Create a release and deployment", async () => {
12 | const output = await executeCommand(() =>
13 | new CreateRelease(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run(
14 | "my space",
15 | "my project",
16 | "1.2.3",
17 | "mychannel",
18 | "special release notes",
19 | ["dev", "prod"],
20 | ["tenantA", "tenantB"],
21 | ["tagme", "tagyou"],
22 | false,
23 | "--myAdditionalArgumentToInclude",
24 | "mygitref",
25 | "mygitcommit"
26 | )
27 | );
28 |
29 | expect(output).toContain(
30 | "create-release --space my space --project my project --releaseNumber 1.2.3 --channel mychannel --gitCommit mygitcommit --gitRef mygitref --releaseNotes special release notes --enableServiceMessages --deployTo dev --deployTo prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --myAdditionalArgumentToInclude"
31 | );
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/createRelease.ts:
--------------------------------------------------------------------------------
1 | import { OctopusToolRunner } from "../../Utils/tool";
2 | import { executeTask } from "../../Utils/octopusTasks";
3 | import { OctoServerConnectionDetails } from "../../Utils/connection";
4 |
5 | export class CreateRelease {
6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {}
7 |
8 | public async run(
9 | space: string,
10 | project: string,
11 | releaseNumber?: string | undefined,
12 | channel?: string | undefined,
13 | customReleaseNotes?: string | undefined,
14 | deployToEnvironments: string[] = [],
15 | deployForTenants: string[] = [],
16 | deployForTenantTags: string[] = [],
17 | deploymentProgress?: boolean | undefined,
18 | additionalArguments?: string | undefined,
19 | gitRef?: string | undefined,
20 | gitCommit?: string | undefined
21 | ) {
22 | this.tool.arg("create-release");
23 | this.tool.arg(["--space", space]);
24 | this.tool.arg(["--project", project]);
25 | this.tool.argIf(releaseNumber, ["--releaseNumber", `${releaseNumber}`]);
26 | this.tool.argIf(channel, ["--channel", `${channel}`]);
27 | this.tool.argIf(gitCommit, ["--gitCommit", `${gitCommit}`]);
28 | this.tool.argIf(gitRef, ["--gitRef", `${gitRef}`]);
29 | this.tool.argIf(customReleaseNotes, ["--releaseNotes", `${customReleaseNotes}`]);
30 | this.tool.arg("--enableServiceMessages");
31 | this.tool.argIf(deploymentProgress, "--progress");
32 | for (const item of deployToEnvironments) {
33 | this.tool.arg(["--deployTo", item]);
34 | }
35 | for (const item of deployForTenants) {
36 | this.tool.arg(["--tenant", item]);
37 | }
38 | for (const item of deployForTenantTags) {
39 | this.tool.arg(["--tenantTag", item]);
40 | }
41 |
42 | await executeTask(this.tool, "(release;create;v5)", this.connection, "Create release succeeded.", "Failed to create release.", additionalArguments);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs";
3 | import { CreateRelease } from "./createRelease";
4 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const space = getRequiredInput("Space");
8 | const project = getRequiredInput("ProjectName");
9 | const releaseNumber = tasks.getInput("ReleaseNumber");
10 | const channel = tasks.getInput("Channel");
11 | const customReleaseNotes = tasks.getInput("CustomReleaseNotes");
12 | const deployToEnvironments = getDelimitedInput("DeployToEnvironment");
13 | const deployForTenants = getDelimitedInput("DeployForTenants");
14 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags");
15 | const deploymentProgress = tasks.getBoolInput("DeploymentProgress");
16 | const additionalArguments = tasks.getInput("AdditionalArguments");
17 | const gitRef = tasks.getInput("GitRef");
18 | const gitCommit = tasks.getInput("GitCommit");
19 |
20 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
21 |
22 | new CreateRelease(getOctopusCliTool(), connection).run(space, project, releaseNumber, channel, customReleaseNotes, deployToEnvironments, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments, gitRef, gitCommit);
23 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/createRelease.ts:
--------------------------------------------------------------------------------
1 | import os from "os";
2 | import { Client, CreateReleaseCommandV1, Logger, ReleaseRepository } from "@octopusdeploy/api-client";
3 | import { TaskWrapper } from "tasks/Utils/taskInput";
4 |
5 | // Returns the release number that was actually created in Octopus
6 | export async function createReleaseFromInputs(client: Client, command: CreateReleaseCommandV1, task: TaskWrapper, logger: Logger): Promise {
7 | logger.info?.("🐙 Creating a release in Octopus Deploy...");
8 |
9 | try {
10 | const repository = new ReleaseRepository(client, command.spaceName);
11 | const response = await repository.create(command);
12 |
13 | if (command.IgnoreIfAlreadyExists) {
14 | client.info(`🎉 Release ${response.ReleaseVersion} is ready for deployment!`);
15 | } else {
16 | client.info(`🎉 Release ${response.ReleaseVersion} created successfully!`);
17 | }
18 |
19 | task.setOutputVariable("release_number", response.ReleaseVersion);
20 |
21 | return response.ReleaseVersion;
22 | } catch (error: unknown) {
23 | if (error instanceof Error) {
24 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true);
25 | } else {
26 | task.setFailure(`"Failed to execute command. ${error}`, true);
27 | }
28 | throw error;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/index.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
3 | import { Logger } from "@octopusdeploy/api-client";
4 | import * as tasks from "azure-pipelines-task-lib/task";
5 | import { Release } from "./release";
6 |
7 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
8 |
9 | const logger: Logger = {
10 | debug: (message) => {
11 | tasks.debug(message);
12 | },
13 | info: (message) => console.log(message),
14 | warn: (message) => tasks.warning(message),
15 | error: (message, err) => {
16 | if (err !== undefined) {
17 | tasks.error(err.message);
18 | } else {
19 | tasks.error(message);
20 | }
21 | },
22 | };
23 |
24 | const task: TaskWrapper = new ConcreteTaskWrapper();
25 |
26 | new Release(connection, task, logger).run();
27 |
--------------------------------------------------------------------------------
/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/release.ts:
--------------------------------------------------------------------------------
1 | import { Client, CreateReleaseCommandV1, Logger, Project, ProjectRepository, resolveSpaceId } from "@octopusdeploy/api-client";
2 | import { getDeepLink, OctoServerConnectionDetails } from "../../Utils/connection";
3 | import { createReleaseFromInputs } from "./createRelease";
4 | import { createCommandFromInputs } from "./inputCommandBuilder";
5 | import os from "os";
6 | import { TaskWrapper } from "tasks/Utils/taskInput";
7 | import path from "path";
8 | import { getVstsEnvironmentVariables } from "../../../tasksLegacy/Utils/environment";
9 | import { v4 as uuidv4 } from "uuid";
10 | import * as tasks from "azure-pipelines-task-lib";
11 | import { getClient } from "../../Utils/client";
12 |
13 | export class Release {
14 | constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {}
15 |
16 | public async run() {
17 | try {
18 | const command = createCommandFromInputs(this.logger, this.task);
19 | const client = await getClient(this.connection, this.logger, "release", "create", 6);
20 | const version = await createReleaseFromInputs(client, command, this.task, this.logger);
21 |
22 | await this.tryCreateSummary(client, command, version);
23 |
24 | this.task.setSuccess("Release creation succeeded.");
25 | } catch (error: unknown) {
26 | if (error instanceof Error) {
27 | this.task.setFailure(`"Failed to successfully create release. ${error.message}${os.EOL}${error.stack}`, true);
28 | } else {
29 | this.task.setFailure(`"Failed to successfully create release. ${error}`, true);
30 | }
31 | throw error;
32 | }
33 | }
34 |
35 | private async tryCreateSummary(client: Client, command: CreateReleaseCommandV1, version: string) {
36 | const spaceId = await resolveSpaceId(client, command.spaceName);
37 | const projectRepo = new ProjectRepository(client, command.spaceName);
38 | const projects = await projectRepo.list({ partialName: command.ProjectName });
39 | const matchedProjects = projects.Items.filter((p: Project) => p.Name.localeCompare(command.ProjectName) === 0);
40 | if (matchedProjects.length === 1) {
41 | const link = getDeepLink(this.connection.url, `${spaceId}/projects/${matchedProjects[0].Id}/deployments/releases/${version}`);
42 | const markdown = `[Release ${version} created for '${matchedProjects[0].Name}'](${link})`;
43 | const markdownFile = path.join(getVstsEnvironmentVariables().defaultWorkingDirectory, `${uuidv4()}.md`);
44 | tasks.writeFile(markdownFile, markdown);
45 | tasks.addAttachment("Distributedtask.Core.Summary", "Octopus Create Release", markdownFile);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV5/deploy.test.ts:
--------------------------------------------------------------------------------
1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing";
2 | import { Deploy } from "./deploy";
3 |
4 | describe("Deploy Release", () => {
5 | test("Create a minimum deployment", async () => {
6 | const output = await executeCommand(() => new Deploy(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run("my space", "my project", "1.2.3", ["dev"]));
7 |
8 | expect(output).toContain("deploy-release --space my space --project my project --releaseNumber 1.2.3 --enableServiceMessages --deployTo dev --server http://octopus.com --apiKey myapikey");
9 | });
10 |
11 | test("Create a deployment", async () => {
12 | const output = await executeCommand(() =>
13 | new Deploy(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run(
14 | "my space",
15 | "my project",
16 | "1.2.3",
17 | ["dev", "prod"],
18 | ["tenantA", "tenantB"],
19 | ["tagme", "tagyou"],
20 | false,
21 | "--myAdditionalArgumentToInclude"
22 | )
23 | );
24 |
25 | expect(output).toContain(
26 | "deploy-release --space my space --project my project --releaseNumber 1.2.3 --enableServiceMessages --deployTo dev --deployTo prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --myAdditionalArgumentToInclude"
27 | );
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV5/deploy.ts:
--------------------------------------------------------------------------------
1 | import { OctopusToolRunner } from "../../Utils/tool";
2 | import { OctoServerConnectionDetails } from "../../Utils/connection";
3 | import { executeTask } from "../../Utils/octopusTasks";
4 |
5 | export class Deploy {
6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {}
7 |
8 | public async run(
9 | space: string,
10 | project: string,
11 | releaseNumber: string,
12 | deployToEnvironments: string[],
13 | deployForTenants: string[] = [],
14 | deployForTenantTags: string[] = [],
15 | showProgress?: boolean | undefined,
16 | additionalArguments?: string | undefined
17 | ) {
18 | this.tool.arg("deploy-release");
19 | this.tool.arg(["--space", space]);
20 | this.tool.arg(["--project", project]);
21 | this.tool.argIf(releaseNumber, ["--releaseNumber", releaseNumber]);
22 | this.tool.arg("--enableServiceMessages");
23 | this.tool.argIf(showProgress, "--progress");
24 | for (const item of deployToEnvironments) {
25 | this.tool.arg(["--deployTo", item]);
26 | }
27 | for (const item of deployForTenants) {
28 | this.tool.arg(["--tenant", item]);
29 | }
30 | for (const item of deployForTenantTags) {
31 | this.tool.arg(["--tenantTag", item]);
32 | }
33 |
34 | let stepIdentifier = "(release;deploy;v5)";
35 | if (deployForTenants.length > 0 || deployForTenantTags.length > 0) {
36 | stepIdentifier = "(release;deploy-tenanted;v5)";
37 | }
38 |
39 | await executeTask(this.tool, stepIdentifier, this.connection, "Deployment succeeded.", "Failed to deploy release.", additionalArguments);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Deploy/DeployV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
3 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs";
4 | import { Deploy } from "./deploy";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const space = getRequiredInput("Space");
8 | const project = getRequiredInput("Project");
9 | const releaseNumber = getRequiredInput("ReleaseNumber");
10 | const deployToEnvironments = getDelimitedInput("Environments");
11 | const deployForTenants = getDelimitedInput("DeployForTenants");
12 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags");
13 | const deploymentProgress = tasks.getBoolInput("ShowProgress");
14 | const additionalArguments = tasks.getInput("AdditionalArguments");
15 |
16 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
17 |
18 | new Deploy(getOctopusCliTool(), connection).run(space, project, releaseNumber, deployToEnvironments, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments);
19 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV6/createDeployment.ts:
--------------------------------------------------------------------------------
1 | import { Client, CreateDeploymentUntenantedCommandV1, DeploymentRepository, EnvironmentRepository, Logger } from "@octopusdeploy/api-client";
2 | import os from "os";
3 | import { TaskWrapper } from "../../Utils/taskInput";
4 | import { ExecutionResult } from "../../Utils/executionResult";
5 |
6 | export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentUntenantedCommandV1, task: TaskWrapper, logger: Logger): Promise {
7 | logger.info?.("🐙 Deploying a release in Octopus Deploy...");
8 |
9 | try {
10 | const deploymentRepository = new DeploymentRepository(client, command.spaceName);
11 | const response = await deploymentRepository.create(command);
12 |
13 | client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`);
14 |
15 | if (response.DeploymentServerTasks.length === 0) {
16 | throw new Error("Expected at least one deployment to be queued.");
17 | }
18 | if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) {
19 | throw new Error("Server task id was not deserialized correctly.");
20 | }
21 |
22 | const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId);
23 |
24 | const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length });
25 |
26 | const envIds = deployments.Items.map((d) => d.EnvironmentId);
27 | const envRepository = new EnvironmentRepository(client, command.spaceName);
28 | const environments = await envRepository.list({ ids: envIds, take: envIds.length });
29 |
30 | const results = response.DeploymentServerTasks.map((x) => {
31 | return {
32 | serverTaskId: x.ServerTaskId,
33 | environmentName: environments.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].EnvironmentId)[0].Name,
34 | type: "Deployment",
35 | } as ExecutionResult;
36 | });
37 |
38 | task.setOutputVariable("server_tasks", JSON.stringify(results));
39 |
40 | return results;
41 | } catch (error: unknown) {
42 | if (error instanceof Error) {
43 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true);
44 | } else {
45 | task.setFailure(`"Failed to execute command. ${error}`, true);
46 | }
47 | throw error;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Deploy/DeployV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV6/icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/source/tasks/Deploy/DeployV6/index.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
2 | import { Deploy } from "./deploy";
3 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
4 | import { Logger } from "@octopusdeploy/api-client";
5 | import * as tasks from "azure-pipelines-task-lib/task";
6 |
7 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
8 |
9 | const logger: Logger = {
10 | debug: (message) => {
11 | tasks.debug(message);
12 | },
13 | info: (message) => console.log(message),
14 | warn: (message) => tasks.warning(message),
15 | error: (message, err) => {
16 | if (err !== undefined) {
17 | tasks.error(err.message);
18 | } else {
19 | tasks.error(message);
20 | }
21 | },
22 | };
23 |
24 | const task: TaskWrapper = new ConcreteTaskWrapper();
25 |
26 | new Deploy(connection, task, logger).run();
27 |
--------------------------------------------------------------------------------
/source/tasks/DeployTenant/TenantedDeployV6/createDeployment.ts:
--------------------------------------------------------------------------------
1 | import { Client, CreateDeploymentTenantedCommandV1, DeploymentRepository, Logger, TenantRepository } from "@octopusdeploy/api-client";
2 | import os from "os";
3 | import { TaskWrapper } from "tasks/Utils/taskInput";
4 | import { ExecutionResult } from "../../Utils/executionResult";
5 |
6 | export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentTenantedCommandV1, task: TaskWrapper, logger: Logger): Promise {
7 | logger.info?.("🐙 Deploying a release in Octopus Deploy...");
8 |
9 | try {
10 | const deploymentRepository = new DeploymentRepository(client, command.spaceName);
11 | const response = await deploymentRepository.createTenanted(command);
12 |
13 | client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`);
14 |
15 | if (response.DeploymentServerTasks.length === 0) {
16 | throw new Error("Expected at least one deployment to be queued.");
17 | }
18 | if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) {
19 | throw new Error("Server task id was not deserialized correctly.");
20 | }
21 |
22 | const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId);
23 |
24 | const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length });
25 |
26 | const tenantIds = deployments.Items.map((d) => d.TenantId || "");
27 | const tenantRepository = new TenantRepository(client, command.spaceName);
28 | const tenants = await tenantRepository.list({ ids: tenantIds, take: tenantIds.length });
29 |
30 | const results = response.DeploymentServerTasks.map((x) => {
31 | return {
32 | serverTaskId: x.ServerTaskId,
33 | environmentName: command.EnvironmentName,
34 | tenantName: tenants.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].TenantId)[0].Name,
35 | type: "Deployment",
36 | } as ExecutionResult;
37 | });
38 |
39 | task.setOutputVariable("server_tasks", JSON.stringify(results));
40 |
41 | return results;
42 | } catch (error: unknown) {
43 | if (error instanceof Error) {
44 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true);
45 | } else {
46 | task.setFailure(`"Failed to execute command. ${error}`, true);
47 | }
48 | throw error;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/source/tasks/DeployTenant/TenantedDeployV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/DeployTenant/TenantedDeployV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/DeployTenant/TenantedDeployV6/icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/source/tasks/DeployTenant/TenantedDeployV6/index.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
2 | import { Deploy } from "./deploy";
3 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
4 | import { Logger } from "@octopusdeploy/api-client";
5 | import * as tasks from "azure-pipelines-task-lib/task";
6 |
7 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
8 |
9 | const logger: Logger = {
10 | debug: (message) => {
11 | tasks.debug(message);
12 | },
13 | info: (message) => console.log(message),
14 | warn: (message) => tasks.warning(message),
15 | error: (message, err) => {
16 | if (err !== undefined) {
17 | tasks.error(err.message);
18 | } else {
19 | tasks.error(message);
20 | }
21 | },
22 | };
23 |
24 | const task: TaskWrapper = new ConcreteTaskWrapper();
25 |
26 | new Deploy(connection, task, logger).run();
27 |
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoCli/OctoCliV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
3 | import { getRequiredInput } from "../../Utils/inputs";
4 | import { OctoCli } from "./octoCli";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const command = getRequiredInput("command");
8 | const args = tasks.getInput("args");
9 |
10 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
11 |
12 | new OctoCli(getOctopusCliTool(), command, connection).run(args);
13 |
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/octoCli.test.ts:
--------------------------------------------------------------------------------
1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing";
2 | import { OctoCli } from "./octoCli";
3 |
4 | describe("Promote Release", () => {
5 | test("Run a simple promote", async () => {
6 | const output = await executeCommand(() =>
7 | new OctoCli(new MockOctopusToolRunner(), "list-projects", {
8 | url: "http://octopus.com",
9 | apiKey: "myapikey",
10 | ignoreSslErrors: true,
11 | }).run('--space "my space"')
12 | );
13 |
14 | expect(output).toContain('list-projects --server http://octopus.com --apiKey myapikey --ignoreSslErrors --space "my space"');
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/octoCli.ts:
--------------------------------------------------------------------------------
1 | import { OctoServerConnectionDetails } from "../../Utils/connection";
2 | import { executeTask } from "../../Utils/octopusTasks";
3 | import { OctopusToolRunner } from "../../Utils/tool";
4 | import * as tasks from "azure-pipelines-task-lib";
5 |
6 | export class OctoCli {
7 | constructor(readonly tool: OctopusToolRunner, readonly command: string, readonly connection: OctoServerConnectionDetails) {}
8 |
9 | public async run(args: string | undefined) {
10 | this.tool.arg(this.command);
11 | tasks.warning("This task is using a deprecated version of the Octopus CLI, we recommend using the latest version.");
12 | await executeTask(this.tool, "(cli;run;v5)", this.connection, "Succeeded executing octo command.", "Failed to execute octo command.", args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/source/tasks/OctoCli/OctoCliV5/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "25fee290-e578-491b-b1db-dbc3980df1d0",
3 | "name": "OctoCli",
4 | "friendlyName": "Invoke Octopus CLI command",
5 | "description": "Invoke an Octopus CLI command",
6 | "helpMarkDown": "set-by-pack.ps1",
7 | "category": "Tool",
8 | "visibility": [
9 | "Build",
10 | "Release"
11 | ],
12 | "author": "Octopus Deploy",
13 | "version": {
14 | "Major": 5,
15 | "Minor": 0,
16 | "Patch": 0
17 | },
18 | "demands": ["octo"],
19 | "minimumAgentVersion": "2.144.0",
20 | "groups": [
21 | {
22 | "name": "advanced",
23 | "displayName": "Advanced Options",
24 | "isExpanded": false
25 | }
26 | ],
27 | "inputs": [
28 | {
29 | "name": "OctoConnectedServiceName",
30 | "type": "connectedService:OctopusEndpoint",
31 | "label": "Octopus Deploy Server",
32 | "defaultValue": "",
33 | "required": true,
34 | "helpMarkDown": "Octopus Deploy server connection"
35 | },
36 | {
37 | "name": "command",
38 | "type": "string",
39 | "label": "command",
40 | "defaultValue": "",
41 | "required": true,
42 | "helpMarkDown": "The Octopus CLI command to execute."
43 | },
44 | {
45 | "name": "args",
46 | "type": "string",
47 | "label": "arguments",
48 | "defaultValue": "",
49 | "required": false,
50 | "helpMarkDown": "The arguments to use for the command."
51 | }
52 | ],
53 | "instanceNameFormat": "Invoke an Octopus CLI command",
54 | "execution": {
55 | "Node10": {
56 | "target": "index.js"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoInstaller/OctoInstallerV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { Installer } from "./installer";
3 |
4 | const version = tasks.getInput("version", true) || "";
5 | new Installer("https://g.octopushq.com").run(version);
6 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/installer.ts:
--------------------------------------------------------------------------------
1 | import * as tools from "azure-pipelines-tool-lib";
2 | import * as tasks from "azure-pipelines-task-lib";
3 | import os from "os";
4 | import path from "path";
5 | import { executeWithSetResult } from "../../Utils/octopusTasks";
6 | import { DownloadEndpointRetriever, Endpoint } from "./downloadEndpointRetriever";
7 |
8 | const TOOL_NAME = "octo";
9 |
10 | const osPlat: string = os.platform();
11 |
12 | export class Installer {
13 | constructor(readonly octopurlsUrl: string) {}
14 |
15 | public async run(versionSpec: string) {
16 | await executeWithSetResult(
17 | async () => {
18 | tasks.warning("This task is using a deprecated version of the Octopus CLI, we recommend using the latest version.");
19 | const endpoint = await new DownloadEndpointRetriever(this.octopurlsUrl).getEndpoint(versionSpec);
20 | let toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version);
21 |
22 | if (!toolPath) {
23 | toolPath = await this.installTool(endpoint);
24 | toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version);
25 | }
26 |
27 | tools.prependPath(toolPath);
28 | },
29 | `Installed octo v${versionSpec}.`,
30 | `Failed to install octo v${versionSpec}.`
31 | );
32 | }
33 |
34 | private async installTool(endpoint: Endpoint): Promise {
35 | if (!endpoint.downloadUrl) {
36 | throw Error(`Failed to download Octopus CLI tool version ${endpoint.version}.`);
37 | }
38 |
39 | const downloadPath = await tools.downloadTool(endpoint.downloadUrl);
40 |
41 | //
42 | // Extract
43 | //
44 | let extPath: string;
45 | if (osPlat == "win32") {
46 | extPath = tasks.getVariable("Agent.TempDirectory") || "";
47 | if (!extPath) {
48 | throw new Error("Expected Agent.TempDirectory to be set");
49 | }
50 |
51 | extPath = path.join(extPath, "n"); // use as short a path as possible due to nested node_modules folders
52 | extPath = await tools.extractZip(downloadPath, extPath);
53 | } else {
54 | extPath = await tools.extractTar(downloadPath);
55 | }
56 |
57 | return await tools.cacheDir(extPath, TOOL_NAME, endpoint.version);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/octopusCLIVersionFetcher.test.ts:
--------------------------------------------------------------------------------
1 | import { OctopusCLIVersionFetcher } from "./octopusCLIVersionFetcher";
2 |
3 | describe("OctopusCLIVersionFetcher tests", () => {
4 | test("Gets latest", () => {
5 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]);
6 |
7 | const version = fetcher.getVersion("*");
8 |
9 | expect(version).toBe("2.1.0");
10 | });
11 |
12 | test("Fixed returns fixed version", () => {
13 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]);
14 |
15 | const version = fetcher.getVersion("1.0.0");
16 |
17 | expect(version).toBe("1.0.0");
18 | });
19 |
20 | test("When version no exists", () => {
21 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]);
22 |
23 | expect(() => fetcher.getVersion("5.0.0")).toThrow();
24 | });
25 |
26 | test("Get latest minor", () => {
27 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]);
28 |
29 | const version = fetcher.getVersion("2.*");
30 |
31 | expect(version).toBe("2.1.0");
32 | });
33 |
34 | test("Get latest patch", () => {
35 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "1.0.3", "2.1.0", "3.0.0"]);
36 |
37 | const version = fetcher.getVersion("1.0.*");
38 |
39 | expect(version).toBe("1.0.3");
40 | });
41 |
42 | test("When version spec if invalid", () => {
43 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]);
44 |
45 | expect(() => fetcher.getVersion("*.*")).toThrow();
46 |
47 | expect(() => fetcher.getVersion("*.2")).toThrow();
48 |
49 | expect(() => fetcher.getVersion("sdfs")).toThrow();
50 | });
51 |
52 | test("Get latest major", () => {
53 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]);
54 |
55 | const version = fetcher.getVersion("2");
56 |
57 | expect(version).toBe("2.1.0");
58 | });
59 |
60 | test("Get latest not pre-release", () => {
61 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "1.0.3", "2.1.0", "3.0.0", "4.0.0-pre"]);
62 |
63 | const version = fetcher.getVersion("*");
64 |
65 | expect(version).toBe("3.0.0");
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/octopusCLIVersionFetcher.ts:
--------------------------------------------------------------------------------
1 | import { maxSatisfying, valid } from "semver";
2 |
3 | export class OctopusCLIVersionFetcher {
4 | constructor(readonly versions: string[]) {}
5 |
6 | public getVersion(versionSpec: string) {
7 | if (versionSpec === "*") {
8 | return maxSatisfying(this.versions, versionSpec);
9 | }
10 |
11 | if (valid(versionSpec) === null) {
12 | const parts = versionSpec.split(".");
13 | if (parts.length > 3) {
14 | throw new Error(`The '${versionSpec}' is an invalid version, a version needs to be a maximum of three parts.`);
15 | }
16 |
17 | if (parts.length === 1) {
18 | const majorVersion = parts[0];
19 | if (Number.isNaN(Number.parseInt(majorVersion))) {
20 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its major part.`);
21 | }
22 | }
23 |
24 | if (parts.length === 2) {
25 | const majorVersion = parts[0];
26 | const minorVersion = parts[1];
27 |
28 | // the major version number must be a number
29 | if (Number.isNaN(Number.parseInt(majorVersion))) {
30 | throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`);
31 | }
32 |
33 | if (minorVersion !== "*" && Number.isNaN(Number.parseInt(minorVersion))) {
34 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its minor part.`);
35 | }
36 | }
37 |
38 | if (parts.length === 3) {
39 | const majorVersion = parts[0];
40 | const minorVersion = parts[1];
41 | const patchVersion = parts[2];
42 |
43 | // the major version number must be a number
44 | if (Number.isNaN(Number.parseInt(majorVersion))) {
45 | throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`);
46 | }
47 |
48 | // the minor version number must be a number
49 | if (Number.isNaN(Number.parseInt(minorVersion))) {
50 | throw new Error(`The '${versionSpec}' version needs to specify a number for its minor part.`);
51 | }
52 |
53 | if (patchVersion !== "*" && Number.isNaN(Number.parseInt(patchVersion))) {
54 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its patch part.`);
55 | }
56 | }
57 | }
58 |
59 | const version = maxSatisfying(this.versions, versionSpec);
60 |
61 | if (!version) {
62 | throw new Error(`A version satisfying '${versionSpec}' could not be found.`);
63 | }
64 |
65 | return version;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV5/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3",
3 | "name": "OctoInstaller",
4 | "friendlyName": "Octopus CLI Installer",
5 | "description": "Install a specific version of the Octopus CLI (C#)",
6 | "helpMarkDown": "Install a specific version of the Octopus CLI (C#)",
7 | "category": "Tool",
8 | "runsOn": [
9 | "Agent",
10 | "DeploymentGroup"
11 | ],
12 | "visibility": [
13 | "Build",
14 | "Release"
15 | ],
16 | "author": "Octopus Deploy",
17 | "version": {
18 | "Major": 5,
19 | "Minor": 0,
20 | "Patch": 0
21 | },
22 | "satisfies": ["octo"],
23 | "demands": [],
24 | "minimumAgentVersion": "2.144.0",
25 | "groups": [
26 | {
27 | "name": "advanced",
28 | "displayName": "Advanced Options",
29 | "isExpanded": false
30 | }
31 | ],
32 | "inputs": [
33 | {
34 | "name": "version",
35 | "type": "string",
36 | "label": "Octopus CLI Version",
37 | "required": true,
38 | "helpMarkDown": "Specify version of OctopusCLI to install.
Versions can be given in the following formats`8.*` => 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 OctopusCLI, from the [this link](https://g.octopushq.com/OctopusCLIVersions)."
39 | }
40 | ],
41 | "instanceNameFormat": "Use Octopus CLI tool version $(version)",
42 | "execution": {
43 | "Node10": {
44 | "target": "index.js"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoInstaller/OctoInstallerV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { Logger } from "@octopusdeploy/api-client";
3 | import { Installer } from "./installer";
4 | import os from "os";
5 |
6 | async function run() {
7 | try {
8 | const version = tasks.getInput("octopusVersion", true) || "";
9 |
10 | const logger: Logger = {
11 | debug: (message) => {
12 | tasks.debug(message);
13 | },
14 | info: (message) => console.log(message),
15 | warn: (message) => tasks.warning(message),
16 | error: (message, err) => {
17 | if (err !== undefined) {
18 | tasks.error(err.message);
19 | } else {
20 | tasks.error(message);
21 | }
22 | },
23 | };
24 |
25 | new Installer("https://raw.githubusercontent.com/OctopusDeploy/cli/main/releases.json", os.platform(), os.arch(), logger).run(version);
26 | } catch (error: unknown) {
27 | if (error instanceof Error) {
28 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true);
29 | } else {
30 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true);
31 | }
32 | }
33 | }
34 |
35 | run();
36 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/installer.ts:
--------------------------------------------------------------------------------
1 | import * as tools from "azure-pipelines-tool-lib";
2 | import * as tasks from "azure-pipelines-task-lib";
3 | import path from "path";
4 | import { executeWithSetResult } from "../../Utils/octopusTasks";
5 | import { DownloadEndpointRetriever, Endpoint } from "./downloadEndpointRetriever";
6 | import { Logger } from "@octopusdeploy/api-client";
7 |
8 | const TOOL_NAME = "octopus";
9 |
10 | export class Installer {
11 | constructor(readonly releasesUrl: string, readonly osPlatform: string, readonly osArch: string, readonly logger: Logger) {}
12 |
13 | public async run(versionSpec: string) {
14 | await executeWithSetResult(
15 | async () => {
16 | const endpoint = await new DownloadEndpointRetriever(this.releasesUrl, this.osPlatform, this.osArch, this.logger).getEndpoint(versionSpec);
17 | let toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version);
18 |
19 | if (!toolPath) {
20 | toolPath = await this.installTool(endpoint);
21 | toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version);
22 | }
23 |
24 | tools.prependPath(toolPath);
25 | },
26 | `Installed octopus v${versionSpec}.`,
27 | `Failed to install octopus v${versionSpec}.`
28 | );
29 | }
30 |
31 | private async installTool(endpoint: Endpoint): Promise {
32 | if (!endpoint.downloadUrl) {
33 | throw Error(`Failed to download Octopus CLI tool version ${endpoint.version}.`);
34 | }
35 |
36 | const downloadPath = await tools.downloadTool(endpoint.downloadUrl);
37 |
38 | //
39 | // Extract
40 | //
41 | let extPath: string;
42 | if (this.osPlatform == "win32") {
43 | extPath = tasks.getVariable("Agent.TempDirectory") || "";
44 | if (!extPath) {
45 | throw new Error("Expected Agent.TempDirectory to be set");
46 | }
47 |
48 | extPath = path.join(extPath, "n"); // use as short a path as possible due to nested node_modules folders
49 | extPath = await tools.extractZip(downloadPath, extPath);
50 | } else {
51 | extPath = await tools.extractTar(downloadPath);
52 | }
53 |
54 | return await tools.cacheDir(extPath, TOOL_NAME, endpoint.version);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/octopusCLIVersionResolver.test.ts:
--------------------------------------------------------------------------------
1 | import { OctopusCLIVersionResolver } from "./octopusCLIVersionResolver";
2 |
3 | describe("OctopusCLIVersionFetcher tests", () => {
4 | test("Gets latest", () => {
5 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]);
6 |
7 | const version = fetcher.getVersion("*");
8 |
9 | expect(version).toBe("2.1.0");
10 | });
11 |
12 | test("Fixed returns fixed version", () => {
13 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]);
14 |
15 | const version = fetcher.getVersion("1.0.0");
16 |
17 | expect(version).toBe("1.0.0");
18 | });
19 |
20 | test("When version no exists", () => {
21 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]);
22 |
23 | expect(() => fetcher.getVersion("5.0.0")).toThrow();
24 | });
25 |
26 | test("Get latest minor", () => {
27 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]);
28 |
29 | const version = fetcher.getVersion("2.*");
30 |
31 | expect(version).toBe("2.1.0");
32 | });
33 |
34 | test("Get latest patch", () => {
35 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0"]);
36 |
37 | const version = fetcher.getVersion("1.0.*");
38 |
39 | expect(version).toBe("1.0.3");
40 | });
41 |
42 | test("When version spec if invalid", () => {
43 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]);
44 |
45 | expect(() => fetcher.getVersion("*.*")).toThrow();
46 |
47 | expect(() => fetcher.getVersion("*.2")).toThrow();
48 |
49 | expect(() => fetcher.getVersion("sdfs")).toThrow();
50 | });
51 |
52 | test("Get latest major", () => {
53 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]);
54 |
55 | const version = fetcher.getVersion("2");
56 |
57 | expect(version).toBe("2.1.0");
58 | });
59 |
60 | test("Get latest not pre-release", () => {
61 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0", "4.0.0-pre"]);
62 |
63 | const version = fetcher.getVersion("*");
64 |
65 | expect(version).toBe("3.0.0");
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/source/tasks/OctoInstaller/OctoInstallerV6/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3",
3 | "name": "OctoInstaller",
4 | "friendlyName": "Octopus CLI Installer",
5 | "description": "Install a specific version of the Octopus CLI (Go)",
6 | "helpMarkDown": "Install a specific version of the Octopus CLI (Go)",
7 | "category": "Tool",
8 | "runsOn": [
9 | "Agent",
10 | "DeploymentGroup"
11 | ],
12 | "visibility": [
13 | "Build",
14 | "Release"
15 | ],
16 | "author": "Octopus Deploy",
17 | "version": {
18 | "Major": 6,
19 | "Minor": 0,
20 | "Patch": 0
21 | },
22 | "satisfies": ["octopus"],
23 | "demands": [],
24 | "minimumAgentVersion": "2.206.1",
25 | "groups": [
26 | {
27 | "name": "advanced",
28 | "displayName": "Advanced Options",
29 | "isExpanded": false
30 | }
31 | ],
32 | "inputs": [
33 | {
34 | "name": "octopusVersion",
35 | "type": "string",
36 | "label": "Octopus CLI Version",
37 | "required": true,
38 | "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)."
39 | }
40 | ],
41 | "instanceNameFormat": "Install Octopus CLI tool version $(version)",
42 | "execution": {
43 | "Node16": {
44 | "target": "index.js"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/source/tasks/OctopusMetadata/OctopusMetadataV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctopusMetadata/OctopusMetadataV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/OctopusMetadata/OctopusMetadataV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/OctopusMetadata/OctopusMetadataV5/index.ts:
--------------------------------------------------------------------------------
1 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs";
2 | import * as tasks from "azure-pipelines-task-lib";
3 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
4 | import { BuildInformation } from "./buildInformation";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const space = getRequiredInput("Space");
8 | const packageIds = getLineSeparatedItems(tasks.getInput("PackageId", true) || "");
9 | const packageVersion = tasks.getInput("PackageVersion", true) || "";
10 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || "");
11 | const additionalArguments = tasks.getInput("AdditionalArguments");
12 |
13 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
14 |
15 | new BuildInformation(getOctopusCliTool(), connection).run(space, packageIds, packageVersion, overwriteMode, additionalArguments);
16 |
--------------------------------------------------------------------------------
/source/tasks/PackNuGet/PackNuGetV6/create-package.ts:
--------------------------------------------------------------------------------
1 | import { Logger, NuGetPackageBuilder, NuGetPackArgs } from "@octopusdeploy/api-client";
2 | import path from "path";
3 | import fs from "fs";
4 | import { InputParameters } from "./input-parameters";
5 | import { isNullOrWhitespace } from "../../../tasksLegacy/Utils/inputs";
6 |
7 | type createPackageResult = {
8 | filePath: string;
9 | filename: string;
10 | };
11 |
12 | export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise {
13 | const builder = new NuGetPackageBuilder();
14 | const inputs: NuGetPackArgs = {
15 | packageId: parameters.packageId,
16 | version: parameters.packageVersion,
17 | outputFolder: parameters.outputPath,
18 | basePath: parameters.sourcePath,
19 | inputFilePatterns: parameters.include,
20 | overwrite: parameters.overwrite,
21 | logger,
22 | };
23 |
24 | inputs.nuspecArgs = {
25 | title: parameters.nuGetTitle,
26 | description: parameters.nuGetDescription,
27 | authors: parameters.nuGetAuthors,
28 | releaseNotes: parameters.nuGetReleaseNotes,
29 | };
30 |
31 | if (!isNullOrWhitespace(parameters.nuGetReleaseNotesFile) && fs.existsSync(parameters.nuGetReleaseNotesFile) && fs.lstatSync(parameters.nuGetReleaseNotesFile).isFile()) {
32 | inputs.nuspecArgs.releaseNotes = fs.readFileSync(parameters.nuGetReleaseNotesFile).toString();
33 | }
34 |
35 | const packageFilename = await builder.pack(inputs);
36 |
37 | return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename };
38 | }
39 |
--------------------------------------------------------------------------------
/source/tasks/PackNuGet/PackNuGetV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/PackNuGet/PackNuGetV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/PackNuGet/PackNuGetV6/icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/source/tasks/PackNuGet/PackNuGetV6/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { Logger } from "@octopusdeploy/api-client";
4 | import { getInputs } from "./input-parameters";
5 | import os from "os";
6 | import { createPackageFromInputs } from "./create-package";
7 |
8 | async function run() {
9 | try {
10 | const parameters = getInputs();
11 |
12 | const logger: Logger = {
13 | debug: (message) => {
14 | tasks.debug(message);
15 | },
16 | info: (message) => console.log(message),
17 | warn: (message) => tasks.warning(message),
18 | error: (message, err) => {
19 | if (err !== undefined) {
20 | tasks.error(err.message);
21 | } else {
22 | tasks.error(message);
23 | }
24 | },
25 | };
26 |
27 | const result = await createPackageFromInputs(parameters, logger);
28 |
29 | tasks.setVariable("package_file_path", result.filePath);
30 | tasks.setVariable("package_filename", result.filename);
31 |
32 | tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded");
33 | } catch (error: unknown) {
34 | if (error instanceof Error) {
35 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true);
36 | } else {
37 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true);
38 | }
39 | }
40 | }
41 |
42 | run();
43 |
--------------------------------------------------------------------------------
/source/tasks/PackNuGet/PackNuGetV6/input-parameters.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs";
3 | import { getLineSeparatedItems } from "../../Utils/inputs";
4 |
5 | export interface InputParameters {
6 | packageId: string;
7 | packageVersion: string;
8 | outputPath: string;
9 | sourcePath: string;
10 | include: string[];
11 | nuGetDescription: string;
12 | nuGetAuthors: string[];
13 | nuGetTitle?: string;
14 | nuGetReleaseNotes?: string;
15 | nuGetReleaseNotesFile?: string;
16 | overwrite?: boolean;
17 | }
18 |
19 | export const getInputs = (): InputParameters => {
20 | return {
21 | packageId: tasks.getInput("PackageId", true) || "",
22 | packageVersion: tasks.getInput("PackageVersion", true) || "",
23 | outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".",
24 | sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".",
25 | include: getLineSeparatedItems(tasks.getInput("Include") || "**"),
26 | nuGetDescription: tasks.getInput("NuGetDescription", true) || "",
27 | nuGetAuthors: getLineSeparatedItems(tasks.getInput("NuGetAuthors", true) || ""),
28 | nuGetTitle: tasks.getInput("NuGetTitle"),
29 | nuGetReleaseNotes: tasks.getInput("NuGetReleaseNotes"),
30 | nuGetReleaseNotesFile: tasks.getInput("NuGetReleaseNotesFile", false),
31 | overwrite: tasks.getBoolInput("Overwrite"),
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/source/tasks/PackZip/PackZipV6/create-package.ts:
--------------------------------------------------------------------------------
1 | import { Logger, ZipPackageBuilder } from "@octopusdeploy/api-client";
2 | import path from "path";
3 | import { InputParameters } from "./input-parameters";
4 |
5 | type createPackageResult = {
6 | filePath: string;
7 | filename: string;
8 | };
9 |
10 | export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise {
11 | const builder = new ZipPackageBuilder();
12 | const packageFilename = await builder.pack({
13 | packageId: parameters.packageId,
14 | version: parameters.packageVersion,
15 | outputFolder: parameters.outputPath,
16 | basePath: parameters.sourcePath,
17 | inputFilePatterns: parameters.include,
18 | overwrite: parameters.overwrite,
19 | logger,
20 | });
21 |
22 | return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename };
23 | }
24 |
--------------------------------------------------------------------------------
/source/tasks/PackZip/PackZipV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/PackZip/PackZipV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/PackZip/PackZipV6/icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/source/tasks/PackZip/PackZipV6/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { Logger } from "@octopusdeploy/api-client";
4 | import { getInputs } from "./input-parameters";
5 | import os from "os";
6 | import { createPackageFromInputs } from "./create-package";
7 |
8 | async function run() {
9 | try {
10 | const parameters = getInputs();
11 |
12 | const logger: Logger = {
13 | debug: (message) => {
14 | tasks.debug(message);
15 | },
16 | info: (message) => console.log(message),
17 | warn: (message) => tasks.warning(message),
18 | error: (message, err) => {
19 | if (err !== undefined) {
20 | tasks.error(err.message);
21 | } else {
22 | tasks.error(message);
23 | }
24 | },
25 | };
26 |
27 | const result = await createPackageFromInputs(parameters, logger);
28 |
29 | tasks.setVariable("package_file_path", result.filePath);
30 | tasks.setVariable("package_filename", result.filename);
31 |
32 | tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded");
33 | } catch (error: unknown) {
34 | if (error instanceof Error) {
35 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true);
36 | } else {
37 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true);
38 | }
39 | }
40 | }
41 |
42 | run();
43 |
--------------------------------------------------------------------------------
/source/tasks/PackZip/PackZipV6/input-parameters.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs";
3 | import { getLineSeparatedItems } from "../../Utils/inputs";
4 |
5 | export interface InputParameters {
6 | packageId: string;
7 | packageVersion: string;
8 | outputPath: string;
9 | sourcePath: string;
10 | include: string[];
11 | overwrite?: boolean;
12 | }
13 |
14 | export const getInputs = (): InputParameters => {
15 | return {
16 | packageId: tasks.getInput("PackageId", true) || "",
17 | packageVersion: tasks.getInput("PackageVersion", true) || "",
18 | outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".",
19 | sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".",
20 | include: getLineSeparatedItems(tasks.getInput("Include") || "**"),
21 | overwrite: tasks.getBoolInput("Overwrite"),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/source/tasks/Promote/PromoteV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Promote/PromoteV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/Promote/PromoteV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
3 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs";
4 | import { Promote } from "./promote";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const space = getRequiredInput("Space");
8 | const project = getRequiredInput("Project");
9 | const from = getRequiredInput("From");
10 | const to = getDelimitedInput("To");
11 | const deployForTenants = getDelimitedInput("DeployForTenants");
12 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags");
13 | const deploymentProgress = tasks.getBoolInput("ShowProgress");
14 | const additionalArguments = tasks.getInput("AdditionalArguments");
15 |
16 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
17 |
18 | new Promote(getOctopusCliTool(), connection).run(space, project, from, to, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments);
19 |
--------------------------------------------------------------------------------
/source/tasks/Promote/PromoteV5/promote.test.ts:
--------------------------------------------------------------------------------
1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing";
2 | import { Promote } from "./promote";
3 |
4 | describe("Promote Release", () => {
5 | test("Run a simple promote", async () => {
6 | const output = await executeCommand(() =>
7 | new Promote(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: true }).run(
8 | "my space",
9 | "my project",
10 | "Dev",
11 | ["int", "prod"],
12 | ["tenantA", "tenantB"],
13 | ["tagme", "tagyou"],
14 | true,
15 | "--myAdditionalArgumentToInclude"
16 | )
17 | );
18 |
19 | expect(output).toContain(
20 | "promote-release --space my space --project my project --from Dev --enableServiceMessages --progress --to int --to prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --ignoreSslErrors --myAdditionalArgumentToInclude"
21 | );
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/source/tasks/Promote/PromoteV5/promote.ts:
--------------------------------------------------------------------------------
1 | import { OctoServerConnectionDetails } from "../../Utils/connection";
2 | import { executeTask } from "../../Utils/octopusTasks";
3 | import { OctopusToolRunner } from "../../Utils/tool";
4 |
5 | export class Promote {
6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {}
7 |
8 | public async run(space: string, project: string, from: string, to: string[], deployForTenants: string[] = [], deployForTenantTags: string[] = [], showProgress?: boolean | undefined, additionalArguments?: string | undefined) {
9 | this.tool.arg("promote-release");
10 | this.tool.arg(["--space", space]);
11 | this.tool.arg(["--project", project]);
12 | this.tool.arg(["--from", from]);
13 | this.tool.arg("--enableServiceMessages");
14 | this.tool.argIf(showProgress, "--progress");
15 | for (const item of to) {
16 | this.tool.arg(["--to", item]);
17 | }
18 | for (const item of deployForTenants) {
19 | this.tool.arg(["--tenant", item]);
20 | }
21 | for (const item of deployForTenantTags) {
22 | this.tool.arg(["--tenantTag", item]);
23 | }
24 |
25 | await executeTask(this.tool, "(release;promote;v5)", this.connection, "Promote release succeeded.", "Failed to promote release.", additionalArguments);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV5/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Push/PushV5/icon.png
--------------------------------------------------------------------------------
/source/tasks/Push/PushV5/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV5/index.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
3 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs";
4 | import { Push } from "./push";
5 | import { getOctopusCliTool } from "../../Utils/tool";
6 |
7 | const space = getRequiredInput("Space");
8 | const packages = getLineSeparatedItems(tasks.getInput("Package", true) || "");
9 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || "");
10 | const additionalArguments = tasks.getInput("AdditionalArguments");
11 |
12 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
13 |
14 | new Push(getOctopusCliTool(), connection).run(space, packages, overwriteMode, additionalArguments);
15 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV5/push.test.ts:
--------------------------------------------------------------------------------
1 | import { mkdtemp, rm } from "fs/promises";
2 | import * as path from "path";
3 | import os, { platform } from "os";
4 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing";
5 | import { Push } from "./push";
6 | import { ReplaceOverwriteMode } from "../../Utils/inputs";
7 | import archiver from "archiver";
8 | import { createWriteStream } from "fs";
9 |
10 | describe("Push", () => {
11 | let tempOutDir: string;
12 | let packagePath1: string, packagePath2: string;
13 |
14 | jest.setTimeout(100000);
15 |
16 | beforeAll(async () => {
17 | tempOutDir = await mkdtemp(path.join(os.tmpdir(), "octopus_"));
18 |
19 | packagePath1 = await createPackage("foo", "1.2.3");
20 | packagePath2 = await createPackage("boo", "1.2.3");
21 | });
22 |
23 | afterAll(async () => {
24 | await rm(tempOutDir, { recursive: true });
25 | });
26 |
27 | async function createPackage(name: string, version: string) {
28 | const packagePath = path.join(tempOutDir, `${name}.${version}.${platform() === "win32" ? "zip" : "tar.gz"}`);
29 | const archiveFile = createWriteStream(packagePath);
30 | const archive = archiver(platform() === "win32" ? "zip" : "tar", { gzip: true });
31 | archive.append("Hello world", { name: "readme" });
32 | archive.pipe(archiveFile);
33 | await archive.finalize();
34 |
35 | return packagePath;
36 | }
37 |
38 | test("Pushes multiple packages with fixed paths", async () => {
39 | const output = await executeCommand(() =>
40 | new Push(new MockOctopusToolRunner(), {
41 | url: "http://octopus.com",
42 | apiKey: "myapikey",
43 | ignoreSslErrors: false,
44 | }).run("my space", [packagePath1, packagePath2], ReplaceOverwriteMode.true, "--myAdditionalArgumentToInclude")
45 | );
46 |
47 | expect(output).toMatch(
48 | /push --space my space --overwrite-mode OverwriteExisting --enableServiceMessages --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --server http:\/\/octopus.com --apiKey myapikey --myAdditionalArgumentToInclude/
49 | );
50 | });
51 |
52 | test("Pushes multiple packages with wildcards", async () => {
53 | const output = await executeCommand(() =>
54 | new Push(new MockOctopusToolRunner(), {
55 | url: "http://octopus.com",
56 | apiKey: "myapikey",
57 | ignoreSslErrors: false,
58 | }).run("my space", [path.join(tempOutDir, "*.*")], ReplaceOverwriteMode.false)
59 | );
60 |
61 | expect(output).toMatch(/push --space my space --overwrite-mode FailIfExists --enableServiceMessages --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --server http:\/\/octopus.com --apiKey myapikey/);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV5/push.ts:
--------------------------------------------------------------------------------
1 | import { OctoServerConnectionDetails } from "../../Utils/connection";
2 | import { ReplaceOverwriteMode } from "../../Utils/inputs";
3 | import { executeTask } from "../../Utils/octopusTasks";
4 | import glob from "glob";
5 | import { OctopusToolRunner } from "../../Utils/tool";
6 |
7 | export class Push {
8 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {}
9 |
10 | public async run(space: string, packages: string[], overwriteMode: ReplaceOverwriteMode, additionalArguments?: string | undefined) {
11 | const matchedPackages = await this.resolveGlobs(packages);
12 |
13 | this.tool.arg("push");
14 | this.tool.arg(["--space", space]);
15 | this.tool.arg(["--overwrite-mode", overwriteMode]);
16 | this.tool.arg("--enableServiceMessages");
17 | for (const item of matchedPackages) {
18 | this.tool.arg(["--package", item]);
19 | }
20 |
21 | await executeTask(this.tool, "(package;push;v5)", this.connection, "Package(s) pushed.", "Failed to push package(s).", additionalArguments);
22 | }
23 |
24 | private resolveGlobs = async (globs: string[]): Promise => {
25 | const globResults = await Promise.all(globs.map(this.pGlobNoNull));
26 | const results = ([] as string[]).concat(...globResults);
27 |
28 | return results;
29 | };
30 |
31 | private pGlobNoNull = (pattern: string): Promise => {
32 | return new Promise((resolve, reject) => {
33 | glob(pattern, { nonull: true }, (err, matches) => {
34 | if (err) {
35 | reject(err);
36 | return;
37 | }
38 | resolve(matches);
39 | });
40 | });
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Push/PushV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/Push/PushV6/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV6/index.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from "@octopusdeploy/api-client";
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
4 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs";
5 | import { Push } from "./push";
6 | import os from "os";
7 | import { getClient } from "../../Utils/client";
8 |
9 | async function run() {
10 | try {
11 | const spaceName = getRequiredInput("Space");
12 | const packages = getLineSeparatedItems(tasks.getInput("Packages", true) || "");
13 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || "");
14 |
15 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
16 |
17 | const logger: Logger = {
18 | debug: (message) => {
19 | tasks.debug(message);
20 | },
21 | info: (message) => console.log(message),
22 | warn: (message) => tasks.warning(message),
23 | error: (message, err) => {
24 | if (err !== undefined) {
25 | tasks.error(err.message);
26 | } else {
27 | tasks.error(message);
28 | }
29 | },
30 | };
31 |
32 | const client = await getClient(connection, logger, "package", "push", 6);
33 | await new Push(client).run(spaceName, packages, overwriteMode);
34 | } catch (error: unknown) {
35 | if (error instanceof Error) {
36 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error.message}${os.EOL}${error.stack}`, true);
37 | } else {
38 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error}`, true);
39 | }
40 | }
41 | }
42 |
43 | run();
44 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV6/push.ts:
--------------------------------------------------------------------------------
1 | import { ReplaceOverwriteMode } from "../../Utils/inputs";
2 | import { Client, OverwriteMode, PackageRepository } from "@octopusdeploy/api-client";
3 | import glob from "glob";
4 |
5 | export class Push {
6 | constructor(readonly client: Client) {}
7 |
8 | public async run(spaceName: string, packages: string[], overwriteMode: ReplaceOverwriteMode) {
9 | const matchedPackages = await this.resolveGlobs(packages);
10 |
11 | let mappedOverwriteMode = OverwriteMode.FailIfExists;
12 | if (overwriteMode === ReplaceOverwriteMode.true) {
13 | mappedOverwriteMode = OverwriteMode.OverwriteExisting;
14 | } else if (overwriteMode === ReplaceOverwriteMode.IgnoreIfExists) {
15 | mappedOverwriteMode = OverwriteMode.IgnoreIfExists;
16 | }
17 |
18 | const repository = new PackageRepository(this.client, spaceName);
19 | await repository.push(matchedPackages, mappedOverwriteMode);
20 |
21 | return packages;
22 | }
23 |
24 | private resolveGlobs = async (globs: string[]): Promise => {
25 | const globResults = await Promise.all(globs.map(this.pGlobNoNull));
26 | const results = ([] as string[]).concat(...globResults);
27 |
28 | return results;
29 | };
30 |
31 | private pGlobNoNull = (pattern: string): Promise => {
32 | return new Promise((resolve, reject) => {
33 | glob(pattern, { nonull: true }, (err, matches) => {
34 | if (err) {
35 | reject(err);
36 | return;
37 | }
38 | resolve(matches);
39 | });
40 | });
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/source/tasks/Push/PushV6/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2",
3 | "name": "OctopusPush",
4 | "friendlyName": "Push Package(s) to Octopus",
5 | "description": "Push your NuGet or Zip package to your Octopus Deploy Server. **v6 of this step requires Octopus 2022.3+**",
6 | "helpMarkDown": "set-by-pack.ps1",
7 | "category": "Package",
8 | "visibility": [
9 | "Build",
10 | "Release"
11 | ],
12 | "author": "Octopus Deploy",
13 | "version": {
14 | "Major": 6,
15 | "Minor": 0,
16 | "Patch": 0
17 | },
18 | "demands": [],
19 | "minimumAgentVersion": "2.206.1",
20 | "inputs": [
21 | {
22 | "name": "OctoConnectedServiceName",
23 | "type": "connectedService:OctopusEndpoint",
24 | "label": "Octopus Deploy Server",
25 | "defaultValue": "",
26 | "required": true,
27 | "helpMarkDown": "Octopus Deploy server connection"
28 | },
29 | {
30 | "name": "Space",
31 | "type": "string",
32 | "label": "Space Name",
33 | "defaultValue": "",
34 | "required": true,
35 | "helpMarkDown": "The space name within Octopus."
36 | },
37 | {
38 | "name": "Packages",
39 | "type": "multiLine",
40 | "label": "Packages",
41 | "defaultValue": "",
42 | "required": true,
43 | "helpMarkDown": "Package files to push. To push multiple packages, enter on multiple lines."
44 | },
45 | {
46 | "name": "Replace",
47 | "type": "pickList",
48 | "label": "Overwrite Mode",
49 | "defaultValue": "false",
50 | "required": true,
51 | "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.",
52 | "options": {
53 | "false": "Fail if exists",
54 | "true": "Overwrite existing",
55 | "IgnoreIfExists": "Ignore if exists"
56 | }
57 | }
58 | ],
59 | "instanceNameFormat": "Push Packages to Octopus",
60 | "execution": {
61 | "Node16": {
62 | "target": "index.js"
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/source/tasks/RunRunbook/RunRunbookV6/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/RunRunbook/RunRunbookV6/icon.png
--------------------------------------------------------------------------------
/source/tasks/RunRunbook/RunRunbookV6/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/source/tasks/RunRunbook/RunRunbookV6/index.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
3 | import { Logger } from "@octopusdeploy/api-client";
4 | import * as tasks from "azure-pipelines-task-lib/task";
5 | import { RunbookRun } from "./runbookRun";
6 |
7 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
8 |
9 | const logger: Logger = {
10 | debug: (message) => {
11 | tasks.debug(message);
12 | },
13 | info: (message) => console.log(message),
14 | warn: (message) => tasks.warning(message),
15 | error: (message, err) => {
16 | if (err !== undefined) {
17 | tasks.error(err.message);
18 | } else {
19 | tasks.error(message);
20 | }
21 | },
22 | };
23 |
24 | const task: TaskWrapper = new ConcreteTaskWrapper();
25 |
26 | new RunbookRun(connection, task, logger).run();
27 |
--------------------------------------------------------------------------------
/source/tasks/RunRunbook/RunRunbookV6/runbookRun.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from "@octopusdeploy/api-client";
2 | import { OctoServerConnectionDetails } from "../../Utils/connection";
3 | import { createRunbookRunFromInputs } from "./runRunbook";
4 | import { createCommandFromInputs } from "./inputCommandBuilder";
5 | import os from "os";
6 | import { TaskWrapper } from "tasks/Utils/taskInput";
7 | import { getClient } from "../../Utils/client";
8 |
9 | export class RunbookRun {
10 | constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {}
11 |
12 | public async run() {
13 | try {
14 | const command = createCommandFromInputs(this.logger, this.task);
15 | const client = await getClient(this.connection, this.logger, "runbook", "run", 6);
16 |
17 | createRunbookRunFromInputs(client, command, this.task, this.logger);
18 |
19 | this.task.setSuccess("Runbook run succeeded.");
20 | } catch (error: unknown) {
21 | if (error instanceof Error) {
22 | this.task.setFailure(`"Failed to successfully run runbook. ${error.message}${os.EOL}${error.stack}`, true);
23 | } else {
24 | this.task.setFailure(`"Failed to successfully run runbook. ${error}`, true);
25 | }
26 | throw error;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/source/tasks/Utils/MockTaskWrapper.ts:
--------------------------------------------------------------------------------
1 | import { TaskWrapper } from "tasks/Utils/taskInput";
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 |
4 | export class MockTaskWrapper implements TaskWrapper {
5 | lastResult?: tasks.TaskResult | undefined = undefined;
6 | lastResultMessage: string | undefined = undefined;
7 | lastResultDone: boolean | undefined = undefined;
8 |
9 | stringValues: Map = new Map();
10 | boolValues: Map = new Map();
11 | outputVariables: Map = new Map();
12 |
13 | addVariableString(name: string, value: string) {
14 | this.stringValues.set(name, value);
15 | }
16 |
17 | addVariableBoolean(name: string, value: boolean) {
18 | this.boolValues.set(name, value);
19 | }
20 |
21 | getInput(name: string, required?: boolean | undefined): string | undefined {
22 | const value = this.stringValues.get(name);
23 | if (required && !value) {
24 | // this replicates the functionality in the Azure Pipeline Task library
25 | throw new Error(`Input required: ${name}`);
26 | }
27 | return value;
28 | }
29 |
30 | getBoolean(name: string, _required?: boolean | undefined): boolean | undefined {
31 | return this.boolValues.get(name);
32 | }
33 |
34 | setSuccess(message: string, done?: boolean | undefined): void {
35 | this.lastResult = tasks.TaskResult.Succeeded;
36 | this.lastResultMessage = message;
37 | this.lastResultDone = done;
38 | }
39 | setFailure(message: string, done?: boolean | undefined): void {
40 | this.lastResult = tasks.TaskResult.Failed;
41 | this.lastResultMessage = message;
42 | this.lastResultDone = done;
43 | }
44 |
45 | setOutputVariable(name: string, value: string): void {
46 | this.outputVariables.set(name, value);
47 | }
48 |
49 | getVariable(name: string): string | undefined {
50 | return this.stringValues.get(name);
51 | }
52 |
53 | getOutputVariable(_step: string, name: string): string | undefined {
54 | return this.getVariable(name);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/source/tasks/Utils/client.ts:
--------------------------------------------------------------------------------
1 | import { Client, ClientConfiguration, Logger } from "@octopusdeploy/api-client";
2 | import { getUserAgentApp } from "./pluginInformation";
3 | import https from "https";
4 | import { OctoServerConnectionDetails } from "./connection";
5 |
6 | export async function getClient(connection: OctoServerConnectionDetails, logger: Logger, stepNoun: string, stepVerb: string, stepVersion: number) {
7 | const config: ClientConfiguration = {
8 | userAgentApp: getUserAgentApp(stepNoun, stepVerb, stepVersion),
9 | instanceURL: connection.url,
10 | apiKey: connection.apiKey,
11 | logging: logger,
12 | };
13 | if (connection.ignoreSslErrors) {
14 | config.httpsAgent = new https.Agent({ rejectUnauthorized: false });
15 | }
16 |
17 | return await Client.create(config);
18 | }
19 |
--------------------------------------------------------------------------------
/source/tasks/Utils/command-line-args.test.ts:
--------------------------------------------------------------------------------
1 | import commandLineArgs from "command-line-args";
2 | import shlex from "shlex";
3 |
4 | describe("cli args", () => {
5 | test("simple args", async () => {
6 | const optionDefs = [{ name: "variable", type: String, multiple: false }];
7 |
8 | const options = commandLineArgs(optionDefs, { argv: ["--variable", "value"] });
9 | expect(options.variable).toBe("value");
10 | });
11 |
12 | test("simple args with alias", async () => {
13 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: false }];
14 |
15 | const options = commandLineArgs(optionDefs, { argv: ["-v", "value"] });
16 | expect(options.variable).toBe("value");
17 | });
18 |
19 | test("simple args with alias 2", async () => {
20 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: false }];
21 |
22 | const options = commandLineArgs(optionDefs, { argv: ["-v", '"value with space"'] });
23 | expect(options.variable).toBe('"value with space"');
24 | });
25 |
26 | test("basic split on space", async () => {
27 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }];
28 | const args = "--variable value1 --variable value2 -v value3";
29 | const splitArgs = shlex.split(args);
30 | const options = commandLineArgs(optionDefs, { argv: splitArgs });
31 | console.log(options);
32 | expect(options.variable).toStrictEqual(["value1", "value2", "value3"]);
33 | });
34 |
35 | test("split on space with quoted values", async () => {
36 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }];
37 | const args = '--variable value1 --variable "value 2" -v value3';
38 | const splitArgs = shlex.split(args);
39 | const options = commandLineArgs(optionDefs, { argv: splitArgs });
40 | console.log(options);
41 | expect(options.variable).toStrictEqual(["value1", "value 2", "value3"]);
42 | });
43 |
44 | test("split on space with quoted values", async () => {
45 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }];
46 | const args = "--variable value1 --variable 'value 2' -v value3";
47 | const splitArgs = shlex.split(args);
48 | const options = commandLineArgs(optionDefs, { argv: splitArgs });
49 | console.log(options);
50 | expect(options.variable).toStrictEqual(["value1", "value 2", "value3"]);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/source/tasks/Utils/connection.test.ts:
--------------------------------------------------------------------------------
1 | import { getDeepLink } from "./connection";
2 |
3 | describe("DeepLinks", () => {
4 | test("URL ends with / returns correctly formatted deep link", async () => {
5 | const url = "https://octopus.com/vdir/";
6 | const expectedDeepLink = "https://octopus.com/vdir/app#/Spaces-1/deployments/Deployments-1";
7 | expect(getDeepLink(url, "Spaces-1/deployments/Deployments-1")).toBe(expectedDeepLink);
8 | });
9 |
10 | test("URL does not end with / return correctly formatted deep link", async () => {
11 | const url = "https://octopus.com/vdir";
12 | const expectedDeepLink = "https://octopus.com/vdir/app#/Spaces-1/deployments/Deployments-1";
13 | expect(getDeepLink(url, "Spaces-1/deployments/Deployments-1")).toBe(expectedDeepLink);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/source/tasks/Utils/connection.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 |
3 | export interface OctoServerConnectionDetails {
4 | url: string;
5 | apiKey: string;
6 | ignoreSslErrors: boolean;
7 | }
8 |
9 | export const DefaultOctoConnectionInputName = "OctoConnectedServiceName";
10 |
11 | export function getDefaultOctopusConnectionDetailsOrThrow() {
12 | const result = getOctopusConnectionDetails(tasks.getInput(DefaultOctoConnectionInputName, true) || "");
13 | if (!result) {
14 | throw new Error("Could not retrieve default Octo connection information");
15 | }
16 | return result;
17 | }
18 |
19 | function getOctopusConnectionDetails(name: string): OctoServerConnectionDetails {
20 | const octoEndpointAuthorization = tasks.getEndpointAuthorization(name, false);
21 |
22 | if (!octoEndpointAuthorization) {
23 | throw new Error(`Could not retrieve the endpoint authorization named ${name}.`);
24 | }
25 |
26 | const ignoreSSL = tasks.getEndpointDataParameter(name, "ignoreSslErrors", true);
27 | return {
28 | url: tasks.getEndpointUrl(name, false) || "",
29 | apiKey: octoEndpointAuthorization.parameters["apitoken"],
30 | ignoreSslErrors: !!ignoreSSL && ignoreSSL.toLowerCase() === "true",
31 | };
32 | }
33 |
34 | export function getDeepLink(baseUrl: string, path: string): string {
35 | if (baseUrl.endsWith("/")) {
36 | baseUrl = baseUrl.substring(0, baseUrl.length - 1);
37 | }
38 | return `${baseUrl}/app#/${path}`;
39 | }
40 |
--------------------------------------------------------------------------------
/source/tasks/Utils/executionResult.ts:
--------------------------------------------------------------------------------
1 | export interface ExecutionResult {
2 | serverTaskId: string;
3 | environmentName: string;
4 | tenantName: string | null;
5 | type: string;
6 | }
7 |
--------------------------------------------------------------------------------
/source/tasks/Utils/octopusTasks.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib";
2 | import os from "os";
3 | import { OctoServerConnectionDetails } from "./connection";
4 | import { connectionArguments, includeAdditionalArgumentsAndProxyConfig } from "./inputs";
5 | import { OctopusToolRunner, runOctopusCli } from "./tool";
6 |
7 | export async function executeTask(tool: OctopusToolRunner, stepIdentifier: string, connection: OctoServerConnectionDetails, successMessage: string, failureMessage: string, additionalArguments?: string | undefined) {
8 | return executeWithSetResult(
9 | async () => {
10 | connectionArguments(connection, tool);
11 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments, tool);
12 |
13 | await runOctopusCli(tool, stepIdentifier);
14 | },
15 | successMessage,
16 | failureMessage
17 | );
18 | }
19 |
20 | export async function executeWithSetResult(func: () => Promise, successMessage: string, failureMessage: string) {
21 | try {
22 | await func();
23 |
24 | tasks.setResult(tasks.TaskResult.Succeeded, successMessage);
25 | } catch (error: unknown) {
26 | if (error instanceof Error) {
27 | tasks.setResult(tasks.TaskResult.Failed, `"${failureMessage}${os.EOL}${error.message}${os.EOL}${error.stack}`, true);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/source/tasks/Utils/pluginInformation.ts:
--------------------------------------------------------------------------------
1 | function getPluginAndVersionInformation() {
2 | return `plugin/${process.env.EXTENSION_VERSION}`;
3 | }
4 |
5 | export function getUserAgentApp(stepNoun: string, stepVerb: string, stepVersion: number): string {
6 | return `${getPluginAndVersionInformation()} (${stepNoun};${stepVerb};v${stepVersion})`;
7 | }
8 |
--------------------------------------------------------------------------------
/source/tasks/Utils/taskInput.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 |
3 | export interface TaskWrapper {
4 | getInput(name: string, required?: boolean): string | undefined;
5 | getBoolean(name: string, required?: boolean): boolean | undefined;
6 | setSuccess(message: string, done?: boolean): void;
7 | setFailure(message: string, done?: boolean): void;
8 | setOutputVariable(name: string, value: string): void;
9 | getVariable(name: string): string | undefined;
10 | getOutputVariable(step: string, name: string): string | undefined;
11 | }
12 |
13 | export class ConcreteTaskWrapper implements TaskWrapper {
14 | public getInput(name: string, required?: boolean): string | undefined {
15 | return tasks.getInput(name, required);
16 | }
17 |
18 | public getBoolean(name: string, required?: boolean): boolean | undefined {
19 | return tasks.getBoolInput(name, required);
20 | }
21 |
22 | public setSuccess(message: string, done?: boolean) {
23 | tasks.setResult(tasks.TaskResult.Succeeded, message, done);
24 | }
25 |
26 | public setFailure(message: string, done?: boolean) {
27 | tasks.setResult(tasks.TaskResult.Failed, message, done);
28 | }
29 |
30 | public setOutputVariable(name: string, value: string) {
31 | tasks.setVariable(name, value, false, true);
32 | }
33 |
34 | public getVariable(name: string): string | undefined {
35 | return tasks.getVariable(name);
36 | }
37 |
38 | public getOutputVariable(step: string, name: string): string | undefined {
39 | return tasks.getVariable(`${step}.${name}`);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/source/tasks/Utils/testing.ts:
--------------------------------------------------------------------------------
1 | import { IExecOptions } from "azure-pipelines-task-lib/toolrunner";
2 | import Q from "q";
3 | import { OctopusToolRunner } from "./tool";
4 | import { stdout } from "test-console";
5 | import os from "os";
6 |
7 | export class MockOctopusToolRunner implements OctopusToolRunner {
8 | arguments: string[] = [];
9 |
10 | arg(val: string | string[]): void {
11 | if (typeof val === "string") {
12 | this.arguments.push(val);
13 | } else {
14 | val.map((s) => this.arguments.push(s));
15 | }
16 | }
17 |
18 | argIf(condition: string | boolean | undefined, val: string | string[]): void {
19 | if (condition) {
20 | this.arg(val);
21 | }
22 | }
23 |
24 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
25 | exec(_?: IExecOptions): Q.Promise {
26 | console.log(this.arguments.join(" "));
27 | return Q.resolve(0);
28 | }
29 |
30 | line(val: string): void {
31 | this.arg(val);
32 | }
33 | }
34 |
35 | export async function executeCommand(command: () => Promise) {
36 | const output = (await stdout.inspectAsync(command)).join(os.EOL);
37 |
38 | console.log(output);
39 |
40 | expect(output).toContain("task.complete result=Succeeded");
41 |
42 | return output;
43 | }
44 |
--------------------------------------------------------------------------------
/source/tasks/Utils/tool.ts:
--------------------------------------------------------------------------------
1 | import { IExecOptions } from "azure-pipelines-task-lib/toolrunner";
2 | import Q from "q";
3 | import * as tasks from "azure-pipelines-task-lib";
4 |
5 | function getExecOptions(stepIdentifier: string): IExecOptions {
6 | return { env: { OCTOEXTENSION: `${process.env.EXTENSION_VERSION} ${stepIdentifier}`, ...process.env } };
7 | }
8 |
9 | export function runOctopusCli(tool: OctopusToolRunner, stepIdentifier: string) {
10 | return tool.exec(getExecOptions(stepIdentifier));
11 | }
12 |
13 | export interface OctopusToolRunner {
14 | arg(val: string | string[]): void;
15 | argIf(condition: string | boolean | undefined, val: string | string[]): void;
16 | line(val: string): void;
17 | exec(options?: IExecOptions): Q.Promise;
18 | }
19 |
20 | export function getOctopusCliTool() {
21 | return tasks.tool("octo");
22 | }
23 |
--------------------------------------------------------------------------------
/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV3/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Deploy/DeployV3/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV3/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV3/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool";
4 | import { getDefaultOctopusConnectionDetailsOrThrow, resolveProjectName } from "../../Utils/connection";
5 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs";
6 | import os from "os";
7 |
8 | async function run() {
9 | try {
10 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
11 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
12 |
13 | const space = tasks.getInput("Space");
14 | const releaseNumber = tasks.getInput("ReleaseNumber", true);
15 | const environments = getRequiredCsvInput("Environments");
16 | const showProgress = tasks.getBoolInput("ShowProgress");
17 | const deploymentForTenants = getOptionalCsvInput("DeployForTenants");
18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags");
19 | const additionalArguments = tasks.getInput("AdditionalArguments");
20 | // @ts-ignore
21 | const project = await resolveProjectName(connection, tasks.getInput("Project", true)).then((x) => x.value);
22 |
23 | const octo = await getOrInstallOctoCommandRunner("deploy-release");
24 |
25 | const configure = [
26 | // @ts-ignore
27 | argumentIfSet(argumentEnquote, "space", space),
28 | argumentEnquote("project", project),
29 | // @ts-ignore
30 | argumentEnquote("releaseNumber", releaseNumber),
31 | connectionArguments(connection),
32 | multiArgument(argumentEnquote, "deployTo", environments),
33 | multiArgument(argumentEnquote, "tenant", deploymentForTenants),
34 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags),
35 | flag("progress", showProgress),
36 | // @ts-ignore
37 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
38 | ];
39 |
40 | let stepIdentifier = "(release;deploy;v3)";
41 | if (deploymentForTenants.length > 0 || deployForTenantTags.length > 0) {
42 | stepIdentifier = "(release;deploy-tenanted;v3)";
43 | }
44 |
45 | const code: number = await octo
46 | .map((x) => x.launchOcto(configure, stepIdentifier))
47 | .getOrElseL((x) => {
48 | throw new Error(x);
49 | });
50 |
51 | tasks.setResult(tasks.TaskResult.Succeeded, "Deploy succeeded with code " + code);
52 | } catch (error: unknown) {
53 | if (error instanceof Error) {
54 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to deploy release. ${error.message}${os.EOL}${error.stack}`, true);
55 | }
56 | }
57 | }
58 |
59 | run();
60 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Deploy/DeployV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Deploy/DeployV4/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, flag, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool";
4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs";
5 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
6 | import os from "os";
7 |
8 | async function run() {
9 | try {
10 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
11 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
12 |
13 | const space = tasks.getInput("Space");
14 | const project = tasks.getInput("Project", true);
15 | const releaseNumber = tasks.getInput("ReleaseNumber", true);
16 | const deployToEnvironments = getRequiredCsvInput("Environments");
17 | const deployForTenants = getOptionalCsvInput("DeployForTenants");
18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags");
19 | const showProgress = tasks.getBoolInput("ShowProgress");
20 | const additionalArguments = tasks.getInput("AdditionalArguments");
21 | await assertOctoVersionAcceptsIds();
22 | const octo = await getOrInstallOctoCommandRunner("deploy-release");
23 |
24 | const configure = [
25 | // @ts-ignore
26 | argumentIfSet(argumentEnquote, "space", space),
27 | // @ts-ignore
28 | argumentEnquote("project", project),
29 | // @ts-ignore
30 | argumentEnquote("releaseNumber", releaseNumber),
31 | connectionArguments(connection),
32 | multiArgument(argumentEnquote, "deployTo", deployToEnvironments),
33 | multiArgument(argumentEnquote, "tenant", deployForTenants),
34 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags),
35 | flag("progress", showProgress),
36 | // @ts-ignore
37 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
38 | ];
39 |
40 | let stepIdentifier = "(release;deploy;v4)";
41 | if (deployForTenants.length > 0 || deployForTenantTags.length > 0) {
42 | stepIdentifier = "(release;deploy-tenanted;v4)";
43 | }
44 |
45 | const code: number = await octo
46 | .map((x) => x.launchOcto(configure, stepIdentifier))
47 | .getOrElseL((x) => {
48 | throw new Error(x);
49 | });
50 |
51 | tasks.setResult(tasks.TaskResult.Succeeded, "Deploy succeeded with code " + code);
52 | } catch (error: unknown) {
53 | if (error instanceof Error) {
54 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to deploy release. ${error.message}${os.EOL}${error.stack}`, true);
55 | }
56 | }
57 | }
58 |
59 | run();
60 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoCli/OctoCliV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctoCli/OctoCliV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoCli/OctoCliV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoCli/OctoCliV4/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { connectionArguments, getOrInstallOctoCommandRunner, includeAdditionalArguments } from "../../Utils/tool";
4 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
5 | import os from "os";
6 |
7 | async function run() {
8 | try {
9 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
10 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
11 | const args = tasks.getInput("args", false);
12 | const command = tasks.getInput("command", true);
13 | // @ts-ignore
14 | const octo = await getOrInstallOctoCommandRunner(command);
15 |
16 | // @ts-ignore
17 | const configure = [connectionArguments(connection), includeAdditionalArguments(args)];
18 |
19 | const code: number = await octo
20 | .map((x) => x.launchOcto(configure, "(cli;run;v4)"))
21 | .getOrElseL((x) => {
22 | throw new Error(x);
23 | });
24 |
25 | tasks.setResult(tasks.TaskResult.Succeeded, `Succeeded executing octo command ${command} with code ${code}`);
26 | } catch (error: unknown) {
27 | if (error instanceof Error) {
28 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute octo command. ${error.message}${os.EOL}${error.stack}`, true);
29 | }
30 | }
31 | }
32 |
33 | run();
34 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoCli/OctoCliV4/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "25fee290-e578-491b-b1db-dbc3980df1d0",
3 | "name": "OctoCli",
4 | "friendlyName": "Invoke Octopus CLI command",
5 | "description": "There is a later version of this task, we recommend using the latest version. Invoke an Octopus CLI command",
6 | "helpMarkDown": "set-by-pack.ps1",
7 | "category": "Tool",
8 | "visibility": ["Build", "Release"],
9 | "author": "Octopus Deploy",
10 | "version": {
11 | "Major": 4,
12 | "Minor": 3,
13 | "Patch": 0
14 | },
15 | "demands": [],
16 | "minimumAgentVersion": "2.144.0",
17 | "groups": [
18 | {
19 | "name": "advanced",
20 | "displayName": "Advanced Options",
21 | "isExpanded": false
22 | }
23 | ],
24 | "inputs": [
25 | {
26 | "name": "OctoConnectedServiceName",
27 | "type": "connectedService:OctopusEndpoint",
28 | "label": "Octopus Deploy Server",
29 | "defaultValue": "",
30 | "required": true,
31 | "helpMarkDown": "Octopus Deploy server connection"
32 | },
33 | {
34 | "name": "command",
35 | "type": "string",
36 | "label": "command",
37 | "defaultValue": "",
38 | "required": true,
39 | "helpMarkDown": "The Octopus CLI command to execute."
40 | },
41 | {
42 | "name": "args",
43 | "type": "string",
44 | "label": "arguments",
45 | "defaultValue": "",
46 | "required": false,
47 | "helpMarkDown": "The arguments to use for the command."
48 | }
49 | ],
50 | "instanceNameFormat": "Invoke an Octopus CLI command",
51 | "execution": {
52 | "Node10": {
53 | "target": "index.js"
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoInstaller/OctoInstallerV4/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { getOrDownloadOcto, resolvePublishedOctoVersion, addToolToPath, getEmbeddedOcto } from "../../Utils/install";
3 | import * as tasks from "azure-pipelines-task-lib/task";
4 | import * as os from "os";
5 |
6 | async function run() {
7 | tasks.warning("This task is deprecated, please use latest version instead.");
8 | const version = tasks.getInput("version");
9 | // @ts-ignore
10 | const forceEmbedded = /embedded/i.test(version);
11 |
12 | try {
13 | if (forceEmbedded) {
14 | console.log("Forcing the use of the embedded Octopus CLI tool.");
15 | await getEmbeddedOcto(tasks.resolve(__dirname, "embedded")).then(addToolToPath);
16 | } else {
17 | const option = await resolvePublishedOctoVersion(version);
18 | console.log(`Using Octopus CLI tool version ${option.version}`);
19 | await getOrDownloadOcto(option).then(addToolToPath);
20 | }
21 |
22 | tasks.setResult(tasks.TaskResult.Succeeded, "");
23 | } catch (error: unknown) {
24 | if (error instanceof Error) {
25 | if (forceEmbedded) {
26 | tasks.setResult(tasks.TaskResult.Failed, `${error.message}${os.EOL}${error.stack}`, true);
27 | return;
28 | }
29 |
30 | console.log(`Failed to resolve Octopus CLI tool version ${version}. Using the embedded version. ${error}`);
31 |
32 | try {
33 | await getEmbeddedOcto(tasks.resolve(__dirname, "embedded")).then(addToolToPath);
34 | } catch (embeddedOctoError: unknown) {
35 | if (embeddedOctoError instanceof Error) {
36 | tasks.setResult(tasks.TaskResult.Failed, `${embeddedOctoError.message}${os.EOL}${embeddedOctoError.stack}`, true);
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | run();
44 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctoInstaller/OctoInstallerV4/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3",
3 | "name": "OctoInstaller",
4 | "friendlyName": "[DEPRECATED] Octopus CLI Installer",
5 | "description": "This task is deprecated, please use latest version instead. Install a specific version of the Octopus CLI",
6 | "helpMarkDown": "Install a specific version of the Octopus CLI",
7 | "category": "Tool",
8 | "runsOn": [
9 | "Agent",
10 | "DeploymentGroup"
11 | ],
12 | "visibility": [
13 | "Build",
14 | "Release"
15 | ],
16 | "author": "Octopus Deploy",
17 | "version": {
18 | "Major": 4,
19 | "Minor": 3,
20 | "Patch": 0
21 | },
22 | "deprecated": true,
23 | "satisfies": ["Octo"],
24 | "demands": [],
25 | "minimumAgentVersion": "2.144.0",
26 | "groups": [
27 | {
28 | "name": "advanced",
29 | "displayName": "Advanced Options",
30 | "isExpanded": false
31 | }
32 | ],
33 | "inputs": [
34 | {
35 | "name": "version",
36 | "type": "string",
37 | "label": "Octopus CLI Version",
38 | "defaultValue": "embedded",
39 | "required": true,
40 | "helpMarkDown": "Specify `latest` or the version number to download. If you specify `embedded`, or the download fails, a built-in copy of the tool will be used."
41 | }
42 | ],
43 | "instanceNameFormat": "[DEPRECATED] Use Octopus CLI tool version $(version)",
44 | "execution": {
45 | "Node10": {
46 | "target": "index.js"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Pack/PackV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Pack/PackV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Pack/PackV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Promote/PromoteV3/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Promote/PromoteV3/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Promote/PromoteV3/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool";
4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs";
5 | import { getDefaultOctopusConnectionDetailsOrThrow, resolveProjectName } from "../../Utils/connection";
6 | import os from "os";
7 |
8 | async function run() {
9 | try {
10 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
11 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
12 |
13 | const space = tasks.getInput("Space");
14 | // @ts-expect-error
15 | const project = await resolveProjectName(connection, tasks.getInput("Project", true)).then((x) => x.value);
16 |
17 | const from = tasks.getInput("From", true);
18 | const to = getRequiredCsvInput("To");
19 | const showProgress = tasks.getBoolInput("ShowProgress");
20 | const deploymentForTenants = getOptionalCsvInput("DeployForTenants");
21 | const deployForTenantTags = getOptionalCsvInput("DeployForTentantTags");
22 | const additionalArguments = tasks.getInput("AdditionalArguments");
23 |
24 | const octo = await getOrInstallOctoCommandRunner("promote-release");
25 |
26 | const configure = [
27 | // @ts-expect-error
28 | argumentIfSet(argumentEnquote, "space", space),
29 | argumentEnquote("project", project),
30 | connectionArguments(connection),
31 | // @ts-expect-error
32 | argumentEnquote("from", from),
33 | multiArgument(argumentEnquote, "to", to),
34 | multiArgument(argumentEnquote, "tenant", deploymentForTenants),
35 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags),
36 | flag("progress", showProgress),
37 | // @ts-expect-error
38 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
39 | ];
40 |
41 | const code: number = await octo
42 | .map((x) => x.launchOcto(configure, "(release;promote;v3)"))
43 | .getOrElseL((x) => {
44 | throw new Error(x);
45 | });
46 |
47 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded promoting release with code " + code);
48 | } catch (error: unknown) {
49 | if (error instanceof Error) {
50 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to promote release. ${error.message}${os.EOL}${error.stack}`, true);
51 | }
52 | }
53 | }
54 |
55 | run();
56 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Promote/PromoteV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Promote/PromoteV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Promote/PromoteV4/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, flag, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool";
4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs";
5 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
6 | import os from "os";
7 |
8 | async function run() {
9 | try {
10 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
11 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
12 |
13 | const space = tasks.getInput("Space");
14 | const project = tasks.getInput("Project", true);
15 | const from = tasks.getInput("From", true);
16 | const to = getRequiredCsvInput("To");
17 | const deployForTenants = getOptionalCsvInput("DeployForTenants");
18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags");
19 | const showProgress = tasks.getBoolInput("ShowProgress");
20 | const additionalArguments = tasks.getInput("AdditionalArguments");
21 |
22 | await assertOctoVersionAcceptsIds();
23 | const octo = await getOrInstallOctoCommandRunner("promote-release");
24 |
25 | const configure = [
26 | // @ts-expect-error
27 | argumentIfSet(argumentEnquote, "space", space),
28 | // @ts-expect-error
29 | argumentEnquote("project", project),
30 | connectionArguments(connection),
31 | // @ts-expect-error
32 | argumentEnquote("from", from),
33 | multiArgument(argumentEnquote, "to", to),
34 | multiArgument(argumentEnquote, "tenant", deployForTenants),
35 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags),
36 | flag("progress", showProgress),
37 | // @ts-expect-error
38 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
39 | ];
40 |
41 | const code: number = await octo
42 | .map((x) => x.launchOcto(configure, "(release;promote;v4)"))
43 | .getOrElseL((x) => {
44 | throw new Error(x);
45 | });
46 |
47 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded promoting release with code " + code);
48 | } catch (error: unknown) {
49 | if (error instanceof Error) {
50 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to promote release. ${error.message}${os.EOL}${error.stack}`, true);
51 | }
52 | }
53 | }
54 |
55 | run();
56 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV3/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Push/PushV3/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV3/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV3/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 |
4 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool";
5 | import { getLineSeparatedItems, resolveGlobs } from "../../Utils/inputs";
6 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
7 | import os from "os";
8 |
9 | async function run() {
10 | try {
11 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
12 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
13 |
14 | const space = tasks.getInput("Space");
15 | // @ts-expect-error
16 | const packages = getLineSeparatedItems(tasks.getInput("Package", true));
17 | const replace = tasks.getBoolInput("Replace");
18 | const additionalArguments = tasks.getInput("AdditionalArguments");
19 |
20 | const octo = await getOrInstallOctoCommandRunner("push");
21 | const matchedPackages = await resolveGlobs(packages);
22 |
23 | const configure = [
24 | connectionArguments(connection),
25 | // @ts-expect-error
26 | argumentIfSet(argumentEnquote, "space", space),
27 | multiArgument(argumentEnquote, "package", matchedPackages),
28 | flag("replace-existing", replace),
29 | // @ts-expect-error
30 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
31 | ];
32 |
33 | const code: number = await octo
34 | .map((x) => x.launchOcto(configure, "(package;push;v3)"))
35 | .getOrElseL((x) => {
36 | throw new Error(x);
37 | });
38 |
39 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded with code " + code);
40 | } catch (error: unknown) {
41 | if (error instanceof Error) {
42 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to push package. ${error.message}${os.EOL}${error.stack}`, true);
43 | }
44 | }
45 | }
46 |
47 | run();
48 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV3/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2",
3 | "name": "OctopusPush",
4 | "friendlyName": "Push Package(s) to Octopus",
5 | "description": "There is a later version of this task, we recommend using the latest version. Push your NuGet or Zip package to your Octopus Deploy Server",
6 | "helpMarkDown": "set-by-pack.ps1",
7 | "category": "Package",
8 | "visibility": ["Build", "Release"],
9 | "author": "Octopus Deploy",
10 | "version": {
11 | "Major": 3,
12 | "Minor": 1,
13 | "Patch": 0
14 | },
15 | "demands": [],
16 | "minimumAgentVersion": "2.144.0",
17 | "groups": [
18 | {
19 | "name": "advanced",
20 | "displayName": "Advanced Options",
21 | "isExpanded": false
22 | }
23 | ],
24 | "inputs": [
25 | {
26 | "name": "OctoConnectedServiceName",
27 | "type": "connectedService:OctopusEndpoint",
28 | "label": "Octopus Deploy Server",
29 | "defaultValue": "",
30 | "required": true,
31 | "helpMarkDown": "Octopus Deploy server connection"
32 | },
33 | {
34 | "name": "Space",
35 | "type": "string",
36 | "label": "Space (Legacy - Use version 4 of this task)",
37 | "defaultValue": "",
38 | "required": false,
39 | "properties": {
40 | "EditableOptions": "False"
41 | },
42 | "helpMarkDown": "Version 3 of this task has limited support for spaces. We recommend using version 4 of this task for a better experience."
43 | },
44 | {
45 | "name": "Package",
46 | "type": "multiLine",
47 | "label": "Package",
48 | "defaultValue": "",
49 | "required": true,
50 | "helpMarkDown": "Package file to push. To push multiple packages, enter on multiple lines."
51 | },
52 | {
53 | "name": "Replace",
54 | "type": "boolean",
55 | "label": "Replace Existing",
56 | "defaultValue": "False",
57 | "required": true,
58 | "helpMarkDown": "If the package already exists in the repository, the default behavior is to reject the new package being pushed. Set this flag to 'True' to overwrite the existing package."
59 | },
60 | {
61 | "name": "AdditionalArguments",
62 | "type": "string",
63 | "label": "Additional Arguments",
64 | "defaultValue": "",
65 | "required": false,
66 | "helpMarkDown": "Additional arguments to be supplied to the Octopus CLI. See the [Octopus CLI documentation](https://g.octopushq.com/OctoExePush) for available parameters.",
67 | "groupName": "advanced"
68 | }
69 | ],
70 | "instanceNameFormat": "Push Packages to Octopus",
71 | "execution": {
72 | "Node10": {
73 | "target": "index.js"
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV4/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Push/PushV4/icon.png
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV4/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Push/PushV4/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 |
4 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, resolveGlobs } from "../../Utils/inputs";
5 | import { argument, argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool";
6 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
7 | import os from "os";
8 |
9 | async function run() {
10 | try {
11 | tasks.warning("There is a later version of this task, we recommend using the latest version.");
12 | const connection = getDefaultOctopusConnectionDetailsOrThrow();
13 |
14 | const space = tasks.getInput("Space");
15 | // @ts-expect-error
16 | const packages = getLineSeparatedItems(tasks.getInput("Package", true));
17 | // @ts-expect-error
18 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true));
19 | const additionalArguments = tasks.getInput("AdditionalArguments");
20 |
21 | await assertOctoVersionAcceptsIds();
22 | const octo = await getOrInstallOctoCommandRunner("push");
23 | const matchedPackages = await resolveGlobs(packages);
24 |
25 | const configure = [
26 | connectionArguments(connection),
27 | // @ts-expect-error
28 | argumentIfSet(argumentEnquote, "space", space),
29 | multiArgument(argumentEnquote, "package", matchedPackages),
30 | argument("overwrite-mode", overwriteMode),
31 | // @ts-expect-error
32 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments),
33 | ];
34 |
35 | const code: number = await octo
36 | .map((x) => x.launchOcto(configure, "(package;push;v4)"))
37 | .getOrElseL((x) => {
38 | throw new Error(x);
39 | });
40 |
41 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded with code " + code);
42 | } catch (error: unknown) {
43 | if (error instanceof Error) {
44 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to push package. ${error.message}${os.EOL}${error.stack}`, true);
45 | }
46 | }
47 | }
48 |
49 | run();
50 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Utils/OctoApiKeyHandler.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { IRequestHandler, IHttpClientResponse } from "typed-rest-client/Interfaces";
3 | import * as http from "http";
4 |
5 | export class OctoApiKeyHandler implements IRequestHandler {
6 | key: string;
7 |
8 | constructor(key: string) {
9 | this.key = key;
10 | }
11 |
12 | prepareRequest(options: http.RequestOptions): void {
13 | // @ts-expect-error
14 | options.headers["X-Octopus-ApiKey"] = this.key;
15 | }
16 |
17 | canHandleAuthentication(): boolean {
18 | return false;
19 | }
20 |
21 | handleAuthentication(): Promise {
22 | throw "This handler does not handle authentication.";
23 | }
24 | }
25 |
26 | export default OctoApiKeyHandler;
27 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Utils/connection.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import * as tasks from "azure-pipelines-task-lib/task";
3 | import { RestClient } from "typed-rest-client/RestClient";
4 | import { getDefaultOctoConnectionInputValue } from "./inputs";
5 | import { either } from "fp-ts";
6 | import OctoApiKeyHandler from "./OctoApiKeyHandler";
7 |
8 | export interface OctoServerConnectionDetails {
9 | url: string;
10 | apiKey: string;
11 | ignoreSslErrors: boolean;
12 | }
13 |
14 | export function getDefaultOctopusConnectionDetailsOrThrow() {
15 | const result = getDefaultOctoConnectionInputValue().map(getOctopusConnectionDetails).toNullable();
16 | if (!result) {
17 | throw new Error("Could not retrieve default Octo connection information");
18 | }
19 | return result;
20 | }
21 |
22 | export function getOctopusConnectionDetails(name: string): OctoServerConnectionDetails {
23 | const octoEndpointAuthorization = tasks.getEndpointAuthorization(name, false);
24 | const ignoreSSL = tasks.getEndpointDataParameter(name, "ignoreSslErrors", true);
25 | return {
26 | // @ts-expect-error
27 | url: tasks.getEndpointUrl(name, false),
28 | // @ts-expect-error
29 | apiKey: octoEndpointAuthorization.parameters["apitoken"],
30 | ignoreSslErrors: !!ignoreSSL && ignoreSSL.toLowerCase() === "true",
31 | };
32 | }
33 |
34 | export function fetchProjectName(details: OctoServerConnectionDetails, projectId: string) {
35 | console.log("Ignore SSL: " + details.ignoreSslErrors);
36 | const client = new RestClient("OctoTFS", details.url, [new OctoApiKeyHandler(details.apiKey)], { ignoreSslError: details.ignoreSslErrors });
37 | return client
38 | .get<{ Name: string }>(`api/projects/${projectId}`)
39 | .then((x) => {
40 | if (x.result) {
41 | return either.right(x.result.Name);
42 | }
43 |
44 | return either.left(`Could not resolve project name given id "${projectId}". Server returned status code: ${x.statusCode}`);
45 | })
46 | .catch((error) => either.left(error));
47 | }
48 |
49 | export const isProjectId = (projectNameOrId: string) => /\w*Projects-\d*/.test(projectNameOrId);
50 |
51 | export function resolveProjectName(connection: OctoServerConnectionDetails, projectNameOrId: string) {
52 | if (isProjectId(projectNameOrId)) {
53 | return fetchProjectName(connection, projectNameOrId);
54 | }
55 |
56 | return Promise.resolve(either.right(projectNameOrId));
57 | }
58 |
--------------------------------------------------------------------------------
/source/tasksLegacy/Utils/inputs.ts:
--------------------------------------------------------------------------------
1 | import * as tasks from "azure-pipelines-task-lib/task";
2 | import { option } from "fp-ts";
3 | import glob from "glob";
4 | import { flatten } from "ramda";
5 |
6 | export enum ReplaceOverwriteMode {
7 | false = "FailIfExists",
8 | true = "OverwriteExisting",
9 | IgnoreIfExists = "IgnoreIfExists",
10 | }
11 |
12 | export function getOverwriteModeFromReplaceInput(replace: string): ReplaceOverwriteMode {
13 | return ReplaceOverwriteMode[replace as keyof typeof ReplaceOverwriteMode] || ReplaceOverwriteMode.false;
14 | }
15 |
16 | export const DefaultOctoConnectionInputName = "OctoConnectedServiceName";
17 |
18 | export const pGlobNoNull = (pattern: string): Promise => {
19 | return new Promise((resolve, reject) => {
20 | glob(pattern, { nonull: true }, (err, matches) => {
21 | if (err) {
22 | reject(err);
23 | return;
24 | }
25 | resolve(matches);
26 | });
27 | });
28 | };
29 |
30 | export function isNullOrWhitespace(value: string | null | undefined): value is null | undefined {
31 | return !value || !/\S/.test(value);
32 | }
33 |
34 | export function safeTrim(value: string | null | undefined): string | null | undefined {
35 | return value ? value.trim() : value;
36 | }
37 |
38 | export function removeTrailingSlashes(value: string | null | undefined): string | null | undefined {
39 | return value ? value.replace(/[/\\]+(?=\s*)$/, "") : value;
40 | }
41 |
42 | export function getLineSeparatedItems(value: string): Array {
43 | return value ? value.split(/[\r\n]+/g).map((x) => x.trim()) : [];
44 | }
45 |
46 | const getRequiredInput = (name: string) => {
47 | return option.fromNullable(tasks.getInput(name, true));
48 | };
49 |
50 | const splitComma = (x: string) => x.split(",").map((x) => x.trim());
51 |
52 | const getOptionalInput = (name: string) => {
53 | return option.fromNullable(tasks.getInput(name, false));
54 | };
55 |
56 | export const getOptionalCsvInput = (name: string) => {
57 | return getOptionalInput(name).map(splitComma).getOrElse([]);
58 | };
59 |
60 | export const getRequiredCsvInput = (name: string) => {
61 | return getRequiredInput(name).map(splitComma).getOrElse([]);
62 | };
63 |
64 | export { getRequiredInput, getOptionalInput };
65 |
66 | export function getDefaultOctoConnectionInputValue() {
67 | return getRequiredInput(DefaultOctoConnectionInputName);
68 | }
69 |
70 | export const resolveGlobs = (globs: string[]): Promise => {
71 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
72 | // @ts-ignore
73 | return Promise.all(globs.map(pGlobNoNull)).then((x) => flatten(x));
74 | };
75 |
--------------------------------------------------------------------------------
/source/widgets/ProjectStatus/configuration.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
37 |
38 |
39 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/source/widgets/ProjectStatus/octo-status.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
51 |
65 |
66 |
67 |
68 |
69 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/task-ids.json:
--------------------------------------------------------------------------------
1 | {
2 | "localtest": {
3 | "OctopusAwaitTask": "885046af-620f-4ac8-961d-e7aaaf8d24ea",
4 | "OctopusCreateRelease": "69084338-2108-493c-98b0-c4410f3e8b7a",
5 | "OctopusDeployRelease": "947a9649-a21f-4402-9a4d-f0267fa69c9c",
6 | "OctopusDeployReleaseTenanted": "091ac6f1-da1b-47d1-acf9-d2b93c4f2d67",
7 | "OctopusPack": "ba79430a-bd26-4fc0-b827-6e442de623e4",
8 | "OctopusPackNuGet": "cbdb1435-36a5-42b1-a419-18888f0df707",
9 | "OctopusPackZip": "8f600674-2a6a-489a-a447-2cce0e26bfb3",
10 | "OctopusPromote": "60c1aa6c-2d9a-455a-a4c6-5835eeddbd2d",
11 | "OctopusPush": "72153241-00c1-4f78-a64f-8b2c0d187b3c",
12 | "OctopusMetadata": "2032df04-eeb2-4afc-9854-cec4d7169e4c",
13 | "OctopusBuildInformation": "00a6aea4-412d-48a5-8af3-369e8654913f",
14 | "OctoInstaller": "0bb4020f-14f3-409b-ac87-b6ec75066419",
15 | "OctoCli": "92d8efb7-1c1f-4803-865e-54baac6ba644",
16 | "OctopusRunRunbook": "01e38835-484e-46eb-a9ed-5c265396146f"
17 | },
18 | "test": {
19 | "OctopusAwaitTask": "38df691d-23eb-48d4-8638-61764f48bacb",
20 | "OctopusCreateRelease": "4E131B60-5532-4362-95B6-7C67D9841B4F",
21 | "OctopusDeployRelease": "8ca1d96a-151d-44b7-bc4f-9251e2ea6971",
22 | "OctopusDeployReleaseTenanted": "a847e2d1-5435-4d52-a774-6d300953e85f",
23 | "OctopusPack": "179fac12-2402-486e-80cf-5a6a8571f7c0",
24 | "OctopusPackNuGet": "72e7a1b6-19bc-48e6-8d20-a81f201d65a3",
25 | "OctopusPackZip": "3f248d80-a755-498d-863c-f936c5821318",
26 | "OctopusPromote": "1627fcfe-f292-4904-adac-26cfb14bdb07",
27 | "OctopusPush": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2",
28 | "OctopusMetadata": "b1861ef4-b62e-40c1-bcb0-be00d454a8a7",
29 | "OctopusBuildInformation": "559b81c9-efc1-40f3-9058-71ab1810d837",
30 | "OctoInstaller": "ccdc7423-0f15-48d1-a104-78e181ea8c2f",
31 | "OctoCli": "ca025f3d-d8b6-4ca2-aa94-7785e784581f",
32 | "OctopusRunRunbook": "5a2273e0-aa4f-4502-bcba-6817835e2bbd"
33 | },
34 | "production": {
35 | "OctopusAwaitTask": "38df691d-23eb-48d4-8638-61764f48bacb",
36 | "OctopusCreateRelease": "4E131B60-5532-4362-95B6-7C67D9841B4F",
37 | "OctopusDeployRelease": "8ca1d96a-151d-44b7-bc4f-9251e2ea6971",
38 | "OctopusDeployReleaseTenanted": "a847e2d1-5435-4d52-a774-6d300953e85f",
39 | "OctopusPack": "179fac12-2402-486e-80cf-5a6a8571f7c0",
40 | "OctopusPackNuGet": "72e7a1b6-19bc-48e6-8d20-a81f201d65a3",
41 | "OctopusPackZip": "3f248d80-a755-498d-863c-f936c5821318",
42 | "OctopusPromote": "1627fcfe-f292-4904-adac-26cfb14bdb07",
43 | "OctopusPush": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2",
44 | "OctopusMetadata": "b1861ef4-b62e-40c1-bcb0-be00d454a8a7",
45 | "OctopusBuildInformation": "559b81c9-efc1-40f3-9058-71ab1810d837",
46 | "OctoInstaller": "7aeae2f5-8529-4326-b127-a66c334c6e60",
47 | "OctoCli": "ac1f0794-ad67-4c60-a386-8704226392be",
48 | "OctopusRunRunbook": "5a2273e0-aa4f-4502-bcba-6817835e2bbd"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node10/tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "sourceMap": true,
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "noImplicitReturns": true,
9 | "noFallthroughCasesInSwitch": true,
10 | "baseUrl": "./source",
11 | "allowSyntheticDefaultImports": true,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------