mirror of
https://github.com/game-ci/unity-activate
synced 2026-02-04 10:19:14 +08:00
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:
21
src/index.js
21
src/index.js
@@ -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
22
src/index.ts
Normal 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();
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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
25
src/model/docker.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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`);
|
||||
});
|
||||
});
|
||||
});
|
||||
22
src/model/image-tag.test.ts
Normal file
22
src/model/image-tag.test.ts
Normal 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
125
src/model/image-tag.ts
Normal 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;
|
||||
@@ -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 };
|
||||
@@ -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
5
src/model/index.ts
Normal 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';
|
||||
@@ -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;
|
||||
@@ -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
18
src/model/input.test.ts
Normal 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
9
src/model/input.ts
Normal 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;
|
||||
37
src/model/platform.test.ts
Normal file
37
src/model/platform.test.ts
Normal 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
51
src/model/platform.ts
Normal 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;
|
||||
Reference in New Issue
Block a user