mirror of
https://github.com/game-ci/unity-builder.git
synced 2025-07-04 12:25:19 -04:00
Implement versioning strategies in js 🧉
This commit is contained in:
parent
2e81e61af3
commit
d75d7890d0
@ -27,7 +27,7 @@ namespace UnityBuilderAction
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set version for this build
|
// Set version for this build
|
||||||
VersionApplicator.SetVersion(options["versioning"], options["version"]);
|
VersionApplicator.SetVersion(options["version"]);
|
||||||
|
|
||||||
// Perform build
|
// Perform build
|
||||||
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
|
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
|
||||||
|
@ -95,7 +95,7 @@ namespace UnityBuilderAction.Versioning
|
|||||||
return Run(@"describe --tags --long --match ""v[0-9]*""");
|
return Run(@"describe --tags --long --match ""v[0-9]*""");
|
||||||
|
|
||||||
// Todo - implement split function based on this more complete query
|
// Todo - implement split function based on this more complete query
|
||||||
return Run(@"git describe --long --tags --dirty --always");
|
// return Run(@"describe --long --tags --dirty --always");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6,53 +6,12 @@ namespace UnityBuilderAction.Versioning
|
|||||||
{
|
{
|
||||||
public class VersionApplicator
|
public class VersionApplicator
|
||||||
{
|
{
|
||||||
enum Strategy
|
public static void SetVersion(string version)
|
||||||
{
|
{
|
||||||
None,
|
if (version == "none") {
|
||||||
Custom,
|
return;
|
||||||
Semantic,
|
|
||||||
Tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetVersion(string strategy, [CanBeNull] string version)
|
|
||||||
{
|
|
||||||
if (!Enum.TryParse<Strategy>(strategy, out Strategy validatedStrategy)) {
|
|
||||||
throw new Exception($"Invalid versioning argument provided. {strategy} is not a valid strategy.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (validatedStrategy) {
|
|
||||||
case Strategy.None:
|
|
||||||
return;
|
|
||||||
case Strategy.Custom:
|
|
||||||
ApplyCustomVersion(version);
|
|
||||||
return;
|
|
||||||
case Strategy.Semantic:
|
|
||||||
ApplySemanticCommitVersion();
|
|
||||||
return;
|
|
||||||
case Strategy.Tag:
|
|
||||||
ApplyVersionFromCurrentTag();
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException("Version strategy has not been implemented.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ApplyCustomVersion(string version)
|
|
||||||
{
|
|
||||||
Apply(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ApplySemanticCommitVersion()
|
|
||||||
{
|
|
||||||
string version = Git.GenerateSemanticCommitVersion();
|
|
||||||
|
|
||||||
Apply(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ApplyVersionFromCurrentTag()
|
|
||||||
{
|
|
||||||
string version = Git.GetTagVersion();
|
|
||||||
|
|
||||||
Apply(version);
|
Apply(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -5,4 +5,5 @@ module.exports = {
|
|||||||
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
||||||
transform: { '^.+\\.(js|jsx)?$': 'babel-jest' },
|
transform: { '^.+\\.(js|jsx)?$': 'babel-jest' },
|
||||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||||
|
setupFilesAfterEnv: ['./src/jest.setup.js'],
|
||||||
};
|
};
|
||||||
|
@ -7,8 +7,8 @@ async function action() {
|
|||||||
Cache.verify();
|
Cache.verify();
|
||||||
|
|
||||||
const { dockerfile, workspace, actionFolder } = Action;
|
const { dockerfile, workspace, actionFolder } = Action;
|
||||||
const buildParameters = BuildParameters.create(Input.getFromUser());
|
const buildParameters = BuildParameters.create(await Input.getFromUser());
|
||||||
const baseImage = new ImageTag({ ...buildParameters, version: buildParameters.unityVersion });
|
const baseImage = new ImageTag(buildParameters);
|
||||||
|
|
||||||
// Build docker image
|
// Build docker image
|
||||||
const builtImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
|
const builtImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
|
||||||
|
15
src/jest.setup.js
Normal file
15
src/jest.setup.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
expect.extend({
|
||||||
|
toBeEitherAFunctionOrAnObject(received) {
|
||||||
|
const type = typeof received;
|
||||||
|
|
||||||
|
const pass = ['object', 'function'].includes(type);
|
||||||
|
const message = `Expected a function or an object, received ${type}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
pass,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('./model/input');
|
17
src/model/__mocks__/input.js
Normal file
17
src/model/__mocks__/input.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Import this named export into your test file:
|
||||||
|
import Platform from '../platform';
|
||||||
|
|
||||||
|
export const mockGetFromUser = jest.fn().mockResolvedValue({
|
||||||
|
version: '',
|
||||||
|
targetPlatform: Platform.types.Test,
|
||||||
|
projectPath: '.',
|
||||||
|
buildName: Platform.types.Test,
|
||||||
|
buildsPath: 'build',
|
||||||
|
buildMethod: undefined,
|
||||||
|
buildVersion: '1.3.37',
|
||||||
|
customParameters: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getFromUser: mockGetFromUser,
|
||||||
|
};
|
5
src/model/__mocks__/versioning.js
Normal file
5
src/model/__mocks__/versioning.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const mockDetermineVersion = jest.fn();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
determineVersion: mockDetermineVersion,
|
||||||
|
};
|
@ -3,27 +3,25 @@ import Platform from './platform';
|
|||||||
class BuildParameters {
|
class BuildParameters {
|
||||||
static create(parameters) {
|
static create(parameters) {
|
||||||
const {
|
const {
|
||||||
unityVersion,
|
version,
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
projectPath,
|
projectPath,
|
||||||
buildName,
|
buildName,
|
||||||
buildsPath,
|
buildsPath,
|
||||||
buildMethod,
|
buildMethod,
|
||||||
versioning,
|
buildVersion,
|
||||||
version,
|
|
||||||
customParameters,
|
customParameters,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unityVersion,
|
version,
|
||||||
platform: targetPlatform,
|
platform: targetPlatform,
|
||||||
projectPath,
|
projectPath,
|
||||||
buildName,
|
buildName,
|
||||||
buildPath: `${buildsPath}/${targetPlatform}`,
|
buildPath: `${buildsPath}/${targetPlatform}`,
|
||||||
buildFile: this.parseBuildFile(buildName, targetPlatform),
|
buildFile: this.parseBuildFile(buildName, targetPlatform),
|
||||||
buildMethod,
|
buildMethod,
|
||||||
versioning,
|
buildVersion,
|
||||||
version,
|
|
||||||
customParameters,
|
customParameters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import Platform from './platform';
|
|||||||
describe('BuildParameters', () => {
|
describe('BuildParameters', () => {
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
const someParameters = {
|
const someParameters = {
|
||||||
unityVersion: 'someVersion',
|
version: 'someVersion',
|
||||||
targetPlatform: 'somePlatform',
|
targetPlatform: 'somePlatform',
|
||||||
projectPath: 'path/to/project',
|
projectPath: 'path/to/project',
|
||||||
buildName: 'someBuildName',
|
buildName: 'someBuildName',
|
||||||
@ -18,9 +18,7 @@ describe('BuildParameters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns the version', () => {
|
it('returns the version', () => {
|
||||||
expect(BuildParameters.create(someParameters).unityVersion).toStrictEqual(
|
expect(BuildParameters.create(someParameters).version).toStrictEqual(someParameters.version);
|
||||||
someParameters.unityVersion,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the platform', () => {
|
it('returns the platform', () => {
|
||||||
|
@ -19,7 +19,7 @@ class Docker {
|
|||||||
|
|
||||||
static async run(image, parameters, silent = false) {
|
static async run(image, parameters, silent = false) {
|
||||||
const {
|
const {
|
||||||
unityVersion,
|
version,
|
||||||
workspace,
|
workspace,
|
||||||
platform,
|
platform,
|
||||||
projectPath,
|
projectPath,
|
||||||
@ -27,8 +27,7 @@ class Docker {
|
|||||||
buildPath,
|
buildPath,
|
||||||
buildFile,
|
buildFile,
|
||||||
buildMethod,
|
buildMethod,
|
||||||
versioning,
|
buildVersion,
|
||||||
version,
|
|
||||||
customParameters,
|
customParameters,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
@ -40,15 +39,14 @@ class Docker {
|
|||||||
--env UNITY_EMAIL \
|
--env UNITY_EMAIL \
|
||||||
--env UNITY_PASSWORD \
|
--env UNITY_PASSWORD \
|
||||||
--env UNITY_SERIAL \
|
--env UNITY_SERIAL \
|
||||||
--env UNITY_VERSION="${unityVersion}" \
|
--env UNITY_VERSION="${version}" \
|
||||||
--env PROJECT_PATH="${projectPath}" \
|
--env PROJECT_PATH="${projectPath}" \
|
||||||
--env BUILD_TARGET="${platform}" \
|
--env BUILD_TARGET="${platform}" \
|
||||||
--env BUILD_NAME="${buildName}" \
|
--env BUILD_NAME="${buildName}" \
|
||||||
--env BUILD_PATH="${buildPath}" \
|
--env BUILD_PATH="${buildPath}" \
|
||||||
--env BUILD_FILE="${buildFile}" \
|
--env BUILD_FILE="${buildFile}" \
|
||||||
--env BUILD_METHOD="${buildMethod}" \
|
--env BUILD_METHOD="${buildMethod}" \
|
||||||
--env VERSIONING="${versioning}" \
|
--env VERSION="${buildVersion}" \
|
||||||
--env VERSION="${version}" \
|
|
||||||
--env CUSTOM_PARAMETERS="${customParameters}" \
|
--env CUSTOM_PARAMETERS="${customParameters}" \
|
||||||
--env HOME=/github/home \
|
--env HOME=/github/home \
|
||||||
--env GITHUB_REF \
|
--env GITHUB_REF \
|
||||||
|
8
src/model/error/command-execution-error.js
Normal file
8
src/model/error/command-execution-error.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class CommandExecutionError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'CommandExecutionError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandExecutionError;
|
14
src/model/error/command-execution-error.test.js
Normal file
14
src/model/error/command-execution-error.test.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import CommandExecutionError from './command-execution-error';
|
||||||
|
|
||||||
|
describe('CommandExecutionError', () => {
|
||||||
|
it('instantiates', () => {
|
||||||
|
expect(() => new CommandExecutionError()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([1, 'one', { name: '!' }])('Displays title %s', message => {
|
||||||
|
const error = new CommandExecutionError(message);
|
||||||
|
|
||||||
|
expect(error.name).toStrictEqual('CommandExecutionError');
|
||||||
|
expect(error.message).toStrictEqual(message.toString());
|
||||||
|
});
|
||||||
|
});
|
8
src/model/error/not-implemented-exception.js
Normal file
8
src/model/error/not-implemented-exception.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class NotImplementedException extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'NotImplementedException';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotImplementedException;
|
14
src/model/error/not-implemented-exception.test.js
Normal file
14
src/model/error/not-implemented-exception.test.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import NotImplementedException from './not-implemented-exception';
|
||||||
|
|
||||||
|
describe('NotImplementedException', () => {
|
||||||
|
it('instantiates', () => {
|
||||||
|
expect(() => new NotImplementedException()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([1, 'one', { name: '!' }])('Displays title %s', message => {
|
||||||
|
const error = new NotImplementedException(message);
|
||||||
|
|
||||||
|
expect(error.name).toStrictEqual('NotImplementedException');
|
||||||
|
expect(error.message).toStrictEqual(message.toString());
|
||||||
|
});
|
||||||
|
});
|
@ -7,5 +7,17 @@ import ImageTag from './image-tag';
|
|||||||
import Platform from './platform';
|
import Platform from './platform';
|
||||||
import Project from './project';
|
import Project from './project';
|
||||||
import Unity from './unity';
|
import Unity from './unity';
|
||||||
|
import Versioning from './versioning';
|
||||||
|
|
||||||
export { Action, BuildParameters, Cache, Docker, Input, ImageTag, Platform, Project, Unity };
|
export {
|
||||||
|
Action,
|
||||||
|
BuildParameters,
|
||||||
|
Cache,
|
||||||
|
Docker,
|
||||||
|
Input,
|
||||||
|
ImageTag,
|
||||||
|
Platform,
|
||||||
|
Project,
|
||||||
|
Unity,
|
||||||
|
Versioning,
|
||||||
|
};
|
||||||
|
@ -12,6 +12,6 @@ describe('Index', () => {
|
|||||||
'Project',
|
'Project',
|
||||||
'Unity',
|
'Unity',
|
||||||
])('exports %s', exportedModule => {
|
])('exports %s', exportedModule => {
|
||||||
expect(typeof Index[exportedModule]).toStrictEqual('function');
|
expect(Index[exportedModule]).toBeEitherAFunctionOrAnObject();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,43 +1,36 @@
|
|||||||
import Platform from './platform';
|
import Platform from './platform';
|
||||||
import ValidationError from './error/validation-error';
|
import Versioning from './versioning';
|
||||||
|
|
||||||
const core = require('@actions/core');
|
const core = require('@actions/core');
|
||||||
|
|
||||||
const versioningStrategies = ['None', 'Semantic', 'Tag', 'Custom'];
|
|
||||||
|
|
||||||
class Input {
|
class Input {
|
||||||
static getFromUser() {
|
static async getFromUser() {
|
||||||
// Input variables specified in workflows using "with" prop.
|
// Input variables specified in workflows using "with" prop.
|
||||||
const unityVersion = core.getInput('unityVersion');
|
const version = core.getInput('unityVersion');
|
||||||
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
|
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
|
||||||
const rawProjectPath = core.getInput('projectPath') || '.';
|
const rawProjectPath = core.getInput('projectPath') || '.';
|
||||||
const buildName = core.getInput('buildName') || targetPlatform;
|
const buildName = core.getInput('buildName') || targetPlatform;
|
||||||
const buildsPath = core.getInput('buildsPath') || 'build';
|
const buildsPath = core.getInput('buildsPath') || 'build';
|
||||||
const buildMethod = core.getInput('buildMethod'); // processed in docker file
|
const buildMethod = core.getInput('buildMethod'); // processed in docker file
|
||||||
const versioning = core.getInput('versioning') || 'Semantic';
|
const versioningStrategy = core.getInput('versioning') || 'Semantic';
|
||||||
const version = core.getInput('version') || '';
|
const specifiedVersion = core.getInput('version') || '';
|
||||||
const customParameters = core.getInput('customParameters') || '';
|
const customParameters = core.getInput('customParameters') || '';
|
||||||
|
|
||||||
// Sanitise input
|
// Sanitise input
|
||||||
const projectPath = rawProjectPath.replace(/\/$/, '');
|
const projectPath = rawProjectPath.replace(/\/$/, '');
|
||||||
|
|
||||||
// Validate input
|
// Parse input
|
||||||
if (!versioningStrategies.includes(versioning)) {
|
const buildVersion = await Versioning.determineVersion(versioningStrategy, specifiedVersion);
|
||||||
throw new ValidationError(
|
|
||||||
`Versioning strategy should be one of ${versioningStrategies.join(', ')}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return sanitised input
|
// Return validated input
|
||||||
return {
|
return {
|
||||||
unityVersion,
|
version,
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
projectPath,
|
projectPath,
|
||||||
buildName,
|
buildName,
|
||||||
buildsPath,
|
buildsPath,
|
||||||
buildMethod,
|
buildMethod,
|
||||||
versioning,
|
buildVersion,
|
||||||
version,
|
|
||||||
customParameters,
|
customParameters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,28 @@
|
|||||||
|
import { mockDetermineVersion } from './__mocks__/versioning';
|
||||||
import Input from './input';
|
import Input from './input';
|
||||||
|
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
jest.mock('./versioning');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDetermineVersion.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
describe('Input', () => {
|
describe('Input', () => {
|
||||||
describe('getFromUser', () => {
|
describe('getFromUser', () => {
|
||||||
it('does not throw', () => {
|
it('does not throw', async () => {
|
||||||
expect(() => Input.getFromUser()).not.toThrow();
|
await expect(Input.getFromUser()).resolves.not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an object', () => {
|
it('returns an object', async () => {
|
||||||
expect(typeof Input.getFromUser()).toStrictEqual('object');
|
await expect(typeof (await Input.getFromUser())).toStrictEqual('object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('calls version generator once', async () => {
|
||||||
|
await Input.getFromUser();
|
||||||
|
|
||||||
|
// Todo - make sure the versioning mock is actually hit after restoreAllMocks is used.
|
||||||
|
expect(mockDetermineVersion).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ import Action from './action';
|
|||||||
|
|
||||||
class Project {
|
class Project {
|
||||||
static get relativePath() {
|
static get relativePath() {
|
||||||
const { projectPath } = Input.getFromUser();
|
const projectPath = Input.getFromUser().then(result => result.projectPath);
|
||||||
|
|
||||||
return `${projectPath}`;
|
return `${projectPath}`;
|
||||||
}
|
}
|
||||||
|
26
src/model/system.js
Normal file
26
src/model/system.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { exec } from '@actions/exec';
|
||||||
|
|
||||||
|
class System {
|
||||||
|
static async run(command, arguments_, options) {
|
||||||
|
let result = '';
|
||||||
|
let error = '';
|
||||||
|
|
||||||
|
const listeners = {
|
||||||
|
stdout: dataBuffer => {
|
||||||
|
result += dataBuffer.toString();
|
||||||
|
},
|
||||||
|
stderr: dataBuffer => {
|
||||||
|
error += dataBuffer.toString();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const exitCode = await exec(command, arguments_, { ...options, listeners });
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default System;
|
187
src/model/versioning.js
Normal file
187
src/model/versioning.js
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import * as core from '@actions/core';
|
||||||
|
import { exec } from '@actions/exec';
|
||||||
|
import NotImplementedException from './error/not-implemented-exception';
|
||||||
|
import ValidationError from './error/validation-error';
|
||||||
|
import System from './system';
|
||||||
|
|
||||||
|
export default class Versioning {
|
||||||
|
static get strategies() {
|
||||||
|
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the branch name of the (related) branch
|
||||||
|
*/
|
||||||
|
static get branch() {
|
||||||
|
return this.headRef || this.ref.slice(11);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For pull requests we can reliably use GITHUB_HEAD_REF
|
||||||
|
*/
|
||||||
|
static get headRef() {
|
||||||
|
return process.env.GITHUB_HEAD_REF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For branches GITHUB_REF will have format `refs/heads/feature-branch-1`
|
||||||
|
*/
|
||||||
|
static get ref() {
|
||||||
|
return process.env.GITHUB_REF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to parse version description into separate fields
|
||||||
|
*/
|
||||||
|
static get descriptionRegex() {
|
||||||
|
return /^v([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async determineVersion(strategy, inputVersion) {
|
||||||
|
// Validate input
|
||||||
|
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let version;
|
||||||
|
switch (strategy) {
|
||||||
|
case this.strategies.None:
|
||||||
|
version = 'none';
|
||||||
|
break;
|
||||||
|
case this.strategies.Custom:
|
||||||
|
version = inputVersion;
|
||||||
|
break;
|
||||||
|
case this.strategies.Semantic:
|
||||||
|
version = await this.generateSemanticVersion();
|
||||||
|
break;
|
||||||
|
case this.strategies.Tag:
|
||||||
|
version = await this.generateTagVersion();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException(`Strategy ${strategy} is not implemented.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically generates a version based on SemVer out of the box.
|
||||||
|
*
|
||||||
|
* The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
|
||||||
|
*
|
||||||
|
* The latest tag dictates `<major>.<minor>`
|
||||||
|
* The number of commits since that tag dictates`<patch>`.
|
||||||
|
*
|
||||||
|
* @See: https://semver.org/
|
||||||
|
*/
|
||||||
|
static async generateSemanticVersion() {
|
||||||
|
await this.fetchAll();
|
||||||
|
|
||||||
|
if (await this.isDirty()) {
|
||||||
|
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await this.hasAnyVersionTags())) {
|
||||||
|
const version = `0.0.${await this.getTotalNumberOfCommits()}`;
|
||||||
|
core.info(`Generated version ${version} (no version tags found).`);
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag, commits, hash } = await this.parseSemanticVersion();
|
||||||
|
core.info(`Found semantic version ${tag}.${commits} for ${this.branch}@${hash}`);
|
||||||
|
|
||||||
|
return `${tag}.${commits}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the proper version for unity based on an existing tag.
|
||||||
|
*/
|
||||||
|
static async generateTagVersion() {
|
||||||
|
let tag = await this.getTag();
|
||||||
|
|
||||||
|
if (tag.charAt(0) === 'v') {
|
||||||
|
tag = tag.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the versionDescription into their named parts.
|
||||||
|
*/
|
||||||
|
static async parseSemanticVersion() {
|
||||||
|
const description = await this.getVersionDescription();
|
||||||
|
const [match, tag, commits, hash] = this.descriptionRegex.exec(description);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Failed to parse git describe output: "${description}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
match,
|
||||||
|
tag,
|
||||||
|
commits,
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchAll() {
|
||||||
|
await exec('git', ['fetch', '--all']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves information about the branch.
|
||||||
|
*
|
||||||
|
* Format: `v0.12-24-gd2198ab`
|
||||||
|
*
|
||||||
|
* In this format v0.12 is the latest tag, 24 are the number of commits since, and gd2198ab
|
||||||
|
* identifies the current commit.
|
||||||
|
*/
|
||||||
|
static async getVersionDescription() {
|
||||||
|
return System.run('git', ['describe', '--long', '--tags', '--always', `origin/${this.branch}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether there are uncommitted changes that are not ignored.
|
||||||
|
*/
|
||||||
|
static async isDirty() {
|
||||||
|
const output = await System.run('git', ['status', '--porcelain']);
|
||||||
|
|
||||||
|
return output !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tag if there is one pointing at HEAD
|
||||||
|
*/
|
||||||
|
static async getTag() {
|
||||||
|
return System.run('git', ['tag', '--points-at', 'HEAD']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the repository has any version tags yet.
|
||||||
|
*/
|
||||||
|
static async hasAnyVersionTags() {
|
||||||
|
const numberOfVersionCommits = await System.run('git', [
|
||||||
|
'--list',
|
||||||
|
'--merged',
|
||||||
|
'HEAD',
|
||||||
|
'|',
|
||||||
|
'grep v[0-9]*',
|
||||||
|
'|',
|
||||||
|
'wc -l',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return numberOfVersionCommits !== '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of commits on head.
|
||||||
|
*/
|
||||||
|
static async getTotalNumberOfCommits() {
|
||||||
|
const numberOfCommitsAsString = await System.run('git', ['rev-list', '--count', 'HEAD']);
|
||||||
|
|
||||||
|
return parseInt(numberOfCommitsAsString, 10);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user