Compare commits

..

17 Commits
v0.14 ... v1.0

Author SHA1 Message Date
Webber
bfe6be7ce2 Update readme for v1.0 🎉 2020-05-23 00:25:26 +02:00
Webber
f15f40d265 Use head for tags 2020-05-22 23:01:58 +02:00
Webber
866f364f64 Use ref instead of tag vs branch 2020-05-22 23:01:58 +02:00
Webber
a245f08e75 rename to throwContextualError 2020-05-22 23:01:58 +02:00
Webber
21c211bbdd rebase on master 2020-05-22 23:01:58 +02:00
Webber
3718e05961 Describe errors in System.run 2020-05-22 23:01:58 +02:00
Webber
0159028bb1 Fix missing await 2020-05-22 23:01:58 +02:00
Webber
054c6bfab3 Catch command for in-shell errors 2020-05-22 23:01:58 +02:00
Webber
8c9ff3249e More info if command gives no output, just the exit code. 2020-05-22 23:01:58 +02:00
Webber
7386c669ad Fix no output from errors 2020-05-22 23:01:58 +02:00
Webber
ce865270c4 Use commit-ish for git description 2020-05-22 23:01:58 +02:00
Webber
7e17091251 Split responsibilities between Input and BuildParameters models 2020-05-22 00:55:26 +02:00
Webber
02ff5bbef2 Add documentation and tests for allowDirtyBuild 2020-05-22 00:55:26 +02:00
Webber
8c177b1bad Add flag for allowing dirty branch 2020-05-22 00:55:26 +02:00
Webber
699621ed21 Run versioning commands in projectPath instead 2020-05-21 14:26:37 +02:00
Webber
44bde7feb9 Base number of commits off of the branch on origin 2020-05-02 16:37:24 +02:00
Webber
5328bda08e Base number of commits off of the branch 2020-05-02 16:37:24 +02:00
13 changed files with 432 additions and 198 deletions

View File

@@ -51,7 +51,7 @@ your license file and add it as a secret.
Then, define the build step as follows:
```yaml
- uses: webbertakken/unity-builder@v0.11
- uses: webbertakken/unity-builder@<version>
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with:
@@ -73,7 +73,7 @@ Instead, three variables will need to be set.
Define the build step as follows:
```yaml
- uses: webbertakken/unity-builder@v0.11
- uses: webbertakken/unity-builder@<version>
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
@@ -177,7 +177,7 @@ jobs:
restore-keys: |
Library-${{ matrix.projectPath }}-
Library-
- uses: webbertakken/unity-builder@v0.11
- uses: webbertakken/unity-builder@<version>
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
@@ -255,33 +255,7 @@ _**default:** Built-in script that will run a build out of the box._
#### versioning
The versioning strategy to use.
Strategies only work when no custom buildMethod is specified.
_**required:** `false`_
_**default:** `Auto`_
#### _These are the available strategies:_
##### None
No version will be set by Builder.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: None
```
Note that the version set in the project will be used instead.
##### Semantic (default)
Builder automatically generates a version based on [semantic versioning](https://semver.org/) out of the box.
The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
The latest tag dictates `<major>.<minor>` and the number of commits since that tag is used in `<patch>`.
Configure a specific versioning strategy
```yaml
- uses: webbertakken/unity-builder@<version>
@@ -289,40 +263,52 @@ The latest tag dictates `<major>.<minor>` and the number of commits since that t
versioning: Semantic
```
This strategy works well for the following reasons:
Find the available strategies below:
- All builds have their unique version
- No version related commits are created
- No knowledge of git or versioning is required
- Developer keeps control over `major` and `minor` versions using tags.
- Zero configuration; It works out of the box
##### Semantic
##### Tag
Versioning out of the box! **(recommended)**
Uses the tag that points at `HEAD` as the version.
> Compatible with **all platforms**.
> Does **not** modify your repository.
> Requires **zero configuration**.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: Tag
```
How it works:
This strategy works well when using a pipeline that specifically runs for tags.
> Generates a version based on [semantic versioning](https://semver.org/).
> Follows `<major>.<minor>.<patch>` for example `0.17.2`.
> The latest tag dictates `<major>.<minor>` (defaults to 0.0 for no tag).
> The number of commits (since the last tag, if any) is used for `<patch>`.
The tag must be a version tag.
No configuration required.
##### Custom
Allows specifying a custom version in the `version` field.
Allows specifying a custom version in the `version` field. **(advanced users)**
> This strategy is useful when your project or pipeline has some kind of orchestration
> that determines the versions.
##### None
No version will be set by Builder. **(not recommended)**
> Not recommended unless you generate a new version in a pre-commit hook. Manually
> setting versions is error-prone.
#### allowDirtyBuild
Allows the branch of the build to be dirty, and still generate the build.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: Custom
version: <some_version>
allowDirtyBuild: true
```
If there is a use case missing from Builder, feel free to create a feature request.
Note that it is generally bad practice to modify your branch
in a CI Pipeline. However there are exceptions where this might
be needed. (use with care).
#### customParameters

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import { Action, BuildParameters, Cache, Docker, Input, ImageTag } from './model';
import { Action, BuildParameters, Cache, Docker, ImageTag } from './model';
const core = require('@actions/core');
@@ -7,7 +7,8 @@ async function action() {
Cache.verify();
const { dockerfile, workspace, actionFolder } = Action;
const buildParameters = BuildParameters.create(await Input.getFromUser());
const buildParameters = await BuildParameters.create();
const baseImage = new ImageTag(buildParameters);
// Build docker image

View File

@@ -0,0 +1,38 @@
/* eslint-disable unicorn/prevent-abbreviations */
// Import these named export into your test file:
export const mockProjectPath = jest.fn().mockResolvedValue('mockProjectPath');
export const mockIsDirtyAllowed = jest.fn().mockResolvedValue(false);
export const mockBranch = jest.fn().mockResolvedValue('mockBranch');
export const mockHeadRef = jest.fn().mockResolvedValue('mockHeadRef');
export const mockRef = jest.fn().mockResolvedValue('mockRef');
export const mockDetermineVersion = jest.fn().mockResolvedValue('1.2.3');
export const mockGenerateSemanticVersion = jest.fn().mockResolvedValue('2.3.4');
export const mockGenerateTagVersion = jest.fn().mockResolvedValue('1.0');
export const mockParseSemanticVersion = jest.fn().mockResolvedValue({});
export const mockFetch = jest.fn().mockImplementation(() => {});
export const mockGetVersionDescription = jest.fn().mockResolvedValue('1.2-3-g12345678-dirty');
export const mockIsDirty = jest.fn().mockResolvedValue(false);
export const mockGetTag = jest.fn().mockResolvedValue('v1.0');
export const mockHasAnyVersionTags = jest.fn().mockResolvedValue(true);
export const mockGetTotalNumberOfCommits = jest.fn().mockResolvedValue(3);
export const mockGit = jest.fn().mockImplementation(() => {});
export default {
projectPath: mockProjectPath,
isDirtyAllowed: mockIsDirtyAllowed,
branch: mockBranch,
headRef: mockHeadRef,
ref: mockRef,
determineVersion: mockDetermineVersion,
generateSemanticVersion: mockGenerateSemanticVersion,
generateTagVersion: mockGenerateTagVersion,
parseSemanticVersion: mockParseSemanticVersion,
fetch: mockFetch,
getVersionDescription: mockGetVersionDescription,
isDirty: mockIsDirty,
getTag: mockGetTag,
hasAnyVersionTags: mockHasAnyVersionTags,
getTotalNumberOfCommits: mockGetTotalNumberOfCommits,
git: mockGit,
};

View File

@@ -1,28 +1,25 @@
import Input from './input';
import Platform from './platform';
import Versioning from './versioning';
class BuildParameters {
static create(parameters) {
const {
version,
targetPlatform,
projectPath,
buildName,
buildsPath,
buildMethod,
buildVersion,
customParameters,
} = parameters;
static async create() {
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform);
const buildVersion = await Versioning.determineVersion(
Input.versioningStrategy,
Input.specifiedVersion,
);
return {
version,
platform: targetPlatform,
projectPath,
buildName,
buildPath: `${buildsPath}/${targetPlatform}`,
buildFile: this.parseBuildFile(buildName, targetPlatform),
buildMethod,
version: Input.unityVersion,
platform: Input.targetPlatform,
projectPath: Input.projectPath,
buildName: Input.buildName,
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
buildFile,
buildMethod: Input.buildMethod,
buildVersion,
customParameters,
customParameters: Input.customParameters,
};
}

View File

@@ -1,82 +1,110 @@
import Versioning from './versioning';
import BuildParameters from './build-parameters';
import Input from './input';
import Platform from './platform';
const determineVersion = jest
.spyOn(Versioning, 'determineVersion')
.mockImplementation(() => '1.3.37');
afterEach(() => {
jest.clearAllMocks();
});
describe('BuildParameters', () => {
describe('create', () => {
const someParameters = {
version: 'someVersion',
targetPlatform: 'somePlatform',
projectPath: 'path/to/project',
buildName: 'someBuildName',
buildsPath: 'someBuildsPath',
buildMethod: 'Namespace.Class.Method',
customParameters: '-someParam someValue',
};
it('does not throw', () => {
expect(() => BuildParameters.create(someParameters)).not.toThrow();
it('does not throw', async () => {
await expect(BuildParameters.create()).resolves.not.toThrow();
});
it('returns the version', () => {
expect(BuildParameters.create(someParameters).version).toStrictEqual(someParameters.version);
it('determines the version only once', async () => {
await BuildParameters.create();
expect(determineVersion).toHaveBeenCalledTimes(1);
});
it('returns the platform', () => {
expect(BuildParameters.create(someParameters).platform).toStrictEqual(
someParameters.targetPlatform,
it('returns the version', async () => {
const mockValue = 'someVersion';
jest.spyOn(Input, 'unityVersion', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ version: mockValue }),
);
});
it('returns the project path', () => {
expect(BuildParameters.create(someParameters).projectPath).toStrictEqual(
someParameters.projectPath,
it('returns the platform', async () => {
const mockValue = 'somePlatform';
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ platform: mockValue }),
);
});
it('returns the build name', () => {
expect(BuildParameters.create(someParameters).buildName).toStrictEqual(
someParameters.buildName,
it('returns the project path', async () => {
const mockValue = 'path/to/project';
jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ projectPath: mockValue }),
);
});
it('returns the build path', () => {
expect(BuildParameters.create(someParameters).buildPath).toStrictEqual(
`${someParameters.buildsPath}/${someParameters.targetPlatform}`,
it('returns the build name', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildName: mockValue }),
);
});
describe('build file', () => {
it('returns the build file', () => {
expect(BuildParameters.create(someParameters).buildFile).toStrictEqual(
someParameters.buildName,
it('returns the build path', async () => {
const mockPath = 'somePath';
const mockPlatform = 'somePlatform';
const expectedBuildPath = `${mockPath}/${mockPlatform}`;
jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildPath: expectedBuildPath }),
);
});
it('returns the build file', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: mockValue }),
);
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s',
async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
);
});
},
);
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s',
(targetPlatform) => {
expect(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile,
).toStrictEqual(`${someParameters.buildName}.exe`);
},
);
test.each([Platform.types.Android])('appends apk for %s', (targetPlatform) => {
expect(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile,
).toStrictEqual(`${someParameters.buildName}.apk`);
});
});
it('returns the build method', () => {
expect(BuildParameters.create(someParameters).buildMethod).toStrictEqual(
someParameters.buildMethod,
test.each([Platform.types.Android])('appends apk for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
);
});
it('returns the custom parameters', () => {
expect(BuildParameters.create(someParameters).customParameters).toStrictEqual(
someParameters.customParameters,
it('returns the build method', async () => {
const mockValue = 'Namespace.ClassName.BuildMethod';
jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildMethod: mockValue }),
);
});
it('returns the custom parameters', async () => {
const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue';
jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ customParameters: mockValue }),
);
});
});

View File

@@ -1,3 +1,4 @@
import * as core from '@actions/core';
import fs from 'fs';
import Action from './action';
import Project from './project';
@@ -14,11 +15,11 @@ class Cache {
return;
}
// eslint-disable-next-line no-console
console.log(`
core.warning(`
Library folder does not exist.
Consider setting up caching to speed up your workflow
If this is not your first build.`);
Consider setting up caching to speed up your workflow,
if this is not your first build.
`);
}
}

View File

@@ -1,38 +1,54 @@
import Platform from './platform';
import Versioning from './versioning';
const core = require('@actions/core');
/**
* Input variables specified in workflows using "with" prop.
*
* Note that input is always passed as a string, even booleans.
*/
class Input {
static async getFromUser() {
// Input variables specified in workflows using "with" prop.
const version = core.getInput('unityVersion');
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
static get unityVersion() {
return core.getInput('unityVersion');
}
static get targetPlatform() {
return core.getInput('targetPlatform') || Platform.default;
}
static get projectPath() {
const rawProjectPath = core.getInput('projectPath') || '.';
const buildName = core.getInput('buildName') || targetPlatform;
const buildsPath = core.getInput('buildsPath') || 'build';
const buildMethod = core.getInput('buildMethod'); // processed in docker file
const versioningStrategy = core.getInput('versioning') || 'Semantic';
const specifiedVersion = core.getInput('version') || '';
const customParameters = core.getInput('customParameters') || '';
return rawProjectPath.replace(/\/$/, '');
}
// Sanitise input
const projectPath = rawProjectPath.replace(/\/$/, '');
static get buildName() {
return core.getInput('buildName') || this.targetPlatform;
}
// Parse input
const buildVersion = await Versioning.determineVersion(versioningStrategy, specifiedVersion);
static get buildsPath() {
return core.getInput('buildsPath') || 'build';
}
// Return validated input
return {
version,
targetPlatform,
projectPath,
buildName,
buildsPath,
buildMethod,
buildVersion,
customParameters,
};
static get buildMethod() {
return core.getInput('buildMethod'); // processed in docker file
}
static get versioningStrategy() {
return core.getInput('versioning') || 'Semantic';
}
static get specifiedVersion() {
return core.getInput('version') || '';
}
static get allowDirtyBuild() {
const input = core.getInput('allowDirtyBuild') || 'false';
return input === 'true' ? 'true' : 'false';
}
static get customParameters() {
return core.getInput('customParameters') || '';
}
}

View File

@@ -1,27 +1,151 @@
import Input from './input';
import Versioning from './versioning';
import * as core from '@actions/core';
const determineVersion = jest
.spyOn(Versioning, 'determineVersion')
.mockImplementation(() => '1.3.37');
import Input from './input';
import Platform from './platform';
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('Input', () => {
describe('getFromUser', () => {
it('does not throw', async () => {
await expect(Input.getFromUser()).resolves.not.toBeNull();
describe('unityVersion', () => {
it('returns the default value', () => {
expect(Input.unityVersion).toStrictEqual('');
});
it('returns an object', async () => {
await expect(typeof (await Input.getFromUser())).toStrictEqual('object');
it('takes input from the users workflow', () => {
const mockValue = '2020.4.99f9';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.unityVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('targetPlatform', () => {
it('returns the default value', () => {
expect(Input.targetPlatform).toStrictEqual(Platform.default);
});
it('calls version generator once', async () => {
await Input.getFromUser();
expect(determineVersion).toHaveBeenCalledTimes(1);
it('takes input from the users workflow', () => {
const mockValue = 'Android';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.targetPlatform).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('projectPath', () => {
it('returns the default value', () => {
expect(Input.projectPath).toStrictEqual('.');
});
it('takes input from the users workflow', () => {
const mockValue = 'customProjectPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.projectPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildName', () => {
it('returns the default value', () => {
expect(Input.buildName).toStrictEqual(Input.targetPlatform);
});
it('takes input from the users workflow', () => {
const mockValue = 'Build';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
it('takes special characters as input', () => {
const mockValue = '1ßúëld2';
jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
});
});
describe('buildsPath', () => {
it('returns the default value', () => {
expect(Input.buildsPath).toStrictEqual('build');
});
it('takes input from the users workflow', () => {
const mockValue = 'customBuildsPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildsPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildMethod', () => {
it('returns the default value', () => {
expect(Input.buildMethod).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = 'Namespace.ClassName.Method';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildMethod).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('versioningStrategy', () => {
it('returns the default value', () => {
expect(Input.versioningStrategy).toStrictEqual('Semantic');
});
it('takes input from the users workflow', () => {
const mockValue = 'Anything';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.versioningStrategy).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('specifiedVersion', () => {
it('returns the default value', () => {
expect(Input.specifiedVersion).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '1.33.7';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.specifiedVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('allowDirtyBuild', () => {
it('returns the default value', () => {
expect(Input.allowDirtyBuild).toStrictEqual('false');
});
it('returns true when string true is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
expect(Input.allowDirtyBuild).toStrictEqual('true');
expect(spy).toHaveBeenCalledTimes(1);
});
it('returns false when string false is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
expect(Input.allowDirtyBuild).toStrictEqual('false');
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('customParameters', () => {
it('returns the default value', () => {
expect(Input.customParameters).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '-imAFlag';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.customParameters).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -1,11 +1,10 @@
import * as core from '@actions/core';
import Input from './input';
import Unity from './unity';
import Action from './action';
class Project {
static get relativePath() {
// Todo - properly use Input for this.
const projectPath = core.getInput('projectPath') || '.';
const { projectPath } = Input;
return `${projectPath}`;
}

View File

@@ -19,22 +19,40 @@ class System {
},
};
const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options });
const showOutput = () => {
if (debug !== '') {
core.debug(debug);
}
if (debug !== '') {
core.debug(debug);
}
if (result !== '') {
core.info(result);
}
if (result !== '') {
core.info(result);
}
if (error !== '') {
core.warning(error);
}
};
if (exitCode !== 0) {
throw new Error(error);
}
const throwContextualError = (message) => {
let commandAsString = command;
if (Array.isArray(arguments_)) {
commandAsString += ` ${arguments_.join(' ')}`;
} else if (typeof arguments_ === 'string') {
commandAsString += ` ${arguments_}`;
}
if (error !== '') {
core.warning(error);
throw new Error(`Failed to run "${commandAsString}".\n ${message}`);
};
try {
const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options });
showOutput();
if (exitCode !== 0) {
throwContextualError(`Command returned non-zero exit code.\nError: ${error}`);
}
} catch (inCommandError) {
showOutput();
throwContextualError(`In-command error caught: ${inCommandError}`);
}
return result;

View File

@@ -1,9 +1,18 @@
import * as core from '@actions/core';
import NotImplementedException from './error/not-implemented-exception';
import ValidationError from './error/validation-error';
import Input from './input';
import System from './system';
export default class Versioning {
static get projectPath() {
return Input.projectPath;
}
static get isDirtyAllowed() {
return Input.allowDirtyBuild === 'true';
}
static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
}
@@ -79,7 +88,7 @@ export default class Versioning {
static async generateSemanticVersion() {
await this.fetch();
if (await this.isDirty()) {
if ((await this.isDirty()) && !this.isDirtyAllowed) {
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
}
@@ -137,10 +146,10 @@ export default class Versioning {
*/
static async fetch() {
try {
await System.run('git', ['fetch', '--unshallow']);
await this.git(['fetch', '--unshallow']);
} catch (error) {
core.warning(error);
await System.run('git', ['fetch']);
core.warning(`Fetch --unshallow caught: ${error}`);
await this.git(['fetch']);
}
}
@@ -153,21 +162,15 @@ export default class Versioning {
* identifies the current commit.
*/
static async getVersionDescription() {
return System.run('git', [
'describe',
'--long',
'--tags',
'--always',
'--debug',
`origin/${this.branch}`,
]);
const commitIsh = (await this.getTag()) ? 'HEAD' : `origin/${this.branch}`;
return this.git(['describe', '--long', '--tags', '--always', '--debug', commitIsh]);
}
/**
* Returns whether there are uncommitted changes that are not ignored.
*/
static async isDirty() {
const output = await System.run('git', ['status', '--porcelain']);
const output = await this.git(['status', '--porcelain']);
return output !== '';
}
@@ -176,7 +179,7 @@ export default class Versioning {
* Get the tag if there is one pointing at HEAD
*/
static async getTag() {
return System.run('git', ['tag', '--points-at', 'HEAD']);
return this.git(['tag', '--points-at', 'HEAD']);
}
/**
@@ -195,10 +198,23 @@ export default class Versioning {
/**
* Get the total number of commits on head.
*
* Note: HEAD should not be used, as it may be detached, resulting in an additional count.
*/
static async getTotalNumberOfCommits() {
const numberOfCommitsAsString = await System.run('git', ['rev-list', '--count', 'HEAD']);
const numberOfCommitsAsString = await this.git([
'rev-list',
'--count',
`origin/${this.branch}`,
]);
return Number.parseInt(numberOfCommitsAsString, 10);
}
/**
* Run git in the specified project path
*/
static async git(arguments_, options = {}) {
return System.run('git', arguments_, { cwd: this.projectPath, ...options });
}
}

View File

@@ -90,6 +90,16 @@ describe('Versioning', () => {
});
});
describe('isDirtyAllowed', () => {
it('does not throw', () => {
expect(() => Versioning.isDirtyAllowed).not.toThrow();
});
it('returns false by default', () => {
expect(Versioning.isDirtyAllowed).toStrictEqual(false);
});
});
describe('descriptionRegex', () => {
it('is a valid regex', () => {
expect(Versioning.descriptionRegex).toBeInstanceOf(RegExp);