Skip to content

Commit

Permalink
Merge pull request #56 from cake-build/bug/gh-55-get-latest-version-f…
Browse files Browse the repository at this point in the history
…rom-github

Gets the latest version of Cake from GitHub
  • Loading branch information
ecampidoglio authored Aug 28, 2024
2 parents 6e4d1bf + f125c66 commit e313098
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 7 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ jobs:
cake-version: tool-manifest
script-path: ${{ env.script-directory }}/build.cake
target: Test-Cake-Version
- name: Get the latest Cake release from GitHub
id: get-latest-cake-release
uses: octokit/[email protected]
with:
route: GET /repos/cake-build/cake/releases/latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set the EXPECTED_CAKE_VERSION environment variable
shell: bash
run: |
version=$(echo ${{ fromJson(steps.get-latest-cake-release.outputs.data).tag_name }} | sed 's/v//')
echo "EXPECTED_CAKE_VERSION=$version" >> $GITHUB_ENV
- name: Run with the latest Cake version
uses: ./
with:
cake-version: latest
script-path: ${{ env.script-directory }}/build.cake
target: Test-Cake-Version
- name: Run automatic bootstrapping of Cake modules (Cake >= 1.0.0)
uses: ./
with:
Expand Down
112 changes: 112 additions & 0 deletions __tests__/cakeRelease.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import * as http from '@actions/http-client';
import * as cakeRelease from '../src/cakeRelease';

describe('When retrieving the latest Cake version', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 200,
result: {
tag_name: 'v1.0.0'
},
headers: {}
}));
});

test('it should return the latest version number from GitHub', async () => {
expect(await cakeRelease.getLatestVersion()).toBe('1.0.0');
});
});

describe('When retrieving the latest Cake version without the \'v\' prefix', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 200,
result: {
tag_name: '1.0.0'
},
headers: {}
}));
});

test('it should return the latest version number from GitHub', async () => {
expect(await cakeRelease.getLatestVersion()).toBe('1.0.0');
});
});

describe('When failing to retrieve the latest Cake version due to a GitHub error', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 500,
result: {},
headers: {}
}));
});

test('it should return null', async () => {
expect(await cakeRelease.getLatestVersion()).toBeNull();
});

test('it should log the fact that the GitHub API returned an error', async () => {
const log = jest.spyOn(console, 'log');
await cakeRelease.getLatestVersion();
expect(log).toHaveBeenCalledWith('Could not determine the latest version of Cake. GitHub returned status code 500');
});
});

describe('When failing to retrieve the latest Cake version due to an empty response from GitHub', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 200,
result: {},
headers: {}
}));
});

test('it should return null', async () => {
expect(await cakeRelease.getLatestVersion()).toBeNull();
});
});

describe('When failing to retrieve the latest Cake version due to a missing tag name in the GitHub response', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 200,
result: {
tag_name: null
},
headers: {}
}));
});

test('it should return null', async () => {
expect(await cakeRelease.getLatestVersion()).toBeNull();
});
});

describe('When failing to retrieve the latest Cake version due to an empty tag name in the GitHub response', () => {
beforeAll(async () => {
jest
.spyOn(http.HttpClient.prototype, 'getJson')
.mockImplementation(async () => ({
statusCode: 200,
result: {
tag_name: ''
},
headers: {}
}));
});

test('it should return null', async () => {
expect(await cakeRelease.getLatestVersion()).toBeNull();
});
});
50 changes: 45 additions & 5 deletions __tests__/cakeTool.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as path from 'path';
import * as dotnet from '../src/dotnet';
import * as cakeTool from '../src/cakeTool';
import * as cakeRelease from '../src/cakeRelease';
import { ToolsDirectory } from '../src/toolsDirectory';

const targetDirectory = path.join('target', 'directory');

jest.mock('../src/dotnet');
jest.mock('../src/cakeRelease');

describe('When installing the Cake Tool based on the tool manifest', () => {
const fakeRestoreTool = dotnet.restoreLocalTools as jest.MockedFunction<typeof dotnet.restoreLocalTools>;
Expand All @@ -26,14 +28,19 @@ describe('When installing the Cake Tool based on the tool manifest', () => {

describe('When installing the Cake Tool without a version number', () => {
const fakeInstallLocalTool = dotnet.installLocalTool as jest.MockedFunction<typeof dotnet.installLocalTool>;
const fakeGetLatestVersion = cakeRelease.getLatestVersion as jest.MockedFunction<typeof cakeRelease.getLatestVersion>;

beforeAll(() => {
fakeGetLatestVersion.mockResolvedValue('theLatestVersion');
});

test('it should install the latest version of the Cake.Tool in the tools directory', async () => {
await cakeTool.install();
expect(fakeInstallLocalTool).toHaveBeenCalledWith(
'Cake.Tool',
'dotnet-cake',
new ToolsDirectory(),
undefined);
'theLatestVersion');
});

test('it should install the latest version of the Cake.Tool in the specified target directory', async () => {
Expand All @@ -43,21 +50,25 @@ describe('When installing the Cake Tool without a version number', () => {
'Cake.Tool',
'dotnet-cake',
targetDir,
undefined);
'theLatestVersion');
});

});

describe('When installing the latest version of the Cake Tool', () => {
const fakeInstallLocalTool = dotnet.installLocalTool as jest.MockedFunction<typeof dotnet.installLocalTool>;
const fakeGetLatestVersion = cakeRelease.getLatestVersion as jest.MockedFunction<typeof cakeRelease.getLatestVersion>;

beforeAll(() => {
fakeGetLatestVersion.mockResolvedValue('theLatestVersion');
});

test('it should install the latest version of the Cake.Tool in the tools directory', async () => {
await cakeTool.install(undefined, { version: 'latest' });
expect(fakeInstallLocalTool).toHaveBeenCalledWith(
'Cake.Tool',
'dotnet-cake',
new ToolsDirectory(),
undefined);
'theLatestVersion');
});

test('it should install the latest version of the Cake.Tool in the specified target directory', async () => {
Expand All @@ -67,7 +78,7 @@ describe('When installing the latest version of the Cake Tool', () => {
'Cake.Tool',
'dotnet-cake',
targetDir,
undefined);
'theLatestVersion');
});
});

Expand Down Expand Up @@ -147,3 +158,32 @@ describe('When failing to install a specific version of the Cake Tool', () => {
.toThrow(installError);
});
});

describe('When failing to retrieve the latest version of the Cake Tool', () => {
const fakeInstallLocalTool = dotnet.installLocalTool as jest.MockedFunction<typeof dotnet.installLocalTool>;
const fakeGetLatestVersion = cakeRelease.getLatestVersion as jest.MockedFunction<typeof cakeRelease.getLatestVersion>;

beforeAll(() => {
fakeInstallLocalTool.mockReset();
fakeGetLatestVersion.mockResolvedValue(null);
});

test('it should install the Cake.Tool without a version number in the tools directory', async () => {
await cakeTool.install();
expect(fakeInstallLocalTool).toHaveBeenCalledWith(
'Cake.Tool',
'dotnet-cake',
new ToolsDirectory(),
undefined);
});

test('it should install the Cake.Tool without a version number in the specified target directory', async () => {
const targetDir = new ToolsDirectory(targetDirectory);
await cakeTool.install(targetDir);
expect(fakeInstallLocalTool).toHaveBeenCalledWith(
'Cake.Tool',
'dotnet-cake',
targetDir,
undefined);
});
});
70 changes: 69 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4148,6 +4148,72 @@ class CakeSwitch {
exports.CakeSwitch = CakeSwitch;


/***/ }),

/***/ 3040:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getLatestVersion = void 0;
const http = __importStar(__nccwpck_require__(6255));
function getLatestVersion() {
return __awaiter(this, void 0, void 0, function* () {
const release = yield getLatestCakeReleaseFromGitHub();
return extractVersionNumber(release);
});
}
exports.getLatestVersion = getLatestVersion;
function getLatestCakeReleaseFromGitHub() {
return __awaiter(this, void 0, void 0, function* () {
const client = new http.HttpClient('cake-build/cake-action');
const response = yield client.getJson('https://api.github.com/repos/cake-build/cake/releases/latest');
if (response.statusCode != 200) {
console.log(`Could not determine the latest version of Cake. GitHub returned status code ${response.statusCode}`);
return null;
}
return response.result;
});
}
function extractVersionNumber(release) {
var _a;
return ((_a = release === null || release === void 0 ? void 0 : release.tag_name) === null || _a === void 0 ? void 0 : _a.replace(/^v/, '')) || null;
}


/***/ }),

/***/ 4574:
Expand Down Expand Up @@ -4191,15 +4257,17 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.install = void 0;
const dotnet = __importStar(__nccwpck_require__(9870));
const toolsDirectory_1 = __nccwpck_require__(6745);
const cakeRelease_1 = __nccwpck_require__(3040);
function install(toolsDir, version) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
switch (version === null || version === void 0 ? void 0 : version.version) {
case 'tool-manifest':
yield dotnet.restoreLocalTools();
break;
case 'latest':
case undefined:
yield installCakeLocalTool(toolsDir);
yield installCakeLocalTool(toolsDir, (_a = yield (0, cakeRelease_1.getLatestVersion)()) !== null && _a !== void 0 ? _a : undefined);
break;
case 'specific':
yield installCakeLocalTool(toolsDir, version.number);
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/exec": "^1.0.1",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1"
},
"devDependencies": {
Expand Down
26 changes: 26 additions & 0 deletions src/cakeRelease.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as http from '@actions/http-client';

export async function getLatestVersion(): Promise<string | null> {
const release = await getLatestCakeReleaseFromGitHub();
return extractVersionNumber(release);
}

async function getLatestCakeReleaseFromGitHub(): Promise<GitHubRelease | null> {
const client = new http.HttpClient('cake-build/cake-action');
const response = await client.getJson<GitHubRelease>('https://api.github.com/repos/cake-build/cake/releases/latest');

if (response.statusCode != 200) {
console.log(`Could not determine the latest version of Cake. GitHub returned status code ${response.statusCode}`);
return null;
}

return response.result;
}

function extractVersionNumber(release: GitHubRelease | null): string | null {
return release?.tag_name?.replace(/^v/, '') || null;
}

interface GitHubRelease {
tag_name: string;
}
3 changes: 2 additions & 1 deletion src/cakeTool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as dotnet from './dotnet';
import { ToolsDirectory } from './toolsDirectory';
import { CakeVersion } from './action';
import { getLatestVersion } from './cakeRelease';

export async function install(toolsDir?: ToolsDirectory, version?: CakeVersion) {
switch (version?.version) {
Expand All @@ -9,7 +10,7 @@ export async function install(toolsDir?: ToolsDirectory, version?: CakeVersion)
break;
case 'latest':
case undefined:
await installCakeLocalTool(toolsDir);
await installCakeLocalTool(toolsDir, await getLatestVersion() ?? undefined);
break;
case 'specific':
await installCakeLocalTool(toolsDir, version.number);
Expand Down

0 comments on commit e313098

Please sign in to comment.