renamed GitVersion.js -> GitVersion.ts, added ToolInstaller and ToolLib
This commit is contained in:
24
src/Interfaces.ts
Normal file
24
src/Interfaces.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export interface IProxy {
|
||||
find: (toolName: string, versionSpec: string, arch?: string) => string;
|
||||
debug: (message: string) => void;
|
||||
exportVariable: (name: string, val: string) => void;
|
||||
addPath: (inputPath: string) => void;
|
||||
which: (tool: string, check?: boolean) => Promise<string>;
|
||||
cacheDir: (sourceDir: string, tool: string, version: string, arch?: string) => Promise<string>;
|
||||
isExplicitVersion: (versionSpec: string) => boolean;
|
||||
evaluateVersions: (versions: string[], versionSpec: string) => string;
|
||||
cleanVersion: (version: string) => string;
|
||||
createTempDir: () => Promise<string>;
|
||||
executeDotNet: (args: string[]) => Promise<IExecSyncResult>;
|
||||
};
|
||||
|
||||
export interface IExecSyncResult {
|
||||
/** standard output */
|
||||
stdout: string;
|
||||
/** error output */
|
||||
stderr: string;
|
||||
/** return code */
|
||||
code: number;
|
||||
/** Error on failure */
|
||||
error: Error;
|
||||
}
|
||||
128
src/ToolInstaller.ts
Normal file
128
src/ToolInstaller.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
import * as httpm from 'typed-rest-client/HttpClient';
|
||||
import { IProxy } from './Interfaces';
|
||||
|
||||
export class ToolInstaller {
|
||||
constructor(public utils: IProxy) {
|
||||
this.httpClient = new httpm.HttpClient("ToolInstaller");
|
||||
}
|
||||
|
||||
public async downloadAndInstall(toolName: string, versionSpec: string, checkLatest: boolean, includePrerelease: boolean): Promise<string> {
|
||||
console.log('');
|
||||
console.log('--------------------------');
|
||||
console.log(`Installing ${toolName} version ` + versionSpec);
|
||||
console.log('--------------------------');
|
||||
|
||||
if (this.utils.isExplicitVersion(versionSpec)) {
|
||||
checkLatest = false; // check latest doesn't make sense when explicit version
|
||||
}
|
||||
|
||||
let toolPath: string;
|
||||
if (!checkLatest) {
|
||||
//
|
||||
// Let's try and resolve the version spec locally first
|
||||
//
|
||||
toolPath = this.utils.find(toolName, versionSpec);
|
||||
}
|
||||
|
||||
if (!toolPath) {
|
||||
let version: string;
|
||||
if (this.utils.isExplicitVersion(versionSpec)) {
|
||||
//
|
||||
// Explicit version was specified. No need to query for list of versions.
|
||||
//
|
||||
version = versionSpec;
|
||||
}
|
||||
else {
|
||||
//
|
||||
// Let's query and resolve the latest version for the versionSpec.
|
||||
// If the version is an explicit version (1.1.1 or v1.1.1) then no need to query.
|
||||
// If your tool doesn't offer a mechanism to query,
|
||||
// then it can only support exact version inputs.
|
||||
//
|
||||
version = await this.queryLatestMatch(toolName, versionSpec, includePrerelease);
|
||||
if (!version) {
|
||||
throw new Error(`Unable to find ${toolName} version '${versionSpec}'.`);
|
||||
}
|
||||
|
||||
//
|
||||
// Check the cache for the resolved version.
|
||||
//
|
||||
toolPath = this.utils.find(toolName, version)
|
||||
}
|
||||
if (!toolPath) {
|
||||
//
|
||||
// Download, extract, cache
|
||||
//
|
||||
toolPath = await this.acquireTool(toolName, version);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Prepend the tools path. This prepends the PATH for the current process and
|
||||
// instructs the agent to prepend for each task that follows.
|
||||
//
|
||||
this.utils.debug(`toolPath: ${toolPath}`);
|
||||
|
||||
if (os.platform() != 'win32') {
|
||||
let dotnetRoot = path.dirname(fs.readlinkSync(await this.utils.which("dotnet")));
|
||||
this.utils.exportVariable('DOTNET_ROOT', dotnetRoot);
|
||||
}
|
||||
this.utils.addPath(toolPath);
|
||||
|
||||
return toolPath;
|
||||
}
|
||||
|
||||
private async queryLatestMatch(toolName: string, versionSpec: string, includePrerelease: boolean): Promise<string> {
|
||||
this.utils.debug(`querying tool versions for ${toolName}${versionSpec ? `@${versionSpec}` : ""} ${includePrerelease ? "including pre-releases" : ""}`);
|
||||
|
||||
var downloadPath = `https://api-v2v3search-0.nuget.org/query?q=${encodeURIComponent(toolName.toLowerCase())}&prerelease=${includePrerelease ? "true" : "false"}&semVerLevel=2.0.0`;
|
||||
var res = await this.httpClient.get(downloadPath);
|
||||
|
||||
if (!res || res.message.statusCode != 200)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
let body: string = await res.readBody();
|
||||
var data = JSON.parse(body).data;
|
||||
|
||||
const versions = (<Array<{ version: string }>>data[0].versions).map(x => x.version);
|
||||
if (!versions || !versions.length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
this.utils.debug(`got versions: ${versions.join(", ")}`);
|
||||
|
||||
return this.utils.evaluateVersions(versions, versionSpec);
|
||||
}
|
||||
|
||||
private async acquireTool(toolName: string, version: string): Promise<string> {
|
||||
|
||||
let tempDirectory = await this.utils.createTempDir();
|
||||
let args = ["tool", "install", toolName, "--tool-path", tempDirectory];
|
||||
|
||||
if (version) {
|
||||
version = this.utils.cleanVersion(version);
|
||||
args = args.concat(["--version", version]);
|
||||
}
|
||||
|
||||
let result = await this.utils.executeDotNet(args);
|
||||
let status = result.code === 0 ? "success" : "failure";
|
||||
let message = result.code === 0 ? result.stdout : result.stderr;
|
||||
|
||||
this.utils.debug(`tool install result: ${status} ${message}`);
|
||||
|
||||
if (result.code) {
|
||||
throw new Error("Error installing tool");
|
||||
}
|
||||
|
||||
return await this.utils.cacheDir(tempDirectory, toolName, version);
|
||||
}
|
||||
|
||||
private httpClient: httpm.HttpClient;
|
||||
}
|
||||
18
src/github/GitVersion.ts
Normal file
18
src/github/GitVersion.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
import { ToolInstaller } from '../ToolInstaller';
|
||||
import { Proxy as utils } from './Proxy';
|
||||
try {
|
||||
let toolInstaller = new ToolInstaller(utils);
|
||||
toolInstaller.downloadAndInstall('GitVersion.Tool', '5.1.2', false, false);
|
||||
// `who-to-greet` input defined in action metadata file
|
||||
const nameToGreet = core.getInput('who-to-greet');
|
||||
console.log(`Hello ${nameToGreet}!`);
|
||||
const time = (new Date()).toTimeString();
|
||||
core.setOutput("time", time);
|
||||
// Get the JSON webhook payload for the event that triggered the workflow
|
||||
const payload = JSON.stringify(github.context.payload, undefined, 2)
|
||||
console.log(`The event payload: ${payload}`);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
20
src/github/Proxy.ts
Normal file
20
src/github/Proxy.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { find, cacheDir } from '@actions/tool-cache';
|
||||
import { debug, exportVariable, addPath } from '@actions/core';
|
||||
import { which } from '@actions/io';
|
||||
|
||||
import { isExplicitVersion, evaluateVersions, createTempDir, cleanVersion, executeDotNet } from './ToolLib';
|
||||
import { IProxy } from '../Interfaces';
|
||||
|
||||
export const Proxy: IProxy = {
|
||||
find : find,
|
||||
debug: debug,
|
||||
exportVariable: exportVariable,
|
||||
addPath: addPath,
|
||||
which: which,
|
||||
cacheDir: cacheDir,
|
||||
isExplicitVersion: isExplicitVersion,
|
||||
evaluateVersions: evaluateVersions,
|
||||
cleanVersion: cleanVersion,
|
||||
createTempDir: createTempDir,
|
||||
executeDotNet: executeDotNet
|
||||
}
|
||||
114
src/github/ToolLib.ts
Normal file
114
src/github/ToolLib.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
import * as uuidV4 from "uuid/v4";
|
||||
|
||||
import * as io from '@actions/io';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import { IExecSyncResult } from '../Interfaces';
|
||||
|
||||
const cmp = require('semver-compare');
|
||||
|
||||
export async function createTempDir(): Promise<string> {
|
||||
const IS_WINDOWS = process.platform === "win32";
|
||||
|
||||
let tempDirectory: string = process.env["RUNNER_TEMP"] || "";
|
||||
|
||||
if (!tempDirectory) {
|
||||
let baseLocation: string;
|
||||
if (IS_WINDOWS) {
|
||||
// On Windows use the USERPROFILE env variable
|
||||
baseLocation = process.env["USERPROFILE"] || "C:\\";
|
||||
} else {
|
||||
if (process.platform === "darwin") {
|
||||
baseLocation = "/Users";
|
||||
} else {
|
||||
baseLocation = "/home";
|
||||
}
|
||||
}
|
||||
tempDirectory = path.join(baseLocation, "actions", "temp");
|
||||
}
|
||||
const dest = path.join(tempDirectory, uuidV4());
|
||||
await io.mkdirP(dest);
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a version spec is an explicit version (e.g. 1.0.1 or v1.0.1)
|
||||
* As opposed to a version spec like 1.x
|
||||
*
|
||||
* @param versionSpec
|
||||
*/
|
||||
export function isExplicitVersion(versionSpec: string): boolean {
|
||||
let c = semver.clean(versionSpec);
|
||||
core.debug('isExplicit: ' + c);
|
||||
|
||||
let valid = semver.valid(c) != null;
|
||||
core.debug('explicit? ' + valid);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cleaned (removed leading/trailing whitespace, remove '=v' prefix)
|
||||
* and parsed version, or null if version is invalid.
|
||||
*/
|
||||
export function cleanVersion(version: string): string {
|
||||
core.debug('cleaning: ' + version);
|
||||
return semver.clean(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluates a list of versions and returns the latest version matching the version spec
|
||||
*
|
||||
* @param versions an array of versions to evaluate
|
||||
* @param versionSpec a version spec (e.g. 1.x)
|
||||
*/
|
||||
export function evaluateVersions(versions: string[], versionSpec: string): string {
|
||||
let version: string;
|
||||
core.debug('evaluating ' + versions.length + ' versions');
|
||||
versions = versions.sort(cmp);
|
||||
for (let i = versions.length - 1; i >= 0; i--) {
|
||||
let potential: string = versions[i];
|
||||
let satisfied: boolean = semver.satisfies(potential, versionSpec);
|
||||
if (satisfied) {
|
||||
version = potential;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (version) {
|
||||
core.debug('matched: ' + version);
|
||||
}
|
||||
else {
|
||||
core.debug('match not found');
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
export async function executeDotNet(args: string[]) : Promise<IExecSyncResult> {
|
||||
let dotnetPath = await io.which("dotnet", true);
|
||||
let resultCode = 0;
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
resultCode = await exec.exec(
|
||||
`"${dotnetPath}"`,
|
||||
args,
|
||||
{
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
stdout += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
stderr += data.toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
code: resultCode,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user