mirror of
https://github.com/game-ci/unity-test-runner.git
synced 2026-02-07 14:29:07 +08:00
Merge branch 'main' of https://github.com/game-ci/unity-test-runner into game-ci-main
This commit is contained in:
@@ -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-test-runner';
|
||||
}
|
||||
},
|
||||
|
||||
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,25 +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 image = new ImageTag({
|
||||
repository: '',
|
||||
name: 'ubuntu',
|
||||
version: 'impish',
|
||||
});
|
||||
|
||||
const baseImage = {
|
||||
toString: () => image.toString().slice(0, image.toString().lastIndexOf('-base-0')),
|
||||
version: image.version,
|
||||
};
|
||||
|
||||
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
|
||||
|
||||
expect(tag).toBeInstanceOf(ImageTag);
|
||||
expect(tag.toString()).toStrictEqual('unity-action:impish-base-0');
|
||||
}, 240000);
|
||||
});
|
||||
30
src/model/docker.test.ts
Normal file
30
src/model/docker.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
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({
|
||||
repository: '',
|
||||
name: 'alpine',
|
||||
version: '3',
|
||||
platform: 'Test',
|
||||
});
|
||||
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,12 +1,12 @@
|
||||
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} \
|
||||
@@ -15,19 +15,20 @@ class Docker {
|
||||
await exec(command, undefined, { silent });
|
||||
|
||||
return tag;
|
||||
}
|
||||
},
|
||||
|
||||
static async run(image, parameters, silent = false) {
|
||||
async run(image, parameters, silent = false) {
|
||||
const {
|
||||
unityVersion,
|
||||
workspace,
|
||||
projectPath,
|
||||
customParameters,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
sshAgent,
|
||||
packageMode,
|
||||
gitPrivateToken,
|
||||
githubToken,
|
||||
} = parameters;
|
||||
|
||||
@@ -41,6 +42,7 @@ class Docker {
|
||||
--env UNITY_SERIAL \
|
||||
--env UNITY_VERSION="${unityVersion}" \
|
||||
--env PROJECT_PATH="${projectPath}" \
|
||||
--env CUSTOM_PARAMETERS="${customParameters}" \
|
||||
--env TEST_MODE="${testMode}" \
|
||||
--env ARTIFACTS_PATH="${artifactsPath}" \
|
||||
--env CUSTOM_PARAMETERS="${customParameters}" \
|
||||
@@ -60,6 +62,7 @@ class Docker {
|
||||
--env RUNNER_TOOL_CACHE \
|
||||
--env RUNNER_TEMP \
|
||||
--env RUNNER_WORKSPACE \
|
||||
--env GIT_PRIVATE_TOKEN="${gitPrivateToken}" \
|
||||
${sshAgent ? '--env SSH_AUTH_SOCK=/ssh-agent' : ''} \
|
||||
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
|
||||
--volume "/home/runner/work/_temp/_github_home":"/root" \
|
||||
@@ -72,7 +75,7 @@ class Docker {
|
||||
${image}`;
|
||||
|
||||
await exec(command, undefined, { silent });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default Docker;
|
||||
@@ -1,45 +0,0 @@
|
||||
import { trimStart } from 'lodash-es';
|
||||
|
||||
class ImageTag {
|
||||
static createForBase({ version, customImage }) {
|
||||
const repository = 'unityci';
|
||||
const name = 'editor';
|
||||
return new this({ repository, name, version, customImage });
|
||||
}
|
||||
|
||||
static createForAction(version) {
|
||||
const repository = '';
|
||||
const name = 'unity-action';
|
||||
return new this({ repository, name, version });
|
||||
}
|
||||
|
||||
constructor({ repository = '', name, version, customImage }) {
|
||||
if (!ImageTag.versionPattern.test(version)) {
|
||||
throw new Error(`Invalid version "${version}".`);
|
||||
}
|
||||
|
||||
Object.assign(this, { repository, name, version, customImage });
|
||||
}
|
||||
|
||||
static get versionPattern() {
|
||||
return /^(20\d{2}\.\d\.\w{3,4}|3)|impish$/;
|
||||
}
|
||||
|
||||
get tag() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
get image() {
|
||||
return trimStart(`${this.repository}/${this.name}`, '/');
|
||||
}
|
||||
|
||||
toString() {
|
||||
if (this.customImage && this.customImage !== '') {
|
||||
return this.customImage;
|
||||
}
|
||||
|
||||
return `${this.image}:${this.tag}-base-0`;
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageTag;
|
||||
@@ -1,47 +0,0 @@
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
describe('ImageTag', () => {
|
||||
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, undefined])('throws for incorrect versions %p', version => {
|
||||
expect(() => new ImageTag({ version })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('returns the correct version', () => {
|
||||
const image = ImageTag.createForBase({ version: '2099.1.1111' });
|
||||
|
||||
expect(image.toString()).toStrictEqual(`unityci/editor:2099.1.1111-base-0`);
|
||||
});
|
||||
|
||||
it('returns customImage if given', () => {
|
||||
const image = ImageTag.createForBase({
|
||||
version: '2099.1.1111',
|
||||
customImage: 'unityci/editor:2099.1.1111-base-0',
|
||||
});
|
||||
|
||||
expect(image.toString()).toStrictEqual(image.customImage);
|
||||
});
|
||||
});
|
||||
});
|
||||
72
src/model/image-tag.test.ts
Normal file
72
src/model/image-tag.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
describe('ImageTag', () => {
|
||||
const some = {
|
||||
repository: 'test1',
|
||||
name: 'test2',
|
||||
version: '2099.9.f9f9',
|
||||
platform: 'Test',
|
||||
builderPlatform: '',
|
||||
};
|
||||
|
||||
const defaults = {
|
||||
repository: 'unityci',
|
||||
name: 'editor',
|
||||
image: 'unityci/editor',
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('can be called', () => {
|
||||
const { platform } = some;
|
||||
expect(() => new ImageTag({ platform })).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts parameters and sets the right properties', () => {
|
||||
const image = new ImageTag(some);
|
||||
|
||||
expect(image.repository).toStrictEqual(some.repository);
|
||||
expect(image.name).toStrictEqual(some.name);
|
||||
expect(image.version).toStrictEqual(some.version);
|
||||
expect(image.platform).toStrictEqual(some.platform);
|
||||
expect(image.builderPlatform).toStrictEqual(some.builderPlatform);
|
||||
});
|
||||
|
||||
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
|
||||
expect(() => new ImageTag({ version, platform: some.platform })).not.toThrow();
|
||||
});
|
||||
|
||||
test.each(['some version', '', 1])('throws for incorrect versions %p', version => {
|
||||
const { platform } = some;
|
||||
expect(() => new ImageTag({ version, platform })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('returns the correct version', () => {
|
||||
const image = new ImageTag({ version: '2099.1.1111', platform: some.platform });
|
||||
|
||||
expect(image.toString()).toStrictEqual(`${defaults.image}:2099.1.1111-0`);
|
||||
});
|
||||
it('returns customImage if given', () => {
|
||||
const image = new ImageTag({
|
||||
version: '2099.1.1111',
|
||||
platform: some.platform,
|
||||
customImage: `${defaults.image}:2099.1.1111@347598437689743986`,
|
||||
});
|
||||
|
||||
expect(image.toString()).toStrictEqual(image.customImage);
|
||||
});
|
||||
|
||||
it('returns the specific build platform', () => {
|
||||
const image = new ImageTag({ version: '2019.2.11f1', platform: 'WebGL' });
|
||||
|
||||
expect(image.toString()).toStrictEqual(`${defaults.image}:2019.2.11f1-webgl-0`);
|
||||
});
|
||||
|
||||
it('returns no specific build platform for generic targetPlatforms', () => {
|
||||
const image = new ImageTag({ platform: 'NoTarget' });
|
||||
|
||||
expect(image.toString()).toStrictEqual(`${defaults.image}:2019.2.11f1-0`);
|
||||
});
|
||||
});
|
||||
});
|
||||
130
src/model/image-tag.ts
Normal file
130
src/model/image-tag.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
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(imageProperties) {
|
||||
const {
|
||||
repository = 'unityci',
|
||||
name = 'editor',
|
||||
version = '2019.2.11f1',
|
||||
platform = Platform.types.StandaloneLinux64,
|
||||
customImage,
|
||||
} = imageProperties;
|
||||
|
||||
if (!ImageTag.versionPattern.test(version)) {
|
||||
throw new Error(`Invalid version "${version}".`);
|
||||
}
|
||||
|
||||
const builderPlatform = ImageTag.getTargetPlatformToImageSuffixMap(platform, version);
|
||||
|
||||
this.repository = repository;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.platform = platform;
|
||||
this.builderPlatform = builderPlatform;
|
||||
this.customImage = 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,8 +0,0 @@
|
||||
import Action from './action';
|
||||
import Docker from './docker';
|
||||
import Input from './input';
|
||||
import ImageTag from './image-tag';
|
||||
import Output from './output';
|
||||
import ResultsCheck from './results-check';
|
||||
|
||||
export { Action, Docker, Input, ImageTag, Output, ResultsCheck };
|
||||
@@ -4,7 +4,7 @@ describe('Index', () => {
|
||||
test.each(['Action', 'Docker', 'ImageTag', 'Input', 'Output', 'ResultsCheck'])(
|
||||
'exports %s',
|
||||
exportedModule => {
|
||||
expect(typeof Index[exportedModule]).toStrictEqual('function');
|
||||
expect(Index[exportedModule]).toBeDefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
6
src/model/index.ts
Normal file
6
src/model/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { default as Action } from './action';
|
||||
export { default as Docker } from './docker';
|
||||
export { default as ImageTag } from './image-tag';
|
||||
export { default as Input } from './input';
|
||||
export { default as Output } from './output';
|
||||
export { default as ResultsCheck } from './results-check';
|
||||
@@ -1,45 +1,45 @@
|
||||
import { getInput } from '@actions/core';
|
||||
import { includes } from 'lodash-es';
|
||||
import UnityVersionParser from './unity-version-parser';
|
||||
import { getInput } from '@actions/core';
|
||||
|
||||
class Input {
|
||||
static get testModes() {
|
||||
const Input = {
|
||||
get testModes() {
|
||||
return ['all', 'playmode', 'editmode'];
|
||||
}
|
||||
},
|
||||
|
||||
static isValidFolderName(folderName) {
|
||||
const validFolderName = new RegExp(/^(\.|\.\/)?(\.?[\w~]+([_-]?[\w~]+)*\/?)*$/);
|
||||
isValidFolderName(folderName) {
|
||||
const validFolderName = new RegExp(/^(\.|\.\/)?(\.?[\w~]+([ _-]?[\w~]+)*\/?)*$/);
|
||||
|
||||
return validFolderName.test(folderName);
|
||||
}
|
||||
},
|
||||
|
||||
static getFromUser() {
|
||||
getFromUser() {
|
||||
// Input variables specified in workflow using "with" prop.
|
||||
const rawUnityVersion = getInput('unityVersion') || 'auto';
|
||||
const customImage = getInput('customImage') || '';
|
||||
const testMode = (getInput('testMode') || 'all').toLowerCase();
|
||||
const rawProjectPath = getInput('projectPath') || '.';
|
||||
const customParameters = getInput('customParameters') || '';
|
||||
const testMode = (getInput('testMode') || 'all').toLowerCase();
|
||||
const rawArtifactsPath = getInput('artifactsPath') || 'artifacts';
|
||||
const rawUseHostNetwork = getInput('useHostNetwork') || 'false';
|
||||
const customParameters = getInput('customParameters') || '';
|
||||
const sshAgent = getInput('sshAgent') || '';
|
||||
const gitPrivateToken = getInput('gitPrivateToken') || '';
|
||||
const githubToken = getInput('githubToken') || '';
|
||||
const checkName = getInput('checkName') || 'Test Results';
|
||||
const rawPackageMode = getInput('packageMode') || 'false';
|
||||
|
||||
// Validate input
|
||||
if (!includes(this.testModes, testMode)) {
|
||||
if (!this.testModes.includes(testMode)) {
|
||||
throw new Error(`Invalid testMode ${testMode}`);
|
||||
}
|
||||
|
||||
if (!this.isValidFolderName(rawArtifactsPath)) {
|
||||
throw new Error(`Invalid artifactsPath "${rawArtifactsPath}"`);
|
||||
}
|
||||
|
||||
if (!this.isValidFolderName(rawProjectPath)) {
|
||||
throw new Error(`Invalid projectPath "${rawProjectPath}"`);
|
||||
}
|
||||
|
||||
if (!this.isValidFolderName(rawArtifactsPath)) {
|
||||
throw new Error(`Invalid artifactsPath "${rawArtifactsPath}"`);
|
||||
}
|
||||
|
||||
if (rawUseHostNetwork !== 'true' && rawUseHostNetwork !== 'false') {
|
||||
throw new Error(`Invalid useHostNetwork "${rawUseHostNetwork}"`);
|
||||
}
|
||||
@@ -61,16 +61,17 @@ class Input {
|
||||
unityVersion,
|
||||
customImage,
|
||||
projectPath,
|
||||
customParameters,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
sshAgent,
|
||||
gitPrivateToken,
|
||||
githubToken,
|
||||
checkName,
|
||||
packageMode,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default Input;
|
||||
@@ -1,9 +0,0 @@
|
||||
const core = require('@actions/core');
|
||||
|
||||
class Output {
|
||||
static async setArtifactsPath(artifactsPath) {
|
||||
await core.setOutput('artifactsPath', artifactsPath);
|
||||
}
|
||||
}
|
||||
|
||||
export default Output;
|
||||
@@ -3,7 +3,7 @@ import Output from './output';
|
||||
describe('Output', () => {
|
||||
describe('setArtifactsPath', () => {
|
||||
it('does not throw', async () => {
|
||||
await expect(Output.setArtifactsPath()).resolves.not.toThrow();
|
||||
await expect(Output.setArtifactsPath('')).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
9
src/model/output.ts
Normal file
9
src/model/output.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as core from '@actions/core';
|
||||
|
||||
const Output = {
|
||||
async setArtifactsPath(artifactsPath) {
|
||||
await core.setOutput('artifactsPath', artifactsPath);
|
||||
},
|
||||
};
|
||||
|
||||
export default Output;
|
||||
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;
|
||||
@@ -3,7 +3,7 @@ import ResultsCheck from './results-check';
|
||||
describe('ResultsCheck', () => {
|
||||
describe('createCheck', () => {
|
||||
it('throws for missing input', () => {
|
||||
expect(() => ResultsCheck.createCheck('', '', '')).rejects.toEqual(Error);
|
||||
expect(() => ResultsCheck.createCheck('', '', '')).rejects;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as github from '@actions/github';
|
||||
import Handlebars from 'handlebars';
|
||||
import ResultsParser from './results-parser';
|
||||
import { RunMeta } from './ts/results-meta.ts';
|
||||
import { RunMeta } from './results-meta';
|
||||
import path from 'path';
|
||||
|
||||
class ResultsCheck {
|
||||
static async createCheck(artifactsPath, githubToken, checkName) {
|
||||
const ResultsCheck = {
|
||||
async createCheck(artifactsPath, githubToken, checkName) {
|
||||
// Validate input
|
||||
if (!fs.existsSync(artifactsPath) || !githubToken || !checkName) {
|
||||
throw new Error(
|
||||
@@ -16,7 +16,7 @@ class ResultsCheck {
|
||||
}
|
||||
|
||||
// Parse all results files
|
||||
const runs = [];
|
||||
const runs: RunMeta[] = [];
|
||||
const files = fs.readdirSync(artifactsPath);
|
||||
await Promise.all(
|
||||
files.map(async filepath => {
|
||||
@@ -30,16 +30,16 @@ class ResultsCheck {
|
||||
|
||||
// Combine all results into a single run summary
|
||||
const runSummary = new RunMeta(checkName);
|
||||
runs.forEach(run => {
|
||||
for (const run of runs) {
|
||||
runSummary.total += run.total;
|
||||
runSummary.passed += run.passed;
|
||||
runSummary.skipped += run.skipped;
|
||||
runSummary.failed += run.failed;
|
||||
runSummary.duration += run.duration;
|
||||
run.suites.forEach(suite => {
|
||||
for (const suite of run.suites) {
|
||||
runSummary.addTests(suite.tests);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Log
|
||||
core.info('=================');
|
||||
@@ -70,9 +70,9 @@ class ResultsCheck {
|
||||
// Call GitHub API
|
||||
await ResultsCheck.requestGitHubCheck(githubToken, checkName, output);
|
||||
return runSummary.failed;
|
||||
}
|
||||
},
|
||||
|
||||
static async requestGitHubCheck(githubToken, checkName, output) {
|
||||
async requestGitHubCheck(githubToken, checkName, output) {
|
||||
const pullRequest = github.context.payload.pull_request;
|
||||
const headSha = (pullRequest && pullRequest.head.sha) || github.context.sha;
|
||||
|
||||
@@ -87,18 +87,18 @@ class ResultsCheck {
|
||||
};
|
||||
|
||||
const octokit = github.getOctokit(githubToken);
|
||||
await octokit.checks.create(createCheckRequest);
|
||||
}
|
||||
await octokit.rest.checks.create(createCheckRequest);
|
||||
},
|
||||
|
||||
static async renderSummary(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/../views/results-check-summary.hbs`, runMetas);
|
||||
}
|
||||
async renderSummary(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/results-check-summary.hbs`, runMetas);
|
||||
},
|
||||
|
||||
static async renderDetails(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/../views/results-check-details.hbs`, runMetas);
|
||||
}
|
||||
async renderDetails(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/results-check-details.hbs`, runMetas);
|
||||
},
|
||||
|
||||
static async render(viewPath, runMetas) {
|
||||
async render(viewPath, runMetas) {
|
||||
Handlebars.registerHelper('indent', toIndent =>
|
||||
toIndent
|
||||
.split('\n')
|
||||
@@ -114,7 +114,7 @@ class ResultsCheck {
|
||||
allowProtoPropertiesByDefault: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default ResultsCheck;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { components } from '@octokit/openapi-types/dist-types/generated/types';
|
||||
import { components } from '@octokit/openapi-types';
|
||||
|
||||
export function timeHelper(seconds: number): string {
|
||||
return `${seconds.toFixed(3)}s`;
|
||||
@@ -42,9 +42,9 @@ export class RunMeta extends Meta {
|
||||
}
|
||||
|
||||
addTests(testSuite: TestMeta[]): void {
|
||||
testSuite.forEach(test => {
|
||||
for (const test of testSuite) {
|
||||
this.addTest(test);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addTest(test: TestMeta): void {
|
||||
@@ -93,6 +93,8 @@ export class TestMeta extends Meta {
|
||||
constructor(suite: string, title: string) {
|
||||
super(title);
|
||||
this.suite = suite;
|
||||
this.result = undefined;
|
||||
this.duration = Number.NaN;
|
||||
}
|
||||
|
||||
isSkipped(): boolean {
|
||||
@@ -104,9 +106,7 @@ export class TestMeta extends Meta {
|
||||
}
|
||||
|
||||
get summary(): string {
|
||||
const dPart = this.isSkipped()
|
||||
? ''
|
||||
: ` in ${timeHelper(this.duration)}`;
|
||||
const dPart = this.isSkipped() ? '' : ` in ${timeHelper(this.duration)}`;
|
||||
return `${this.mark} **${this.title}** - ${this.result}${dPart}`;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as xmljs from 'xml-js';
|
||||
import * as fs from 'fs';
|
||||
import * as xmljs from 'xml-js';
|
||||
import ResultsParser from './results-parser';
|
||||
import { TestMeta } from './results-meta';
|
||||
|
||||
describe('ResultsParser', () => {
|
||||
describe('parseResults', () => {
|
||||
it('throws for missing file', () => {
|
||||
expect(() => ResultsParser.parseResults('')).rejects.toEqual(Error);
|
||||
expect(() => ResultsParser.parseResults('')).rejects;
|
||||
});
|
||||
|
||||
it('parses editmode-results.xml', () => {
|
||||
@@ -19,14 +20,14 @@ describe('ResultsParser', () => {
|
||||
|
||||
describe('convertResults', () => {
|
||||
it('converts editmode-results.xml', () => {
|
||||
const file = fs.readFileSync('./artifacts/editmode-results.xml');
|
||||
const file = fs.readFileSync('./artifacts/editmode-results.xml', 'utf8');
|
||||
const filedata = xmljs.xml2js(file, { compact: true });
|
||||
const result = ResultsParser.convertResults('editmode-results.xml', filedata);
|
||||
expect(result.suites.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('converts playmode-results.xml', () => {
|
||||
const file = fs.readFileSync('./artifacts/playmode-results.xml');
|
||||
const file = fs.readFileSync('./artifacts/playmode-results.xml', 'utf8');
|
||||
const filedata = xmljs.xml2js(file, { compact: true });
|
||||
const result = ResultsParser.convertResults('playmode-results.xml', filedata);
|
||||
expect(result.suites.length).toEqual(3);
|
||||
@@ -53,27 +54,9 @@ describe('ResultsParser', () => {
|
||||
const result = ResultsParser.convertSuite(targetSuite);
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Inner Suite Full Name',
|
||||
title: 'testC',
|
||||
},
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Suite Full Name',
|
||||
title: 'testA',
|
||||
},
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Suite Full Name',
|
||||
title: 'testB',
|
||||
},
|
||||
new TestMeta('Inner Suite Full Name', 'testC'),
|
||||
new TestMeta('Suite Full Name', 'testA'),
|
||||
new TestMeta('Suite Full Name', 'testB'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -149,7 +132,7 @@ describe('ResultsParser', () => {
|
||||
expect(result.annotation).toBeUndefined();
|
||||
});
|
||||
|
||||
test('prepare annotation', () => {
|
||||
test('prepare annotation without console output', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
@@ -179,6 +162,40 @@ describe('ResultsParser', () => {
|
||||
title: 'Test Full Name',
|
||||
});
|
||||
});
|
||||
|
||||
test('prepare annotation with console output', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
fullname: 'Test Full Name',
|
||||
duration: '3.14',
|
||||
},
|
||||
output: {
|
||||
_cdata: '[Warning] This is a warning',
|
||||
},
|
||||
failure: {
|
||||
message: { _cdata: 'Message CDATA' },
|
||||
'stack-trace': {
|
||||
_cdata:
|
||||
'at Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.suite).toBe('Test Suite');
|
||||
expect(result.title).toBe('Test Name');
|
||||
expect(result.duration).toBe(3.14);
|
||||
expect(result.annotation).toMatchObject({
|
||||
annotation_level: 'failure',
|
||||
end_line: 10,
|
||||
message: 'Message CDATA',
|
||||
path: '/github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs',
|
||||
raw_details:
|
||||
'[Warning] This is a warning\nat Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10',
|
||||
start_line: 10,
|
||||
title: 'Test Full Name',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAnnotationPoint', () => {
|
||||
@@ -189,14 +206,16 @@ describe('ResultsParser', () => {
|
||||
});
|
||||
|
||||
test('simple annotation point', () => {
|
||||
const result = ResultsParser.findAnnotationPoint(`at Tests.PlayModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:39
|
||||
const result =
|
||||
ResultsParser.findAnnotationPoint(`at Tests.PlayModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:39
|
||||
at UnityEngine.TestTools.TestEnumerator+<Execute>d__6.MoveNext () [0x00038] in /github/workspace/unity-project/Library/PackageCache/com.unity.test-framework@1.1.19/UnityEngine.TestRunner/NUnitExtensions/Attributes/TestEnumerator.cs:36`);
|
||||
expect(result.path).toBe('/github/workspace/unity-project/Assets/Tests/PlayModeTest.cs');
|
||||
expect(result.line).toBe(39);
|
||||
});
|
||||
|
||||
test('first entry with non-zero line number annotation point', () => {
|
||||
const result = ResultsParser.findAnnotationPoint(`at FluentAssertions.Execution.LateBoundTestFramework.Throw (System.String message) [0x00044] in <527a5493e59e45679b35c1e8d65350b3>:0
|
||||
const result =
|
||||
ResultsParser.findAnnotationPoint(`at FluentAssertions.Execution.LateBoundTestFramework.Throw (System.String message) [0x00044] in <527a5493e59e45679b35c1e8d65350b3>:0
|
||||
at FluentAssertions.Execution.TestFrameworkProvider.Throw (System.String message) [0x00011] in <527a5493e59e45679b35c1e8d65350b3>:0
|
||||
at FluentAssertions.Execution.DefaultAssertionStrategy.HandleFailure (System.String message) [0x00005] in <527a5493e59e45679b35c1e8d65350b3>:0
|
||||
at Tests.PlayModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:39
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as xmljs from 'xml-js';
|
||||
import * as fs from 'fs';
|
||||
import * as xmljs from 'xml-js';
|
||||
import { RunMeta, TestMeta } from './results-meta';
|
||||
import path from 'path';
|
||||
import { RunMeta, TestMeta } from './ts/results-meta.ts';
|
||||
|
||||
class ResultsParser {
|
||||
static async parseResults(filepath) {
|
||||
const ResultsParser = {
|
||||
async parseResults(filepath): Promise<RunMeta> {
|
||||
if (!fs.existsSync(filepath)) {
|
||||
throw new Error(`Missing file! {"filepath": "${filepath}"}`);
|
||||
}
|
||||
@@ -16,15 +16,15 @@ class ResultsParser {
|
||||
core.info(`File ${filepath} parsed...`);
|
||||
|
||||
return ResultsParser.convertResults(path.basename(filepath), results);
|
||||
}
|
||||
},
|
||||
|
||||
static convertResults(filename, filedata) {
|
||||
convertResults(filename, filedata): RunMeta {
|
||||
core.info(`Start analyzing results: ${filename}`);
|
||||
|
||||
const run = filedata['test-run'];
|
||||
const runMeta = new RunMeta(filename);
|
||||
const tests = ResultsParser.convertSuite(run['test-suite']);
|
||||
core.debug(tests);
|
||||
core.debug(tests.toString());
|
||||
|
||||
runMeta.total = Number(run._attributes.total);
|
||||
runMeta.failed = Number(run._attributes.failed);
|
||||
@@ -34,18 +34,18 @@ class ResultsParser {
|
||||
runMeta.addTests(tests);
|
||||
|
||||
return runMeta;
|
||||
}
|
||||
},
|
||||
|
||||
static convertSuite(suites) {
|
||||
convertSuite(suites) {
|
||||
if (Array.isArray(suites)) {
|
||||
const innerResult = [];
|
||||
suites.forEach(suite => {
|
||||
const innerResult: TestMeta[] = [];
|
||||
for (const suite of suites) {
|
||||
innerResult.push(...ResultsParser.convertSuite(suite));
|
||||
});
|
||||
}
|
||||
return innerResult;
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const result: TestMeta[] = [];
|
||||
const innerSuite = suites['test-suite'];
|
||||
if (innerSuite) {
|
||||
result.push(...ResultsParser.convertSuite(innerSuite));
|
||||
@@ -57,22 +57,22 @@ class ResultsParser {
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
static convertTests(suite, tests) {
|
||||
convertTests(suite, tests): TestMeta[] {
|
||||
if (Array.isArray(tests)) {
|
||||
const result = [];
|
||||
tests.forEach(test => {
|
||||
result.push(ResultsParser.convertTestCase(suite, test));
|
||||
});
|
||||
const result: TestMeta[] = [];
|
||||
for (const testCase of tests) {
|
||||
result.push(ResultsParser.convertTestCase(suite, testCase));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return [ResultsParser.convertTestCase(suite, tests)];
|
||||
}
|
||||
},
|
||||
|
||||
static convertTestCase(suite, testCase) {
|
||||
const { _attributes, failure } = testCase;
|
||||
convertTestCase(suite, testCase): TestMeta {
|
||||
const { _attributes, failure, output } = testCase;
|
||||
const { name, fullname, result, duration } = _attributes;
|
||||
const testMeta = new TestMeta(suite, name);
|
||||
testMeta.result = result;
|
||||
@@ -96,6 +96,14 @@ class ResultsParser {
|
||||
return testMeta;
|
||||
}
|
||||
|
||||
const rawDetails = [trace];
|
||||
|
||||
if (output && output._cdata) {
|
||||
rawDetails.unshift(output._cdata);
|
||||
} else {
|
||||
core.debug(`No console output for test case: ${fullname}`);
|
||||
}
|
||||
|
||||
testMeta.annotation = {
|
||||
path: point.path,
|
||||
start_line: point.line,
|
||||
@@ -103,20 +111,23 @@ class ResultsParser {
|
||||
annotation_level: 'failure',
|
||||
title: fullname,
|
||||
message: failure.message._cdata ? failure.message._cdata : 'Test Failed!',
|
||||
raw_details: trace,
|
||||
raw_details: rawDetails.join('\n'),
|
||||
start_column: 0,
|
||||
end_column: 0,
|
||||
blob_href: '',
|
||||
};
|
||||
core.info(
|
||||
`- ${testMeta.annotation.path}:${testMeta.annotation.start_line} - ${testMeta.annotation.title}`,
|
||||
);
|
||||
return testMeta;
|
||||
}
|
||||
},
|
||||
|
||||
static findAnnotationPoint(trace) {
|
||||
findAnnotationPoint(trace) {
|
||||
// Find first entry with non-zero line number in stack trace
|
||||
const items = trace.match(/at .* in ((?<path>[^:]+):(?<line>\d+))/g);
|
||||
if (Array.isArray(items)) {
|
||||
const result = [];
|
||||
items.forEach(item => {
|
||||
const result: { path: any; line: number }[] = [];
|
||||
for (const item of items) {
|
||||
const match = item.match(/at .* in ((?<path>[^:]+):(?<line>\d+))/);
|
||||
const point = {
|
||||
path: match ? match.groups.path : '',
|
||||
@@ -125,7 +136,7 @@ class ResultsParser {
|
||||
if (point.line > 0) {
|
||||
result.push(point);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (result.length > 0) {
|
||||
return result[0];
|
||||
}
|
||||
@@ -136,7 +147,7 @@ class ResultsParser {
|
||||
path: match ? match.groups.path : '',
|
||||
line: match ? Number(match.groups.line) : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default ResultsParser;
|
||||
@@ -1,20 +1,20 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
class UnityVersionParser {
|
||||
static get versionPattern() {
|
||||
const UnityVersionParser = {
|
||||
get versionPattern() {
|
||||
return /20\d{2}\.\d\.\w{3,4}|3/;
|
||||
}
|
||||
},
|
||||
|
||||
static parse(projectVersionTxt) {
|
||||
parse(projectVersionTxt) {
|
||||
const matches = projectVersionTxt.match(UnityVersionParser.versionPattern);
|
||||
if (!matches || matches.length === 0) {
|
||||
throw new Error(`Failed to parse version from "${projectVersionTxt}".`);
|
||||
}
|
||||
return matches[0];
|
||||
}
|
||||
},
|
||||
|
||||
static read(projectPath) {
|
||||
read(projectPath) {
|
||||
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(
|
||||
@@ -22,7 +22,7 @@ class UnityVersionParser {
|
||||
);
|
||||
}
|
||||
return UnityVersionParser.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default UnityVersionParser;
|
||||
Reference in New Issue
Block a user