diff --git a/.gitignore b/.gitignore
index 2394333d9..a1e88f9b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,7 +36,7 @@
*_i.c
*_p.c
Ankh.Load
-Backup*
+Backup*/
CVS/
PrecompiledWeb/
UpgradeLog*.*
@@ -58,3 +58,10 @@ source/packages
source/OctopusTools.v2.ncrunchsolution
*.orig
*.userprefs
+*.lock.json
+.vs
+.vscode
+/tools/
+/artifacts/
+/publish/
+TestResult.xml
\ No newline at end of file
diff --git a/source/Octopus.Cli/Octopus.Cli.nuspec b/BuildAssets/OctopusTools.nuspec
similarity index 88%
rename from source/Octopus.Cli/Octopus.Cli.nuspec
rename to BuildAssets/OctopusTools.nuspec
index 60bce278b..58e8335c3 100644
--- a/source/Octopus.Cli/Octopus.Cli.nuspec
+++ b/BuildAssets/OctopusTools.nuspec
@@ -20,8 +20,6 @@
true
-
-
-
+
diff --git a/source/Octopus.Cli/init.ps1 b/BuildAssets/init.ps1
similarity index 100%
rename from source/Octopus.Cli/init.ps1
rename to BuildAssets/init.ps1
diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml
deleted file mode 100644
index c1a0dc7bf..000000000
--- a/GitVersionConfig.yaml
+++ /dev/null
@@ -1 +0,0 @@
-mode: ContinuousDeployment
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..38433ec27
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,11 @@
+version: '{build}'
+configuration: Release
+platform: Any CPU
+build_script:
+- cmd: build.cmd
+artifacts:
+- path: artifacts\**\*.*
+cache:
+- '%USERPROFILE%\.nuget\packages'
+test: off
+deploy: off
diff --git a/build.cake b/build.cake
new file mode 100644
index 000000000..a49189c8b
--- /dev/null
+++ b/build.cake
@@ -0,0 +1,207 @@
+//////////////////////////////////////////////////////////////////////
+// TOOLS
+//////////////////////////////////////////////////////////////////////
+#tool "nuget:?package=GitVersion.CommandLine&prerelease"
+#tool "nuget:?package=ILRepack"
+#addin "nuget:?package=Newtonsoft.Json"
+#addin "nuget:?package=SharpCompress"
+
+using Path = System.IO.Path;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using IO = System.IO;
+using SharpCompress;
+using SharpCompress.Common;
+using SharpCompress.Writer;
+
+//////////////////////////////////////////////////////////////////////
+// ARGUMENTS
+//////////////////////////////////////////////////////////////////////
+var target = Argument("target", "Default");
+var configuration = Argument("configuration", "Release");
+
+///////////////////////////////////////////////////////////////////////////////
+// GLOBAL VARIABLES
+///////////////////////////////////////////////////////////////////////////////
+var publishDir = "./publish";
+var artifactsDir = "./artifacts";
+var assetDir = "./BuildAssets";
+var globalAssemblyFile = "./source/Octo/Properties/AssemblyInfo.cs";
+var projectToPublish = "./source/Octo";
+var projectToPublishProjectJson = Path.Combine(projectToPublish, "project.json");
+var octopusClientFolder = "./source/Octopus.Client";
+var isContinuousIntegrationBuild = !BuildSystem.IsLocalBuild;
+var octoPublishFolder = Path.Combine(publishDir, "Octo");
+var octoMergedFolder = Path.Combine(publishDir, "OctoMerged");
+
+var gitVersionInfo = GitVersion(new GitVersionSettings {
+ OutputType = GitVersionOutput.Json
+});
+
+var nugetVersion = gitVersionInfo.NuGetVersion;
+
+///////////////////////////////////////////////////////////////////////////////
+// SETUP / TEARDOWN
+///////////////////////////////////////////////////////////////////////////////
+Setup(context =>
+{
+ Information("Building OctopusClients v{0}", nugetVersion);
+});
+
+Teardown(context =>
+{
+ Information("Finished running tasks.");
+});
+
+//////////////////////////////////////////////////////////////////////
+// PRIVATE TASKS
+//////////////////////////////////////////////////////////////////////
+
+Task("__Default")
+ .IsDependentOn("__Clean")
+ .IsDependentOn("__Restore")
+ .IsDependentOn("__UpdateAssemblyVersionInformation")
+ .IsDependentOn("__Build")
+ .IsDependentOn("__Test")
+ .IsDependentOn("__Publish")
+ .IsDependentOn("__PackNuget");
+
+Task("__Clean")
+ .Does(() =>
+{
+ CleanDirectory(artifactsDir);
+ CleanDirectory(publishDir);
+ CleanDirectories("./source/**/bin");
+ CleanDirectories("./source/**/obj");
+});
+
+Task("__Restore")
+ .Does(() => DotNetCoreRestore());
+
+Task("__UpdateAssemblyVersionInformation")
+ .WithCriteria(isContinuousIntegrationBuild)
+ .Does(() =>
+{
+ GitVersion(new GitVersionSettings {
+ UpdateAssemblyInfo = true,
+ UpdateAssemblyInfoFilePath = globalAssemblyFile
+ });
+
+ Information("AssemblyVersion -> {0}", gitVersionInfo.AssemblySemVer);
+ Information("AssemblyFileVersion -> {0}", $"{gitVersionInfo.MajorMinorPatch}.0");
+ Information("AssemblyInformationalVersion -> {0}", gitVersionInfo.InformationalVersion);
+ if(BuildSystem.IsRunningOnTeamCity)
+ BuildSystem.TeamCity.SetBuildNumber(gitVersionInfo.NuGetVersion);
+ if(BuildSystem.IsRunningOnAppVeyor)
+ AppVeyor.UpdateBuildVersion(gitVersionInfo.NuGetVersion);
+});
+
+Task("__Build")
+ .IsDependentOn("__UpdateProjectJsonVersion")
+ .IsDependentOn("__UpdateAssemblyVersionInformation")
+ .Does(() =>
+{
+ DotNetCoreBuild("**/project.json", new DotNetCoreBuildSettings
+ {
+ Configuration = configuration
+ });
+});
+
+Task("__Test")
+ .Does(() =>
+{
+ GetFiles("**/*Tests/project.json")
+ .ToList()
+ .ForEach(testProjectFile =>
+ {
+ DotNetCoreTest(testProjectFile.ToString(), new DotNetCoreTestSettings
+ {
+ Configuration = configuration,
+ WorkingDirectory = Path.GetDirectoryName(testProjectFile.ToString())
+ });
+ });
+});
+
+Task("__UpdateProjectJsonVersion")
+ .WithCriteria(isContinuousIntegrationBuild)
+ .Does(() =>
+{
+ Information("Updating {0} version -> {1}", projectToPublishProjectJson, nugetVersion);
+ ModifyJson(Path.Combine(octopusClientFolder, "project.json"), json => json["version"] = nugetVersion);
+ ModifyJson(projectToPublishProjectJson, json => json["version"] = nugetVersion);
+});
+
+private void ModifyJson(string jsonFile, Action modify)
+{
+ var json = JsonConvert.DeserializeObject(IO.File.ReadAllText(jsonFile));
+ modify(json);
+ IO.File.WriteAllText(jsonFile, JsonConvert.SerializeObject(json, Formatting.Indented));
+}
+
+
+Task("__Publish")
+ .Does(() =>
+{
+ DotNetCorePublish(projectToPublish, new DotNetCorePublishSettings
+ {
+ Configuration = configuration,
+ OutputDirectory = octoPublishFolder
+ });
+});
+
+Task("__MergeOctoExe")
+ .Does(() => {
+ CreateDirectory(octoMergedFolder);
+ ILRepack(
+ Path.Combine(octoMergedFolder, "Octo.exe"),
+ Path.Combine(octoPublishFolder, "Octo.exe"),
+ IO.Directory.EnumerateFiles(octoPublishFolder, "*.dll").Select(f => (FilePath) f),
+ new ILRepackSettings {
+ Internalize = true,
+ Libs = new List() { octoPublishFolder }
+ }
+ );
+ DeleteFile(Path.Combine(octoMergedFolder, "Octo.pdb"));
+ CopyFileToDirectory(Path.Combine(octoPublishFolder, "Octo.exe.config"), octoMergedFolder);
+ });
+
+Task("__PackNuget")
+ .IsDependentOn("__Publish")
+ .IsDependentOn("__PackOctopusToolsNuget")
+ .IsDependentOn("__PackClientNuget");
+
+Task("__PackClientNuget")
+ .Does(() => {
+ DotNetCorePack(octopusClientFolder, new DotNetCorePackSettings {
+ Configuration = configuration,
+ OutputDirectory = artifactsDir
+ });
+ });
+
+Task("__PackOctopusToolsNuget")
+ .IsDependentOn("__MergeOctoExe")
+ .Does(() => {
+ var nugetPackDir = Path.Combine(publishDir, "nuget");
+ var nuspecFile = "OctopusTools.nuspec";
+
+ CopyDirectory(octoMergedFolder, nugetPackDir);
+ CopyFileToDirectory(Path.Combine(assetDir, "init.ps1"), nugetPackDir);
+ CopyFileToDirectory(Path.Combine(assetDir, nuspecFile), nugetPackDir);
+
+ NuGetPack(Path.Combine(nugetPackDir, nuspecFile), new NuGetPackSettings {
+ Version = nugetVersion,
+ OutputDirectory = artifactsDir
+ });
+ });
+
+
+//////////////////////////////////////////////////////////////////////
+// TASKS
+//////////////////////////////////////////////////////////////////////
+Task("Default")
+ .IsDependentOn("__Default");
+
+//////////////////////////////////////////////////////////////////////
+// EXECUTION
+//////////////////////////////////////////////////////////////////////
+RunTarget(target);
diff --git a/build.cmd b/build.cmd
index 479c6687e..f77be6b72 100644
--- a/build.cmd
+++ b/build.cmd
@@ -1,3 +1,24 @@
-@echo off
+@ECHO OFF
+REM see http://joshua.poehls.me/powershell-batch-file-wrapper/
-PowerShell.exe -ExecutionPolicy Bypass -File "tools\Build.ps1"
+SET SCRIPTNAME=%~d0%~p0%~n0.ps1
+SET ARGS=%*
+IF [%ARGS%] NEQ [] GOTO ESCAPE_ARGS
+
+:POWERSHELL
+PowerShell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Unrestricted -Command "& { $ErrorActionPreference = 'Stop'; & '%SCRIPTNAME%' @args; EXIT $LASTEXITCODE }" %ARGS%
+EXIT /B %ERRORLEVEL%
+
+:ESCAPE_ARGS
+SET ARGS=%ARGS:"=\"%
+SET ARGS=%ARGS:`=``%
+SET ARGS=%ARGS:'=`'%
+SET ARGS=%ARGS:$=`$%
+SET ARGS=%ARGS:{=`}%
+SET ARGS=%ARGS:}=`}%
+SET ARGS=%ARGS:(=`(%
+SET ARGS=%ARGS:)=`)%
+SET ARGS=%ARGS:,=`,%
+SET ARGS=%ARGS:^%=%
+
+GOTO POWERSHELL
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 000000000..ab54dc8a7
--- /dev/null
+++ b/build.ps1
@@ -0,0 +1,189 @@
+##########################################################################
+# This is the Cake bootstrapper script for PowerShell.
+# This file was downloaded from https://github.com/cake-build/resources
+# Feel free to change this file to fit your needs.
+##########################################################################
+
+<#
+
+.SYNOPSIS
+This is a Powershell script to bootstrap a Cake build.
+
+.DESCRIPTION
+This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
+and execute your Cake build script with the parameters you provide.
+
+.PARAMETER Script
+The build script to execute.
+.PARAMETER Target
+The build script target to run.
+.PARAMETER Configuration
+The build configuration to use.
+.PARAMETER Verbosity
+Specifies the amount of information to be displayed.
+.PARAMETER Experimental
+Tells Cake to use the latest Roslyn release.
+.PARAMETER WhatIf
+Performs a dry run of the build script.
+No tasks will be executed.
+.PARAMETER Mono
+Tells Cake to use the Mono scripting engine.
+.PARAMETER SkipToolPackageRestore
+Skips restoring of packages.
+.PARAMETER ScriptArgs
+Remaining arguments are added here.
+
+.LINK
+http://cakebuild.net
+
+#>
+
+[CmdletBinding()]
+Param(
+ [string]$Script = "build.cake",
+ [string]$Target = "Default",
+ [ValidateSet("Release", "Debug")]
+ [string]$Configuration = "Release",
+ [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
+ [string]$Verbosity = "Verbose",
+ [switch]$Experimental = $true,
+ [Alias("DryRun","Noop")]
+ [switch]$WhatIf,
+ [switch]$Mono,
+ [switch]$SkipToolPackageRestore,
+ [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
+ [string[]]$ScriptArgs
+)
+
+[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
+function MD5HashFile([string] $filePath)
+{
+ if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
+ {
+ return $null
+ }
+
+ [System.IO.Stream] $file = $null;
+ [System.Security.Cryptography.MD5] $md5 = $null;
+ try
+ {
+ $md5 = [System.Security.Cryptography.MD5]::Create()
+ $file = [System.IO.File]::OpenRead($filePath)
+ return [System.BitConverter]::ToString($md5.ComputeHash($file))
+ }
+ finally
+ {
+ if ($file -ne $null)
+ {
+ $file.Dispose()
+ }
+ }
+}
+
+Write-Host "Preparing to run build script..."
+
+if(!$PSScriptRoot){
+ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
+}
+
+$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
+$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
+$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
+$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
+$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
+$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
+
+# Should we use mono?
+$UseMono = "";
+if($Mono.IsPresent) {
+ Write-Verbose -Message "Using the Mono based scripting engine."
+ $UseMono = "-mono"
+}
+
+# Should we use the new Roslyn?
+$UseExperimental = "";
+if($Experimental.IsPresent -and !($Mono.IsPresent)) {
+ Write-Verbose -Message "Using experimental version of Roslyn."
+ $UseExperimental = "-experimental"
+}
+
+# Is this a dry run?
+$UseDryRun = "";
+if($WhatIf.IsPresent) {
+ $UseDryRun = "-dryrun"
+}
+
+# Make sure tools folder exists
+if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
+ Write-Verbose -Message "Creating tools directory..."
+ New-Item -Path $TOOLS_DIR -Type directory | out-null
+}
+
+# Make sure that packages.config exist.
+if (!(Test-Path $PACKAGES_CONFIG)) {
+ Write-Verbose -Message "Downloading packages.config..."
+ try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
+ Throw "Could not download packages.config."
+ }
+}
+
+# Try find NuGet.exe in path if not exists
+if (!(Test-Path $NUGET_EXE)) {
+ Write-Verbose -Message "Trying to find nuget.exe in PATH..."
+ $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) }
+ $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
+ if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
+ Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
+ $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
+ }
+}
+
+# Try download NuGet.exe if not exists
+if (!(Test-Path $NUGET_EXE)) {
+ Write-Verbose -Message "Downloading NuGet.exe..."
+ try {
+ (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
+ } catch {
+ Throw "Could not download NuGet.exe."
+ }
+}
+
+# Save nuget.exe path to environment to be available to child processed
+$ENV:NUGET_EXE = $NUGET_EXE
+
+# Restore tools from NuGet?
+if(-Not $SkipToolPackageRestore.IsPresent) {
+ Push-Location
+ Set-Location $TOOLS_DIR
+
+ # Check for changes in packages.config and remove installed tools if true.
+ [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
+ if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
+ ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
+ Write-Verbose -Message "Missing or changed package.config hash..."
+ Remove-Item * -Recurse -Exclude packages.config,nuget.exe
+ }
+
+ Write-Verbose -Message "Restoring tools from NuGet..."
+ $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
+
+ if ($LASTEXITCODE -ne 0) {
+ Throw "An error occured while restoring NuGet tools."
+ }
+ else
+ {
+ $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
+ }
+ Write-Verbose -Message ($NuGetOutput | out-string)
+ Pop-Location
+}
+
+# Make sure that Cake has been installed.
+if (!(Test-Path $CAKE_EXE)) {
+ Throw "Could not find Cake.exe at $CAKE_EXE"
+}
+
+# Start Cake
+Write-Host "Running build script..."
+Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
+exit $LASTEXITCODE
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100644
index 000000000..6e8f207c8
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+
+##########################################################################
+# This is the Cake bootstrapper script for Linux and OS X.
+# This file was downloaded from https://github.com/cake-build/resources
+# Feel free to change this file to fit your needs.
+##########################################################################
+
+# Define directories.
+SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TOOLS_DIR=$SCRIPT_DIR/tools
+NUGET_EXE=$TOOLS_DIR/nuget.exe
+CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
+PACKAGES_CONFIG=$TOOLS_DIR/packages.config
+PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
+
+# Define md5sum or md5 depending on Linux/OSX
+MD5_EXE=
+if [[ "$(uname -s)" == "Darwin" ]]; then
+ MD5_EXE="md5 -r"
+else
+ MD5_EXE="md5sum"
+fi
+
+# Define default arguments.
+SCRIPT="build.cake"
+TARGET="Default"
+CONFIGURATION="Release"
+VERBOSITY="verbose"
+DRYRUN=
+SHOW_VERSION=false
+SCRIPT_ARGUMENTS=()
+
+# Parse arguments.
+for i in "$@"; do
+ case $1 in
+ -s|--script) SCRIPT="$2"; shift ;;
+ -t|--target) TARGET="$2"; shift ;;
+ -c|--configuration) CONFIGURATION="$2"; shift ;;
+ -v|--verbosity) VERBOSITY="$2"; shift ;;
+ -d|--dryrun) DRYRUN="-dryrun" ;;
+ --version) SHOW_VERSION=true ;;
+ --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
+ *) SCRIPT_ARGUMENTS+=("$1") ;;
+ esac
+ shift
+done
+
+# Make sure the tools folder exist.
+if [ ! -d "$TOOLS_DIR" ]; then
+ mkdir "$TOOLS_DIR"
+fi
+
+# Make sure that packages.config exist.
+if [ ! -f "$TOOLS_DIR/packages.config" ]; then
+ echo "Downloading packages.config..."
+ curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
+ if [ $? -ne 0 ]; then
+ echo "An error occured while downloading packages.config."
+ exit 1
+ fi
+fi
+
+# Download NuGet if it does not exist.
+if [ ! -f "$NUGET_EXE" ]; then
+ echo "Downloading NuGet..."
+ curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
+ if [ $? -ne 0 ]; then
+ echo "An error occured while downloading nuget.exe."
+ exit 1
+ fi
+fi
+
+# Restore tools from NuGet.
+pushd "$TOOLS_DIR" >/dev/null
+if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then
+ find . -type d ! -name . | xargs rm -rf
+fi
+
+mono "$NUGET_EXE" install -ExcludeVersion
+if [ $? -ne 0 ]; then
+ echo "Could not restore NuGet packages."
+ exit 1
+fi
+
+$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5
+
+popd >/dev/null
+
+# Make sure that Cake has been installed.
+if [ ! -f "$CAKE_EXE" ]; then
+ echo "Could not find Cake.exe at '$CAKE_EXE'."
+ exit 1
+fi
+
+# Start Cake
+if $SHOW_VERSION; then
+ exec mono "$CAKE_EXE" -version
+else
+ exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
+fi
\ No newline at end of file
diff --git a/gitversion.yml b/gitversion.yml
new file mode 100644
index 000000000..c31148888
--- /dev/null
+++ b/gitversion.yml
@@ -0,0 +1 @@
+mode: Mainline
\ No newline at end of file
diff --git a/source/Dockerfile b/source/Dockerfile
deleted file mode 100644
index 8639d5ecf..000000000
--- a/source/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM mono:onbuild
-ENTRYPOINT [ "mono", "Octo.exe" ]
-CMD [ "help" ]
diff --git a/source/Octopus.Cli.Tests/Client/OctopusSessionFactoryFixture.cs b/source/Octo.Tests/Client/OctopusSessionFactoryFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Client/OctopusSessionFactoryFixture.cs
rename to source/Octo.Tests/Client/OctopusSessionFactoryFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/ApiCommandFixture.cs b/source/Octo.Tests/Commands/ApiCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/ApiCommandFixture.cs
rename to source/Octo.Tests/Commands/ApiCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/ApiCommandFixtureBase.cs b/source/Octo.Tests/Commands/ApiCommandFixtureBase.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/ApiCommandFixtureBase.cs
rename to source/Octo.Tests/Commands/ApiCommandFixtureBase.cs
diff --git a/source/Octopus.Cli.Tests/Commands/CleanEnvironmentCommandFixture.cs b/source/Octo.Tests/Commands/CleanEnvironmentCommandFixture.cs
similarity index 85%
rename from source/Octopus.Cli.Tests/Commands/CleanEnvironmentCommandFixture.cs
rename to source/Octo.Tests/Commands/CleanEnvironmentCommandFixture.cs
index 0e2c33f39..6bb1dcc0a 100644
--- a/source/Octopus.Cli.Tests/Commands/CleanEnvironmentCommandFixture.cs
+++ b/source/Octo.Tests/Commands/CleanEnvironmentCommandFixture.cs
@@ -5,6 +5,7 @@
using Octopus.Cli.Commands;
using Octopus.Cli.Infrastructure;
using Octopus.Client.Model;
+using FluentAssertions;
#pragma warning disable 618
namespace Octopus.Cli.Tests.Commands
@@ -104,26 +105,32 @@ public void ShouldRemoveMachinesBelongingToMultipleEnvironmentsInsteadOfDeleting
Repository.Machines.Received().Delete(machineList[1]);
}
- [Test, ExpectedException(typeof(CommandException), ExpectedMessage = "Please specify an environment name using the parameter: --environment=XYZ")]
+ [Test]
public void ShouldNotCleanEnvironmentWithMissingEnvironmentArgs()
{
- listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ Action exec = () => listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ exec.ShouldThrow()
+ .WithMessage("Please specify an environment name using the parameter: --environment=XYZ");
}
- [Test, ExpectedException(typeof(CommandException), ExpectedMessage = "Please specify a status using the parameter: --status or --health-status")]
+ [Test]
public void ShouldNotCleanEnvironmentWithMissingStatusArgs()
{
CommandLineArgs.Add("-environment=Development");
- listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ Action exec = () => listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ exec.ShouldThrow()
+ .WithMessage("Please specify a status using the parameter: --status or --health-status");
}
- [Test, ExpectedException(typeof(CouldNotFindException), ExpectedMessage = "Could not find the specified environment; either it does not exist or you lack permissions to view it.")]
+ [Test]
public void ShouldNotCleanTheEnvironmentIfEnvironmentNotFound()
{
CommandLineArgs.Add("-environment=Development");
CommandLineArgs.Add("-status=Offline");
- listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ Action exec = () => listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ exec.ShouldThrow()
+ .WithMessage("Could not find the specified environment; either it does not exist or you lack permissions to view it.");
}
}
}
diff --git a/source/Octopus.Cli.Tests/Commands/CreateChannelCommandFixture.cs b/source/Octo.Tests/Commands/CreateChannelCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/CreateChannelCommandFixture.cs
rename to source/Octo.Tests/Commands/CreateChannelCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/CreateReleaseCommandFixture.cs b/source/Octo.Tests/Commands/CreateReleaseCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/CreateReleaseCommandFixture.cs
rename to source/Octo.Tests/Commands/CreateReleaseCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/DeleteAutoDeployOverrideCommandFixture.cs b/source/Octo.Tests/Commands/DeleteAutoDeployOverrideCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/DeleteAutoDeployOverrideCommandFixture.cs
rename to source/Octo.Tests/Commands/DeleteAutoDeployOverrideCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/DeployReleaseCommandTestFixture.cs b/source/Octo.Tests/Commands/DeployReleaseCommandTestFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/DeployReleaseCommandTestFixture.cs
rename to source/Octo.Tests/Commands/DeployReleaseCommandTestFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/DummyApiCommand.cs b/source/Octo.Tests/Commands/DummyApiCommand.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/DummyApiCommand.cs
rename to source/Octo.Tests/Commands/DummyApiCommand.cs
diff --git a/source/Octopus.Cli.Tests/Commands/HelpCommandFixture.cs b/source/Octo.Tests/Commands/HelpCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/HelpCommandFixture.cs
rename to source/Octo.Tests/Commands/HelpCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/ListEnvironmentsCommandFixture.cs b/source/Octo.Tests/Commands/ListEnvironmentsCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/ListEnvironmentsCommandFixture.cs
rename to source/Octo.Tests/Commands/ListEnvironmentsCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/ListMachinesCommandFixture.cs b/source/Octo.Tests/Commands/ListMachinesCommandFixture.cs
similarity index 98%
rename from source/Octopus.Cli.Tests/Commands/ListMachinesCommandFixture.cs
rename to source/Octo.Tests/Commands/ListMachinesCommandFixture.cs
index 412953d78..6e5069ba7 100644
--- a/source/Octopus.Cli.Tests/Commands/ListMachinesCommandFixture.cs
+++ b/source/Octo.Tests/Commands/ListMachinesCommandFixture.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Octopus.Cli.Commands;
@@ -310,13 +311,15 @@ public void ShouldSupportStateFilters()
Log.Received().Information(MachineLogFormat, "PC01466", "Healthy - Disabled", "Machines-002", "Development");
}
- [Test, ExpectedException(typeof(CommandException), ExpectedMessage = "The `--health-status` parameter is only available on Octopus Server instances from 3.4.0 onwards.")]
+ [Test]
public void ShouldThrowIfUsingHealthStatusPre34()
{
CommandLineArgs.Add("--health-status=Online");
Repository.Client.RootDocument.Version = "3.1.0";
- listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ Action exec = () => listMachinesCommand.Execute(CommandLineArgs.ToArray());
+ exec.ShouldThrow()
+ .WithMessage("The `--health-status` parameter is only available on Octopus Server instances from 3.4.0 onwards.");
}
[Test]
diff --git a/source/Octopus.Cli.Tests/Commands/ListProjectsCommandFixture.cs b/source/Octo.Tests/Commands/ListProjectsCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/ListProjectsCommandFixture.cs
rename to source/Octo.Tests/Commands/ListProjectsCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/ListReleasesCommandFixture.cs b/source/Octo.Tests/Commands/ListReleasesCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/ListReleasesCommandFixture.cs
rename to source/Octo.Tests/Commands/ListReleasesCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/OptionsFixture.cs b/source/Octo.Tests/Commands/OptionsFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/OptionsFixture.cs
rename to source/Octo.Tests/Commands/OptionsFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/OverrideAutoDeployCommandFixture.cs b/source/Octo.Tests/Commands/OverrideAutoDeployCommandFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/OverrideAutoDeployCommandFixture.cs
rename to source/Octo.Tests/Commands/OverrideAutoDeployCommandFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/PackageVersionResolverFixture.cs b/source/Octo.Tests/Commands/PackageVersionResolverFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/PackageVersionResolverFixture.cs
rename to source/Octo.Tests/Commands/PackageVersionResolverFixture.cs
diff --git a/source/Octopus.Cli.Tests/Commands/Resources/CreateRelease.config.txt b/source/Octo.Tests/Commands/Resources/CreateRelease.config.txt
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/Resources/CreateRelease.config.txt
rename to source/Octo.Tests/Commands/Resources/CreateRelease.config.txt
diff --git a/source/Octopus.Cli.Tests/Commands/SpeakCommand.cs b/source/Octo.Tests/Commands/SpeakCommand.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Commands/SpeakCommand.cs
rename to source/Octo.Tests/Commands/SpeakCommand.cs
diff --git a/source/Octopus.Cli.Tests/Extensions/TimeSpanExtensionsFixture.cs b/source/Octo.Tests/Extensions/TimeSpanExtensionsFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Extensions/TimeSpanExtensionsFixture.cs
rename to source/Octo.Tests/Extensions/TimeSpanExtensionsFixture.cs
diff --git a/source/Octopus.Cli.Tests/Helpers/SemanticVersionInfo.cs b/source/Octo.Tests/Helpers/SemanticVersionInfo.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Helpers/SemanticVersionInfo.cs
rename to source/Octo.Tests/Helpers/SemanticVersionInfo.cs
diff --git a/source/Octopus.Cli.Tests/Helpers/TestCommandExtensions.cs b/source/Octo.Tests/Helpers/TestCommandExtensions.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Helpers/TestCommandExtensions.cs
rename to source/Octo.Tests/Helpers/TestCommandExtensions.cs
diff --git a/source/Octo.Tests/Octo.Tests.xproj b/source/Octo.Tests/Octo.Tests.xproj
new file mode 100644
index 000000000..9baf07fef
--- /dev/null
+++ b/source/Octo.Tests/Octo.Tests.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ b05e3858-5607-46db-876c-4e1f5728a4bb
+ Octopus.Cli.Tests
+ .\obj
+ .\bin\
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/source/Octopus.Cli.Tests/Properties/AssemblyInfo.cs b/source/Octo.Tests/Properties/AssemblyInfo.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Properties/AssemblyInfo.cs
rename to source/Octo.Tests/Properties/AssemblyInfo.cs
diff --git a/source/Octopus.Cli.Tests/Util/UriExtensionsFixture.cs b/source/Octo.Tests/Util/UriExtensionsFixture.cs
similarity index 100%
rename from source/Octopus.Cli.Tests/Util/UriExtensionsFixture.cs
rename to source/Octo.Tests/Util/UriExtensionsFixture.cs
diff --git a/source/Octopus.Cli.Tests/app.config b/source/Octo.Tests/app.config
similarity index 100%
rename from source/Octopus.Cli.Tests/app.config
rename to source/Octo.Tests/app.config
diff --git a/source/Octo.Tests/project.json b/source/Octo.Tests/project.json
new file mode 100644
index 000000000..db41d2d8b
--- /dev/null
+++ b/source/Octo.Tests/project.json
@@ -0,0 +1,32 @@
+{
+ "version": "0.0.0-*",
+ "dependencies": {
+ "GitVersionTask": "3.3.0",
+ "Newtonsoft.Json": "9.0.1",
+ "NSubstitute": "1.7.2.0",
+ "FluentAssertions": "4.12.0",
+ "NUnit": "3.4.1",
+ "Serilog": "2.1.0",
+ "dotnet-test-nunit": "3.4.0-beta-2",
+ "Octopus.Client": {
+ "target": "project"
+ },
+ "Octo": {
+ "target": "project"
+ }
+ },
+ "testRunner": "nunit",
+ "frameworks": {
+ "net452": {
+ "frameworkAssemblies": {
+ "System.Core": "*",
+ "System.Net": "*",
+ "System.Xml.Linq": "*",
+ "System.Data.DataSetExtensions": "*",
+ "Microsoft.CSharp": "*",
+ "System.Data": "*",
+ "System.Xml": "*"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/Octo.exe.sln b/source/Octo.exe.sln
deleted file mode 100644
index 95cab3b2f..000000000
--- a/source/Octo.exe.sln
+++ /dev/null
@@ -1,49 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octopus.Cli", "Octopus.Cli\Octopus.Cli.csproj", "{D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octopus.Cli.Tests", "Octopus.Cli.Tests\Octopus.Cli.Tests.csproj", "{B05E3858-5607-46DB-876C-4E1F5728A4BB}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A88CC6CE-8758-46E3-B646-26088F238774}"
- ProjectSection(SolutionItems) = preProject
- Solution Items\SolutionInfo.cs = Solution Items\SolutionInfo.cs
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|Mixed Platforms = Debug|Mixed Platforms
- Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
- Release|Mixed Platforms = Release|Mixed Platforms
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Debug|Any CPU.ActiveCfg = Debug|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Debug|Mixed Platforms.Build.0 = Debug|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Debug|x86.ActiveCfg = Debug|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Debug|x86.Build.0 = Debug|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Release|Any CPU.ActiveCfg = Release|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Release|Mixed Platforms.ActiveCfg = Release|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Release|Mixed Platforms.Build.0 = Release|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Release|x86.ActiveCfg = Release|x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}.Release|x86.Build.0 = Release|x86
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Debug|x86.ActiveCfg = Debug|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Release|Any CPU.Build.0 = Release|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}.Release|x86.ActiveCfg = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/source/Octo.exe.sln.DotSettings b/source/Octo.exe.sln.DotSettings
deleted file mode 100644
index 542dd4c63..000000000
--- a/source/Octo.exe.sln.DotSettings
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
\ No newline at end of file
diff --git a/source/Octopus.Cli/Commands/ApiCommand.cs b/source/Octo/Commands/ApiCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ApiCommand.cs
rename to source/Octo/Commands/ApiCommand.cs
diff --git a/source/Octopus.Cli/Commands/ChannelVersionRuleTester.cs b/source/Octo/Commands/ChannelVersionRuleTester.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ChannelVersionRuleTester.cs
rename to source/Octo/Commands/ChannelVersionRuleTester.cs
diff --git a/source/Octopus.Cli/Commands/CleanEnvironmentCommand.cs b/source/Octo/Commands/CleanEnvironmentCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CleanEnvironmentCommand.cs
rename to source/Octo/Commands/CleanEnvironmentCommand.cs
diff --git a/source/Octopus.Cli/Commands/CreateAutoDeployOverrideCommand.cs b/source/Octo/Commands/CreateAutoDeployOverrideCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CreateAutoDeployOverrideCommand.cs
rename to source/Octo/Commands/CreateAutoDeployOverrideCommand.cs
diff --git a/source/Octopus.Cli/Commands/CreateChannelCommand.cs b/source/Octo/Commands/CreateChannelCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CreateChannelCommand.cs
rename to source/Octo/Commands/CreateChannelCommand.cs
diff --git a/source/Octopus.Cli/Commands/CreateEnvironmentCommand.cs b/source/Octo/Commands/CreateEnvironmentCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CreateEnvironmentCommand.cs
rename to source/Octo/Commands/CreateEnvironmentCommand.cs
diff --git a/source/Octopus.Cli/Commands/CreateProjectCommand.cs b/source/Octo/Commands/CreateProjectCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CreateProjectCommand.cs
rename to source/Octo/Commands/CreateProjectCommand.cs
diff --git a/source/Octopus.Cli/Commands/CreateReleaseCommand.cs b/source/Octo/Commands/CreateReleaseCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/CreateReleaseCommand.cs
rename to source/Octo/Commands/CreateReleaseCommand.cs
diff --git a/source/Octopus.Cli/Commands/DeleteAutoDeployOverrideCommand.cs b/source/Octo/Commands/DeleteAutoDeployOverrideCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/DeleteAutoDeployOverrideCommand.cs
rename to source/Octo/Commands/DeleteAutoDeployOverrideCommand.cs
diff --git a/source/Octopus.Cli/Commands/DeleteReleasesCommand.cs b/source/Octo/Commands/DeleteReleasesCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/DeleteReleasesCommand.cs
rename to source/Octo/Commands/DeleteReleasesCommand.cs
diff --git a/source/Octopus.Cli/Commands/DeployReleaseCommand.cs b/source/Octo/Commands/DeployReleaseCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/DeployReleaseCommand.cs
rename to source/Octo/Commands/DeployReleaseCommand.cs
diff --git a/source/Octopus.Cli/Commands/DeploymentCommandBase.cs b/source/Octo/Commands/DeploymentCommandBase.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/DeploymentCommandBase.cs
rename to source/Octo/Commands/DeploymentCommandBase.cs
diff --git a/source/Octopus.Cli/Commands/DumpDeploymentsCommand.cs b/source/Octo/Commands/DumpDeploymentsCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/DumpDeploymentsCommand.cs
rename to source/Octo/Commands/DumpDeploymentsCommand.cs
diff --git a/source/Octopus.Cli/Commands/ExportCommand.cs b/source/Octo/Commands/ExportCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ExportCommand.cs
rename to source/Octo/Commands/ExportCommand.cs
diff --git a/source/Octopus.Cli/Commands/HealthStatusProvider.cs b/source/Octo/Commands/HealthStatusProvider.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/HealthStatusProvider.cs
rename to source/Octo/Commands/HealthStatusProvider.cs
diff --git a/source/Octopus.Cli/Commands/HelpCommand.cs b/source/Octo/Commands/HelpCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/HelpCommand.cs
rename to source/Octo/Commands/HelpCommand.cs
diff --git a/source/Octopus.Cli/Commands/IChannelVersionRuleTester.cs b/source/Octo/Commands/IChannelVersionRuleTester.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/IChannelVersionRuleTester.cs
rename to source/Octo/Commands/IChannelVersionRuleTester.cs
diff --git a/source/Octopus.Cli/Commands/IPackageBuilder.cs b/source/Octo/Commands/IPackageBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/IPackageBuilder.cs
rename to source/Octo/Commands/IPackageBuilder.cs
diff --git a/source/Octopus.Cli/Commands/IPackageVersionResolver.cs b/source/Octo/Commands/IPackageVersionResolver.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/IPackageVersionResolver.cs
rename to source/Octo/Commands/IPackageVersionResolver.cs
diff --git a/source/Octopus.Cli/Commands/IReleasePlanBuilder.cs b/source/Octo/Commands/IReleasePlanBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/IReleasePlanBuilder.cs
rename to source/Octo/Commands/IReleasePlanBuilder.cs
diff --git a/source/Octopus.Cli/Commands/ImportCommand.cs b/source/Octo/Commands/ImportCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ImportCommand.cs
rename to source/Octo/Commands/ImportCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListEnvironmentsCommand.cs b/source/Octo/Commands/ListEnvironmentsCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListEnvironmentsCommand.cs
rename to source/Octo/Commands/ListEnvironmentsCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListLatestDeploymentsCommand.cs b/source/Octo/Commands/ListLatestDeploymentsCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListLatestDeploymentsCommand.cs
rename to source/Octo/Commands/ListLatestDeploymentsCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListMachinesCommand.cs b/source/Octo/Commands/ListMachinesCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListMachinesCommand.cs
rename to source/Octo/Commands/ListMachinesCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListProjectsCommand.cs b/source/Octo/Commands/ListProjectsCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListProjectsCommand.cs
rename to source/Octo/Commands/ListProjectsCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListReleasesCommand.cs b/source/Octo/Commands/ListReleasesCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListReleasesCommand.cs
rename to source/Octo/Commands/ListReleasesCommand.cs
diff --git a/source/Octopus.Cli/Commands/ListTenantsCommand.cs b/source/Octo/Commands/ListTenantsCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ListTenantsCommand.cs
rename to source/Octo/Commands/ListTenantsCommand.cs
diff --git a/source/Octopus.Cli/Commands/NuGet/PackageBuilder.cs b/source/Octo/Commands/NuGet/PackageBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/NuGet/PackageBuilder.cs
rename to source/Octo/Commands/NuGet/PackageBuilder.cs
diff --git a/source/Octopus.Cli/Commands/NuGetPackageBuilder.cs b/source/Octo/Commands/NuGetPackageBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/NuGetPackageBuilder.cs
rename to source/Octo/Commands/NuGetPackageBuilder.cs
diff --git a/source/Octopus.Cli/Commands/OptionGroup.cs b/source/Octo/Commands/OptionGroup.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/OptionGroup.cs
rename to source/Octo/Commands/OptionGroup.cs
diff --git a/source/Octopus.Cli/Commands/Options.cs b/source/Octo/Commands/Options.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/Options.cs
rename to source/Octo/Commands/Options.cs
diff --git a/source/Octopus.Cli/Commands/PackCommand.cs b/source/Octo/Commands/PackCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/PackCommand.cs
rename to source/Octo/Commands/PackCommand.cs
diff --git a/source/Octopus.Cli/Commands/PackageVersionResolver.cs b/source/Octo/Commands/PackageVersionResolver.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/PackageVersionResolver.cs
rename to source/Octo/Commands/PackageVersionResolver.cs
diff --git a/source/Octopus.Cli/Commands/ProjectExport.cs b/source/Octo/Commands/ProjectExport.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ProjectExport.cs
rename to source/Octo/Commands/ProjectExport.cs
diff --git a/source/Octopus.Cli/Commands/PromoteReleaseCommand.cs b/source/Octo/Commands/PromoteReleaseCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/PromoteReleaseCommand.cs
rename to source/Octo/Commands/PromoteReleaseCommand.cs
diff --git a/source/Octopus.Cli/Commands/PushCommand.cs b/source/Octo/Commands/PushCommand.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/PushCommand.cs
rename to source/Octo/Commands/PushCommand.cs
diff --git a/source/Octopus.Cli/Commands/ReleasePlan.cs b/source/Octo/Commands/ReleasePlan.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ReleasePlan.cs
rename to source/Octo/Commands/ReleasePlan.cs
diff --git a/source/Octopus.Cli/Commands/ReleasePlanBuilder.cs b/source/Octo/Commands/ReleasePlanBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ReleasePlanBuilder.cs
rename to source/Octo/Commands/ReleasePlanBuilder.cs
diff --git a/source/Octopus.Cli/Commands/ReleasePlanItem.cs b/source/Octo/Commands/ReleasePlanItem.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ReleasePlanItem.cs
rename to source/Octo/Commands/ReleasePlanItem.cs
diff --git a/source/Octopus.Cli/Commands/TaskOutputProgressPrinter.cs b/source/Octo/Commands/TaskOutputProgressPrinter.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/TaskOutputProgressPrinter.cs
rename to source/Octo/Commands/TaskOutputProgressPrinter.cs
diff --git a/source/Octopus.Cli/Commands/ZipPackageBuilder.cs b/source/Octo/Commands/ZipPackageBuilder.cs
similarity index 100%
rename from source/Octopus.Cli/Commands/ZipPackageBuilder.cs
rename to source/Octo/Commands/ZipPackageBuilder.cs
diff --git a/source/Octopus.Cli/Diagnostics/LogExtensions.cs b/source/Octo/Diagnostics/LogExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Diagnostics/LogExtensions.cs
rename to source/Octo/Diagnostics/LogExtensions.cs
diff --git a/source/Octopus.Cli/Diagnostics/LoggingModule.cs b/source/Octo/Diagnostics/LoggingModule.cs
similarity index 100%
rename from source/Octopus.Cli/Diagnostics/LoggingModule.cs
rename to source/Octo/Diagnostics/LoggingModule.cs
diff --git a/source/Octopus.Cli/Exporters/BaseExporter.cs b/source/Octo/Exporters/BaseExporter.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/BaseExporter.cs
rename to source/Octo/Exporters/BaseExporter.cs
diff --git a/source/Octopus.Cli/Exporters/ExportMetadata.cs b/source/Octo/Exporters/ExportMetadata.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/ExportMetadata.cs
rename to source/Octo/Exporters/ExportMetadata.cs
diff --git a/source/Octopus.Cli/Exporters/ExporterAttribute.cs b/source/Octo/Exporters/ExporterAttribute.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/ExporterAttribute.cs
rename to source/Octo/Exporters/ExporterAttribute.cs
diff --git a/source/Octopus.Cli/Exporters/ExporterLocator.cs b/source/Octo/Exporters/ExporterLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/ExporterLocator.cs
rename to source/Octo/Exporters/ExporterLocator.cs
diff --git a/source/Octopus.Cli/Exporters/FileSystemExporter.cs b/source/Octo/Exporters/FileSystemExporter.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/FileSystemExporter.cs
rename to source/Octo/Exporters/FileSystemExporter.cs
diff --git a/source/Octopus.Cli/Exporters/IExporter.cs b/source/Octo/Exporters/IExporter.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/IExporter.cs
rename to source/Octo/Exporters/IExporter.cs
diff --git a/source/Octopus.Cli/Exporters/IExporterLocator.cs b/source/Octo/Exporters/IExporterLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/IExporterLocator.cs
rename to source/Octo/Exporters/IExporterLocator.cs
diff --git a/source/Octopus.Cli/Exporters/IExporterMetadata.cs b/source/Octo/Exporters/IExporterMetadata.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/IExporterMetadata.cs
rename to source/Octo/Exporters/IExporterMetadata.cs
diff --git a/source/Octopus.Cli/Exporters/ProjectExporter.cs b/source/Octo/Exporters/ProjectExporter.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/ProjectExporter.cs
rename to source/Octo/Exporters/ProjectExporter.cs
diff --git a/source/Octopus.Cli/Exporters/ReleaseExporter.cs b/source/Octo/Exporters/ReleaseExporter.cs
similarity index 100%
rename from source/Octopus.Cli/Exporters/ReleaseExporter.cs
rename to source/Octo/Exporters/ReleaseExporter.cs
diff --git a/source/Octopus.Cli/Extensions/AttributeExtensions.cs b/source/Octo/Extensions/AttributeExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Extensions/AttributeExtensions.cs
rename to source/Octo/Extensions/AttributeExtensions.cs
diff --git a/source/Octopus.Cli/Extensions/DynamicExtensions.cs b/source/Octo/Extensions/DynamicExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Extensions/DynamicExtensions.cs
rename to source/Octo/Extensions/DynamicExtensions.cs
diff --git a/source/Octopus.Cli/Extensions/TimeSpanExtensions.cs b/source/Octo/Extensions/TimeSpanExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Extensions/TimeSpanExtensions.cs
rename to source/Octo/Extensions/TimeSpanExtensions.cs
diff --git a/source/Octopus.Cli/Importers/BaseImporter.cs b/source/Octo/Importers/BaseImporter.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/BaseImporter.cs
rename to source/Octo/Importers/BaseImporter.cs
diff --git a/source/Octopus.Cli/Importers/BaseValidatedImportSettings.cs b/source/Octo/Importers/BaseValidatedImportSettings.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/BaseValidatedImportSettings.cs
rename to source/Octo/Importers/BaseValidatedImportSettings.cs
diff --git a/source/Octopus.Cli/Importers/CheckedReferences.cs b/source/Octo/Importers/CheckedReferences.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/CheckedReferences.cs
rename to source/Octo/Importers/CheckedReferences.cs
diff --git a/source/Octopus.Cli/Importers/FileSystemImporter.cs b/source/Octo/Importers/FileSystemImporter.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/FileSystemImporter.cs
rename to source/Octo/Importers/FileSystemImporter.cs
diff --git a/source/Octopus.Cli/Importers/IImporter.cs b/source/Octo/Importers/IImporter.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/IImporter.cs
rename to source/Octo/Importers/IImporter.cs
diff --git a/source/Octopus.Cli/Importers/IImporterLocator.cs b/source/Octo/Importers/IImporterLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/IImporterLocator.cs
rename to source/Octo/Importers/IImporterLocator.cs
diff --git a/source/Octopus.Cli/Importers/IImporterMetadata.cs b/source/Octo/Importers/IImporterMetadata.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/IImporterMetadata.cs
rename to source/Octo/Importers/IImporterMetadata.cs
diff --git a/source/Octopus.Cli/Importers/ImporterAttribute.cs b/source/Octo/Importers/ImporterAttribute.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/ImporterAttribute.cs
rename to source/Octo/Importers/ImporterAttribute.cs
diff --git a/source/Octopus.Cli/Importers/ImporterLocator.cs b/source/Octo/Importers/ImporterLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/ImporterLocator.cs
rename to source/Octo/Importers/ImporterLocator.cs
diff --git a/source/Octopus.Cli/Importers/ProjectImporter.cs b/source/Octo/Importers/ProjectImporter.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/ProjectImporter.cs
rename to source/Octo/Importers/ProjectImporter.cs
diff --git a/source/Octopus.Cli/Importers/ReleaseImporter.cs b/source/Octo/Importers/ReleaseImporter.cs
similarity index 100%
rename from source/Octopus.Cli/Importers/ReleaseImporter.cs
rename to source/Octo/Importers/ReleaseImporter.cs
diff --git a/source/Octopus.Cli/Infrastructure/AutofacExtensions.cs b/source/Octo/Infrastructure/AutofacExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/AutofacExtensions.cs
rename to source/Octo/Infrastructure/AutofacExtensions.cs
diff --git a/source/Octopus.Cli/Infrastructure/CommandAttribute.cs b/source/Octo/Infrastructure/CommandAttribute.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/CommandAttribute.cs
rename to source/Octo/Infrastructure/CommandAttribute.cs
diff --git a/source/Octopus.Cli/Infrastructure/CommandException.cs b/source/Octo/Infrastructure/CommandException.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/CommandException.cs
rename to source/Octo/Infrastructure/CommandException.cs
diff --git a/source/Octopus.Cli/Infrastructure/CommandLocator.cs b/source/Octo/Infrastructure/CommandLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/CommandLocator.cs
rename to source/Octo/Infrastructure/CommandLocator.cs
diff --git a/source/Octopus.Cli/Infrastructure/CouldNotFindException.cs b/source/Octo/Infrastructure/CouldNotFindException.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/CouldNotFindException.cs
rename to source/Octo/Infrastructure/CouldNotFindException.cs
diff --git a/source/Octopus.Cli/Infrastructure/ICommand.cs b/source/Octo/Infrastructure/ICommand.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/ICommand.cs
rename to source/Octo/Infrastructure/ICommand.cs
diff --git a/source/Octopus.Cli/Infrastructure/ICommandLocator.cs b/source/Octo/Infrastructure/ICommandLocator.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/ICommandLocator.cs
rename to source/Octo/Infrastructure/ICommandLocator.cs
diff --git a/source/Octopus.Cli/Infrastructure/ICommandMetadata.cs b/source/Octo/Infrastructure/ICommandMetadata.cs
similarity index 100%
rename from source/Octopus.Cli/Infrastructure/ICommandMetadata.cs
rename to source/Octo/Infrastructure/ICommandMetadata.cs
diff --git a/source/Octopus.Cli/Model/ChannelVersionRuleTestResult.cs b/source/Octo/Model/ChannelVersionRuleTestResult.cs
similarity index 100%
rename from source/Octopus.Cli/Model/ChannelVersionRuleTestResult.cs
rename to source/Octo/Model/ChannelVersionRuleTestResult.cs
diff --git a/source/Octo/Octo.xproj b/source/Octo/Octo.xproj
new file mode 100644
index 000000000..d863fe12a
--- /dev/null
+++ b/source/Octo/Octo.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ d1bfd88f-fb8e-49ec-968f-f4d9a8a52519
+ Octopus.Cli
+ .\obj
+ .\bin\
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/source/Octopus.Cli/Program.cs b/source/Octo/Program.cs
similarity index 100%
rename from source/Octopus.Cli/Program.cs
rename to source/Octo/Program.cs
diff --git a/source/Octopus.Cli/Properties/AssemblyInfo.cs b/source/Octo/Properties/AssemblyInfo.cs
similarity index 100%
rename from source/Octopus.Cli/Properties/AssemblyInfo.cs
rename to source/Octo/Properties/AssemblyInfo.cs
diff --git a/source/Octopus.Cli/Properties/Icon.ico b/source/Octo/Properties/Icon.ico
similarity index 100%
rename from source/Octopus.Cli/Properties/Icon.ico
rename to source/Octo/Properties/Icon.ico
diff --git a/source/Octopus.Cli/Repositories/ActionTemplateRepository.cs b/source/Octo/Repositories/ActionTemplateRepository.cs
similarity index 100%
rename from source/Octopus.Cli/Repositories/ActionTemplateRepository.cs
rename to source/Octo/Repositories/ActionTemplateRepository.cs
diff --git a/source/Octopus.Cli/Repositories/IActionTemplateRepository.cs b/source/Octo/Repositories/IActionTemplateRepository.cs
similarity index 100%
rename from source/Octopus.Cli/Repositories/IActionTemplateRepository.cs
rename to source/Octo/Repositories/IActionTemplateRepository.cs
diff --git a/source/Octopus.Cli/Repositories/IOctopusRepositoryFactory.cs b/source/Octo/Repositories/IOctopusRepositoryFactory.cs
similarity index 100%
rename from source/Octopus.Cli/Repositories/IOctopusRepositoryFactory.cs
rename to source/Octo/Repositories/IOctopusRepositoryFactory.cs
diff --git a/source/Octopus.Cli/Repositories/OctopusRepositoryCommonQueries.cs b/source/Octo/Repositories/OctopusRepositoryCommonQueries.cs
similarity index 100%
rename from source/Octopus.Cli/Repositories/OctopusRepositoryCommonQueries.cs
rename to source/Octo/Repositories/OctopusRepositoryCommonQueries.cs
diff --git a/source/Octopus.Cli/Repositories/OctopusRepositoryFactory.cs b/source/Octo/Repositories/OctopusRepositoryFactory.cs
similarity index 100%
rename from source/Octopus.Cli/Repositories/OctopusRepositoryFactory.cs
rename to source/Octo/Repositories/OctopusRepositoryFactory.cs
diff --git a/source/Octopus.Cli/Util/AssemblyExtensions.cs b/source/Octo/Util/AssemblyExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/AssemblyExtensions.cs
rename to source/Octo/Util/AssemblyExtensions.cs
diff --git a/source/Octopus.Cli/Util/DeletionOptions.cs b/source/Octo/Util/DeletionOptions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/DeletionOptions.cs
rename to source/Octo/Util/DeletionOptions.cs
diff --git a/source/Octopus.Cli/Util/FeatureDetectionExtensions.cs b/source/Octo/Util/FeatureDetectionExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/FeatureDetectionExtensions.cs
rename to source/Octo/Util/FeatureDetectionExtensions.cs
diff --git a/source/Octopus.Cli/Util/FeedCustomExpressionHelper.cs b/source/Octo/Util/FeedCustomExpressionHelper.cs
similarity index 100%
rename from source/Octopus.Cli/Util/FeedCustomExpressionHelper.cs
rename to source/Octo/Util/FeedCustomExpressionHelper.cs
diff --git a/source/Octopus.Cli/Util/Humanize.cs b/source/Octo/Util/Humanize.cs
similarity index 100%
rename from source/Octopus.Cli/Util/Humanize.cs
rename to source/Octo/Util/Humanize.cs
diff --git a/source/Octopus.Cli/Util/IOctopusFileSystem.cs b/source/Octo/Util/IOctopusFileSystem.cs
similarity index 100%
rename from source/Octopus.Cli/Util/IOctopusFileSystem.cs
rename to source/Octo/Util/IOctopusFileSystem.cs
diff --git a/source/Octopus.Cli/Util/LazyExtensions.cs b/source/Octo/Util/LazyExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/LazyExtensions.cs
rename to source/Octo/Util/LazyExtensions.cs
diff --git a/source/Octopus.Cli/Util/LineSplitter.cs b/source/Octo/Util/LineSplitter.cs
similarity index 100%
rename from source/Octopus.Cli/Util/LineSplitter.cs
rename to source/Octo/Util/LineSplitter.cs
diff --git a/source/Octopus.Cli/Util/ListExtensions.cs b/source/Octo/Util/ListExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/ListExtensions.cs
rename to source/Octo/Util/ListExtensions.cs
diff --git a/source/Octopus.Cli/Util/NumericExtensions.cs b/source/Octo/Util/NumericExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/NumericExtensions.cs
rename to source/Octo/Util/NumericExtensions.cs
diff --git a/source/Octopus.Cli/Util/OctopusPhysicalFileSystem.cs b/source/Octo/Util/OctopusPhysicalFileSystem.cs
similarity index 100%
rename from source/Octopus.Cli/Util/OctopusPhysicalFileSystem.cs
rename to source/Octo/Util/OctopusPhysicalFileSystem.cs
diff --git a/source/Octopus.Cli/Util/ReplaceStatus.cs b/source/Octo/Util/ReplaceStatus.cs
similarity index 100%
rename from source/Octopus.Cli/Util/ReplaceStatus.cs
rename to source/Octo/Util/ReplaceStatus.cs
diff --git a/source/Octopus.Cli/Util/ResourceCollectionExtensions.cs b/source/Octo/Util/ResourceCollectionExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/ResourceCollectionExtensions.cs
rename to source/Octo/Util/ResourceCollectionExtensions.cs
diff --git a/source/Octopus.Cli/Util/StringExtensions.cs b/source/Octo/Util/StringExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/StringExtensions.cs
rename to source/Octo/Util/StringExtensions.cs
diff --git a/source/Octopus.Cli/Util/UriExtensions.cs b/source/Octo/Util/UriExtensions.cs
similarity index 100%
rename from source/Octopus.Cli/Util/UriExtensions.cs
rename to source/Octo/Util/UriExtensions.cs
diff --git a/source/Octopus.Cli/app.config b/source/Octo/app.config
similarity index 100%
rename from source/Octopus.Cli/app.config
rename to source/Octo/app.config
diff --git a/source/Octo/init.ps1 b/source/Octo/init.ps1
new file mode 100644
index 000000000..cf2469ee6
--- /dev/null
+++ b/source/Octo/init.ps1
@@ -0,0 +1,6 @@
+param($installPath, $toolsPath, $package, $project)
+
+$path = $env:PATH
+if (!$path.Contains($toolsPath)) {
+ $env:PATH += ";$toolsPath"
+}
\ No newline at end of file
diff --git a/source/Octo/project.json b/source/Octo/project.json
new file mode 100644
index 000000000..c523b3137
--- /dev/null
+++ b/source/Octo/project.json
@@ -0,0 +1,52 @@
+{
+ "version": "0.0.0-*",
+ "dependencies": {
+ "Autofac": "3.5.2",
+ "GitVersionTask": "3.3.0",
+ "ILRepack": "2.0.8",
+ "MarkdownSharp": "1.13.0.0",
+ "Microsoft.Web.Xdt": "2.1.1",
+ "Newtonsoft.Json": "9.0.1",
+ "NuGet.CommandLine": "3.4.3",
+ "NuGet.Common": "3.4.3",
+ "NuGet.Frameworks": "3.4.3",
+ "NuGet.Logging": "3.4.3",
+ "NuGet.Packaging": "3.4.3",
+ "NuGet.Packaging.Core": "3.4.3",
+ "NuGet.Packaging.Core.Types": "3.4.3",
+ "NuGet.Versioning": "3.4.3",
+ "Octostache": "1.0.2.29",
+ "Serilog": "2.1.0",
+ "Serilog.Sinks.ColoredConsole": "2.0.0",
+ "Serilog.Sinks.Trace": "2.0.0",
+ "Sprache": "2.0.0.50",
+ "Octopus.Client": {
+ "target": "project"
+ }
+ },
+ "frameworks": {
+ "net45": {
+ "frameworkAssemblies": {
+ "System.ComponentModel.Composition": "*",
+ "System.ComponentModel.DataAnnotations": "*",
+ "System.Configuration": "*",
+ "System.Core": "*",
+ "System.IO.Compression": "*",
+ "System.Net": "*",
+ "System.Runtime.Serialization": "*",
+ "System.Web": "*",
+ "System.Xml.Linq": "*",
+ "System.Data.DataSetExtensions": "*",
+ "Microsoft.CSharp": "*",
+ "System.Data": "*",
+ "System.Xml": "*"
+ },
+ "buildOptions": {
+ "platform": "anycpu32bitpreferred"
+ }
+ }
+ },
+ "buildOptions": {
+ "emitEntryPoint": true
+ }
+}
\ No newline at end of file
diff --git a/source/Octopus.Cli.Tests/Octopus.Cli.Tests.csproj b/source/Octopus.Cli.Tests/Octopus.Cli.Tests.csproj
deleted file mode 100644
index 867349d64..000000000
--- a/source/Octopus.Cli.Tests/Octopus.Cli.Tests.csproj
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
- Debug
- AnyCPU
- {B05E3858-5607-46DB-876C-4E1F5728A4BB}
- Library
- Properties
- Octopus.Cli.Tests
- Octopus.Cli.Tests
- v4.5
- 512
- ..\
- true
-
-
-
-
-
- true
- full
- false
- bin\
- DEBUG;TRACE
- prompt
- 4
- x86
- false
-
-
- pdbonly
- true
- bin\
- TRACE
- prompt
- 4
- true
- false
-
-
-
- ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
- True
-
-
- ..\packages\NUnit.2.6.3\lib\nunit.framework.dll
-
-
- ..\packages\Octopus.Client.3.4.0\lib\net40\Octopus.Client.dll
- True
-
-
- ..\packages\Serilog.2.1.0\lib\net45\Serilog.dll
- True
-
-
-
-
-
-
-
-
-
-
- ..\packages\NSubstitute.1.7.2.0\lib\NET40\NSubstitute.dll
-
-
-
-
- Properties\SolutionInfo.cs
-
-
- Properties\VersionInfo.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}
- OctopusTools
-
-
-
-
-
- PreserveNewest
-
-
- Designer
-
-
-
-
-
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Cli.Tests/Octopus.Cli.Tests.ncrunchproject b/source/Octopus.Cli.Tests/Octopus.Cli.Tests.ncrunchproject
deleted file mode 100644
index 96f3af12d..000000000
--- a/source/Octopus.Cli.Tests/Octopus.Cli.Tests.ncrunchproject
+++ /dev/null
@@ -1,28 +0,0 @@
-
- 1000
- false
- false
- false
- true
- false
- false
- false
- false
- false
- true
- true
- false
- true
- true
- true
- 60000
-
-
-
- AutoDetect
- STA
- x86
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Cli.Tests/VersioningFixture.cs b/source/Octopus.Cli.Tests/VersioningFixture.cs
deleted file mode 100644
index 43edfd5f2..000000000
--- a/source/Octopus.Cli.Tests/VersioningFixture.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using NUnit.Framework;
-using Octopus.Cli.Tests.Helpers;
-using Octopus.Client;
-
-namespace Octopus.Cli.Tests
-{
- [TestFixture]
- public class VersioningFixture
- {
- [Test]
- public void TheTestAssemblyIsVersioned_WithGitVersion()
- {
- Assert.That(new SemanticVersionInfo(GetType().Assembly).Major, Is.GreaterThan(1));
- }
-
- [Test]
- public void AllOctopusAssemblies_ShouldHaveTheSameSemanticVersion()
- {
- var octocliVersion = (new SemanticVersionInfo(typeof(Octopus.Cli.Program).Assembly));
- var octopusClientVersion = new SemanticVersionInfo(typeof(IOctopusRepository).Assembly);
-
- var isClientPreRelease = !string.IsNullOrEmpty(octopusClientVersion.PreReleaseTag);
- var isThisPreRelease = !string.IsNullOrEmpty(octocliVersion.PreReleaseTag);
-
- Console.WriteLine($"Octopus.Client: {octopusClientVersion.SemVer}");
- Console.WriteLine($"Octopus.Cli (Octo.exe): {octocliVersion.SemVer}");
-
- if (isClientPreRelease) Assert.That(isThisPreRelease, "We are using a pre-release version of Octopus.Client, so octo.exe should also be versioned as a pre-release. We should only build full-releases of octo.exe using full releases of Octopus.Client.");
- else Assert.That(true);
- }
- }
-}
diff --git a/source/Octopus.Cli.Tests/packages.config b/source/Octopus.Cli.Tests/packages.config
deleted file mode 100644
index f6fc62d31..000000000
--- a/source/Octopus.Cli.Tests/packages.config
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Cli/Octopus.Cli.csproj b/source/Octopus.Cli/Octopus.Cli.csproj
deleted file mode 100644
index 26bb1a742..000000000
--- a/source/Octopus.Cli/Octopus.Cli.csproj
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
-
- Debug
- x86
- {D1BFD88F-FB8E-49EC-968F-F4D9A8A52519}
- Exe
- Properties
- Octopus.Cli
- Octo
- v4.5
-
-
- 512
- ..\
- true
-
-
-
-
- x86
- true
- full
- false
- bin\
- DEBUG;TRACE
- prompt
- 4
- true
- false
- default
-
-
- x86
- none
- true
- bin\
- TRACE
- prompt
- 4
- true
- false
-
-
- Properties\Icon.ico
- false
-
-
-
- ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll
- True
-
-
- ..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll
- True
-
-
- ..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll
- True
-
-
- ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
- True
-
-
- ..\packages\NuGet.Common.3.4.3\lib\net45\NuGet.Common.dll
- True
-
-
- ..\packages\NuGet.Frameworks.3.4.3\lib\net45\NuGet.Frameworks.dll
- True
-
-
- ..\packages\NuGet.Logging.3.4.3\lib\net45\NuGet.Logging.dll
- True
-
-
- ..\packages\NuGet.Packaging.3.4.3\lib\net45\NuGet.Packaging.dll
- True
-
-
- ..\packages\NuGet.Packaging.Core.3.4.3\lib\net45\NuGet.Packaging.Core.dll
- True
-
-
- ..\packages\NuGet.Packaging.Core.Types.3.4.3\lib\net45\NuGet.Packaging.Core.Types.dll
- True
-
-
- ..\packages\NuGet.Versioning.3.4.3\lib\net45\NuGet.Versioning.dll
- True
-
-
- ..\packages\Octopus.Client.3.4.0\lib\net40\Octopus.Client.dll
- True
-
-
- ..\packages\Octostache.1.0.2.29\lib\net40\Octostache.dll
- True
-
-
- ..\packages\Serilog.2.1.0\lib\net45\Serilog.dll
- True
-
-
- ..\packages\Serilog.Sinks.ColoredConsole.2.0.0\lib\net45\Serilog.Sinks.ColoredConsole.dll
- True
-
-
- ..\packages\Serilog.Sinks.Trace.2.0.0\lib\net45\Serilog.Sinks.Trace.dll
- True
-
-
- ..\packages\Sprache.2.0.0.50\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid+Xamarin.iOS10+MonoTouch\Sprache.dll
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Properties\SolutionInfo.cs
-
-
- Properties\VersionInfo.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- Designer
-
-
-
-
-
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Cli/OctopusTools.v2.ncrunchproject b/source/Octopus.Cli/OctopusTools.v2.ncrunchproject
deleted file mode 100644
index 96f3af12d..000000000
--- a/source/Octopus.Cli/OctopusTools.v2.ncrunchproject
+++ /dev/null
@@ -1,28 +0,0 @@
-
- 1000
- false
- false
- false
- true
- false
- false
- false
- false
- false
- true
- true
- false
- true
- true
- true
- 60000
-
-
-
- AutoDetect
- STA
- x86
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Cli/packages.config b/source/Octopus.Cli/packages.config
deleted file mode 100644
index a781669ce..000000000
--- a/source/Octopus.Cli/packages.config
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/source/Octopus.Client.Tests/Conventions/ClientConventions.cs b/source/Octopus.Client.Tests/Conventions/ClientConventions.cs
new file mode 100644
index 000000000..9906eb9ae
--- /dev/null
+++ b/source/Octopus.Client.Tests/Conventions/ClientConventions.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Linq;
+using Autofac;
+using Autofac.Util;
+using Conventional;
+using Conventional.Conventions;
+using Nancy.Extensions;
+using NUnit.Framework;
+using Octopus.Client.Model;
+using Octopus.Client.Repositories;
+using Octopus.Client.Tests.Extensions;
+
+namespace Octopus.Client.Tests.Conventions
+{
+ [TestFixture]
+ public class ClientConventions
+ {
+ private static readonly Type[] RepositoryInterfaceTypes = typeof(IOctopusClient).Assembly.GetLoadableTypes()
+ .Where(t => t.IsInterface && t.Name.EndsWith("Repository"))
+ .Where(t => t != typeof(IOctopusRepository))
+ .ToArray();
+ private static readonly Type[] RepositoryTypes = typeof(IOctopusClient).Assembly.GetLoadableTypes()
+ .Where(t => !t.IsInterface && t.Name.EndsWith("Repository"))
+ .Where(t => t != typeof(OctopusRepository))
+ .ToArray();
+ private static readonly Type[] ResourceTypes = typeof(IOctopusClient).Assembly.GetLoadableTypes()
+ .Where(t => t.Name.EndsWith("Resource"))
+ .ToArray();
+ private static readonly Type[] RepositoryResourceTypes = ResourceTypes
+ .Where(res => RepositoryTypes
+ .Any(rep => rep.BaseType?.IsGenericType == true && rep.BaseType?.GetGenericArguments().Contains(res) == true))
+ .ToArray();
+
+ [Test]
+ public void AllRepositoriesShouldBeAvailableViaIOctopusRepository()
+ {
+ var exposedTypes = typeof (IOctopusRepository).GetProperties()
+ .Select(p => p.PropertyType)
+ .ToArray();
+
+ var missingTypes = RepositoryInterfaceTypes.Except(exposedTypes).ToArray();
+ if (missingTypes.Any())
+ {
+ Assert.Fail($"All *Repository types should be exposed by {nameof(IOctopusRepository)}. Missing: {missingTypes.Select(t => t.Name).CommaSeperate()}");
+ }
+ }
+
+ [Test]
+ public void AllRepositoriesShouldBeAvailableViaOctopusRepository()
+ {
+ var exposedTypes = typeof (OctopusRepository).GetProperties()
+ .Select(p => p.PropertyType)
+ .ToArray();
+
+ var missingTypes = RepositoryInterfaceTypes.Except(exposedTypes).ToArray();
+ if (missingTypes.Any())
+ {
+ Assert.Fail($"All *Repository types should be exposed by {nameof(OctopusRepository)}. Missing: {missingTypes.Select(t => t.Name).CommaSeperate()}");
+ }
+ }
+
+ [Test]
+ public void AllRepositoriesShouldImplementNonGenericSimpleInterface()
+ {
+ var repositoryInterfaceMap = RepositoryTypes.Select(r => new { Repository = r, Interface = r.GetInterfaces().Where(i => !i.IsGenericType)}).ToArray();
+ var missingInterface = repositoryInterfaceMap.Where(x => x.Interface == null).ToArray();
+
+ if (missingInterface.Any())
+ {
+ Assert.Fail($"All *Repository types should implement a non-generic interface representing the repository contract {nameof(OctopusRepository)}.{Environment.NewLine}{missingInterface.Select(x => $"{x.Repository.Name} expected to implement I{x.Repository.Name}").NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void AllRepositoriesShouldExposePublicMembersViaTheirInterfaces()
+ {
+ var exposureMap = RepositoryTypes
+ .Select(r => new
+ {
+ Repository = r,
+ DeclaredMethodMap = r.GetMethods()
+ .Where(m => m.DeclaringType == r)
+ .Select(m => new
+ {
+ DelcaredMethod = m,
+ r.GetInterfaces().Select(r.GetInterfaceMap).FirstOrDefault(map => map.TargetMethods.Contains(m)).InterfaceType
+ }).ToArray(),
+ }).ToArray();
+
+ var missingExposure = exposureMap.Where(x => x.DeclaredMethodMap.Any(map => map.InterfaceType == null)).ToArray();
+ if (missingExposure.Any())
+ {
+ Assert.Fail($"The following repositories do not expose at least one of their public members by interface, and hence won't be accessible by clients.{Environment.NewLine}{exposureMap.Where(x => x.DeclaredMethodMap.Any(map => map.InterfaceType == null)).Select(x => $"{x.Repository.Name}: {x.DeclaredMethodMap.Where(map => map.InterfaceType == null).Select(map => $"{map.DelcaredMethod.Name}({map.DelcaredMethod.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}").CommaSeperate()})").CommaSeperate()}").NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void AllRepositoryInterfacesShouldFollowTheseConventions()
+ {
+ RepositoryInterfaceTypes
+ .MustConformTo(Convention.MustLiveInNamespace("Octopus.Client.Repositories"))
+ .AndMustConformTo(Convention.NameMustEndWith("Repository"))
+ .WithFailureAssertion(Assert.Fail);
+ }
+
+ [Test]
+ public void AllResourcesShouldLiveInTheCorrectParentNamespace()
+ {
+ ResourceTypes
+ .MustConformTo(new MustLiveInParentNamespaceConventionSpecification("Octopus.Client.Model"))
+ .WithFailureAssertion(Assert.Fail);
+ }
+
+ [Test]
+ public void AllResourcePropertiesShouldHavePublicGetters()
+ {
+ ResourceTypes
+ .MustConformTo(Convention.PropertiesMustHavePublicGetters)
+ .WithFailureAssertion(Assert.Fail);
+ }
+
+ [Test]
+ public void AllResourcePropertiesShouldHavePublicSetters()
+ {
+ ResourceTypes
+ .Except(new [] {typeof(LifecycleResource), typeof(DeploymentProcessResource)})
+ .MustConformTo(Convention.PropertiesMustHavePublicSetters)
+ .WithFailureAssertion(Assert.Fail);
+ }
+
+ [Test]
+ public void RepositoriesShouldNotRepresentMultipleResourceTypes()
+ {
+ var repositoryResourceMap = RepositoryTypes
+ .Select(t => new
+ {
+ Repository = t,
+ ResourceTypes = t.GetInterfaces().Concat(new [] { t.BaseType })
+ .Where(i => i.IsGenericType)
+ .SelectMany(i => i.GetGenericArguments())
+ .Distinct()
+ .ToArray()
+ }).ToArray();
+
+ var confusedRepositories = repositoryResourceMap.Where(x => x.ResourceTypes.Length > 1).ToArray();
+
+ if (confusedRepositories.Any())
+ {
+ Assert.Fail($"Repositories should represent consistent Resource type. These repositories have an identity crisis: {Environment.NewLine}{confusedRepositories.Select(x => $"{x.Repository.Name}<{x.ResourceTypes.Select(r => r.Name).CommaSeperate()}>").NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void RepositoriesShouldBeNamedLikeTheirResourceType()
+ {
+ var repositoryResourceMap = RepositoryTypes
+ .Select(t => new
+ {
+ Repository = t,
+ ResourceTypes = t.GetInterfaces().Concat(new[] { t.BaseType })
+ .Where(i => i.IsGenericType)
+ .SelectMany(i => i.GetGenericArguments())
+ .Distinct()
+ .ToArray()
+ }).ToArray();
+
+ var confusingRepositories = repositoryResourceMap
+ .Where(x => x.ResourceTypes.Any())
+ .Where(x => !x.Repository.Name.StartsWith(x.ResourceTypes.First().Name.Replace("Resource", "")))
+ .ToArray();
+
+ if (confusingRepositories.Any())
+ {
+ Assert.Fail($"Repositories should be named like their Resource type. These repositories could be confusing: {Environment.NewLine}{confusingRepositories.Select(x => $"{x.Repository.Name}<{x.ResourceTypes.Select(r => r.Name).CommaSeperate()}> - based on the resource type this should be named something like {x.ResourceTypes.First().Name.Replace("Resource", "")}Repository? Or maybe this is using the wrong generic type argument?").NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void TopLevelResourcesWithAPublicNamePropertyShouldProbablyImplementINamedResource()
+ {
+ var ignored = new[]
+ {
+ typeof (DeploymentResource),
+ typeof (TaskResource)
+ };
+
+ var shouldProbablyBeINamedResource = RepositoryResourceTypes
+ .Except(ignored)
+ .Where(t => t.GetProperty("Name") != null && !t.IsAssignableTo())
+ .ToArray();
+
+ if (shouldProbablyBeINamedResource.Any())
+ {
+ Assert.Fail($"The following top-level resource types have a Name property, and should probably implement INamedResource: {Environment.NewLine}{shouldProbablyBeINamedResource.Select(t => t.Name).NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void SomeINamedResourcesShouldNeverBeIFindByNameToAvoidGettingTheWrongAnswer()
+ {
+ var denied = new[]
+ {
+ typeof (IChannelRepository),
+ typeof (IDeploymentProcessRepository),
+ typeof (ITaskRepository)
+ };
+
+ var misleadingRepositories = denied.Where(r => r.IsAssignableToGenericType(typeof (IFindByName<>))).ToArray();
+
+ if (misleadingRepositories.Any())
+ {
+ Assert.Fail($"The following repositories allow the client to FindByName, but this will end up returning a misleading result, and the resource should be loaded in another way:{Environment.NewLine}{misleadingRepositories.Select(r => $"{r.Name}").NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void RepositoriesThatGetNamedResourcesShouldUsuallyImplementIFindByName()
+ {
+ var ignored = new[]
+ {
+ typeof (IChannelRepository),
+ typeof (IProjectTriggerRepository)
+ };
+
+ var getsResources = RepositoryInterfaceTypes
+ .Except(ignored)
+ .Where(t => t.IsAssignableToGenericType(typeof (IGet<>)))
+ .ToArray();
+
+ var getsNamedResources = getsResources
+ .Where(t => t.GetInterfaces()
+ .Where(i => i.IsClosedTypeOf(typeof(IGet<>)))
+ .Any(i => i.GenericTypeArguments.Any(r => r.IsAssignableTo())))
+ .ToArray();
+
+ var canFindByName = getsNamedResources
+ .Where(t => t.GetInterfaces().Any(i => i.IsClosedTypeOf(typeof (IFindByName<>))))
+ .ToArray();
+
+ var missingFindByName = getsNamedResources.Except(canFindByName).ToArray();
+
+ if (missingFindByName.Any())
+ {
+ Assert.Fail($"Repositories that implement IGet should usually implement IFindByName, unless that named resource is a singleton or owned by another aggregate.{Environment.NewLine}{missingFindByName.Select(t => t.Name).NewLineSeperate()}");
+ }
+ }
+
+ [Test]
+ public void MostRepositoriesThatGetResourcesShouldImplementIPaginate()
+ {
+ var ignored = new[]
+ {
+ typeof (IDeploymentProcessRepository),
+ typeof (IInterruptionRepository),
+ typeof (IEventRepository),
+ typeof (IVariableSetRepository),
+ typeof (IChannelRepository),
+ typeof (IProjectTriggerRepository)
+ };
+
+ var missing = RepositoryInterfaceTypes
+ .Except(ignored)
+ .Where(t => t.IsAssignableToGenericType(typeof(IGet<>)))
+ .Except(RepositoryInterfaceTypes.Where(t => t.IsAssignableToGenericType(typeof (IPaginate<>))))
+ .ToArray();
+
+ if (missing.Any())
+ {
+ Assert.Fail($"Most repositories that get resources should implement IPaginate unless the repository should target one specific resource like a singleton or child of another aggregate.{Environment.NewLine}{missing.Select(t => t.Name).NewLineSeperate()}");
+ }
+ }
+
+ public class MustLiveInParentNamespaceConventionSpecification : ConventionSpecification
+ {
+ private readonly string parentNamespace;
+
+ protected override string FailureMessage => "Must live in parent namespace {0} but actually lives in namespace {1}";
+
+ public MustLiveInParentNamespaceConventionSpecification(string parentNamespace)
+ {
+ this.parentNamespace = parentNamespace;
+ }
+
+ public override ConventionResult IsSatisfiedBy(Type type)
+ {
+ if (type.Namespace != null && type.Namespace.StartsWith(parentNamespace))
+ return ConventionResult.Satisfied(type.FullName);
+ return ConventionResult.NotSatisfied(type.FullName, string.Format(FailureMessage, parentNamespace, type.Namespace));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/Octopus.Client.Tests/DefaultLinkResolverFixture.cs b/source/Octopus.Client.Tests/DefaultLinkResolverFixture.cs
new file mode 100644
index 000000000..5dbe421cb
--- /dev/null
+++ b/source/Octopus.Client.Tests/DefaultLinkResolverFixture.cs
@@ -0,0 +1,75 @@
+using System;
+using NUnit.Framework;
+
+namespace Octopus.Client.Tests
+{
+ [TestFixture]
+ public class DefaultLinkResolverFixture
+ {
+ [Test]
+ [TestCase("~/", "http://octopus/")]
+ [TestCase("~/api", "http://octopus/api")]
+ [TestCase("~/api/foo", "http://octopus/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/api/foo/bar")]
+ public void ShouldResolveWhenNoSuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+
+ [Test]
+ [TestCase("~/", "http://octopus/")]
+ [TestCase("~/api", "http://octopus/api")]
+ [TestCase("~/api/foo", "http://octopus/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/api/foo/bar")]
+ public void ShouldResolveWhenApiSuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/api"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+
+ [Test]
+ [TestCase("~/", "http://octopus/vdir/")]
+ [TestCase("~/api", "http://octopus/vdir/api")]
+ [TestCase("~/api/foo", "http://octopus/vdir/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/vdir/api/foo/bar")]
+ public void ShouldResolveWhenVirtualDirectorySuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/vdir"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+
+ [Test]
+ [TestCase("~/", "http://octopus/vdir/")]
+ [TestCase("~/api", "http://octopus/vdir/api")]
+ [TestCase("~/api/foo", "http://octopus/vdir/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/vdir/api/foo/bar")]
+ public void ShouldResolveWhenVirtualDirectoryApiSuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/vdir/api"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+
+ [Test]
+ [TestCase("~/", "http://octopus/vdir1/vdir2/")]
+ [TestCase("~/api", "http://octopus/vdir1/vdir2/api")]
+ [TestCase("~/api/foo", "http://octopus/vdir1/vdir2/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/vdir1/vdir2/api/foo/bar")]
+ public void ShouldResolveWhenNestedVirtualDirectorySuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/vdir1/vdir2"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+
+ [Test]
+ [TestCase("~/", "http://octopus/vdir1/vdir2/")]
+ [TestCase("~/api", "http://octopus/vdir1/vdir2/api")]
+ [TestCase("~/api/foo", "http://octopus/vdir1/vdir2/api/foo")]
+ [TestCase("~/api/foo/bar", "http://octopus/vdir1/vdir2/api/foo/bar")]
+ public void ShouldResolveWhenNestedVirtualDirectoryApiSuffixGiven(string link, string expectedResult)
+ {
+ var resolver = new DefaultLinkResolver(new Uri("http://octopus/vdir1/vdir2/api"));
+ Assert.That(resolver.Resolve(link).ToString(), Is.EqualTo(expectedResult));
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/Octopus.Client.Tests/Extensions/StringExtensions.cs b/source/Octopus.Client.Tests/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..dff74fc25
--- /dev/null
+++ b/source/Octopus.Client.Tests/Extensions/StringExtensions.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+
+namespace Octopus.Client.Tests.Extensions
+{
+ public static class StringExtensions
+ {
+ public static string RemoveNewlines(this string str)
+ {
+ return str?.Replace("\r", "").Replace("\n", "");
+ }
+
+ public static string NewLineSeperate(this IEnumerable