Upgrade to use typescript instead of javascript and use game-ci images

* Upgrade to using typescript instead of javascript and use game-ci images instead of gableroux images

* Fix errors

* Update bash script

* Fix test

* Remove test

* Add versioning

* Update husky

* Update husky

* Update husky

* Update package.json

* Update husky and image-tag

* Use 'yarn lint-staged' instead of 'npx lint-staged'

* Update entrypoint.sh
This commit is contained in:
David Finol
2022-01-10 21:14:47 -06:00
committed by GitHub
parent 3d32abb6d9
commit a89f0fae68
46 changed files with 6565 additions and 3760 deletions

View File

@@ -1,21 +0,0 @@
import { Action, Docker, Input, ImageTag } from './model';
const core = require('@actions/core');
async function action() {
Action.checkCompatibility();
const { dockerfile, workspace, actionFolder } = Action;
const { unityVersion } = Input.getFromUser();
const baseImage = ImageTag.createForBase(unityVersion);
// Build docker image
const actionImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
// Run docker image
await Docker.run(actionImage, { workspace, unityVersion });
}
action().catch(error => {
core.setFailed(error.message);
});

22
src/index.ts Normal file
View File

@@ -0,0 +1,22 @@
import * as core from '@actions/core';
import { Action, Docker, ImageTag, Input } from './model';
async function run() {
try {
Action.checkCompatibility();
const { dockerfile, workspace, actionFolder } = Action;
const unityVersion = Input.unityVersion;
const baseImage = new ImageTag(unityVersion);
// Build docker image
const actionImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
// Run docker image
await Docker.run(actionImage, { workspace, unityVersion });
} catch (error: any) {
core.setFailed(error.message);
}
}
run();

View File

@@ -1,6 +1,6 @@
import path from 'path';
import fs from 'fs';
import Action from './action';
import fs from 'fs';
import path from 'path';
describe('Action', () => {
describe('compatibility check', () => {
@@ -14,16 +14,16 @@ describe('Action', () => {
});
it('returns the root folder of the action', () => {
const { rootFolder, name } = Action;
const { rootFolder, canonicalName } = Action;
expect(path.basename(rootFolder)).toStrictEqual(name);
expect(path.basename(rootFolder)).toStrictEqual(canonicalName);
expect(fs.existsSync(rootFolder)).toStrictEqual(true);
});
it('returns the action folder', () => {
const { actionFolder } = Action;
expect(path.basename(actionFolder)).toStrictEqual('action');
expect(path.basename(actionFolder)).toStrictEqual('dist');
expect(fs.existsSync(actionFolder)).toStrictEqual(true);
});

View File

@@ -1,48 +1,48 @@
import path from 'path';
class Action {
static get supportedPlatforms() {
const Action = {
get supportedPlatforms() {
return ['linux'];
}
},
static get isRunningLocally() {
get isRunningLocally() {
return process.env.RUNNER_WORKSPACE === undefined;
}
},
static get isRunningFromSource() {
get isRunningFromSource() {
return path.basename(__dirname) === 'model';
}
},
static get name() {
get canonicalName() {
return 'unity-activate';
}
},
static get rootFolder() {
get rootFolder() {
if (Action.isRunningFromSource) {
return path.dirname(path.dirname(path.dirname(__filename)));
}
return path.dirname(path.dirname(__filename));
}
},
static get actionFolder() {
return `${Action.rootFolder}/action`;
}
get actionFolder() {
return `${Action.rootFolder}/dist`;
},
static get dockerfile() {
get dockerfile() {
return `${Action.actionFolder}/Dockerfile`;
}
},
static get workspace() {
get workspace() {
return process.env.GITHUB_WORKSPACE;
}
},
static checkCompatibility() {
checkCompatibility() {
const currentPlatform = process.platform;
if (!Action.supportedPlatforms.includes(currentPlatform)) {
throw new Error(`Currently ${currentPlatform}-platform is not supported`);
}
}
}
},
};
export default Action;

View File

@@ -1,20 +0,0 @@
import Action from './action';
import Docker from './docker';
import ImageTag from './image-tag';
describe('Docker', () => {
it('builds', async () => {
const path = Action.actionFolder;
const dockerfile = `${path}/Dockerfile`;
const baseImage = new ImageTag({
repository: '',
name: 'alpine',
version: '3',
});
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
expect(tag).toBeInstanceOf(ImageTag);
expect(tag.toString()).toStrictEqual('unity-action:3');
}, 240000);
});

25
src/model/docker.test.ts Normal file
View File

@@ -0,0 +1,25 @@
import Action from './action';
import Docker from './docker';
import ImageTag from './image-tag';
describe('Docker', () => {
it.skip('builds', async () => {
const path = Action.actionFolder;
const dockerfile = `${path}/Dockerfile`;
const baseImage = new ImageTag('2019.2.11f1');
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
expect(tag).toBeInstanceOf(ImageTag);
expect(tag.toString()).toStrictEqual('unity-builder:3');
}, 240_000);
it.skip('runs', async () => {
const image = 'unity-builder:2019.2.11f1-webgl';
const parameters = {
workspace: Action.rootFolder,
projectPath: `${Action.rootFolder}/test-project`,
buildName: 'someBuildName',
buildsPath: 'build',
method: '',
};
await Docker.run(image, parameters);
});
});

View File

@@ -1,23 +1,23 @@
import { exec } from '@actions/exec';
import ImageTag from './image-tag';
import { exec } from '@actions/exec';
class Docker {
static async build(buildParameters, silent = false) {
const Docker = {
async build(buildParameters, silent = false) {
const { path, dockerfile, baseImage } = buildParameters;
const { version } = baseImage;
const tag = ImageTag.createForAction(version);
const tag = new ImageTag(version);
const command = `docker build ${path} \
--file ${dockerfile} \
--build-arg IMAGE=${baseImage} \
--tag ${tag}`;
await exec(command, null, { silent });
await exec(command, undefined, { silent });
return tag;
}
},
static async run(image, parameters, silent = false) {
async run(image, parameters, silent = false) {
const { unityVersion, workspace } = parameters;
const command = `docker run \
@@ -50,8 +50,8 @@ class Docker {
--volume "${workspace}":"/github/workspace" \
${image}`;
await exec(command, null, { silent });
}
}
await exec(command, undefined, { silent });
},
};
export default Docker;

View File

@@ -1,41 +0,0 @@
import { trimStart } from 'lodash-es';
class ImageTag {
static createForBase(version) {
const repository = 'gableroux';
const name = 'unity3d';
return new this({ repository, name, version });
}
static createForAction(version) {
const repository = '';
const name = 'unity-action';
return new this({ repository, name, version });
}
constructor({ repository = '', name, version }) {
if (!ImageTag.versionPattern.test(version)) {
throw new Error(`Invalid version "${version}".`);
}
Object.assign(this, { repository, name, version });
}
static get versionPattern() {
return /^20\d{2}\.\d\.\w{3,4}|3$/;
}
get tag() {
return this.version;
}
get image() {
return trimStart(`${this.repository}/${this.name}`, '/');
}
toString() {
return `${this.image}:${this.tag}`;
}
}
export default ImageTag;

View File

@@ -1,38 +0,0 @@
import ImageTag from './image-tag';
describe('UnityImageVersion', () => {
describe('constructor', () => {
const some = {
name: 'someName',
version: '2020.0.00f0',
};
it('can be called', () => {
expect(() => new ImageTag(some)).not.toThrow();
});
it('accepts parameters and sets the right properties', () => {
const image = new ImageTag(some);
expect(image.repository).toStrictEqual('');
expect(image.name).toStrictEqual(some.name);
expect(image.version).toStrictEqual(some.version);
});
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
expect(() => new ImageTag({ version })).not.toThrow();
});
test.each(['some version', '', 1, null])('throws for incorrect versions %p', version => {
expect(() => new ImageTag({ version })).toThrow();
});
});
describe('toString', () => {
it('returns the correct version', () => {
const image = ImageTag.createForBase('2099.1.1111');
expect(image.toString()).toStrictEqual(`gableroux/unity3d:2099.1.1111`);
});
});
});

View File

@@ -0,0 +1,22 @@
import ImageTag from './image-tag';
describe('ImageTag', () => {
describe('constructor', () => {
const unityVersion = '2020.0.00f0';
it('can be called', () => {
expect(() => new ImageTag(unityVersion)).not.toThrow();
});
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', (version) => {
expect(() => new ImageTag(version)).not.toThrow();
});
});
describe('toString', () => {
it('returns the correct version', () => {
const image = new ImageTag('2099.1.1111');
expect(image.toString()).toStrictEqual(`unityci/editor:2099.1.1111-linux-il2cpp-0`);
});
});
});

125
src/model/image-tag.ts Normal file
View File

@@ -0,0 +1,125 @@
import Platform from './platform';
class ImageTag {
public repository: string;
public name: string;
public version: string;
public platform: any;
public builderPlatform: string;
public customImage: any;
constructor(unityVersion: string) {
if (!ImageTag.versionPattern.test(unityVersion)) {
throw new Error(`Invalid version "${unityVersion}".`);
}
const builderPlatform = ImageTag.getTargetPlatformToImageSuffixMap(
Platform.types.StandaloneLinux64,
unityVersion,
);
this.repository = 'unityci';
this.name = 'editor';
this.version = unityVersion;
this.platform = Platform.types.StandaloneLinux64;
this.builderPlatform = builderPlatform;
this.customImage = '';
}
static get versionPattern() {
return /^20\d{2}\.\d\.\w{3,4}|3$/;
}
static get imageSuffixes() {
return {
generic: '',
webgl: 'webgl',
mac: 'mac-mono',
windows: 'windows-mono',
linux: 'base',
linuxIl2cpp: 'linux-il2cpp',
android: 'android',
ios: 'ios',
facebook: 'facebook',
};
}
static getTargetPlatformToImageSuffixMap(platform, version) {
const { generic, webgl, mac, windows, linux, linuxIl2cpp, android, ios, facebook } =
ImageTag.imageSuffixes;
const [major, minor] = version.split('.').map((digit) => Number(digit));
// @see: https://docs.unity3d.com/ScriptReference/BuildTarget.html
switch (platform) {
case Platform.types.StandaloneOSX:
return mac;
case Platform.types.StandaloneWindows:
return windows;
case Platform.types.StandaloneWindows64:
return windows;
case Platform.types.StandaloneLinux64: {
// Unity versions before 2019.3 do not support il2cpp
if (major >= 2020 || (major === 2019 && minor >= 3)) {
return linuxIl2cpp;
}
return linux;
}
case Platform.types.iOS:
return ios;
case Platform.types.Android:
return android;
case Platform.types.WebGL:
return webgl;
case Platform.types.WSAPlayer:
return windows;
case Platform.types.PS4:
return windows;
case Platform.types.XboxOne:
return windows;
case Platform.types.tvOS:
return windows;
case Platform.types.Switch:
return windows;
// Unsupported
case Platform.types.Lumin:
return windows;
case Platform.types.BJM:
return windows;
case Platform.types.Stadia:
return windows;
case Platform.types.Facebook:
return facebook;
case Platform.types.NoTarget:
return generic;
// Test specific
case Platform.types.Test:
return generic;
default:
throw new Error(`
Platform must be one of the ones described in the documentation.
"${platform}" is currently not supported.`);
}
}
get tag() {
return `${this.version}-${this.builderPlatform}`.replace(/-+$/, '');
}
get image() {
return `${this.repository}/${this.name}`.replace(/^\/+/, '');
}
toString() {
const { image, tag, customImage } = this;
if (customImage && customImage !== '') {
return customImage;
}
const dockerRepoVersion = 0;
return `${image}:${tag}-${dockerRepoVersion}`;
}
}
export default ImageTag;

View File

@@ -1,6 +0,0 @@
import Action from './action';
import Docker from './docker';
import Input from './input';
import ImageTag from './image-tag';
export { Action, Docker, Input, ImageTag };

View File

@@ -1,7 +1,7 @@
import * as Index from '.';
describe('Index', () => {
test.each(['Action', 'Docker', 'ImageTag', 'Input'])('exports %s', exportedModule => {
expect(typeof Index[exportedModule]).toStrictEqual('function');
test.each(['Action', 'Docker', 'ImageTag', 'Input'])('exports %s', (exportedModule) => {
expect(Index[exportedModule]).toBeDefined();
});
});

5
src/model/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export { default as Action } from './action';
export { default as Input } from './input';
export { default as Docker } from './docker';
export { default as ImageTag } from './image-tag';

View File

@@ -1,15 +0,0 @@
const core = require('@actions/core');
class Input {
static getFromUser() {
// Input variables specified in workflow using "with" prop.
const unityVersion = core.getInput('unityVersion') || '2019.2.11f1';
// Return sanitised input
return {
unityVersion,
};
}
}
export default Input;

View File

@@ -1,13 +0,0 @@
import Input from './input';
describe('Input', () => {
describe('getFromUser', () => {
it('does not throw', () => {
expect(() => Input.getFromUser()).not.toThrow();
});
it('returns an object', () => {
expect(typeof Input.getFromUser()).toStrictEqual('object');
});
});
});

18
src/model/input.test.ts Normal file
View File

@@ -0,0 +1,18 @@
import * as core from '@actions/core';
import Input from './input';
describe('Input', () => {
describe('unityVersion', () => {
it('returns the default value', () => {
expect(Input.unityVersion).toStrictEqual('2019.2.11f1');
});
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);
});
});
});

9
src/model/input.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as core from '@actions/core';
const Input = {
get unityVersion() {
return core.getInput('unityVersion') || '2019.2.11f1';
},
};
export default Input;

View File

@@ -0,0 +1,37 @@
import Platform from './platform';
describe('Platform', () => {
describe('default', () => {
it('does not throw', () => {
expect(() => Platform.default).not.toThrow();
});
it('returns a string', () => {
expect(typeof Platform.default).toStrictEqual('string');
});
it('returns a platform', () => {
expect(Object.values(Platform.types)).toContain(Platform.default);
});
});
describe('isWindows', () => {
it('returns true for windows', () => {
expect(Platform.isWindows(Platform.types.StandaloneWindows64)).toStrictEqual(true);
});
it('returns false for MacOS', () => {
expect(Platform.isWindows(Platform.types.StandaloneOSX)).toStrictEqual(false);
});
});
describe('isAndroid', () => {
it('returns true for Android', () => {
expect(Platform.isAndroid(Platform.types.Android)).toStrictEqual(true);
});
it('returns false for Windows', () => {
expect(Platform.isAndroid(Platform.types.StandaloneWindows64)).toStrictEqual(false);
});
});
});

51
src/model/platform.ts Normal file
View File

@@ -0,0 +1,51 @@
const Platform = {
get default() {
return Platform.types.StandaloneWindows64;
},
get types() {
return {
StandaloneOSX: 'StandaloneOSX',
StandaloneWindows: 'StandaloneWindows',
StandaloneWindows64: 'StandaloneWindows64',
StandaloneLinux64: 'StandaloneLinux64',
iOS: 'iOS',
Android: 'Android',
WebGL: 'WebGL',
WSAPlayer: 'WSAPlayer',
PS4: 'PS4',
XboxOne: 'XboxOne',
tvOS: 'tvOS',
Switch: 'Switch',
// Unsupported
Lumin: 'Lumin',
BJM: 'BJM',
Stadia: 'Stadia',
Facebook: 'Facebook',
NoTarget: 'NoTarget',
// Test specific
Test: 'Test',
};
},
isWindows(platform) {
switch (platform) {
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
return true;
default:
return false;
}
},
isAndroid(platform) {
switch (platform) {
case Platform.types.Android:
return true;
default:
return false;
}
},
};
export default Platform;