From 7117ea876084bce8ba5a614189c5043fd2c823f4 Mon Sep 17 00:00:00 2001 From: Mattias Karlsson Date: Tue, 29 Oct 2019 16:49:05 +0100 Subject: [PATCH 1/2] (GH-1) Add specifying/pinning Cake version support * fixes #1 --- __tests__/dotnet.test.ts | 65 +++++++++++++++++++++++++++++++++------- action.yml | 3 ++ lib/dotnet.js | 37 ++++++++++++++++------- lib/main.js | 3 +- src/dotnet.ts | 53 +++++++++++++++++++++++++------- src/main.ts | 3 +- 6 files changed, 131 insertions(+), 33 deletions(-) diff --git a/__tests__/dotnet.test.ts b/__tests__/dotnet.test.ts index b21381c6..dab0ffde 100644 --- a/__tests__/dotnet.test.ts +++ b/__tests__/dotnet.test.ts @@ -6,6 +6,18 @@ import { Platform } from '../src/platform'; import { ToolsDirectory } from '../src/toolsDirectory'; const targetDirectory = path.join('target', 'directory'); +const cakeVersion = '0.35.0'; +const installedCakeVersion = '0.34.0'; +const containsFileWithOldToolInstalled = (fileName: string) => { + switch (fileName) { + case 'dotnet-cake': + case 'dotnet-cake.exe': + case path.join('.store', 'cake.tool', installedCakeVersion, 'project.assets.json'): + return true; + default: + return false; + } + }; jest.mock('@actions/core'); jest.mock('@actions/exec'); @@ -38,6 +50,16 @@ describe('When successfully installing the Cake Tool locally', () => { await DotNet.installLocalCakeTool(new ToolsDirectory(targetDirectory)); expect(fakeExec).toBeCalledWith('dotnet tool install', ['--tool-path', targetDirectory, 'Cake.Tool']); }); + + test('it should install specific version of the Cake.Tool in the tools directory', async () => { + await DotNet.installLocalCakeTool(undefined, cakeVersion); + expect(fakeExec).toBeCalledWith('dotnet tool install', ['--version', cakeVersion, '--tool-path', 'tools', 'Cake.Tool']); + }); + + test('it should install specific version of the Cake.Tool in the specified target directory', async () => { + await DotNet.installLocalCakeTool(new ToolsDirectory(targetDirectory), cakeVersion); + expect(fakeExec).toBeCalledWith('dotnet tool install', ['--version', cakeVersion, '--tool-path', targetDirectory, 'Cake.Tool']); + }); }); describe('When failing to install the Cake Tool locally', () => { @@ -54,6 +76,12 @@ describe('When failing to install the Cake Tool locally', () => { test('it should throw an error containing the exit code', async () => { await expect(DotNet.installLocalCakeTool()).rejects.toThrowError('-99'); }); + + test('it should throw an error containing the exit code', async () => { + const directoryWithCakeTool = new ToolsDirectory(targetDirectory); + directoryWithCakeTool.containsFile = jest.fn().mockImplementation(containsFileWithOldToolInstalled); + await expect(DotNet.installLocalCakeTool(directoryWithCakeTool, cakeVersion)).rejects.toThrowError('-99'); + }); }); describe('When successfully installing a tool locally', () => { @@ -64,12 +92,12 @@ describe('When successfully installing a tool locally', () => { }); test('it should install the specified tool in the tools directory', async () => { - await DotNet.installLocalTool('The.Tool'); + await DotNet.installLocalTool('The.Tool', 'dotnet-tool'); expect(fakeExec).toBeCalledWith('dotnet tool install', ['--tool-path', 'tools', 'The.Tool']); }); test('it should install the specified tool in the specified target directory', async () => { - await DotNet.installLocalTool('The.Tool', new ToolsDirectory(targetDirectory)); + await DotNet.installLocalTool('The.Tool', 'dotnet-tool', new ToolsDirectory(targetDirectory)); expect(fakeExec).toBeCalledWith('dotnet tool install', ['--tool-path', targetDirectory, 'The.Tool']); }); }); @@ -81,12 +109,12 @@ describe('When failing to install the a tool locally', () => { fakeExec.mockReturnValue(Promise.resolve(-99)); }); - test('it should throw an error containing the tool name', async () => { - await expect(DotNet.installLocalTool('The.Tool')).rejects.toThrowError('The.Tool'); + test('it should throw an error containing the package id', async () => { + await expect(DotNet.installLocalTool('The.Tool', 'dotnet-tool')).rejects.toThrowError('The.Tool'); }); test('it should throw an error containing the exit code', async () => { - await expect(DotNet.installLocalTool('The.Tool')).rejects.toThrowError('-99'); + await expect(DotNet.installLocalTool('The.Tool', 'dotnet-tool')).rejects.toThrowError('-99'); }); }); @@ -95,9 +123,8 @@ describe('When installing the Cake Tool locally to a directory where it already const fakeExec = exec as jest.MockedFunction; beforeAll(() => { - directoryWithCakeTool.containsFile = jest.fn().mockImplementation(() => { - return true; - }); + directoryWithCakeTool.containsFile = jest.fn().mockImplementation(containsFileWithOldToolInstalled); + fakeExec.mockReturnValue(Promise.resolve(0)); }); test('it should not attempt to install the Cake.Tool in the same directory', () => { @@ -108,10 +135,26 @@ describe('When installing the Cake Tool locally to a directory where it already }); test('it should look for the Cake.Tool executable with extension on Windows', () => { - Platform.isWindows = jest.fn().mockImplementation(() => { - return true; - }); + Platform.isWindows = jest.fn().mockImplementation(() => true); DotNet.installLocalCakeTool(directoryWithCakeTool); expect(directoryWithCakeTool.containsFile).toBeCalledWith(expect.stringMatching('dotnet-cake.exe$')); }); + + test('it should look for the Cake.Tool executable without extension on Posix', () => { + Platform.isWindows = jest.fn().mockImplementation(() => false); + DotNet.installLocalCakeTool(directoryWithCakeTool); + expect(directoryWithCakeTool.containsFile).toBeCalledWith(expect.stringMatching('dotnet-cake$')); + }); + + test('it should uninstall and install specific version of the Cake.Tool in the specified target directory', async () => { + await DotNet.installLocalCakeTool(directoryWithCakeTool, cakeVersion); + expect(fakeExec).toBeCalledWith('dotnet tool uninstall', ['--tool-path', targetDirectory, 'Cake.Tool']); + expect(fakeExec).toBeCalledWith('dotnet tool install', ['--version', cakeVersion, '--tool-path', targetDirectory, 'Cake.Tool']); + }); + + test('it should not uninstall and install already installed specific version of the Cake.Tool', async () => { + await DotNet.installLocalCakeTool(directoryWithCakeTool, installedCakeVersion); + expect(fakeExec).not.toBeCalledWith('dotnet tool uninstall', ['--tool-path', installedCakeVersion, 'Cake.Tool']); + expect(fakeExec).not.toBeCalledWith('dotnet tool install', ['--version', installedCakeVersion, '--tool-path', targetDirectory, 'Cake.Tool']); + }); }); diff --git a/action.yml b/action.yml index 78e2619f..99269fd6 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: target: description: 'The name of the task to execute. Note that this argument must be supported by the script.' required: false + cake-version: + description: 'The version of Cake to install.' + required: false runs: using: 'node12' main: 'lib/main.js' diff --git a/lib/dotnet.js b/lib/dotnet.js index 2d381ae1..ec361fcb 100644 --- a/lib/dotnet.js +++ b/lib/dotnet.js @@ -15,31 +15,48 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); +const path = __importStar(require("path")); const core = __importStar(require("@actions/core")); const exec_1 = require("@actions/exec"); const toolsDirectory_1 = require("./toolsDirectory"); const platform_1 = require("./platform"); const dotnetToolInstall = 'dotnet tool install'; +const dotnetToolUnInstall = 'dotnet tool uninstall'; const dotnetCake = 'dotnet-cake'; class DotNet { static disableTelemetry() { core.exportVariable('DOTNET_CLI_TELEMETRY_OPTOUT', '1'); } - static installLocalCakeTool(targetDirectory = new toolsDirectory_1.ToolsDirectory()) { + static installLocalCakeTool(targetDirectory = new toolsDirectory_1.ToolsDirectory(), version) { return __awaiter(this, void 0, void 0, function* () { - const cakeTool = platform_1.Platform.isWindows() ? `${dotnetCake}.exe` : dotnetCake; - if (targetDirectory.containsFile(cakeTool)) { - core.info(`The Cake.Tool already exists in ${targetDirectory}, skipping installation`); - return; - } - return DotNet.installLocalTool('Cake.Tool', targetDirectory); + return DotNet.installLocalTool('Cake.Tool', dotnetCake, targetDirectory, version); }); } - static installLocalTool(toolName, targetDirectory = new toolsDirectory_1.ToolsDirectory()) { + static installLocalTool(packageId, toolName, targetDirectory = new toolsDirectory_1.ToolsDirectory(), version) { return __awaiter(this, void 0, void 0, function* () { - const exitCode = yield exec_1.exec(dotnetToolInstall, ['--tool-path', targetDirectory.path, toolName]); + const toolExecutable = `${toolName}${platform_1.Platform.isWindows() ? '.exe' : ''}`; + if (targetDirectory.containsFile(toolExecutable)) { + if (version) { + const toolInstallPath = path.join('.store', packageId.toLowerCase(), version.toLowerCase(), 'project.assets.json'); + if (targetDirectory.containsFile(toolInstallPath)) { + core.info(`The ${packageId} version ${version} already exists in ${targetDirectory}, skipping installation`); + return; + } + const uninstallExitCode = yield exec_1.exec(dotnetToolUnInstall, ['--tool-path', targetDirectory.path, packageId]); + if (uninstallExitCode != 0) { + throw new Error(`Failed to uninstall previous version of ${packageId}. Exit code: ${uninstallExitCode}`); + } + } + else { + core.info(`The ${packageId} already exists in ${targetDirectory}, skipping installation`); + return; + } + } + const versionArg = (version) ? ['--version', version] : []; + const installArgs = [...versionArg, '--tool-path', targetDirectory.path, packageId]; + const exitCode = yield exec_1.exec(dotnetToolInstall, installArgs); if (exitCode != 0) { - throw new Error(`Failed to install ${toolName}. Exit code: ${exitCode}`); + throw new Error(`Failed to install ${packageId}. Exit code: ${exitCode}`); } }); } diff --git a/lib/main.js b/lib/main.js index fada6211..57985601 100644 --- a/lib/main.js +++ b/lib/main.js @@ -25,10 +25,11 @@ function run() { try { const scriptPath = core.getInput('script-path'); const target = new cakeParameter_1.CakeArgument('target', core.getInput('target')); + const version = core.getInput('cake-version'); const toolsDir = new toolsDirectory_1.ToolsDirectory(); toolsDir.create(); dotnet_1.DotNet.disableTelemetry(); - yield dotnet_1.DotNet.installLocalCakeTool(toolsDir); + yield dotnet_1.DotNet.installLocalCakeTool(toolsDir, version); yield cake_1.CakeTool.runScript(scriptPath, toolsDir, target); } catch (error) { diff --git a/src/dotnet.ts b/src/dotnet.ts index 3a90d187..ae9e2c4c 100644 --- a/src/dotnet.ts +++ b/src/dotnet.ts @@ -1,9 +1,11 @@ +import * as path from 'path'; import * as core from '@actions/core'; import { exec } from '@actions/exec'; import { ToolsDirectory } from './toolsDirectory'; import { Platform } from './platform'; const dotnetToolInstall = 'dotnet tool install'; +const dotnetToolUnInstall = 'dotnet tool uninstall'; const dotnetCake = 'dotnet-cake'; export class DotNet { @@ -11,22 +13,53 @@ export class DotNet { core.exportVariable('DOTNET_CLI_TELEMETRY_OPTOUT', '1'); } - static async installLocalCakeTool(targetDirectory: ToolsDirectory = new ToolsDirectory()) { - const cakeTool = Platform.isWindows() ? `${dotnetCake}.exe` : dotnetCake; + static async installLocalCakeTool( + targetDirectory: ToolsDirectory = new ToolsDirectory(), + version?: string + ) { + return DotNet.installLocalTool('Cake.Tool', dotnetCake, targetDirectory, version); + } + + static async installLocalTool( + packageId: string, + toolName: string, + targetDirectory: ToolsDirectory = new ToolsDirectory(), + version?: string + ) { + const toolExecutable = `${toolName}${Platform.isWindows() ? '.exe' : ''}`; + + if (targetDirectory.containsFile(toolExecutable)) { + if (version) { + const toolInstallPath = path.join( + '.store', + packageId.toLowerCase(), + version.toLowerCase(), + 'project.assets.json' + ); - if (targetDirectory.containsFile(cakeTool)) { - core.info(`The Cake.Tool already exists in ${targetDirectory}, skipping installation`); - return; + if (targetDirectory.containsFile(toolInstallPath)) { + core.info(`The ${packageId} version ${version} already exists in ${targetDirectory}, skipping installation`); + return; + } + + const uninstallExitCode = await exec(dotnetToolUnInstall, ['--tool-path', targetDirectory.path, packageId]); + + if (uninstallExitCode != 0) { + throw new Error(`Failed to uninstall previous version of ${packageId}. Exit code: ${uninstallExitCode}`); + } + } else { + core.info(`The ${packageId} already exists in ${targetDirectory}, skipping installation`); + return; + } } - return DotNet.installLocalTool('Cake.Tool', targetDirectory); - } + const versionArg = (version) ? ['--version', version] : []; + const installArgs = [...versionArg, '--tool-path', targetDirectory.path, packageId]; - static async installLocalTool(toolName: string, targetDirectory: ToolsDirectory = new ToolsDirectory()) { - const exitCode = await exec(dotnetToolInstall, ['--tool-path', targetDirectory.path, toolName]); + const exitCode = await exec(dotnetToolInstall, installArgs); if (exitCode != 0) { - throw new Error(`Failed to install ${toolName}. Exit code: ${exitCode}`); + throw new Error(`Failed to install ${packageId}. Exit code: ${exitCode}`); } } } diff --git a/src/main.ts b/src/main.ts index 0e65a533..ca1bf743 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,13 +8,14 @@ export async function run() { try { const scriptPath = core.getInput('script-path'); const target = new CakeArgument('target', core.getInput('target')); + const version = core.getInput('cake-version'); const toolsDir = new ToolsDirectory(); toolsDir.create(); DotNet.disableTelemetry(); - await DotNet.installLocalCakeTool(toolsDir); + await DotNet.installLocalCakeTool(toolsDir, version); await CakeTool.runScript(scriptPath, toolsDir, target); } catch (error) { core.setFailed(error.message); From d4cab8688d12ea700b106c933dc99f0839e26199 Mon Sep 17 00:00:00 2001 From: Mattias Karlsson Date: Tue, 29 Oct 2019 17:22:08 +0100 Subject: [PATCH 2/2] Add integration tests --- .github/workflows/tests.yml | 14 ++++++++++++++ build.cake | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7d5e9b8e..3862cdc3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,3 +22,17 @@ jobs: uses: ./ with: target: Successful-Task + - name: Run a specific version ( 0.34.1 ) + env: + TEST_CAKE_VERSION: 0.34.1 + uses: ./ + with: + cake-version: 0.34.1 + target: Version-Check-Task + - name: Run a specific version ( 0.33.0 ) + env: + TEST_CAKE_VERSION: 0.33.0 + uses: ./ + with: + cake-version: 0.33.0 + target: Version-Check-Task diff --git a/build.cake b/build.cake index 8c9fa8f6..9901179d 100644 --- a/build.cake +++ b/build.cake @@ -12,4 +12,19 @@ Task("Failing-Task") throw new Exception("Failed"); }); +Task("Version-Check-Task") + .Does(context => +{ + Version expect = Version.TryParse($"{EnvironmentVariable("TEST_CAKE_VERSION")}.0", out Version parsedVersion) + ? parsedVersion + : null; + + if (expect != context.Environment.Runtime.CakeVersion) + { + throw new Exception($"Expected version {expect} got {context.Environment.Runtime.CakeVersion}"); + } + + Information("Successful"); +}); + RunTarget(target);