refactor: parameters into cli structure

pull/413/head
Webber 2022-08-29 02:02:59 +02:00
parent 6b32132ec0
commit ccf0047c1c
55 changed files with 785 additions and 533 deletions

View File

@ -11,14 +11,14 @@ env:
jobs:
buildForAllPlatformsUbuntu:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.engineVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
engineVersion:
- 2019.2.11f1
- 2019.3.15f1
targetPlatform:
@ -59,7 +59,7 @@ jobs:
- uses: ./
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
@ -68,6 +68,6 @@ jobs:
###########################
- uses: actions/upload-artifact@v2
with:
name: Build Ubuntu (${{ matrix.unityVersion }})
name: Build Ubuntu (${{ matrix.engineVersion }})
path: build
retention-days: 14

View File

@ -35,7 +35,7 @@ jobs:
matrix:
projectPath:
- test-project
unityVersion:
engineVersion:
# - 2019.2.11f1
- 2019.3.15f1
targetPlatform:
@ -82,7 +82,7 @@ jobs:
cloudRunnerCluster: aws
versioning: None
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
postBuildSteps: |
@ -118,12 +118,12 @@ jobs:
path: build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar
retention-days: 14
k8sBuilds:
name: K8s (GKE Autopilot) build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
name: K8s (GKE Autopilot) build for ${{ matrix.targetPlatform }} on version ${{ matrix.engineVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
unityVersion:
engineVersion:
# - 2019.2.11f1
- 2019.3.15f1
targetPlatform:
@ -166,7 +166,7 @@ jobs:
TARGET_PLATFORM: ${{ matrix.targetPlatform }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KUBE_CONFIG: ${{ steps.read-base64.outputs.base64 }}
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
cloudRunnerTests: true
versioning: None
@ -184,7 +184,7 @@ jobs:
kubeConfig: ${{ steps.read-base64.outputs.base64 }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectPath: test-project
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
versioning: None
postBuildSteps: |
- name: upload

View File

@ -10,14 +10,14 @@ env:
jobs:
buildForAllPlatformsWindows:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.engineVersion }}
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
engineVersion:
- 2020.3.24f1
targetPlatform:
- StandaloneOSX # Build a MacOS executable
@ -58,7 +58,7 @@ jobs:
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
# We use dirty build because we are replacing the default project settings file above
@ -69,6 +69,6 @@ jobs:
###########################
- uses: actions/upload-artifact@v2
with:
name: Build MacOS (${{ matrix.unityVersion }})
name: Build MacOS (${{ matrix.engineVersion }})
path: build
retention-days: 14

View File

@ -10,14 +10,14 @@ env:
jobs:
buildForAllPlatformsWindows:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.engineVersion }}
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
engineVersion:
- 2020.3.24f1
targetPlatform:
- StandaloneWindows64 # Build a Windows 64-bit standalone.
@ -61,7 +61,7 @@ jobs:
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
engineVersion: ${{ matrix.engineVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true
@ -72,6 +72,6 @@ jobs:
###########################
- uses: actions/upload-artifact@v2
with:
name: Build Windows (${{ matrix.unityVersion }})
name: Build Windows (${{ matrix.engineVersion }})
path: build
retention-days: 14

View File

@ -6,7 +6,7 @@ inputs:
required: true
default: ''
description: 'Platform that the build should target.'
unityVersion:
engineVersion:
required: false
default: 'auto'
description: 'Version of unity to use for building the project. Use "auto" to get from your ProjectSettings/ProjectVersion.txt'
@ -184,7 +184,8 @@ runs:
- run: |
deno run --allow-run ./src/index.ts build \
--targetPlatform="${{ inputs.targetPlatform }}" \
--unityVersion="${{ inputs.unityVersion }}" \
--engineVersion="${{ inputs.engineVersion }}" \
--engineVersion="${{ inputs.engineVersion }}" \
--customImage="${{ inputs.customImage }}" \
--projectPath="${{ inputs.projectPath }}" \
--buildName="${{ inputs.buildName }}" \

View File

@ -1,12 +1,14 @@
import yargs from 'https://deno.land/x/yargs@v17.5.1-deno/deno.ts';
import { yargs, YargsInstance, YargsArguments } from './dependencies.ts';
import { default as getHomeDir } from 'https://deno.land/x/dir@1.5.1/home_dir/mod.ts';
import { engineDetection } from './middleware/engine-detection/index.ts';
import { CommandInterface } from './command/command-interface.ts';
import { configureLogger } from './middleware/logger-verbosity/index.ts';
import { CommandFactory } from './command/command-factory.ts';
import { Engine } from './model/engine/engine.ts';
import { branchDetection } from './middleware/branch-detection/index.ts';
export class Cli {
private readonly yargs: yargs.Argv;
private readonly yargs: YargsInstance;
private readonly cliStorageAbsolutePath: string;
private readonly cliStorageCanonicalPath: string;
private readonly configFileName: string;
@ -28,6 +30,11 @@ export class Cli {
await this.parse();
if (log.isVeryVerbose) {
log.debug(`Parsed command: ${this.command.name} (${this.command.constructor.name})`);
log.debug(`Parsed arguments: ${JSON.stringify(this.options, null, 2)}`);
}
return {
command: this.command,
options: this.options,
@ -60,35 +67,43 @@ export class Cli {
this.yargs
.options('quiet', {
alias: 'q',
default: false,
description: 'Suppress all output',
type: 'boolean',
demandOption: false,
default: false,
})
.options('verbose', {
alias: 'v',
default: false,
description: 'Enable verbose logging',
type: 'boolean',
demandOption: false,
default: false,
})
.options('veryVerbose', {
alias: 'vv',
default: false,
description: 'Enable very verbose logging',
type: 'boolean',
demandOption: false,
default: false,
})
.options('maxVerbose', {
alias: 'vvv',
default: false,
description: 'Enable debug logging',
demandOption: false,
type: 'boolean',
default: false,
})
.default([{ logLevel: 'placeholder' }, { logLevelName: 'placeholder' }])
.middleware([configureLogger], true);
}
private globalOptions() {
this.yargs
.help('help')
.showHelpOnFail(false, 'Specify --help for available options')
.epilogue('for more information, find our manual at https://game.ci/docs/cli')
.middleware([])
.showHelpOnFail(true)
.exitProcess(true) // prevents `_handle` from being lost
.strict(true);
}
@ -98,17 +113,35 @@ export class Cli {
.positional('projectPath', {
describe: 'Path to the project',
type: 'string',
demandOption: false,
default: '.',
})
.coerce('projectPath', async (arg) => {
return arg.replace(/^~/, getHomeDir()).replace(/\/$/, '');
})
.middleware([engineDetection, branchDetection])
// Todo - remove these lines with release 3.0.0
.option('unityVersion', {
describe: 'Override the engine version to be used',
type: 'string',
})
.deprecateOption('unityVersion', 'This parameter will be removed. Use engineVersion instead')
.middleware([
engineDetection, // Command is engine specific
async (args) => {
await this.registerCommand(args, yargs);
if (!args.unityVersion || args.unityVersion === 'auto' || args.engine !== Engine.unity) return;
args.engineVersion = args.unityVersion;
args.unityVersion = undefined;
},
]);
])
// End todo
.middleware([async (args) => this.registerCommand(args, yargs)]);
});
}
private async registerCommand(args: yargs.Arguments, yargs) {
private async registerCommand(args: YargsArguments, yargs: YargsInstance) {
const { engine, engineVersion, _: command } = args;
this.command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(command);
@ -117,6 +150,8 @@ export class Cli {
}
private async parse() {
this.options = await this.yargs.parseAsync();
const { _, $0, ...options } = await this.yargs.parseAsync();
this.options = options;
}
}

View File

@ -0,0 +1,83 @@
import { YargsInstance, YargsArguments } from '../dependencies.ts';
export class AndroidOptions {
public static configureCommonOptions(yargs: YargsInstance): void {
yargs
.option('androidAppBundle', {
description: 'Build an Android App Bundle',
type: 'boolean',
demandOption: false,
default: false,
})
.options({
androidKeystoreName: {
description: 'Name of the keystore',
type: 'string',
demandOption: false,
default: '',
},
androidKeystoreBase64: {
description: 'Base64 encoded contents of the keystore',
type: 'string',
demandOption: false,
default: '',
},
androidKeystorePass: {
description: 'Password for the keystore',
type: 'string',
demandOption: false,
default: '',
deprecated: 'Use androidKeystorePassword instead',
},
androidKeystorePassword: {
description: 'Password for the keystore',
type: 'string',
demandOption: false,
default: '',
},
androidKeyAlias: {
description: 'Alias for the keystore',
type: 'string',
demandOption: false,
default: '',
},
androidKeyAliasName: {
description: 'Name of the keystore',
type: 'string',
demandOption: false,
default: '',
deprecated: 'Use androidKeyAlias instead',
},
androidKeyAliasPassword: {
description: 'Password for the androidKeyAlias',
type: 'string',
demandOption: false,
default: '',
requires: ['androidKeyAlias'],
},
androidKeyAliasPass: {
description: 'Password for the androidKeyAlias',
type: 'string',
demandOption: false,
default: '',
deprecated: 'Use androidKeyAliasPassword instead',
},
})
.option('androidTargetSdkVersion', {
description: 'Custom Android SDK target version',
type: 'number',
demandOption: false,
default: '',
})
.default('androidSdkManagerParameters', '') // Placeholder, consumed in middleware
.middleware([AndroidOptions.determineSdkManagerParameters]);
}
private static determineSdkManagerParameters(argv: YargsArguments) {
const { androidTargetSdkVersion } = argv;
if (!androidTargetSdkVersion) return;
argv.androidSdkManagerParameters = `platforms;android-${androidTargetSdkVersion}`;
}
}

View File

@ -0,0 +1,28 @@
import { YargsInstance } from '../dependencies.ts';
import Unity from '../model/unity/unity.ts';
export class BuildOptions {
public static configure(yargs: YargsInstance): void {
yargs
.demandOption('targetPlatform', 'Target platform is mandatory for builds')
.option('buildName', {
description: 'Name of the build',
type: 'string',
default: '',
})
.option('buildsPath', {
description: 'Path for outputting the builds to',
type: 'string',
demandOption: false,
default: 'build',
})
.default('buildPath', '')
.default('buildFile', '')
.middleware(async (argv) => {
const { buildName, buildsPath, targetPlatform, androidAppBundle } = argv;
argv.buildName = buildName || targetPlatform;
argv.buildPath = `${buildsPath}/${targetPlatform}`;
argv.buildFile = Unity.determineBuildFileName(buildName, targetPlatform, androidAppBundle);
});
}
}

View File

@ -0,0 +1,61 @@
import type { YargsInstance } from '../dependencies.ts';
import UnityTargetPlatform from '../model/unity/unity-target-platform.ts';
import { UnityTargetPlatforms } from '../model/unity/unity-target-platforms.ts';
export class UnityOptions {
public static configure = async (yargs: YargsInstance) => {
yargs
.option('targetPlatform', {
alias: 't',
description: 'The platform to build your project for',
choices: UnityTargetPlatforms.all,
demandOption: false,
default: UnityTargetPlatform.default,
})
.options({
unityEmail: {
alias: 'u',
description: 'Email address for your Unity account',
type: 'string',
demandOption: false,
default: '',
},
unityPassword: {
alias: 'p',
description: 'Password for your Unity account',
type: 'string',
demandOption: false,
default: '',
},
unitySerial: {
alias: 's',
description: 'Serial number identifying a pro-license seat',
type: 'string',
demandOption: false,
default: '',
},
unityLicense: {
alias: 'l',
description: 'Contents of, or path to your Unity License File (.ulf)',
type: 'string',
demandOption: false,
default: '',
},
})
.coerce('unityLicense', async (arg) => {
return arg.endsWith('.ulf') ? Deno.readTextFile(arg, { encoding: 'utf8' }) : arg;
})
.option('customImage', {
description: String.dedent`
Custom docker image to use inside the command.
For more information see https://game.ci/docs/docker/versions`,
type: 'string',
})
.option('usymUploadAuthToken', {
description: '<missing description>',
type: 'string',
demandOption: false,
default: '',
});
};
}

View File

@ -0,0 +1,37 @@
import { YargsInstance } from '../dependencies.ts';
import { VersioningStrategies } from '../model/versioning/versioning-strategies.ts';
import { VersioningStrategy } from '../model/versioning/versioning-strategy.ts';
import { buildVersioning } from '../middleware/build-versioning/index.ts';
export class VersioningOptions {
public static async configure(yargs: YargsInstance): void {
yargs
.option('versioningStrategy', {
description: 'Versioning strategy',
choices: VersioningStrategies.all,
demandOption: true,
default: VersioningStrategy.Semantic,
})
.option('version', {
description: String.dedent`
Custom version to use for the build.
Only used when versioningStrategy is set to Custom`,
type: 'string',
default: '',
})
.option('androidVersionCode', {
description: String.dedent`
Custom version code for android specifically.`,
type: 'string',
default: '',
})
.option('allowDirtyBuild', {
description: 'Allow a dirty build',
type: 'boolean',
demandOption: false,
default: false,
})
.default('buildVersion', 'placeholder')
.middleware([buildVersioning]);
}
}

View File

@ -3,6 +3,10 @@ import { Action, Cache, Docker, ImageTag, Input, Output } from '../../model/inde
import PlatformSetup from '../../model/platform-setup.ts';
import MacBuilder from '../../model/mac-builder.ts';
import { CommandBase } from '../command-base.ts';
import { UnityOptions } from '../../command-options/unity-options.ts';
import { YargsInstance } from '../../dependencies.ts';
import { VersioningOptions } from '../../command-options/versioning-options.ts';
import { BuildOptions } from '../../command-options/build-options.ts';
export class UnityBuildCommand extends CommandBase implements CommandInterface {
public async execute(options): Promise<boolean> {
@ -32,10 +36,9 @@ export class UnityBuildCommand extends CommandBase implements CommandInterface {
}
}
public async configureOptions(instance): Promise<void> {
instance.option('buildName', {
description: 'Name of the build',
type: 'string',
});
public async configureOptions(yargs: YargsInstance): Promise<void> {
await UnityOptions.configure(yargs);
await VersioningOptions.configure(yargs);
await BuildOptions.configure(yargs);
}
}

View File

@ -2,7 +2,7 @@ import { NonExistentCommand } from './null/non-existent-command.ts';
import { UnityBuildCommand } from './build/unity-build-command.ts';
import { CommandInterface } from './command-interface.ts';
import { UnityRemoteBuildCommand } from './remote/unity-remote-build-command.ts';
import { Engine } from '../model/engine.ts';
import { Engine } from '../model/engine/engine.ts';
export class CommandFactory {
constructor() {}

View File

@ -58,6 +58,7 @@ export const configureLogger = async (verbosity: Verbosity) => {
// Verbosity
window.log.verbosity = verbosity;
window.log.verbosityName = Verbosity[verbosity];
window.log.isQuiet = isQuiet;
window.log.isVerbose = isVerbose;
window.log.isVeryVerbose = isVeryVerbose;

View File

@ -20,8 +20,8 @@ import { Command } from 'https://deno.land/x/cmd@v1.2.0/commander/index.ts';
import { getUnityChangeset as getUnityChangeSet } from 'https://deno.land/x/unity_changeset@2.0.0/src/index.ts';
import { Buffer } from 'https://deno.land/std@0.151.0/io/buffer.ts';
import { config, configSync } from 'https://deno.land/std@0.151.0/dotenv/mod.ts';
import yargs from 'https://deno.land/x/yargs@v17.4.0-deno/deno.ts';
import * as yargsTypes from 'https://deno.land/x/yargs@v17.4.0-deno/deno-types.ts';
import yargs from 'https://deno.land/x/yargs@v17.5.1-deno/deno.ts';
import type { Arguments as YargsArguments } from 'https://deno.land/x/yargs@v17.5.1-deno/deno-types.ts';
// Internally managed packages
import waitUntil from './module/wait-until.ts';
@ -42,6 +42,9 @@ const __dirname = path.dirname(path.fromFileUrl(import.meta.url));
const { V1EnvVar, V1EnvVarSource, V1SecretKeySelector } = k8s;
type YargsInstance = yargs.Argv;
export type { YargsArguments, YargsInstance };
export {
__dirname,
__filename,
@ -74,5 +77,4 @@ export {
Writable,
yaml,
yargs,
yargsTypes,
};

14
src/global.d.ts vendored
View File

@ -1,6 +1,11 @@
import { Verbosity } from './core/logger/index.ts';
let log: {
declare global {
interface String {
dedent(indentedString: string): string;
}
let log: {
verbosity: Verbosity;
isQuiet: boolean;
isVerbose: boolean;
@ -10,7 +15,12 @@ let log: {
info: (msg: any, ...args: any[]) => void;
warning: (msg: any, ...args: any[]) => void;
error: (msg: any, ...args: any[]) => void;
};
};
}
declare interface String {
dedent(indentedString: string): string;
}
declare interface Window {
log: any;

View File

@ -9,7 +9,8 @@ class GameCI {
if (!success) throw new Error(`${command.name} failed.`);
} catch (error) {
log.error(error);
// eslint-disable-next-line no-console
console.error(error);
Deno.exit(1);
}
}

View File

@ -1,6 +1,6 @@
import { Parameters } from '../../../model/index.ts';
import { fsSync as fs, getUnityChangeSet } from '../../../dependencies.ts';
import System from '../../../model/system.ts';
import System from '../../../model/system/system.ts';
class SetupMac {
static unityHubPath = `"/Applications/Unity Hub.app/Contents/MacOS/Unity Hub"`;

View File

@ -1,7 +1,7 @@
import { fsSync as fs } from '../../../dependencies.ts';
import { Parameters } from '../../../model/index.ts';
import ValidateWindows from '../platform-validation/validate-windows.ts';
import System from '../../../model/system.ts';
import System from '../../../model/system/system.ts';
class SetupWindows {
public static async setup(parameters: Parameters) {

View File

@ -0,0 +1,33 @@
import System from '../../model/system/system.ts';
export class BranchDetector {
public static async getCurrentBranch(projectPath) {
// GitHub pull request, GitHub non pull request
let branchName = this.headRef || this.ref?.slice(11);
// Local
if (!branchName) {
const { status, output } = await System.shellRun('git branch --show-current', { cwd: projectPath });
if (!status.success) throw new Error('did not expect "git branch --show-current"');
branchName = output;
}
return branchName;
}
/**
* For pull requests we can reliably use GITHUB_HEAD_REF
* @deprecated
*/
private get headRef() {
return Deno.env.get('GITHUB_HEAD_REF');
}
/**
* For branches GITHUB_REF will have format `refs/heads/feature-branch-1`
* @deprecated
*/
private get ref() {
return Deno.env.get('GITHUB_REF');
}
}

View File

@ -0,0 +1,13 @@
import System from '../../model/system/system.ts';
import { BranchDetector } from './branch-detector.ts';
export const branchDetection = async (argv) => {
const { projectPath } = argv;
const branch = await BranchDetector.getCurrentBranch(projectPath);
// Todo - determine if we ever want to run the cli on a project that has no git repo.
if (!branch) throw new Error('Running GameCI CLI on a project without a git repository is not supported.');
argv.branch = branch;
};

View File

@ -1,15 +1,7 @@
import { semver } from '../dependencies.ts';
import { semver } from '../../dependencies.ts';
export default class AndroidVersioning {
static determineVersionCode(version, inputVersionCode) {
if (!inputVersionCode) {
return AndroidVersioning.versionToVersionCode(version);
}
return inputVersionCode;
}
static versionToVersionCode(version) {
export default class AndroidBuildVersionGenerator {
public static determineVersionCode(version) {
if (version === 'none') {
log.info(`Versioning strategy is set to ${version}, so android version code should not be applied.`);
@ -37,10 +29,4 @@ export default class AndroidVersioning {
return versionCode;
}
static determineSdkManagerParameters(targetSdkVersion) {
const parsedVersion = Number.parseInt(targetSdkVersion.slice(-2), 10);
return Number.isNaN(parsedVersion) ? '' : `platforms;android-${parsedVersion}`;
}
}

View File

@ -1,102 +1,33 @@
import NotImplementedException from './error/not-implemented-exception.ts';
import ValidationError from './error/validation-error.ts';
import Input from './input.ts';
import System from './system.ts';
import { Action } from './index.ts';
import NotImplementedException from '../../model/error/not-implemented-exception.ts';
import Input from '../../model/input.ts';
import System from '../../model/system/system.ts';
import { Action } from '../../model/index.ts';
import { VersioningStrategy } from '../../model/versioning/versioning-strategy.ts';
export default class Versioning {
static get projectPath() {
return Input.projectPath;
}
static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
}
static get grepCompatibleInputVersionRegex() {
return '^v?([0-9]+\\.)*[0-9]+.*';
}
/**
* Get the branch name of the (related) branch
*/
static async getCurrentBranch() {
// GitHub pull request, GitHub non pull request
let branchName = this.headRef || this.ref?.slice(11);
// Local
if (!branchName) {
const { status, output } = await System.shellRun('git branch --show-current');
if (!status.success) throw new Error('did not expect "git branch --show-current"');
branchName = output;
}
return branchName;
}
/**
* For pull requests we can reliably use GITHUB_HEAD_REF
*/
static get headRef() {
return Deno.env.get('GITHUB_HEAD_REF');
}
/**
* For branches GITHUB_REF will have format `refs/heads/feature-branch-1`
*/
static get ref() {
return Deno.env.get('GITHUB_REF');
}
/**
* The commit SHA that triggered the workflow run.
*/
static get sha() {
return Deno.env.get('GITHUB_SHA');
}
/**
* Maximum number of lines to print when logging the git diff
*/
static get maxDiffLines() {
return 60;
}
/**
* Regex to parse version description into separate fields
*/
static get descriptionRegex1() {
return /^v?([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static get descriptionRegex2() {
return /^v?([\d.]+-\w+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static get descriptionRegex3() {
return /^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static async determineBuildVersion(strategy: string, inputVersion: string, allowDirtyBuild: boolean) {
// Validate input
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
throw new ValidationError(`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`);
export default class BuildVersionGenerator {
private readonly maxDiffLines: number = 60;
private readonly projectPath: string;
constructor(projectPath, currentBranch) {
this.projectPath = projectPath;
this.currentBranch = currentBranch;
}
public async determineBuildVersion(strategy: string, inputVersion: string, allowDirtyBuild: boolean) {
log.info('Versioning strategy:', strategy);
let version;
switch (strategy) {
case this.strategies.None:
case VersioningStrategy.None:
version = 'none';
break;
case this.strategies.Custom:
case VersioningStrategy.Custom:
version = inputVersion;
break;
case this.strategies.Semantic:
case VersioningStrategy.Semantic:
version = await this.generateSemanticVersion(allowDirtyBuild);
break;
case this.strategies.Tag:
case VersioningStrategy.Tag:
version = await this.generateTagVersion();
break;
default:
@ -108,6 +39,38 @@ export default class Versioning {
return version;
}
private get grepCompatibleInputVersionRegex() {
return '^v?([0-9]+\\.)*[0-9]+.*';
}
/**
* Get the branch name of the (related) branch
*/
private async getCurrentBranch() {}
/**
* The commit SHA that triggered the workflow run.
* @deprecated
*/
private get sha() {
return Deno.env.get('GITHUB_SHA');
}
/**
* Regex to parse version description into separate fields
*/
private get descriptionRegex1() {
return /^v?([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g;
}
private get descriptionRegex2() {
return /^v?([\d.]+-\w+)-(\d+)-g(\w+)-?(\w+)*/g;
}
private get descriptionRegex3() {
return /^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g;
}
/**
* Log up to maxDiffLines of the git diff.
*/
@ -128,13 +91,13 @@ export default class Versioning {
*
* @See: https://semver.org/
*/
static async generateSemanticVersion(allowDirtyBuild) {
private async generateSemanticVersion(allowDirtyBuild) {
if (await this.isShallow()) {
await this.fetch();
}
if ((await this.isDirty()) && !allowDirtyBuild) {
await Versioning.logDiff();
await BuildVersionGenerator.logDiff();
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
}
@ -168,7 +131,7 @@ export default class Versioning {
/**
* Generate the proper version for unity based on an existing tag.
*/
static async generateTagVersion() {
private async generateTagVersion() {
let tag = await this.getTag();
if (tag.charAt(0) === 'v') {
@ -181,7 +144,7 @@ export default class Versioning {
/**
* Parses the versionDescription into their named parts.
*/
static async parseSemanticVersion() {
private async parseSemanticVersion() {
const description = await this.getVersionDescription();
try {
@ -225,7 +188,7 @@ export default class Versioning {
/**
* Returns whether the repository is shallow.
*/
static async isShallow() {
private async isShallow() {
const output = await this.git('rev-parse --is-shallow-repository');
return output !== 'false';
@ -238,7 +201,7 @@ export default class Versioning {
*
* Note: `--all` should not be used, and would break fetching for push event.
*/
static async fetch() {
private async fetch() {
try {
await this.git('fetch --unshallow');
} catch {
@ -255,7 +218,7 @@ export default class Versioning {
* In this format v0.12 is the latest tag, 24 are the number of commits since, and gd2198ab
* identifies the current commit.
*/
static async getVersionDescription() {
private async getVersionDescription() {
let commitIsh = '';
// In CI the repo is checked out in detached head mode.
@ -271,7 +234,7 @@ export default class Versioning {
/**
* Returns whether there are uncommitted changes that are not ignored.
*/
static async isDirty() {
private async isDirty() {
const output = await this.git('status --porcelain');
const isDirty = output !== '';
@ -288,7 +251,7 @@ export default class Versioning {
/**
* Get the tag if there is one pointing at HEAD
*/
static async getTag() {
private async getTag() {
return await this.git('tag --points-at HEAD');
}
@ -297,7 +260,7 @@ export default class Versioning {
*
* Note: Currently this is run in all OSes, so the syntax must be cross-platform.
*/
static async hasAnyVersionTags() {
private async hasAnyVersionTags() {
const command = `git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`;
// Todo - make sure this cwd is actually passed in somehow
@ -318,7 +281,7 @@ export default class Versioning {
*
* Note: HEAD should not be used, as it may be detached, resulting in an additional count.
*/
static async getTotalNumberOfCommits() {
private async getTotalNumberOfCommits() {
const numberOfCommitsAsString = await this.git(`rev-list --count ${this.sha}`);
return Number.parseInt(numberOfCommitsAsString, 10);
@ -327,7 +290,7 @@ export default class Versioning {
/**
* Run git in the specified project path
*/
static async git(arguments_, options = {}) {
private async git(arguments_, options = {}) {
const result = await System.run(`git ${arguments_}`, { cwd: this.projectPath, ...options });
log.warning(result);

View File

@ -0,0 +1,14 @@
import BuildVersionGenerator from './build-version-generator.ts';
import AndroidBuildVersionGenerator from './android-build-version-generator.ts';
export const buildVersioning = async (argv) => {
const { projectPath, versioningStrategy, version, allowDirtyBuild, androidVersionCode, buildVersion } = argv;
const buildVersionGenerator = new BuildVersionGenerator(projectPath);
argv.buildVersion = await buildVersionGenerator.determineBuildVersion(versioningStrategy, version, allowDirtyBuild);
if (!androidVersionCode) {
argv.androidVersionCode = AndroidBuildVersionGenerator.determineVersionCode(buildVersion);
}
};

View File

@ -1,15 +1,22 @@
export class EngineDetector {
private projectPath: string;
import UnityVersionDetector from './unity-version-detector.ts';
constructor(subCommands: string[], args: string[]) {
this.projectPath = subCommands[0] || args.projectPath || '.';
export class EngineDetector {
private readonly projectPath: string;
constructor(projectPath) {
this.projectPath = projectPath;
}
public async detect(): Promise<{ engine: string; engineVersion: string }> {
// Todo - detect and return real versions
if (UnityVersionDetector.isUnityProject(this.projectPath)) {
const engineVersion = await UnityVersionDetector.getUnityVersion(this.projectPath);
return { engine: 'unity', engineVersion };
}
return {
engine: 'unity',
engineVersion: '2020.1.0f1',
engine: 'unknown',
engineVersion: 'unknown',
};
}
}

View File

@ -1,35 +1,35 @@
import UnityVersioning from './unity-versioning.ts';
import UnityVersionDetector from './unity-version-detector.ts';
describe('Unity Versioning', () => {
describe('parse', () => {
it('throws for empty string', () => {
expect(() => UnityVersioning.parse('')).toThrow(Error);
expect(() => UnityVersionDetector.parse('')).toThrow(Error);
});
it('parses from ProjectVersion.txt', () => {
const projectVersionContents = `m_EditorVersion: 2019.2.11f1
m_EditorVersionWithRevision: 2019.2.11f1 (5f859a4cfee5)`;
expect(UnityVersioning.parse(projectVersionContents)).toBe('2019.2.11f1');
expect(UnityVersionDetector.parse(projectVersionContents)).toBe('2019.2.11f1');
});
});
describe('read', () => {
it('throws for invalid path', () => {
expect(() => UnityVersioning.read('')).toThrow(Error);
expect(() => UnityVersionDetector.read('')).toThrow(Error);
});
it('reads from test-project', () => {
expect(UnityVersioning.read('./test-project')).toBe('2019.2.11f1');
expect(UnityVersionDetector.read('./test-project')).toBe('2019.2.11f1');
});
});
describe('determineUnityVersion', () => {
it('defaults to parsed version', () => {
expect(UnityVersioning.determineUnityVersion('./test-project', 'auto')).toBe('2019.2.11f1');
expect(UnityVersionDetector.determineUnityVersion('./test-project', 'auto')).toBe('2019.2.11f1');
});
it('use specified unityVersion', () => {
expect(UnityVersioning.determineUnityVersion('./test-project', '1.2.3')).toBe('1.2.3');
expect(UnityVersionDetector.determineUnityVersion('./test-project', '1.2.3')).toBe('1.2.3');
});
});
});

View File

@ -1,16 +1,22 @@
import { fsSync as fs, path } from '../dependencies.ts';
import { fsSync as fs, path } from '../../dependencies.ts';
export default class UnityVersioning {
export default class UnityVersionDetector {
static get versionPattern() {
return /20\d{2}\.\d\.\w{3,4}|3/;
}
static determineUnityVersion(projectPath, unityVersion) {
if (unityVersion === 'auto') {
return UnityVersioning.read(projectPath);
public static isUnityProject(projectPath) {
try {
UnityVersionDetector.read(projectPath);
return true;
} catch {
return false;
}
}
return unityVersion;
static getUnityVersion(projectPath) {
return UnityVersionDetector.read(projectPath);
}
static read(projectPath) {
@ -19,11 +25,11 @@ export default class UnityVersioning {
throw new Error(`Project settings file not found at "${filePath}". Have you correctly set the projectPath?`);
}
return UnityVersioning.parse(Deno.readTextFileSync(filePath, 'utf8'));
return UnityVersionDetector.parse(Deno.readTextFileSync(filePath, 'utf8'));
}
static parse(projectVersionTxt) {
const matches = projectVersionTxt.match(UnityVersioning.versionPattern);
const matches = projectVersionTxt.match(UnityVersionDetector.versionPattern);
if (!matches || matches.length === 0) {
throw new Error(`Failed to parse version from "${projectVersionTxt}".`);
}

View File

@ -17,4 +17,12 @@ export const configureLogger = async (argv) => {
}
await createLoggerAndSetVerbosity(verbosity);
argv.logLevel = log.verbosity;
argv.logLevelName = log.verbosityName;
argv.quiet = undefined;
argv.verbose = undefined;
argv.veryVerbose = undefined;
argv.maxVerbose = undefined;
};

View File

@ -1,11 +1,11 @@
// Import this named export into your test file:
import Platform from '../platform.ts';
import UnityTargetPlatform from '../unity/unity-target-platform.ts';
export const mockGetFromUser = jest.fn().mockResolvedValue({
editorVersion: '',
targetPlatform: Platform.types.Test,
targetPlatform: UnityTargetPlatform.Test,
projectPath: '.',
buildName: Platform.types.Test,
buildName: UnityTargetPlatform.Test,
buildsPath: 'build',
buildMethod: undefined,
buildVersion: '1.3.37',

View File

@ -1,41 +1,41 @@
import AndroidVersioning from './android-versioning.ts';
import AndroidBuildVersionGenerator from '../middleware/build-versioning/android-build-version-generator.ts';
describe('Android Versioning', () => {
describe('versionToVersionCode', () => {
it('defaults to 0 when versioning strategy is none', () => {
expect(AndroidVersioning.versionToVersionCode('none')).toBe(0);
expect(AndroidBuildVersionGenerator.versionToVersionCode('none')).toBe(0);
});
it('defaults to 1 when version is not a valid semver', () => {
expect(AndroidVersioning.versionToVersionCode('abcd')).toBe(1);
expect(AndroidBuildVersionGenerator.versionToVersionCode('abcd')).toBe(1);
});
it('returns a number', () => {
expect(AndroidVersioning.versionToVersionCode('123.456.789')).toBe(123_456_789);
expect(AndroidBuildVersionGenerator.versionToVersionCode('123.456.789')).toBe(123_456_789);
});
it('throw when generated version code is too large', () => {
expect(() => AndroidVersioning.versionToVersionCode('2050.0.0')).toThrow();
expect(() => AndroidBuildVersionGenerator.versionToVersionCode('2050.0.0')).toThrow();
});
});
describe('determineVersionCode', () => {
it('defaults to parsed version', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', '')).toBe(1_002_003);
expect(AndroidBuildVersionGenerator.determineVersionCode('1.2.3', '')).toBe(1_002_003);
});
it('use specified code', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', 2)).toBe(2);
expect(AndroidBuildVersionGenerator.determineVersionCode('1.2.3', 2)).toBe(2);
});
});
describe('determineSdkManagerParameters', () => {
it('defaults to blank', () => {
expect(AndroidVersioning.determineSdkManagerParameters('AndroidApiLevelAuto')).toBe('');
expect(AndroidBuildVersionGenerator.determineSdkManagerParameters('AndroidApiLevelAuto')).toBe('');
});
it('uses the specified api level', () => {
expect(AndroidVersioning.determineSdkManagerParameters('AndroidApiLevel30')).toBe('platforms;android-30');
expect(AndroidBuildVersionGenerator.determineSdkManagerParameters('AndroidApiLevel30')).toBe('platforms;android-30');
});
});
});

View File

@ -1,21 +1,21 @@
import Versioning from './versioning.ts';
import UnityVersioning from './unity-versioning.ts';
import AndroidVersioning from './android-versioning.ts';
import BuildVersionGenerator from '../middleware/build-versioning/build-version-generator.ts';
import UnityVersionDetector from '../middleware/engine-detection/unity-version-detector.ts';
import AndroidBuildVersionGenerator from '../middleware/build-versioning/android-build-version-generator.ts';
import Parameters from './parameters.ts';
import Input from './input.ts';
import Platform from './platform.ts';
import UnityTargetPlatform from './unity/unity-target-platform.ts';
// Todo - Don't use process.env directly, that's what the input model class is for.
const testLicense =
'<?xml version="1.0" encoding="UTF-8"?><root>\n <License id="Terms">\n <MachineBindings>\n <Binding Key="1" Value="576562626572264761624c65526f7578"/>\n <Binding Key="2" Value="576562626572264761624c65526f7578"/>\n </MachineBindings>\n <MachineID Value="D7nTUnjNAmtsUMcnoyrqkgIbYdM="/>\n <SerialHash Value="2033b8ac3e6faa3742ca9f0bfae44d18f2a96b80"/>\n <Features>\n <Feature Value="33"/>\n <Feature Value="1"/>\n <Feature Value="12"/>\n <Feature Value="2"/>\n <Feature Value="24"/>\n <Feature Value="3"/>\n <Feature Value="36"/>\n <Feature Value="17"/>\n <Feature Value="19"/>\n <Feature Value="62"/>\n </Features>\n <DeveloperData Value="AQAAAEY0LUJHUlgtWEQ0RS1aQ1dWLUM1SlctR0RIQg=="/>\n <SerialMasked Value="F4-BGRX-XD4E-ZCWV-C5JW-XXXX"/>\n <StartDate Value="2021-02-08T00:00:00"/>\n <UpdateDate Value="2021-02-09T00:34:57"/>\n <InitialActivationDate Value="2021-02-08T00:34:56"/>\n <LicenseVersion Value="6.x"/>\n <ClientProvidedVersion Value="2018.4.30f1"/>\n <AlwaysOnline Value="false"/>\n <Entitlements>\n <Entitlement Ns="unity_editor" Tag="UnityPersonal" Type="EDITOR" ValidTo="9999-12-31T00:00:00"/>\n <Entitlement Ns="unity_editor" Tag="DarkSkin" Type="EDITOR_FEATURE" ValidTo="9999-12-31T00:00:00"/>\n </Entitlements>\n </License>\n<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#Terms"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>m0Db8UK+ktnOLJBtHybkfetpcKo=</DigestValue></Reference></SignedInfo><SignatureValue>o/pUbSQAukz7+ZYAWhnA0AJbIlyyCPL7bKVEM2lVqbrXt7cyey+umkCXamuOgsWPVUKBMkXtMH8L\n5etLmD0getWIhTGhzOnDCk+gtIPfL4jMo9tkEuOCROQAXCci23VFscKcrkB+3X6h4wEOtA2APhOY\nB+wvC794o8/82ffjP79aVAi57rp3Wmzx+9pe9yMwoJuljAy2sc2tIMgdQGWVmOGBpQm3JqsidyzI\nJWG2kjnc7pDXK9pwYzXoKiqUqqrut90d+kQqRyv7MSZXR50HFqD/LI69h68b7P8Bjo3bPXOhNXGR\n9YCoemH6EkfCJxp2gIjzjWW+l2Hj2EsFQi8YXw==</SignatureValue></Signature></root>';
Deno.env.set('UNITY_LICENSE', testLicense);
const determineVersion = jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
const determineVersion = jest.spyOn(BuildVersionGenerator, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
const determineUnityVersion = jest
.spyOn(UnityVersioning, 'determineUnityVersion')
.spyOn(UnityVersionDetector, 'determineUnityVersion')
.mockImplementation(() => '2019.2.11f1');
const determineSdkManagerParameters = jest
.spyOn(AndroidVersioning, 'determineSdkManagerParameters')
.spyOn(AndroidBuildVersionGenerator, 'determineSdkManagerParameters')
.mockImplementation(() => 'platforms;android-30');
afterEach(() => {
@ -88,7 +88,7 @@ describe('BuildParameters', () => {
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue }));
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
test.each([UnityTargetPlatform.StandaloneWindows, UnityTargetPlatform.StandaloneWindows64])(
'appends exe for %s',
async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
@ -97,14 +97,14 @@ describe('BuildParameters', () => {
},
);
test.each([Platform.types.Android])('appends apk for %s', async (targetPlatform) => {
test.each([UnityTargetPlatform.Android])('appends apk for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.apk` }));
});
test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => {
test.each([UnityTargetPlatform.Android])('appends aab for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);

View File

@ -3,7 +3,7 @@ import CloudRunner from './cloud-runner.ts';
import Input from '../input.ts';
import { CloudRunnerStatics } from './cloud-runner-statics.ts';
import { TaskParameterSerializer } from './services/task-parameter-serializer.ts';
import UnityVersioning from '../unity-versioning.ts';
import UnityVersionDetector from '../../middleware/engine-detection/unity-version-detector.ts';
import { Cli } from '../cli/cli.ts';
import CloudRunnerLogger from './services/cloud-runner-logger.ts';
import { v4 as uuidv4 } from '../../../node_modules/uuid';
@ -20,7 +20,7 @@ describe('Cloud Runner', () => {
Cli.options = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
engineVersion: UnityVersionDetector.read('test-project'),
targetPlatform: 'StandaloneLinux64',
customJob: `
- name: 'step 1'
@ -68,7 +68,7 @@ describe('Cloud Runner', () => {
Cli.options = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
engineVersion: UnityVersionDetector.determineUnityVersion('test-project', UnityVersionDetector.read('test-project')),
targetPlatform: 'StandaloneLinux64',
cacheKey: `test-case-${uuidv4()}`,
};
@ -96,7 +96,7 @@ describe('Cloud Runner', () => {
Cli.options = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
engineVersion: UnityVersionDetector.read('test-project'),
cloudRunnerCluster: 'local-system',
targetPlatform: 'StandaloneLinux64',
customJob: `
@ -124,7 +124,7 @@ describe('Cloud Runner', () => {
Cli.options = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
engineVersion: UnityVersionDetector.read('test-project'),
cloudRunnerCluster: 'test',
targetPlatform: 'StandaloneLinux64',
};

View File

@ -2,7 +2,7 @@ import { fs, uuid, path, __dirname } from '../../../dependencies.ts';
import Parameters from '../../parameters.ts';
import { Cli } from '../../cli/cli.ts';
import Input from '../../input.ts';
import UnityVersioning from '../../unity-versioning.ts';
import UnityVersionDetector from '../../../middleware/engine-detection/unity-version-detector.ts';
import CloudRunner from '../cloud-runner.ts';
import { CloudRunnerSystem } from '../services/cloud-runner-system/index.ts';
import { Caching } from './caching.ts';
@ -16,7 +16,7 @@ describe('Cloud Runner Caching', () => {
Cli.options = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
engineVersion: UnityVersionDetector.read('test-project'),
targetPlatform: 'StandaloneLinux64',
cacheKey: `test-case-${uuid()}`,
};

View File

@ -1,6 +1,6 @@
import ImageEnvironmentFactory from './image-environment-factory.ts';
import { path, fsSync as fs } from '../dependencies.ts';
import System from './system.ts';
import System from './system/system.ts';
class Docker {
static async run(image, parameters) {

View File

@ -1,4 +1,4 @@
import Platform from './platform.ts';
import UnityTargetPlatform from './unity/unity-target-platform.ts';
import Parameters from './parameters.ts';
@ -78,10 +78,10 @@ class ImageTag {
// @see: https://docs.unity3d.com/ScriptReference/BuildTarget.html
switch (platform) {
case Platform.types.StandaloneOSX:
case UnityTargetPlatform.StandaloneOSX:
return mac;
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
case UnityTargetPlatform.StandaloneWindows:
case UnityTargetPlatform.StandaloneWindows64:
// Can only build windows-il2cpp on a windows based system
if (process.platform === 'win32') {
// Unity versions before 2019.3 do not support il2cpp
@ -94,7 +94,7 @@ class ImageTag {
}
return windows;
case Platform.types.StandaloneLinux64: {
case UnityTargetPlatform.StandaloneLinux64: {
// Unity versions before 2019.3 do not support il2cpp
if (major >= 2020 || (major === 2019 && minor >= 3)) {
return linuxIl2cpp;
@ -102,45 +102,45 @@ class ImageTag {
return linux;
}
case Platform.types.iOS:
case UnityTargetPlatform.iOS:
return ios;
case Platform.types.Android:
case UnityTargetPlatform.Android:
return android;
case Platform.types.WebGL:
case UnityTargetPlatform.WebGL:
return webgl;
case Platform.types.WSAPlayer:
case UnityTargetPlatform.WSAPlayer:
if (process.platform !== 'win32') {
throw new Error(`WSAPlayer can only be built on a windows base OS`);
}
return wsaPlayer;
case Platform.types.PS4:
case UnityTargetPlatform.PS4:
return windows;
case Platform.types.XboxOne:
case UnityTargetPlatform.XboxOne:
return windows;
case Platform.types.tvOS:
case UnityTargetPlatform.tvOS:
if (process.platform !== 'win32') {
throw new Error(`tvOS can only be built on a windows base OS`);
}
return tvos;
case Platform.types.Switch:
case UnityTargetPlatform.Switch:
return windows;
// Unsupported
case Platform.types.Lumin:
case UnityTargetPlatform.Lumin:
return windows;
case Platform.types.BJM:
case UnityTargetPlatform.BJM:
return windows;
case Platform.types.Stadia:
case UnityTargetPlatform.Stadia:
return windows;
case Platform.types.Facebook:
case UnityTargetPlatform.Facebook:
return facebook;
case Platform.types.NoTarget:
case UnityTargetPlatform.NoTarget:
return generic;
// Test specific
case Platform.types.Test:
case UnityTargetPlatform.Test:
return generic;
default:
throw new Error(`

View File

@ -5,10 +5,10 @@ import Docker from './docker.ts';
import Input from './input.ts';
import ImageTag from './image-tag.ts';
import Output from './output.ts';
import Platform from './platform.ts';
import UnityTargetPlatform from './unity/unity-target-platform.ts';
import Project from './project.ts';
import Unity from './unity.ts';
import Versioning from './versioning.ts';
import Unity from './unity/unity.ts';
import BuildVersionGenerator from '../middleware/build-versioning/build-version-generator.ts';
import CloudRunner from './cloud-runner/cloud-runner.ts';
export {
@ -19,9 +19,9 @@ export {
Input,
ImageTag,
Output,
Platform,
UnityTargetPlatform,
Project,
Unity,
Versioning,
BuildVersionGenerator,
CloudRunner,
};

View File

@ -1,22 +1,22 @@
import { core } from '../dependencies.ts';
import Input from './input.ts';
import Platform from './platform.ts';
import UnityTargetPlatform from './unity/unity-target-platform.ts';
afterEach(() => {
jest.restoreAllMocks();
});
describe('Input', () => {
describe('unityVersion', () => {
describe('engineVersion', () => {
it('returns the default value', () => {
expect(Input.unityVersion).toStrictEqual('auto');
expect(Input.engineVersion).toStrictEqual('auto');
});
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(Input.engineVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
@ -35,7 +35,7 @@ describe('Input', () => {
describe('targetPlatform', () => {
it('returns the default value', () => {
expect(Input.targetPlatform).toStrictEqual(Platform.default);
expect(Input.targetPlatform).toStrictEqual(UnityTargetPlatform.default);
});
it('takes input from the users workflow', () => {

View File

@ -1,7 +1,6 @@
import { fsSync as fs, path, core } from '../dependencies.ts';
import { Cli } from './cli/cli.ts';
import CloudRunnerQueryOverride from './cloud-runner/services/cloud-runner-query-override.ts';
import Platform from './platform.ts';
import { CliArguments } from '../core/cli/cli-arguments.ts';
/**
@ -102,42 +101,10 @@ class Input {
return this.get('GITHUB_RUN_NUMBER') || '0';
}
public get targetPlatform() {
return this.get('targetPlatform') || Platform.default;
}
public get unityVersion() {
return this.get('unityVersion') || 'auto';
}
public get unityEmail() {
return this.get('unityEmail') || '';
}
public get unityPassword() {
return this.get('unityPassword') || '';
}
public get unityLicense() {
return this.get('unityLicense') || '';
}
public get unityLicenseFile() {
return this.get('unityLicenseFile') || '';
}
public get unitySerial() {
return this.get('unitySerial') || '';
}
public get usymUploadAuthToken() {
return this.get('usymUploadAuthToken') || '';
}
public get customImage() {
return this.get('customImage') || '';
}
public get projectPath() {
let input = this.get('projectPath');
@ -156,10 +123,6 @@ class Input {
return this.get('buildName');
}
public get buildsPath() {
return this.get('buildsPath') || 'build';
}
public get buildMethod() {
return this.get('buildMethod') || ''; // Processed in docker file
}
@ -168,14 +131,6 @@ class Input {
return this.get('customParameters') || '';
}
public get versioningStrategy() {
return this.get('versioning') || 'Semantic';
}
public get specifiedVersion() {
return this.get('version') || '';
}
public get androidVersionCode() {
return this.get('androidVersionCode');
}
@ -250,13 +205,6 @@ class Input {
return this.get('chownFilesTo') || '';
}
public get allowDirtyBuild() {
const input = this.get('allowDirtyBuild');
log.debug('input === ', input);
return input || false === true;
}
public get postBuildSteps() {
return this.get('postBuildSteps') || '';
}

View File

@ -1,5 +1,5 @@
import { Parameters } from './parameters.ts';
import System from './system.ts';
import System from './system/system.ts';
class MacBuilder {
public static async run(actionFolder) {

View File

@ -1,9 +1,9 @@
import { default as getHomeDir } from 'https://deno.land/x/dir@1.5.1/home_dir/mod.ts';
import AndroidVersioning from './android-versioning.ts';
import AndroidBuildVersionGenerator from '../middleware/build-versioning/android-build-version-generator.ts';
import Input from './input.ts';
import Platform from './platform.ts';
import UnityVersioning from './unity-versioning.ts';
import Versioning from './versioning.ts';
import UnityTargetPlatform from './unity/unity-target-platform.ts';
import UnityVersionDetector from '../middleware/engine-detection/unity-version-detector.ts';
import BuildVersionGenerator from '../middleware/build-versioning/build-version-generator.ts';
import { GitRepoReader } from './input-readers/git-repo.ts';
import { CommandInterface } from '../command/command-interface.ts';
import { Environment } from '../core/env/environment.ts';
@ -94,39 +94,13 @@ class Parameters {
}
public async parse(): Promise<Parameters> {
const cliStoragePath = `${getHomeDir()}/.game-ci`;
const targetPlatform = this.input.get('targetPlatform');
const buildsPath = this.input.get('buildsPath');
const projectPath = this.get('projectPath');
const unityVersion = this.get('unityVersion');
const versioningStrategy = this.get('versioningStrategy');
const specifiedVersion = this.get('specifiedVersion');
const allowDirtyBuild = this.get('allowDirtyBuild');
const androidTargetSdkVersion = this.get('androidTargetSdkVersion');
const buildName = this.input.get('buildName') || targetPlatform;
const buildFile = Parameters.parseBuildFile(buildName, targetPlatform, this.get('androidAppBundle'));
const buildPath = `${buildsPath}/${targetPlatform}`;
const editorVersion = UnityVersioning.determineUnityVersion(projectPath, unityVersion);
const buildVersion = await Versioning.determineBuildVersion(versioningStrategy, specifiedVersion, allowDirtyBuild);
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.get('androidVersionCode'));
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(androidTargetSdkVersion);
const branch = (await Versioning.getCurrentBranch()) || (await GitRepoReader.GetBranch());
const branch = (await BuildVersionGenerator.getCurrentBranch()) || (await GitRepoReader.GetBranch());
const parameters = {
branch,
unityEmail: this.get('unityEmail'),
unityPassword: this.get('unityPassword'),
unityLicense: this.get('unityLicense'),
unityLicenseFile: this.get('unityLicenseFile'),
unitySerial: this.getUnitySerial(),
cliStoragePath,
editorVersion,
customImage: this.get('customImage'),
usymUploadAuthToken: this.get('usymUploadAuthToken'),
editorVersion: engineVersion,
runnerTempPath: this.env.get('RUNNER_TEMP'),
targetPlatform,
projectPath,
buildName,
buildPath,
buildFile,
@ -156,18 +130,6 @@ class Parameters {
};
}
static parseBuildFile(filename, platform, androidAppBundle) {
if (Platform.isWindows(platform)) {
return `${filename}.exe`;
}
if (Platform.isAndroid(platform)) {
return androidAppBundle ? `${filename}.aab` : `${filename}.apk`;
}
return filename;
}
private getUnitySerial() {
let unitySerial = this.get('unitySerial');

View File

@ -1,37 +0,0 @@
import Platform from './platform.ts';
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);
});
});
});

View File

@ -1,53 +0,0 @@
class Platform {
static get default() {
return Platform.types.StandaloneWindows64;
}
static 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',
};
}
static isWindows(platform) {
switch (platform) {
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
return true;
default:
return false;
}
}
static isAndroid(platform) {
switch (platform) {
case Platform.types.Android:
return true;
default:
return false;
}
}
}
export default Platform;

View File

@ -1,5 +1,5 @@
import Input from './input.ts';
import Unity from './unity.ts';
import Unity from './unity/unity.ts';
import Action from './action.ts';
class Project {

View File

@ -1,5 +1,5 @@
export interface RunOptions {
pwd: string;
cwd: string;
attach: boolean;
}
@ -14,12 +14,7 @@ class System {
*
* @throws {Error} if anything was output to stderr.
*/
static async run(rawCommand: string, options: RunOptions = {}): Promise<string> {
const { pwd } = options;
let command = rawCommand;
if (pwd) command = `cd ${pwd} ; ${command}`;
static async run(command: string, options: RunOptions = {}): Promise<string> {
const isWindows = Deno.build.os === 'windows';
const shellMethod = isWindows ? System.powershellRun : System.shellRun;
@ -28,14 +23,20 @@ class System {
return shellMethod(command, options);
}
static async shellRun(command: string, options: RunOptions = {}): Promise<string> {
const { attach } = options;
static async shellRun(rawCommand: string, options: RunOptions = {}): Promise<string> {
const { attach, cwd } = options;
let command = rawCommand;
if (cwd) command = `cd ${cwd} ; ${command}`;
return attach ? System.runAndAttach('sh', ['-c', command]) : System.runAndCapture('sh', ['-c', command]);
}
static async powershellRun(command: string, options: RunOptions = {}): Promise<string> {
const { attach } = options;
static async powershellRun(rawCommand: string, options: RunOptions = {}): Promise<string> {
const { attach, cwd } = options;
let command = rawCommand;
if (cwd) command = `cd ${cwd} ; ${command}`;
return attach ? System.runAndAttach('powershell', [command]) : System.runAndCapture('powershell', [command]);
}

View File

@ -1,7 +0,0 @@
class Unity {
static get libraryFolder() {
return 'Library';
}
}
export default Unity;

View File

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

View File

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

View File

@ -0,0 +1,27 @@
import { UnityTargetPlatform } from '../index.ts';
export class UnityTargetPlatforms {
public static readonly all = [
UnityTargetPlatform.Android,
UnityTargetPlatform.iOS,
UnityTargetPlatform.StandaloneLinux64,
UnityTargetPlatform.StandaloneOSX,
UnityTargetPlatform.StandaloneWindows,
UnityTargetPlatform.StandaloneWindows64,
UnityTargetPlatform.Switch,
UnityTargetPlatform.tvOS,
UnityTargetPlatform.WebGL,
UnityTargetPlatform.WSAPlayer,
UnityTargetPlatform.XboxOne,
// Unsupported
UnityTargetPlatform.Lumin,
UnityTargetPlatform.BJM,
UnityTargetPlatform.Stadia,
UnityTargetPlatform.Facebook,
UnityTargetPlatform.NoTarget,
// Test specific
UnityTargetPlatform.Test,
];
}

View File

@ -0,0 +1,21 @@
import UnityTargetPlatform from './unity-target-platform.ts';
class Unity {
static get libraryFolder() {
return 'Library';
}
static determineBuildFileName(buildName, platform, androidAppBundle) {
if (UnityTargetPlatform.isWindows(platform)) {
return `${buildName}.exe`;
}
if (UnityTargetPlatform.isAndroid(platform)) {
return androidAppBundle ? `${buildName}.aab` : `${buildName}.apk`;
}
return buildName;
}
}
export default Unity;

View File

@ -0,0 +1,7 @@
import { VersioningStrategy } from './versioning-strategy.ts';
export class VersioningStrategies {
public static get all() {
return [VersioningStrategy.None, VersioningStrategy.Semantic, VersioningStrategy.Tag, VersioningStrategy.Custom];
}
}

View File

@ -0,0 +1,6 @@
export class VersioningStrategy {
public static None: 'None';
public static Semantic: 'Semantic';
public static Tag: 'Tag';
public static Custom: 'Custom';
}

View File

@ -1,8 +1,8 @@
import { core } from '../../dependencies.ts';
import NotImplementedException from './error/not-implemented-exception.ts';
import System from './system.ts';
import Versioning from './versioning.ts';
import { validVersionTagInputs, invalidVersionTagInputs } from './__data__/versions.ts';
import NotImplementedException from '../error/not-implemented-exception.ts';
import System from '../system/system.ts';
import BuildVersionGenerator from '../../middleware/build-versioning/build-version-generator.ts';
import { validVersionTagInputs, invalidVersionTagInputs } from '../__data__/versions.ts';
afterEach(() => {
jest.restoreAllMocks();
@ -11,27 +11,27 @@ afterEach(() => {
describe('Versioning', () => {
describe('strategies', () => {
it('returns an object', () => {
expect(typeof Versioning.strategies).toStrictEqual('object');
expect(typeof BuildVersionGenerator.strategies).toStrictEqual('object');
});
it('has items', () => {
expect(Object.values(Versioning.strategies).length).toBeGreaterThan(2);
expect(Object.values(BuildVersionGenerator.strategies).length).toBeGreaterThan(2);
});
it('has an opt out option', () => {
expect(Versioning.strategies).toHaveProperty('None');
expect(BuildVersionGenerator.strategies).toHaveProperty('None');
});
it('has the semantic option', () => {
expect(Versioning.strategies).toHaveProperty('Semantic');
expect(BuildVersionGenerator.strategies).toHaveProperty('Semantic');
});
it('has a strategy for tags', () => {
expect(Versioning.strategies).toHaveProperty('Tag');
expect(BuildVersionGenerator.strategies).toHaveProperty('Tag');
});
it('has an option that allows custom input', () => {
expect(Versioning.strategies).toHaveProperty('Custom');
expect(BuildVersionGenerator.strategies).toHaveProperty('Custom');
});
});
@ -39,7 +39,7 @@ describe('Versioning', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const matchInputUsingGrep = async (input) => {
const output = await System.run('sh', undefined, {
input: Buffer.from(`echo '${input}' | grep -E '${Versioning.grepCompatibleInputVersionRegex}'`),
input: Buffer.from(`echo '${input}' | grep -E '${BuildVersionGenerator.grepCompatibleInputVersionRegex}'`),
silent: true,
});
@ -57,34 +57,34 @@ describe('Versioning', () => {
describe('branch', () => {
it('returns headRef when set', async () => {
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue('feature-branch-1');
const headReference = jest.spyOn(BuildVersionGenerator, 'headRef', 'get').mockReturnValue('feature-branch-1');
await expect(Versioning.getCurrentBranch).resolves.toStrictEqual('feature-branch-1');
await expect(BuildVersionGenerator.getCurrentBranch).resolves.toStrictEqual('feature-branch-1');
expect(headReference).toHaveBeenCalledTimes(1);
});
it('returns part of Ref when set', () => {
jest.spyOn(Versioning, 'headRef', 'get').mockImplementation();
const reference = jest.spyOn(Versioning, 'ref', 'get').mockReturnValue('refs/heads/feature-branch-2');
jest.spyOn(BuildVersionGenerator, 'headRef', 'get').mockImplementation();
const reference = jest.spyOn(BuildVersionGenerator, 'ref', 'get').mockReturnValue('refs/heads/feature-branch-2');
await expect(Versioning.getCurrentBranch).resolves.toStrictEqual('feature-branch-2');
await expect(BuildVersionGenerator.getCurrentBranch).resolves.toStrictEqual('feature-branch-2');
expect(reference).toHaveBeenCalledTimes(2);
});
it('prefers headRef over ref when set', () => {
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue('feature-branch-1');
const reference = jest.spyOn(Versioning, 'ref', 'get').mockReturnValue('refs/heads/feature-2');
const headReference = jest.spyOn(BuildVersionGenerator, 'headRef', 'get').mockReturnValue('feature-branch-1');
const reference = jest.spyOn(BuildVersionGenerator, 'ref', 'get').mockReturnValue('refs/heads/feature-2');
await expect(Versioning.getCurrentBranch).resolves.toStrictEqual('feature-branch-1');
await expect(BuildVersionGenerator.getCurrentBranch).resolves.toStrictEqual('feature-branch-1');
expect(headReference).toHaveBeenCalledTimes(1);
expect(reference).toHaveBeenCalledTimes(0);
});
it('returns undefined when headRef and ref are not set', async () => {
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockImplementation();
const reference = jest.spyOn(Versioning, 'ref', 'get').mockImplementation();
const headReference = jest.spyOn(BuildVersionGenerator, 'headRef', 'get').mockImplementation();
const reference = jest.spyOn(BuildVersionGenerator, 'ref', 'get').mockImplementation();
await expect(Versioning.getCurrentBranch).resolves.not.toBeDefined();
await expect(BuildVersionGenerator.getCurrentBranch).resolves.not.toBeDefined();
expect(headReference).toHaveBeenCalledTimes(1);
expect(reference).toHaveBeenCalledTimes(1);
@ -93,23 +93,23 @@ describe('Versioning', () => {
describe('headRef', () => {
it('does not throw', () => {
expect(() => Versioning.headRef).not.toThrow();
expect(() => BuildVersionGenerator.headRef).not.toThrow();
});
});
describe('ref', () => {
it('does not throw', () => {
expect(() => Versioning.ref).not.toThrow();
expect(() => BuildVersionGenerator.ref).not.toThrow();
});
});
describe('isDirtyAllowed', () => {
it('does not throw', () => {
expect(() => Versioning.isDirtyAllowed).not.toThrow();
expect(() => BuildVersionGenerator.isDirtyAllowed).not.toThrow();
});
it('returns false by default', () => {
expect(Versioning.isDirtyAllowed).toStrictEqual(false);
expect(BuildVersionGenerator.isDirtyAllowed).toStrictEqual(false);
});
});
@ -117,17 +117,17 @@ describe('Versioning', () => {
it('calls git diff', async () => {
// allowDirtyBuild: true
jest.spyOn(core, 'getInput').mockReturnValue('true');
jest.spyOn(Versioning, 'isShallow').mockResolvedValue(true);
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
jest.spyOn(Versioning, 'fetch').mockImplementation();
jest.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(true);
jest.spyOn(BuildVersionGenerator, 'isShallow').mockResolvedValue(true);
jest.spyOn(BuildVersionGenerator, 'isDirty').mockResolvedValue(false);
jest.spyOn(BuildVersionGenerator, 'fetch').mockImplementation();
jest.spyOn(BuildVersionGenerator, 'hasAnyVersionTags').mockResolvedValue(true);
jest
.spyOn(Versioning, 'parseSemanticVersion')
.spyOn(BuildVersionGenerator, 'parseSemanticVersion')
.mockResolvedValue({ match: '', tag: 'mocktag', commits: 'abcdef', hash: '75822BCAF' });
const logDiffSpy = jest.spyOn(Versioning, 'logDiff');
const logDiffSpy = jest.spyOn(BuildVersionGenerator, 'logDiff');
const gitSpy = jest.spyOn(System, 'run').mockImplementation();
await Versioning.generateSemanticVersion();
await BuildVersionGenerator.generateSemanticVersion();
expect(logDiffSpy).toHaveBeenCalledTimes(1);
expect(gitSpy).toHaveBeenCalledTimes(1);
@ -140,39 +140,39 @@ describe('Versioning', () => {
describe('descriptionRegex1', () => {
it('is a valid regex', () => {
expect(Versioning.descriptionRegex1).toBeInstanceOf(RegExp);
expect(BuildVersionGenerator.descriptionRegex1).toBeInstanceOf(RegExp);
});
test.each(['v1.1-1-g12345678', 'v0.1-2-g12345678', 'v0.0-500-gA9B6C3D0-dirty'])(
'is happy with valid %s',
(description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeTruthy();
expect(BuildVersionGenerator.descriptionRegex1.test(description)).toBeTruthy();
},
);
test.each(['1.1-1-g12345678', '0.1-2-g12345678', '0.0-500-gA9B6C3D0-dirty'])(
'accepts valid semantic versions without v-prefix %s',
(description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeTruthy();
expect(BuildVersionGenerator.descriptionRegex1.test(description)).toBeTruthy();
},
);
test.each(['v0', 'v0.1', 'v0.1.2', 'v0.1-2', 'v0.1-2-g'])('does not like %s', (description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeFalsy();
expect(BuildVersionGenerator.descriptionRegex1.test(description)).toBeFalsy();
// Also, never expect without the v to work for any of these cases.
expect(Versioning.descriptionRegex1.test(description?.slice(1))).toBeFalsy();
expect(BuildVersionGenerator.descriptionRegex1.test(description?.slice(1))).toBeFalsy();
});
});
describe('determineBuildVersion', () => {
test.each(['somethingRandom'])('throws for invalid strategy %s', async (strategy) => {
await expect(Versioning.determineBuildVersion(strategy, '')).rejects.toThrowErrorMatchingSnapshot();
await expect(BuildVersionGenerator.determineBuildVersion(strategy, '')).rejects.toThrowErrorMatchingSnapshot();
});
describe('opt out strategy', () => {
it("returns 'none'", async () => {
await expect(Versioning.determineBuildVersion('None', 'v1.0')).resolves.toMatchInlineSnapshot(`"none"`);
await expect(BuildVersionGenerator.determineBuildVersion('None', 'v1.0')).resolves.toMatchInlineSnapshot(`"none"`);
});
});
@ -180,25 +180,25 @@ describe('Versioning', () => {
test.each(['v0.1', '1', 'CamelCase', 'dashed-version'])(
'returns the inputVersion for %s',
async (inputVersion) => {
await expect(Versioning.determineBuildVersion('Custom', inputVersion)).resolves.toStrictEqual(inputVersion);
await expect(BuildVersionGenerator.determineBuildVersion('Custom', inputVersion)).resolves.toStrictEqual(inputVersion);
},
);
});
describe('semantic strategy', () => {
it('refers to generateSemanticVersion', async () => {
const generateSemanticVersion = jest.spyOn(Versioning, 'generateSemanticVersion').mockResolvedValue('1.3.37');
const generateSemanticVersion = jest.spyOn(BuildVersionGenerator, 'generateSemanticVersion').mockResolvedValue('1.3.37');
await expect(Versioning.determineBuildVersion('Semantic', '')).resolves.toStrictEqual('1.3.37');
await expect(BuildVersionGenerator.determineBuildVersion('Semantic', '')).resolves.toStrictEqual('1.3.37');
expect(generateSemanticVersion).toHaveBeenCalledTimes(1);
});
});
describe('tag strategy', () => {
it('refers to generateTagVersion', async () => {
const generateTagVersion = jest.spyOn(Versioning, 'generateTagVersion').mockResolvedValue('0.1');
const generateTagVersion = jest.spyOn(BuildVersionGenerator, 'generateTagVersion').mockResolvedValue('0.1');
await expect(Versioning.determineBuildVersion('Tag', '')).resolves.toStrictEqual('0.1');
await expect(BuildVersionGenerator.determineBuildVersion('Tag', '')).resolves.toStrictEqual('0.1');
expect(generateTagVersion).toHaveBeenCalledTimes(1);
});
});
@ -207,24 +207,24 @@ describe('Versioning', () => {
it('throws a not implemented exception', async () => {
const strategy = 'Test';
// @ts-ignore
jest.spyOn(Versioning, 'strategies', 'get').mockReturnValue({ [strategy]: strategy });
await expect(Versioning.determineBuildVersion(strategy, '')).rejects.toThrowError(NotImplementedException);
jest.spyOn(BuildVersionGenerator, 'strategies', 'get').mockReturnValue({ [strategy]: strategy });
await expect(BuildVersionGenerator.determineBuildVersion(strategy, '')).rejects.toThrowError(NotImplementedException);
});
});
});
describe('generateTagVersion', () => {
it('removes the v', async () => {
jest.spyOn(Versioning, 'getTag').mockResolvedValue('v1.3.37');
await expect(Versioning.generateTagVersion()).resolves.toStrictEqual('1.3.37');
jest.spyOn(BuildVersionGenerator, 'getTag').mockResolvedValue('v1.3.37');
await expect(BuildVersionGenerator.generateTagVersion()).resolves.toStrictEqual('1.3.37');
});
});
describe('parseSemanticVersion', () => {
it('returns the named parts', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('v0.1-2-g12345678');
jest.spyOn(BuildVersionGenerator, 'getVersionDescription').mockResolvedValue('v0.1-2-g12345678');
await expect(Versioning.parseSemanticVersion()).resolves.toMatchObject({
await expect(BuildVersionGenerator.parseSemanticVersion()).resolves.toMatchObject({
tag: '0.1',
commits: '2',
hash: '12345678',
@ -232,9 +232,9 @@ describe('Versioning', () => {
});
it('throws when no match could be made', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
jest.spyOn(BuildVersionGenerator, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
await expect(Versioning.parseSemanticVersion()).toMatchObject({});
await expect(BuildVersionGenerator.parseSemanticVersion()).toMatchObject({});
});
});
@ -242,7 +242,7 @@ describe('Versioning', () => {
it('returns the commands output', async () => {
const runOutput = 'someValue';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.getVersionDescription()).resolves.toStrictEqual(runOutput);
await expect(BuildVersionGenerator.getVersionDescription()).resolves.toStrictEqual(runOutput);
});
});
@ -250,13 +250,13 @@ describe('Versioning', () => {
it('returns true when the repo is shallow', async () => {
const runOutput = 'true\n';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isShallow()).resolves.toStrictEqual(true);
await expect(BuildVersionGenerator.isShallow()).resolves.toStrictEqual(true);
});
it('returns false when the repo is not shallow', async () => {
const runOutput = 'false\n';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isShallow()).resolves.toStrictEqual(false);
await expect(BuildVersionGenerator.isShallow()).resolves.toStrictEqual(false);
});
});
@ -264,14 +264,14 @@ describe('Versioning', () => {
it('awaits the command', async () => {
jest.spyOn(core, 'warning').mockImplementation(() => {});
jest.spyOn(System, 'run').mockImplementation();
await expect(Versioning.fetch()).resolves.not.toThrow();
await expect(BuildVersionGenerator.fetch()).resolves.not.toThrow();
});
it('falls back to the second strategy when the first fails', async () => {
jest.spyOn(core, 'warning').mockImplementation(() => {});
const gitFetch = jest.spyOn(System, 'run').mockImplementation();
await expect(Versioning.fetch()).resolves.not.toThrow();
await expect(BuildVersionGenerator.fetch()).resolves.not.toThrow();
expect(gitFetch).toHaveBeenCalledTimes(1);
});
});
@ -280,35 +280,35 @@ describe('Versioning', () => {
it('returns a proper version from description', async () => {
jest.spyOn(System, 'run').mockImplementation();
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
jest.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(true);
jest.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(2);
jest.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
jest.spyOn(BuildVersionGenerator, 'isDirty').mockResolvedValue(false);
jest.spyOn(BuildVersionGenerator, 'hasAnyVersionTags').mockResolvedValue(true);
jest.spyOn(BuildVersionGenerator, 'getTotalNumberOfCommits').mockResolvedValue(2);
jest.spyOn(BuildVersionGenerator, 'parseSemanticVersion').mockResolvedValue({
match: '0.1-2-g1b345678',
tag: '0.1',
commits: '2',
hash: '1b345678',
});
await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual('0.1.2');
await expect(BuildVersionGenerator.generateSemanticVersion()).resolves.toStrictEqual('0.1.2');
});
it('throws when dirty', async () => {
jest.spyOn(System, 'run').mockImplementation();
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(true);
await expect(Versioning.generateSemanticVersion()).rejects.toThrowError();
jest.spyOn(BuildVersionGenerator, 'isDirty').mockResolvedValue(true);
await expect(BuildVersionGenerator.generateSemanticVersion()).rejects.toThrowError();
});
it('falls back to commits only, when no tags are present', async () => {
const commits = Math.round(Math.random() * 10);
jest.spyOn(System, 'run').mockImplementation();
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
jest.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(false);
jest.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(commits);
jest.spyOn(BuildVersionGenerator, 'isDirty').mockResolvedValue(false);
jest.spyOn(BuildVersionGenerator, 'hasAnyVersionTags').mockResolvedValue(false);
jest.spyOn(BuildVersionGenerator, 'getTotalNumberOfCommits').mockResolvedValue(commits);
await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual(`0.0.${commits}`);
await expect(BuildVersionGenerator.generateSemanticVersion()).resolves.toStrictEqual(`0.0.${commits}`);
});
});
@ -316,13 +316,13 @@ describe('Versioning', () => {
it('returns true when there are files listed', async () => {
const runOutput = 'file.ext\nfile2.ext';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isDirty()).resolves.toStrictEqual(true);
await expect(BuildVersionGenerator.isDirty()).resolves.toStrictEqual(true);
});
it('returns false when there is no output', async () => {
const runOutput = '';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isDirty()).resolves.toStrictEqual(false);
await expect(BuildVersionGenerator.isDirty()).resolves.toStrictEqual(false);
});
});
@ -330,7 +330,7 @@ describe('Versioning', () => {
it('returns the commands output', async () => {
const runOutput = 'v1.0';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.getTag()).resolves.toStrictEqual(runOutput);
await expect(BuildVersionGenerator.getTag()).resolves.toStrictEqual(runOutput);
});
});
@ -338,20 +338,20 @@ describe('Versioning', () => {
it('returns false when the command returns 0', async () => {
const runOutput = '0';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(false);
await expect(BuildVersionGenerator.hasAnyVersionTags()).resolves.toStrictEqual(false);
});
it('returns true when the command returns >= 0', async () => {
const runOutput = '9';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(true);
await expect(BuildVersionGenerator.hasAnyVersionTags()).resolves.toStrictEqual(true);
});
});
describe('getTotalNumberOfCommits', () => {
it('returns a number from the command', async () => {
jest.spyOn(System, 'run').mockResolvedValue('9');
await expect(Versioning.getTotalNumberOfCommits()).resolves.toStrictEqual(9);
await expect(BuildVersionGenerator.getTotalNumberOfCommits()).resolves.toStrictEqual(9);
});
});
});