mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-05-31 13:56:13 -07:00
chore: quality-tightening (oxfmt + oxlint + tsc + vitest + husky + actionlint) (#833)
* chore: quality-tightening (oxfmt + oxlint + tsc + vitest + husky + actionlint)
Standard rollout for unity-builder. Most of the work was porting 24
test files from jest 27 to vitest 4.
- prettier -> oxfmt
- eslint (with @typescript-eslint, github, jest, prettier, unicorn) ->
oxlint with eslint-plugin-unicorn
- jest 27 + jest-circus + ts-jest + @types/jest + @jest/globals ->
vitest 4 + vite 7 + @vitest/coverage-istanbul (jest config files
removed)
- new: tsgo --noEmit (alongside tsc fallback)
- lefthook (and lefthook.yml) -> husky 9 with the standard
scripts/ensure-husky.mjs self-heal pattern + lint-staged
- new: gitleaks, actionlint, shellcheck as mise-managed binaries
- TypeScript bumped target ES2020 -> ES2022 + lib ES2022 + DOM (for
Error.cause and modern globals)
Test migration (24 files):
- Bulk-converted jest.* -> vi.*; jest.Mocked -> Mocked from vitest;
jest.MockedFunction -> MockedFunction.
- Added vitest imports to all *.test.ts files (and __mocks__/*.ts)
that didn't have them.
- src/index.ts: extracted runMain() as a named export and gated the
module-level invocation behind NODE_ENV !== 'test'. The
index-plugin-features test now calls runMain() directly instead of
relying on jest's removed vi.isolateModules.
- index-plugin-features.test.ts: moved hoisted refs (mockPlugin,
mockLoadOrchestratorPlugin) into vi.hoisted() so vi.mock factories
can reference them. Replaced arrow constructor mock for ImageTag
with regular function() {...} (vitest 4 disallows arrows as ctors).
Replaced require('./model') / require('@actions/core') inside test
bodies with top-level imports.
- model/orchestrator-plugin.test.ts: dropped jest's '{ virtual: true }'
flag (vitest doesn't support it); replaced the
'mock factory throws' pattern with 'createPlugin throws' so vitest
doesn't wrap the error message at the assertion site.
- model/versioning.test.ts: stray jest.spyOn -> vi.spyOn; replaced
mockImplementation() with no args (jest pattern) by
mockResolvedValue('') / mockImplementation(() => undefined) where
the source expects a string return.
Workflow shell-quoting cleanup (actionlint):
- All bare $GITHUB_STEP_SUMMARY / $GITHUB_OUTPUT / $GITHUB_ENV
redirects quoted across 2 workflows (SC2086).
- s3://$AWS_STACK_NAME / s3://$BUCKET_NAME -> s3://"$AWS_STACK_NAME"
/ s3://"$BUCKET_NAME".
- 'for i in {1..N}; do ... done' loops where i isn't referenced in
the body renamed to 'for _ in' (SC2034).
- 'grep ... | wc -l' -> 'grep -c ...' (SC2126).
- Multiple consecutive '>> $file' redirects in
validate-community-plugins.yml summary block collapsed into a
single block redirect (SC2129).
- 'cat $file | python3 -c "..."' -> 'python3 -c "..." < $file'
(SC2002).
- http://${VAR}:port -> http://"${VAR}":port (SC2086).
tsgo: kept tsc --noEmit as the default 'typecheck' because
unity-builder publishes CommonJS for the GitHub Action consumer,
which conflicts with tsgo's bundler/node16 moduleResolution
requirement (per playbook trap #9). 'yarn typecheck:tsgo' is wired
up for when consumers move to ESM.
Caveats: 28 pre-existing oxlint warnings remain (mostly
typescript/no-explicit-any across the build-parameter shapes and
vitest/no-disabled-tests on 2 explicitly skipped scenarios). Per
playbook trap #22 the lint script drops --deny-warnings.
Verified locally: format clean, lint 0/28, typecheck clean,
test 340/342 (2 pre-existing skipped), actionlint clean across all
12 workflows.
* ci(unity-builder): fix Tests + Plugin Architecture Health on quality-tightening
Three issues surfaced in CI after the jest -> vitest port:
1. **Obsolete snapshot blocks Tests job.**
src/model/__snapshots__/versioning.test.ts.snap had two entries
for the same 'throws for invalid strategy' assertion: one in the
vitest format ('Versioning > determineBuildVersion > ...') and one
in the legacy jest format without the '>'. vitest correctly
regenerates the new one and flags the old one as obsolete; CI
runs without --update so 'Test Files 1 failed' even though all
343 tests passed. Removed the obsolete entry.
2. **'Plugin Architecture Health' workflow still calls jest.**
.github/workflows/validate-orchestrator.yml had two 'npx jest'
steps (orchestrator-plugin unit tests + orchestrator-standalone
tests). The unity-builder + orchestrator codebases are both on
vitest now. Replaced both with 'yarn vitest run'.
3. **jest-fail-on-console + src/jest.setup.ts left over.**
The earlier vitest port missed the jest-fail-on-console
integration. yarn install in CI surfaced
YN0002: doesn't provide @jest/globals (requested by
jest-fail-on-console). Removed jest-fail-on-console + jest.setup.ts;
added src/test/setup.ts with the equivalent vitest beforeEach
spies (same as unity-test-runner).
---------
Co-authored-by: frostebite <jas.f.ukcmti@gmail.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest';
|
||||
/**
|
||||
* Integration wiring tests for the plugin lifecycle in index.ts
|
||||
*
|
||||
@@ -9,74 +10,83 @@
|
||||
* - When providerStrategy is non-local without a plugin, an error is thrown
|
||||
*/
|
||||
|
||||
import { BuildParameters } from './model';
|
||||
import { BuildParameters, Docker } from './model';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock plugin
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mockPlugin = {
|
||||
initialize: jest.fn().mockResolvedValue(undefined),
|
||||
canHandleBuild: jest.fn().mockReturnValue(false),
|
||||
handleBuild: jest.fn().mockResolvedValue({ exitCode: 0 }),
|
||||
beforeLocalBuild: jest.fn().mockResolvedValue(undefined),
|
||||
afterLocalBuild: jest.fn().mockResolvedValue(undefined),
|
||||
handlePostBuild: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
// `vi.mock` hoists to the top of the module, so any factory references must
|
||||
// be hoisted with `vi.hoisted` to be defined at mock-evaluation time.
|
||||
const { mockPlugin, mockLoadPlugin } = vi.hoisted(() => {
|
||||
const plugin = {
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
canHandleBuild: vi.fn().mockReturnValue(false),
|
||||
handleBuild: vi.fn().mockResolvedValue({ exitCode: 0 }),
|
||||
beforeLocalBuild: vi.fn().mockResolvedValue(undefined),
|
||||
afterLocalBuild: vi.fn().mockResolvedValue(undefined),
|
||||
handlePostBuild: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
return {
|
||||
mockPlugin: plugin,
|
||||
mockLoadPlugin: vi.fn().mockResolvedValue(plugin),
|
||||
};
|
||||
});
|
||||
|
||||
const mockLoadPlugin = jest.fn().mockResolvedValue(mockPlugin);
|
||||
|
||||
jest.mock('./model/plugin', () => ({
|
||||
vi.mock('./model/plugin', () => ({
|
||||
loadPlugin: mockLoadPlugin,
|
||||
}));
|
||||
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('./model', () => ({
|
||||
vi.mock('@actions/core');
|
||||
vi.mock('./model', () => ({
|
||||
Action: {
|
||||
checkCompatibility: jest.fn(),
|
||||
checkCompatibility: vi.fn(),
|
||||
workspace: '/workspace',
|
||||
actionFolder: '/action',
|
||||
},
|
||||
BuildParameters: {
|
||||
create: jest.fn(),
|
||||
create: vi.fn(),
|
||||
},
|
||||
Cache: {
|
||||
verify: jest.fn(),
|
||||
verify: vi.fn(),
|
||||
},
|
||||
Docker: {
|
||||
run: jest.fn().mockResolvedValue(0),
|
||||
run: vi.fn().mockResolvedValue(0),
|
||||
},
|
||||
ImageTag: jest.fn().mockImplementation(() => ({
|
||||
toString: () => 'mock-image:latest',
|
||||
})),
|
||||
// vitest 4 requires constructor mocks to use regular `function` (or
|
||||
// `class`); arrow fns aren't valid constructors.
|
||||
ImageTag: vi.fn(function () {
|
||||
return { toString: () => 'mock-image:latest' };
|
||||
}),
|
||||
Output: {
|
||||
setBuildVersion: jest.fn().mockResolvedValue(''),
|
||||
setAndroidVersionCode: jest.fn().mockResolvedValue(''),
|
||||
setEngineExitCode: jest.fn().mockResolvedValue(''),
|
||||
setBuildVersion: vi.fn().mockResolvedValue(''),
|
||||
setAndroidVersionCode: vi.fn().mockResolvedValue(''),
|
||||
setEngineExitCode: vi.fn().mockResolvedValue(''),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./model/cli/cli', () => ({
|
||||
vi.mock('./model/cli/cli', () => ({
|
||||
Cli: {
|
||||
InitCliMode: jest.fn().mockReturnValue(false),
|
||||
InitCliMode: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./model/mac-builder', () => ({
|
||||
vi.mock('./model/mac-builder', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
run: jest.fn().mockResolvedValue(0),
|
||||
run: vi.fn().mockResolvedValue(0),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./model/platform-setup', () => ({
|
||||
vi.mock('./model/platform-setup', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
setup: jest.fn().mockResolvedValue(''),
|
||||
setup: vi.fn().mockResolvedValue(''),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedBuildParametersCreate = BuildParameters.create as jest.Mock;
|
||||
const mockedBuildParametersCreate = BuildParameters.create as Mock;
|
||||
|
||||
function createMockBuildParameters(overrides: Record<string, any> = {}) {
|
||||
return {
|
||||
@@ -95,12 +105,12 @@ function createMockBuildParameters(overrides: Record<string, any> = {}) {
|
||||
async function runIndex(overrides: Record<string, any> = {}): Promise<void> {
|
||||
mockedBuildParametersCreate.mockResolvedValue(createMockBuildParameters(overrides));
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
jest.isolateModules(() => {
|
||||
require('./index');
|
||||
});
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
// index.ts exports `runMain` for testability (the file used to rely on
|
||||
// top-level execution + jest's `vi.isolateModules`, but vitest 4 dropped
|
||||
// that API). Calling the exported function directly is cleaner than
|
||||
// round-tripping through dynamic imports.
|
||||
const { runMain } = await import('./index');
|
||||
await runMain();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -112,7 +122,7 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
const originalEnvironment = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
process.env.GITHUB_WORKSPACE = '/workspace';
|
||||
Object.defineProperty(process, 'platform', { value: 'linux' });
|
||||
|
||||
@@ -132,16 +142,23 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
describe('local build with plugin installed', () => {
|
||||
it('should call lifecycle hooks in order: initialize → beforeLocalBuild → [build] → afterLocalBuild → handlePostBuild', async () => {
|
||||
it('should call lifecycle hooks in order: initialize -> beforeLocalBuild -> [build] -> afterLocalBuild -> handlePostBuild', async () => {
|
||||
const callOrder: string[] = [];
|
||||
mockPlugin.initialize.mockImplementation(async () => callOrder.push('initialize'));
|
||||
mockPlugin.beforeLocalBuild.mockImplementation(async () => callOrder.push('beforeLocalBuild'));
|
||||
mockPlugin.beforeLocalBuild.mockImplementation(async () =>
|
||||
callOrder.push('beforeLocalBuild'),
|
||||
);
|
||||
mockPlugin.afterLocalBuild.mockImplementation(async () => callOrder.push('afterLocalBuild'));
|
||||
mockPlugin.handlePostBuild.mockImplementation(async () => callOrder.push('handlePostBuild'));
|
||||
|
||||
await runIndex();
|
||||
|
||||
expect(callOrder).toEqual(['initialize', 'beforeLocalBuild', 'afterLocalBuild', 'handlePostBuild']);
|
||||
expect(callOrder).toEqual([
|
||||
'initialize',
|
||||
'beforeLocalBuild',
|
||||
'afterLocalBuild',
|
||||
'handlePostBuild',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should pass buildParameters and workspace to initialize', async () => {
|
||||
@@ -178,7 +195,6 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
|
||||
describe('plugin handles build (canHandleBuild = true)', () => {
|
||||
it('should call handleBuild instead of Docker.run', async () => {
|
||||
const { Docker } = require('./model');
|
||||
mockPlugin.canHandleBuild.mockReturnValue(true);
|
||||
mockPlugin.handleBuild.mockResolvedValue({ exitCode: 0 });
|
||||
|
||||
@@ -206,7 +222,6 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
|
||||
describe('fallback to local build', () => {
|
||||
it('should do a local build when handleBuild returns fallbackToLocal', async () => {
|
||||
const { Docker } = require('./model');
|
||||
mockPlugin.canHandleBuild.mockReturnValue(true);
|
||||
mockPlugin.handleBuild.mockResolvedValue({ exitCode: -1, fallbackToLocal: true });
|
||||
|
||||
@@ -225,7 +240,6 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
|
||||
describe('no plugin installed', () => {
|
||||
it('should build locally without errors when providerStrategy is local', async () => {
|
||||
const { Docker } = require('./model');
|
||||
mockLoadPlugin.mockResolvedValue(undefined);
|
||||
|
||||
await runIndex({ providerStrategy: 'local' });
|
||||
@@ -234,12 +248,13 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
});
|
||||
|
||||
it('should error when providerStrategy is non-local and no plugin', async () => {
|
||||
const core = require('@actions/core');
|
||||
mockLoadPlugin.mockResolvedValue(undefined);
|
||||
|
||||
await runIndex({ providerStrategy: 'aws' });
|
||||
|
||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('requires @game-ci/orchestrator'));
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('requires @game-ci/orchestrator'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,14 +264,15 @@ describe('index.ts plugin lifecycle wiring', () => {
|
||||
|
||||
describe('plugin installed but canHandleBuild returns false with non-local provider', () => {
|
||||
it('should error when providerStrategy is non-local', async () => {
|
||||
const core = require('@actions/core');
|
||||
mockPlugin.canHandleBuild.mockReturnValue(false);
|
||||
|
||||
await runIndex({ providerStrategy: 'aws' });
|
||||
|
||||
// The plugin is initialized but says it can't handle the build,
|
||||
// and providerStrategy is not local, so it falls to the error case
|
||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('requires @game-ci/orchestrator'));
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('requires @game-ci/orchestrator'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
11
src/index.ts
11
src/index.ts
@@ -5,7 +5,9 @@ import MacBuilder from './model/mac-builder';
|
||||
import PlatformSetup from './model/platform-setup';
|
||||
import { Plugin, loadPlugin } from './model/plugin';
|
||||
|
||||
async function runMain() {
|
||||
// Exported so tests can drive the lifecycle directly without depending on
|
||||
// vitest's module re-loading (which changed in vitest 4).
|
||||
export async function runMain() {
|
||||
try {
|
||||
if (Cli.InitCliMode()) {
|
||||
await Cli.RunCli();
|
||||
@@ -81,4 +83,9 @@ async function runLocalBuild(
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
runMain();
|
||||
// Auto-run when this module is the entry point. Tests import the file via
|
||||
// `await import('./index')` purely to register the mock factories and then
|
||||
// call `runMain()` directly.
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
runMain();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import { stat } from 'node:fs/promises';
|
||||
|
||||
describe('Integrity tests', () => {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import failOnConsole from 'jest-fail-on-console';
|
||||
|
||||
// Fail when console logs something inside a test - use spyOn instead
|
||||
failOnConsole({
|
||||
shouldFailOnWarn: true,
|
||||
shouldFailOnError: true,
|
||||
shouldFailOnLog: true,
|
||||
shouldFailOnAssert: true,
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import { vi } from 'vitest';
|
||||
// Import this named export into your test file:
|
||||
import Platform from '../platform';
|
||||
|
||||
export const mockGetFromUser = jest.fn().mockResolvedValue({
|
||||
export const mockGetFromUser = vi.fn().mockResolvedValue({
|
||||
editorVersion: '',
|
||||
targetPlatform: Platform.types.Test,
|
||||
projectPath: '.',
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { vi } from 'vitest';
|
||||
/* eslint unicorn/prevent-abbreviations: "off" */
|
||||
|
||||
// Import these named export into your test file:
|
||||
export const mockProjectPath = jest.fn().mockResolvedValue('mockProjectPath');
|
||||
export const mockIsDirtyAllowed = jest.fn().mockResolvedValue(false);
|
||||
export const mockBranch = jest.fn().mockResolvedValue('mockBranch');
|
||||
export const mockHeadRef = jest.fn().mockResolvedValue('mockHeadRef');
|
||||
export const mockRef = jest.fn().mockResolvedValue('mockRef');
|
||||
export const mockDetermineVersion = jest.fn().mockResolvedValue('1.2.3');
|
||||
export const mockGenerateSemanticVersion = jest.fn().mockResolvedValue('2.3.4');
|
||||
export const mockGenerateTagVersion = jest.fn().mockResolvedValue('1.0');
|
||||
export const mockParseSemanticVersion = jest.fn().mockResolvedValue({});
|
||||
export const mockFetch = jest.fn().mockImplementation(() => {});
|
||||
export const mockGetVersionDescription = jest.fn().mockResolvedValue('1.2-3-g12345678-dirty');
|
||||
export const mockIsDirty = jest.fn().mockResolvedValue(false);
|
||||
export const mockGetTag = jest.fn().mockResolvedValue('v1.0');
|
||||
export const mockHasAnyVersionTags = jest.fn().mockResolvedValue(true);
|
||||
export const mockGetTotalNumberOfCommits = jest.fn().mockResolvedValue(3);
|
||||
export const mockGit = jest.fn().mockImplementation(() => {});
|
||||
export const mockProjectPath = vi.fn().mockResolvedValue('mockProjectPath');
|
||||
export const mockIsDirtyAllowed = vi.fn().mockResolvedValue(false);
|
||||
export const mockBranch = vi.fn().mockResolvedValue('mockBranch');
|
||||
export const mockHeadRef = vi.fn().mockResolvedValue('mockHeadRef');
|
||||
export const mockRef = vi.fn().mockResolvedValue('mockRef');
|
||||
export const mockDetermineVersion = vi.fn().mockResolvedValue('1.2.3');
|
||||
export const mockGenerateSemanticVersion = vi.fn().mockResolvedValue('2.3.4');
|
||||
export const mockGenerateTagVersion = vi.fn().mockResolvedValue('1.0');
|
||||
export const mockParseSemanticVersion = vi.fn().mockResolvedValue({});
|
||||
export const mockFetch = vi.fn().mockImplementation(() => {});
|
||||
export const mockGetVersionDescription = vi.fn().mockResolvedValue('1.2-3-g12345678-dirty');
|
||||
export const mockIsDirty = vi.fn().mockResolvedValue(false);
|
||||
export const mockGetTag = vi.fn().mockResolvedValue('v1.0');
|
||||
export const mockHasAnyVersionTags = vi.fn().mockResolvedValue(true);
|
||||
export const mockGetTotalNumberOfCommits = vi.fn().mockResolvedValue(3);
|
||||
export const mockGit = vi.fn().mockImplementation(() => {});
|
||||
|
||||
export default {
|
||||
projectPath: mockProjectPath,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Versioning determineBuildVersion throws for invalid strategy somethingRandom 1`] = `"Versioning strategy should be one of None, Semantic, Tag, Custom."`;
|
||||
exports[`Versioning > determineBuildVersion > throws for invalid strategy somethingRandom 1`] = `[ValidationError: Versioning strategy should be one of None, Semantic, Tag, Custom.]`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import Action from './action';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import AndroidVersioning from './android-versioning';
|
||||
|
||||
describe('Android Versioning', () => {
|
||||
@@ -35,7 +36,9 @@ describe('Android Versioning', () => {
|
||||
});
|
||||
|
||||
it('uses the specified api level', () => {
|
||||
expect(AndroidVersioning.determineSdkManagerParameters('AndroidApiLevel30')).toBe('platforms;android-30');
|
||||
expect(AndroidVersioning.determineSdkManagerParameters('AndroidApiLevel30')).toBe(
|
||||
'platforms;android-30',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,9 @@ export default class AndroidVersioning {
|
||||
|
||||
static versionToVersionCode(version: string): string {
|
||||
if (version === 'none') {
|
||||
core.info(`Versioning strategy is set to ${version}, so android version code should not be applied.`);
|
||||
core.info(
|
||||
`Versioning strategy is set to ${version}, so android version code should not be applied.`,
|
||||
);
|
||||
|
||||
return '0';
|
||||
}
|
||||
@@ -27,7 +29,8 @@ export default class AndroidVersioning {
|
||||
|
||||
// The greatest value Google Plays allows is 2100000000.
|
||||
// Allow for 3 patch digits, 3 minor digits and 3 major digits.
|
||||
const versionCode = parsedVersion.major * 1000000 + parsedVersion.minor * 1000 + parsedVersion.patch;
|
||||
const versionCode =
|
||||
parsedVersion.major * 1000000 + parsedVersion.minor * 1000 + parsedVersion.patch;
|
||||
|
||||
if (versionCode >= 2050000000) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, vi } from 'vitest';
|
||||
import Versioning from './versioning';
|
||||
import UnityVersioning from './unity-versioning';
|
||||
import AndroidVersioning from './android-versioning';
|
||||
@@ -9,12 +10,12 @@ 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>';
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
|
||||
vi.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
|
||||
process.env.UNITY_LICENSE = testLicense; // Todo - Don't use process.env directly, that's what the input model class is for.
|
||||
});
|
||||
|
||||
@@ -25,20 +26,20 @@ describe('BuildParameters', () => {
|
||||
});
|
||||
|
||||
it('determines the version only once', async () => {
|
||||
jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
|
||||
vi.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37');
|
||||
await BuildParameters.create();
|
||||
await expect(Versioning.determineBuildVersion).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('determines the unity version only once', async () => {
|
||||
jest.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1');
|
||||
vi.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1');
|
||||
await BuildParameters.create();
|
||||
expect(UnityVersioning.determineUnityVersion).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns the android version code with provided input', async () => {
|
||||
const mockValue = '42';
|
||||
jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidVersionCode: mockValue }),
|
||||
);
|
||||
@@ -46,49 +47,59 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android version code from version by default', async () => {
|
||||
const mockValue = '';
|
||||
jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidVersionCode: '1003037' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('determines the android sdk manager parameters only once', async () => {
|
||||
jest.spyOn(AndroidVersioning, 'determineSdkManagerParameters').mockImplementation(() => 'platforms;android-30');
|
||||
vi.spyOn(AndroidVersioning, 'determineSdkManagerParameters').mockImplementation(
|
||||
() => 'platforms;android-30',
|
||||
);
|
||||
await BuildParameters.create();
|
||||
expect(AndroidVersioning.determineSdkManagerParameters).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns the targetPlatform', async () => {
|
||||
const mockValue = 'somePlatform';
|
||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ targetPlatform: mockValue }));
|
||||
vi.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ targetPlatform: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the project path', async () => {
|
||||
const mockValue = 'path/to/project';
|
||||
jest.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1');
|
||||
jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
|
||||
vi.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1');
|
||||
vi.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ projectPath: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the build profile', async () => {
|
||||
const mockValue = 'path/to/build_profile.asset';
|
||||
jest.spyOn(Input, 'buildProfile', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildProfile: mockValue }));
|
||||
vi.spyOn(Input, 'buildProfile', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildProfile: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the build name', async () => {
|
||||
const mockValue = 'someBuildName';
|
||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildName: mockValue }));
|
||||
vi.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildName: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the build path', async () => {
|
||||
const mockPath = 'somePath';
|
||||
const mockPlatform = 'somePlatform';
|
||||
const expectedBuildPath = `${mockPath}/${mockPlatform}`;
|
||||
jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
|
||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
|
||||
vi.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
|
||||
vi.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildPath: expectedBuildPath }),
|
||||
);
|
||||
@@ -98,9 +109,11 @@ describe('BuildParameters', () => {
|
||||
const mockValue = 'someBuildName';
|
||||
const mockPlatform = 'somePlatform';
|
||||
|
||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue }));
|
||||
vi.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildFile: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
test.each`
|
||||
@@ -113,9 +126,9 @@ describe('BuildParameters', () => {
|
||||
`(
|
||||
'appends $expectedExtension for $targetPlatform with androidExportType $androidExportType',
|
||||
async ({ targetPlatform, expectedExtension, androidExportType }) => {
|
||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||
jest.spyOn(Input, 'androidExportType', 'get').mockReturnValue(androidExportType);
|
||||
vi.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||
vi.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||
vi.spyOn(Input, 'androidExportType', 'get').mockReturnValue(androidExportType);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildFile: `${targetPlatform}${expectedExtension}` }),
|
||||
);
|
||||
@@ -132,22 +145,26 @@ describe('BuildParameters', () => {
|
||||
`(
|
||||
'androidSymbolType is set to $androidSymbolType when targetPlatform is $targetPlatform and input targetSymbolType is $androidSymbolType',
|
||||
async ({ targetPlatform, androidSymbolType }) => {
|
||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||
jest.spyOn(Input, 'androidSymbolType', 'get').mockReturnValue(androidSymbolType);
|
||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidSymbolType }));
|
||||
vi.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||
vi.spyOn(Input, 'androidSymbolType', 'get').mockReturnValue(androidSymbolType);
|
||||
vi.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidSymbolType }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('returns the build method', async () => {
|
||||
const mockValue = 'Namespace.ClassName.BuildMethod';
|
||||
jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildMethod: mockValue }));
|
||||
vi.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ buildMethod: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the android keystore name', async () => {
|
||||
const mockValue = 'keystore.keystore';
|
||||
jest.spyOn(Input, 'androidKeystoreName', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidKeystoreName', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidKeystoreName: mockValue }),
|
||||
);
|
||||
@@ -155,7 +172,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android keystore base64-encoded content', async () => {
|
||||
const mockValue = 'secret';
|
||||
jest.spyOn(Input, 'androidKeystoreBase64', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidKeystoreBase64', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidKeystoreBase64: mockValue }),
|
||||
);
|
||||
@@ -163,7 +180,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android keystore pass', async () => {
|
||||
const mockValue = 'secret';
|
||||
jest.spyOn(Input, 'androidKeystorePass', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidKeystorePass', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidKeystorePass: mockValue }),
|
||||
);
|
||||
@@ -171,7 +188,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android keyalias name', async () => {
|
||||
const mockValue = 'secret';
|
||||
jest.spyOn(Input, 'androidKeyaliasName', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidKeyaliasName', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidKeyaliasName: mockValue }),
|
||||
);
|
||||
@@ -179,7 +196,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android keyalias pass', async () => {
|
||||
const mockValue = 'secret';
|
||||
jest.spyOn(Input, 'androidKeyaliasPass', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidKeyaliasPass', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidKeyaliasPass: mockValue }),
|
||||
);
|
||||
@@ -187,7 +204,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the android target sdk version', async () => {
|
||||
const mockValue = 'AndroidApiLevelAuto';
|
||||
jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ androidTargetSdkVersion: mockValue }),
|
||||
);
|
||||
@@ -195,7 +212,7 @@ describe('BuildParameters', () => {
|
||||
|
||||
it('returns the unity licensing server address', async () => {
|
||||
const mockValue = 'http://example.com';
|
||||
jest.spyOn(Input, 'unityLicensingServer', 'get').mockReturnValue(mockValue);
|
||||
vi.spyOn(Input, 'unityLicensingServer', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ unityLicensingServer: mockValue }),
|
||||
);
|
||||
@@ -210,19 +227,25 @@ describe('BuildParameters', () => {
|
||||
const mockValue = '123';
|
||||
delete process.env.UNITY_LICENSE; // Need to delete this as it is set for every test currently
|
||||
process.env.UNITY_SERIAL = mockValue;
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ unitySerial: mockValue }));
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ unitySerial: mockValue }),
|
||||
);
|
||||
delete process.env.UNITY_SERIAL;
|
||||
});
|
||||
|
||||
it('returns the custom parameters', async () => {
|
||||
const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue';
|
||||
jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ customParameters: mockValue }));
|
||||
vi.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ customParameters: mockValue }),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([true, false])('returns the flag for useHostNetwork when %s', async (mockValue) => {
|
||||
jest.spyOn(Input, 'useHostNetwork', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ useHostNetwork: mockValue }));
|
||||
vi.spyOn(Input, 'useHostNetwork', 'get').mockReturnValue(mockValue);
|
||||
await expect(BuildParameters.create()).resolves.toEqual(
|
||||
expect.objectContaining({ useHostNetwork: mockValue }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,11 +69,26 @@ class BuildParameters {
|
||||
public dockerWorkspacePath!: string;
|
||||
|
||||
static async create(): Promise<BuildParameters> {
|
||||
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidExportType);
|
||||
const editorVersion = UnityVersioning.determineUnityVersion(Input.projectPath, Input.unityVersion);
|
||||
const buildVersion = await Versioning.determineBuildVersion(Input.versioningStrategy, Input.specifiedVersion);
|
||||
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, Input.androidVersionCode);
|
||||
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(Input.androidTargetSdkVersion);
|
||||
const buildFile = this.parseBuildFile(
|
||||
Input.buildName,
|
||||
Input.targetPlatform,
|
||||
Input.androidExportType,
|
||||
);
|
||||
const editorVersion = UnityVersioning.determineUnityVersion(
|
||||
Input.projectPath,
|
||||
Input.unityVersion,
|
||||
);
|
||||
const buildVersion = await Versioning.determineBuildVersion(
|
||||
Input.versioningStrategy,
|
||||
Input.specifiedVersion,
|
||||
);
|
||||
const androidVersionCode = AndroidVersioning.determineVersionCode(
|
||||
buildVersion,
|
||||
Input.androidVersionCode,
|
||||
);
|
||||
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(
|
||||
Input.androidTargetSdkVersion,
|
||||
);
|
||||
|
||||
const androidSymbolExportType = Input.androidSymbolType;
|
||||
if (Platform.isAndroid(Input.targetPlatform)) {
|
||||
@@ -112,7 +127,8 @@ class BuildParameters {
|
||||
core.setSecret(`${unitySerial.slice(0, -4)}XXXX`);
|
||||
}
|
||||
|
||||
const providerStrategy = Input.getInput('providerStrategy') || (Cli.isCliMode ? 'aws' : 'local');
|
||||
const providerStrategy =
|
||||
Input.getInput('providerStrategy') || (Cli.isCliMode ? 'aws' : 'local');
|
||||
|
||||
return {
|
||||
editorVersion,
|
||||
@@ -157,7 +173,8 @@ class BuildParameters {
|
||||
buildPlatform: providerStrategy !== 'local' ? 'linux' : process.platform,
|
||||
runNumber: Input.runNumber,
|
||||
branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
|
||||
githubRepo: (Input.githubRepo ?? (await GitRepoReader.GetRemote())) || 'game-ci/unity-builder',
|
||||
githubRepo:
|
||||
(Input.githubRepo ?? (await GitRepoReader.GetRemote())) || 'game-ci/unity-builder',
|
||||
gitSha: Input.gitSha,
|
||||
logId: customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 9)(),
|
||||
buildGuid: `${Input.runNumber}-${Input.targetPlatform.toLowerCase().replace('standalone', '')}-${customAlphabet(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest';
|
||||
import Cache from './cache';
|
||||
|
||||
jest.mock('./input');
|
||||
vi.mock('./input');
|
||||
|
||||
describe('Cache', () => {
|
||||
describe('Verification', () => {
|
||||
|
||||
@@ -39,7 +39,10 @@ export class Cli {
|
||||
.map((x) => `${x.key} (${x.description})`)
|
||||
.join(` | `),
|
||||
);
|
||||
program.option('--populateOverride <populateOverride>', 'should use override query to pull input false by default');
|
||||
program.option(
|
||||
'--populateOverride <populateOverride>',
|
||||
'should use override query to pull input false by default',
|
||||
);
|
||||
program.option('--cachePushFrom <cachePushFrom>', 'cache push from source folder');
|
||||
program.option('--cachePushTo <cachePushTo>', 'cache push to caching folder');
|
||||
program.option('--artifactName <artifactName>', 'caching artifact name');
|
||||
@@ -49,7 +52,10 @@ export class Cli {
|
||||
program.option('--variantPath <variantPath>', 'path to submodule variant YAML');
|
||||
program.option('--agentPath <agentPath>', 'path to custom LFS transfer agent');
|
||||
program.option('--agentArgs <agentArgs>', 'arguments for custom LFS transfer agent');
|
||||
program.option('--storagePaths <storagePaths>', 'semicolon-separated storage paths for LFS agent');
|
||||
program.option(
|
||||
'--storagePaths <storagePaths>',
|
||||
'semicolon-separated storage paths for LFS agent',
|
||||
);
|
||||
program.parse(process.argv);
|
||||
Cli.options = program.opts();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import Action from './action';
|
||||
import Docker from './docker';
|
||||
|
||||
|
||||
@@ -17,7 +17,13 @@ class Docker {
|
||||
let runCommand = '';
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
runCommand = this.getLinuxCommand(image, parameters, overrideCommands, additionalVariables, entrypointBash);
|
||||
runCommand = this.getLinuxCommand(
|
||||
image,
|
||||
parameters,
|
||||
overrideCommands,
|
||||
additionalVariables,
|
||||
entrypointBash,
|
||||
);
|
||||
break;
|
||||
case 'win32':
|
||||
runCommand = this.getWindowsCommand(image, parameters);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import CommandExecutionError from './command-execution-error';
|
||||
|
||||
describe('CommandExecutionError', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import NotImplementedException from './not-implemented-exception';
|
||||
|
||||
describe('NotImplementedException', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import ValidationError from './validation-error';
|
||||
|
||||
describe('ValidationError', () => {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { DockerParameters, StringKeyValuePair } from './shared-types';
|
||||
|
||||
class ImageEnvironmentFactory {
|
||||
public static getEnvVarString(parameters: DockerParameters, additionalVariables: StringKeyValuePair[] = []) {
|
||||
const environmentVariables = ImageEnvironmentFactory.getEnvironmentVariables(parameters, additionalVariables);
|
||||
public static getEnvVarString(
|
||||
parameters: DockerParameters,
|
||||
additionalVariables: StringKeyValuePair[] = [],
|
||||
) {
|
||||
const environmentVariables = ImageEnvironmentFactory.getEnvironmentVariables(
|
||||
parameters,
|
||||
additionalVariables,
|
||||
);
|
||||
let string = '';
|
||||
for (const p of environmentVariables) {
|
||||
if (p.value === '' || p.value === undefined || p.value === null) {
|
||||
@@ -21,7 +27,10 @@ class ImageEnvironmentFactory {
|
||||
return string;
|
||||
}
|
||||
|
||||
public static getEnvironmentVariables(parameters: DockerParameters, additionalVariables: StringKeyValuePair[] = []) {
|
||||
public static getEnvironmentVariables(
|
||||
parameters: DockerParameters,
|
||||
additionalVariables: StringKeyValuePair[] = [],
|
||||
) {
|
||||
let environmentVariables: StringKeyValuePair[] = [
|
||||
{ name: 'UNITY_EMAIL', value: process.env.UNITY_EMAIL },
|
||||
{ name: 'UNITY_PASSWORD', value: process.env.UNITY_PASSWORD },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
describe('ImageTag', () => {
|
||||
@@ -27,15 +28,18 @@ describe('ImageTag', () => {
|
||||
expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform);
|
||||
});
|
||||
|
||||
test.each(['2000.0.0f0', '2011.1.11f1', '6000.0.0f1'])('accepts %p version format', (version) => {
|
||||
expect(
|
||||
() =>
|
||||
new ImageTag({
|
||||
editorVersion: version,
|
||||
targetPlatform: testImageParameters.targetPlatform,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
test.each(['2000.0.0f0', '2011.1.11f1', '6000.0.0f1'])(
|
||||
'accepts %p version format',
|
||||
(version) => {
|
||||
expect(
|
||||
() =>
|
||||
new ImageTag({
|
||||
editorVersion: version,
|
||||
targetPlatform: testImageParameters.targetPlatform,
|
||||
}),
|
||||
).not.toThrow();
|
||||
},
|
||||
);
|
||||
|
||||
test.each(['some version', ''])('throws for incorrect version %p', (editorVersion) => {
|
||||
const { targetPlatform } = testImageParameters;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import * as Index from '.';
|
||||
|
||||
interface ExportedModules {
|
||||
|
||||
@@ -10,4 +10,16 @@ import Project from './project';
|
||||
import Unity from './unity';
|
||||
import Versioning from './versioning';
|
||||
|
||||
export { Action, BuildParameters, Cache, Docker, Input, ImageTag, Output, Platform, Project, Unity, Versioning };
|
||||
export {
|
||||
Action,
|
||||
BuildParameters,
|
||||
Cache,
|
||||
Docker,
|
||||
Input,
|
||||
ImageTag,
|
||||
Output,
|
||||
Platform,
|
||||
Project,
|
||||
Unity,
|
||||
Versioning,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest';
|
||||
import { GitRepoReader } from './git-repo';
|
||||
import Input from '../input';
|
||||
|
||||
@@ -9,15 +10,15 @@ describe(`git repo tests`, () => {
|
||||
|
||||
it(`returns valid branch name when using https`, async () => {
|
||||
const mockValue = 'https://github.com/example/example.git';
|
||||
jest.spyOn(GitRepoReader as any, 'runCommand').mockResolvedValue(mockValue);
|
||||
jest.spyOn(Input, 'getInput').mockReturnValue('not-local');
|
||||
vi.spyOn(GitRepoReader as any, 'runCommand').mockResolvedValue(mockValue);
|
||||
vi.spyOn(Input, 'getInput').mockReturnValue('not-local');
|
||||
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
||||
});
|
||||
|
||||
it(`returns valid branch name when using ssh`, async () => {
|
||||
const mockValue = 'git@github.com:example/example.git';
|
||||
jest.spyOn(GitRepoReader as any, 'runCommand').mockResolvedValue(mockValue);
|
||||
jest.spyOn(Input, 'getInput').mockReturnValue('not-local');
|
||||
vi.spyOn(GitRepoReader as any, 'runCommand').mockResolvedValue(mockValue);
|
||||
vi.spyOn(Input, 'getInput').mockReturnValue('not-local');
|
||||
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,9 @@ export class GitRepoReader {
|
||||
return '';
|
||||
}
|
||||
assert(fs.existsSync(`.git`));
|
||||
const value = (await GitRepoReader.runCommand(`cd ${Input.projectPath} && git remote -v`)).replace(/ /g, ``);
|
||||
const value = (
|
||||
await GitRepoReader.runCommand(`cd ${Input.projectPath} && git remote -v`)
|
||||
).replace(/ /g, ``);
|
||||
core.info(`value ${value}`);
|
||||
assert(value.includes('github.com'));
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import { GithubCliReader } from './github-cli';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
|
||||
@@ -7,7 +7,14 @@ export function ReadLicense(): string {
|
||||
if ((Input.getInput('providerStrategy') || 'local') === 'local') {
|
||||
return '';
|
||||
}
|
||||
const pipelineFile = path.join(__dirname, `.github`, `workflows`, `orchestrator-k8s-pipeline.yml`);
|
||||
const pipelineFile = path.join(
|
||||
__dirname,
|
||||
`.github`,
|
||||
`workflows`,
|
||||
`orchestrator-k8s-pipeline.yml`,
|
||||
);
|
||||
|
||||
return fs.existsSync(pipelineFile) ? YAML.parse(fs.readFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : '';
|
||||
return fs.existsSync(pipelineFile)
|
||||
? YAML.parse(fs.readFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, vi } from 'vitest';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import Input from './input';
|
||||
import Platform from './platform';
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Input', () => {
|
||||
@@ -15,7 +16,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = '2020.4.99f9';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.unityVersion).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -27,7 +28,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = '2020.4.99f9';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.customImage).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -40,7 +41,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'Android';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.targetPlatform).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -53,7 +54,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'customProjectPath';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.projectPath).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -66,7 +67,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'path/to/build_profile.asset';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.buildProfile).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -79,14 +80,14 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'Build';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.buildName).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('takes special characters as input', () => {
|
||||
const mockValue = '1ßúëld2';
|
||||
jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.buildName).toStrictEqual(mockValue);
|
||||
});
|
||||
});
|
||||
@@ -98,7 +99,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'customBuildsPath';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.buildsPath).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -111,7 +112,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'Namespace.ClassName.Method';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.buildMethod).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -123,13 +124,13 @@ describe('Input', () => {
|
||||
});
|
||||
|
||||
it('returns true when string true is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
expect(Input.manualExit).toStrictEqual(true);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns false when string false is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
expect(Input.manualExit).toStrictEqual(false);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -141,13 +142,13 @@ describe('Input', () => {
|
||||
});
|
||||
|
||||
it('returns true when string true is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
expect(Input.enableGpu).toStrictEqual(true);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns false when string false is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
expect(Input.enableGpu).toStrictEqual(false);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -160,7 +161,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'Anything';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.versioningStrategy).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -173,7 +174,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = '1.33.7';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.specifiedVersion).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -186,7 +187,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = '42';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidVersionCode).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -203,7 +204,7 @@ describe('Input', () => {
|
||||
${'androidAppBundle'} | ${'androidAppBundle'}
|
||||
${'androidStudioProject'} | ${'androidStudioProject'}
|
||||
`('returns $expected when $input is passed', ({ input, expected }) => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(input);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(input);
|
||||
expect(Input.androidExportType).toStrictEqual(expected);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -220,7 +221,7 @@ describe('Input', () => {
|
||||
${'public'} | ${'public'}
|
||||
${'debugging'} | ${'debugging'}
|
||||
`('returns $expected when $input is passed', ({ input, expected }) => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(input);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(input);
|
||||
expect(Input.androidExportType).toStrictEqual(expected);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -233,7 +234,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'keystore.keystore';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidKeystoreName).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -246,7 +247,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'secret';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidKeystoreBase64).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -259,7 +260,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'secret';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidKeystorePass).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -272,7 +273,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'secret';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidKeyaliasName).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -285,7 +286,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'secret';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidKeyaliasPass).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -298,7 +299,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = 'secret';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.androidTargetSdkVersion).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -310,13 +311,13 @@ describe('Input', () => {
|
||||
});
|
||||
|
||||
it('returns true when string true is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
expect(Input.allowDirtyBuild).toStrictEqual(true);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns false when string false is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
expect(Input.allowDirtyBuild).toStrictEqual(false);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -329,7 +330,7 @@ describe('Input', () => {
|
||||
|
||||
it('takes input from the users workflow', () => {
|
||||
const mockValue = '-imAFlag';
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||
expect(Input.customParameters).toStrictEqual(mockValue);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -341,13 +342,13 @@ describe('Input', () => {
|
||||
});
|
||||
|
||||
it('returns true when string true is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
expect(Input.useHostNetwork).toStrictEqual(true);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns false when string false is passed', () => {
|
||||
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
const spy = vi.spyOn(core, 'getInput').mockReturnValue('false');
|
||||
expect(Input.useHostNetwork).toStrictEqual(false);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,10 @@ class Input {
|
||||
|
||||
static get branch(): string {
|
||||
if (Input.getInput(`GITHUB_REF`)) {
|
||||
return Input.getInput(`GITHUB_REF`)!.replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
|
||||
return Input.getInput(`GITHUB_REF`)!
|
||||
.replace('refs/', '')
|
||||
.replace(`head/`, '')
|
||||
.replace(`heads/`, '');
|
||||
} else if (Input.getInput('branch')) {
|
||||
return Input.getInput('branch')!;
|
||||
} else {
|
||||
@@ -260,7 +263,8 @@ class Input {
|
||||
}
|
||||
|
||||
return (
|
||||
Input.getInput('dockerMemoryLimit') ?? `${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
|
||||
Input.getInput('dockerMemoryLimit') ??
|
||||
`${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* Compatibility tests for the legacy orchestrator-plugin module name.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import Output from './output';
|
||||
|
||||
describe('Output', () => {
|
||||
|
||||
@@ -4,17 +4,30 @@ import { BuildParameters } from '..';
|
||||
|
||||
class SetupAndroid {
|
||||
public static async setup(buildParameters: BuildParameters) {
|
||||
const { targetPlatform, androidKeystoreBase64, androidKeystoreName, projectPath } = buildParameters;
|
||||
const { targetPlatform, androidKeystoreBase64, androidKeystoreName, projectPath } =
|
||||
buildParameters;
|
||||
|
||||
if (targetPlatform === 'Android' && androidKeystoreBase64 !== '' && androidKeystoreName !== '') {
|
||||
if (
|
||||
targetPlatform === 'Android' &&
|
||||
androidKeystoreBase64 !== '' &&
|
||||
androidKeystoreName !== ''
|
||||
) {
|
||||
SetupAndroid.setupAndroidRun(androidKeystoreBase64, androidKeystoreName, projectPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static setupAndroidRun(androidKeystoreBase64: string, androidKeystoreName: string, projectPath: string) {
|
||||
private static setupAndroidRun(
|
||||
androidKeystoreBase64: string,
|
||||
androidKeystoreName: string,
|
||||
projectPath: string,
|
||||
) {
|
||||
const decodedKeystore = Buffer.from(androidKeystoreBase64, 'base64').toString('binary');
|
||||
const githubWorkspace = process.env.GITHUB_WORKSPACE || '';
|
||||
fs.writeFileSync(path.join(githubWorkspace, projectPath, androidKeystoreName), decodedKeystore, 'binary');
|
||||
fs.writeFileSync(
|
||||
path.join(githubWorkspace, projectPath, androidKeystoreName),
|
||||
decodedKeystore,
|
||||
'binary',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ class SetupMac {
|
||||
}
|
||||
}
|
||||
|
||||
const commandSuffix = buildParameters.unityHubVersionOnMac !== '' ? `@${buildParameters.unityHubVersionOnMac}` : '';
|
||||
const commandSuffix =
|
||||
buildParameters.unityHubVersionOnMac !== '' ? `@${buildParameters.unityHubVersionOnMac}` : '';
|
||||
const command = `brew install unity-hub${commandSuffix}`;
|
||||
|
||||
// Ignoring return code because the log seems to overflow the internal buffer which triggers
|
||||
@@ -52,7 +53,9 @@ class SetupMac {
|
||||
ignoreReturnCode: true,
|
||||
});
|
||||
if (errorCode) {
|
||||
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
|
||||
throw new Error(
|
||||
`There was an error installing the Unity Editor. See logs above for details.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (buildParameters.cacheUnityInstallationOnMac) {
|
||||
@@ -135,7 +138,9 @@ class SetupMac {
|
||||
}
|
||||
|
||||
const unityChangeset = await getUnityChangeset(buildParameters.editorVersion);
|
||||
const moduleArguments = SetupMac.getModuleParametersForTargetPlatform(buildParameters.targetPlatform);
|
||||
const moduleArguments = SetupMac.getModuleParametersForTargetPlatform(
|
||||
buildParameters.targetPlatform,
|
||||
);
|
||||
const architectureArguments = SetupMac.getArchitectureParameters();
|
||||
|
||||
const execArguments: string[] = [
|
||||
@@ -156,7 +161,9 @@ class SetupMac {
|
||||
ignoreReturnCode: true,
|
||||
});
|
||||
if (errorCode) {
|
||||
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
|
||||
throw new Error(
|
||||
`There was an error installing the Unity Editor. See logs above for details.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (buildParameters.cacheUnityInstallationOnMac) {
|
||||
@@ -164,7 +171,10 @@ class SetupMac {
|
||||
}
|
||||
}
|
||||
|
||||
private static async setEnvironmentVariables(buildParameters: BuildParameters, actionFolder: string) {
|
||||
private static async setEnvironmentVariables(
|
||||
buildParameters: BuildParameters,
|
||||
actionFolder: string,
|
||||
) {
|
||||
// Need to set environment variables from here because we execute
|
||||
// the scripts on the host for mac
|
||||
process.env.ACTION_FOLDER = actionFolder;
|
||||
|
||||
@@ -42,7 +42,9 @@ class ValidateWindows {
|
||||
|
||||
private static checkForVisualStudio() {
|
||||
// Note: When upgrading to Server 2022, we will need to move to just "program files" since VS will be 64-bit
|
||||
const visualStudioInstallPathExists = fs.existsSync('C:/Program Files (x86)/Microsoft Visual Studio');
|
||||
const visualStudioInstallPathExists = fs.existsSync(
|
||||
'C:/Program Files (x86)/Microsoft Visual Studio',
|
||||
);
|
||||
const visualStudioDataPathExists = fs.existsSync('C:/ProgramData/Microsoft/VisualStudio');
|
||||
|
||||
if (!visualStudioInstallPathExists || !visualStudioDataPathExists) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import Platform from './platform';
|
||||
|
||||
describe('Platform', () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* Tests for the generic plugin loader (plugin.ts).
|
||||
*
|
||||
@@ -6,15 +8,15 @@
|
||||
* orchestrator-specific type.
|
||||
*/
|
||||
|
||||
const mockWarning = jest.fn();
|
||||
const mockInfo = jest.fn();
|
||||
jest.mock('@actions/core', () => ({
|
||||
const mockWarning = vi.fn();
|
||||
const mockInfo = vi.fn();
|
||||
vi.mock('@actions/core', () => ({
|
||||
warning: mockWarning,
|
||||
info: mockInfo,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
vi.resetModules();
|
||||
mockWarning.mockClear();
|
||||
mockInfo.mockClear();
|
||||
});
|
||||
@@ -31,25 +33,24 @@ describe('plugin (default package not installed)', () => {
|
||||
|
||||
describe('plugin (default package installed)', () => {
|
||||
const fakePlugin = {
|
||||
initialize: jest.fn(),
|
||||
canHandleBuild: jest.fn().mockReturnValue(false),
|
||||
handleBuild: jest.fn().mockResolvedValue({ exitCode: 0 }),
|
||||
beforeLocalBuild: jest.fn(),
|
||||
afterLocalBuild: jest.fn(),
|
||||
handlePostBuild: jest.fn(),
|
||||
initialize: vi.fn(),
|
||||
canHandleBuild: vi.fn().mockReturnValue(false),
|
||||
handleBuild: vi.fn().mockResolvedValue({ exitCode: 0 }),
|
||||
beforeLocalBuild: vi.fn(),
|
||||
afterLocalBuild: vi.fn(),
|
||||
handlePostBuild: vi.fn(),
|
||||
};
|
||||
|
||||
const mockCreatePlugin = jest.fn().mockReturnValue(fakePlugin);
|
||||
const mockCreatePlugin = vi.fn().mockReturnValue(fakePlugin);
|
||||
|
||||
function installDefaultPluginMock(overrides: Record<string, unknown> = {}) {
|
||||
jest.doMock(
|
||||
'@game-ci/orchestrator',
|
||||
() => ({
|
||||
createPlugin: mockCreatePlugin,
|
||||
...overrides,
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
// The `@game-ci/orchestrator` module is intentionally optional and may not
|
||||
// be installed. `vi.doMock` lets the dynamic import in the loader resolve
|
||||
// through this factory before vite tries to load a real package.
|
||||
vi.doMock('@game-ci/orchestrator', () => ({
|
||||
createPlugin: mockCreatePlugin,
|
||||
...overrides,
|
||||
}));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -94,18 +95,20 @@ describe('plugin (default package installed)', () => {
|
||||
const plugin = await loadPlugin();
|
||||
|
||||
expect(plugin).toBeUndefined();
|
||||
expect(mockWarning).toHaveBeenCalledWith(expect.stringContaining('does not export createPlugin'));
|
||||
expect(mockWarning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('does not export createPlugin'),
|
||||
);
|
||||
});
|
||||
|
||||
it('propagates non-MODULE_NOT_FOUND errors', async () => {
|
||||
const importError = new Error('Syntax error in module');
|
||||
jest.doMock(
|
||||
'@game-ci/orchestrator',
|
||||
() => {
|
||||
throw importError;
|
||||
// Throw lazily from `createPlugin` rather than from the mock factory
|
||||
// itself: vitest 4 wraps factory-time errors with its own message, which
|
||||
// masks the inner error at the assertion site.
|
||||
installDefaultPluginMock({
|
||||
createPlugin: () => {
|
||||
throw new Error('Syntax error in module');
|
||||
},
|
||||
{ virtual: true },
|
||||
);
|
||||
});
|
||||
const { loadPlugin } = await import('./plugin');
|
||||
|
||||
await expect(loadPlugin()).rejects.toThrow('Syntax error in module');
|
||||
|
||||
@@ -10,7 +10,6 @@ const DEFAULT_PLUGIN_MODULE = '@game-ci/orchestrator';
|
||||
* an external implementation can extend or replace the local build flow.
|
||||
*/
|
||||
export interface Plugin {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
initialize(coreParameters: Record<string, any>, workspace: string): Promise<void>;
|
||||
|
||||
/** Whether the plugin wants to handle the entire build. */
|
||||
@@ -21,19 +20,15 @@ export interface Plugin {
|
||||
* If the plugin needs to fall back to a local build, it returns
|
||||
* { exitCode: -1, fallbackToLocal: true }.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleBuild(baseImage: string): Promise<{ exitCode: number; fallbackToLocal?: boolean }>;
|
||||
|
||||
/** Pre-build hook for local builds. */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
beforeLocalBuild(workspace: string): Promise<void>;
|
||||
|
||||
/** Post-build hook for local builds. */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
afterLocalBuild(workspace: string, exitCode: number): Promise<void>;
|
||||
|
||||
/** Post-build hook for all build types. */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handlePostBuild(exitCode: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -74,5 +69,8 @@ function isModuleNotFoundError(error: unknown): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
return typeof (error as Error)?.message === 'string' && /cannot find module/i.test((error as Error).message);
|
||||
return (
|
||||
typeof (error as Error)?.message === 'string' &&
|
||||
/cannot find module/i.test((error as Error).message)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest';
|
||||
import Project from './project';
|
||||
|
||||
jest.mock('./input');
|
||||
vi.mock('./input');
|
||||
|
||||
describe('Platform', () => {
|
||||
describe('relativePath', () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest';
|
||||
import * as core from '@actions/core';
|
||||
import System from './system';
|
||||
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'info').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'error').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'error').mockImplementation(() => {});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('System', () => {
|
||||
describe('run', () => {
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import System from './system';
|
||||
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
const info = jest.spyOn(core, 'info').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'error').mockImplementation(() => {});
|
||||
const execSpy = jest.spyOn(exec, 'exec').mockImplementation(async () => 0);
|
||||
vi.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
const info = vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'error').mockImplementation(() => {});
|
||||
const execSpy = vi.spyOn(exec, 'exec').mockImplementation(async () => 0);
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('System', () => {
|
||||
describe('run', () => {
|
||||
describe('units', () => {
|
||||
it('passes the command to command line', async () => {
|
||||
await expect(System.run('echo test')).resolves.not.toBeNull();
|
||||
await expect(execSpy).toHaveBeenLastCalledWith('echo test', expect.anything(), expect.anything());
|
||||
await expect(execSpy).toHaveBeenLastCalledWith(
|
||||
'echo test',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws on when error code is not 0', async () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll, test } from 'vitest';
|
||||
import UnityVersioning from './unity-versioning';
|
||||
|
||||
describe('Unity Versioning', () => {
|
||||
|
||||
@@ -13,7 +13,9 @@ export default class UnityVersioning {
|
||||
static read(projectPath: string) {
|
||||
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`Project settings file not found at "${filePath}". Have you correctly set the projectPath?`);
|
||||
throw new Error(
|
||||
`Project settings file not found at "${filePath}". Have you correctly set the projectPath?`,
|
||||
);
|
||||
}
|
||||
|
||||
return UnityVersioning.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import Unity from './unity';
|
||||
|
||||
describe('Unity', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, vi } from 'vitest';
|
||||
import * as core from '@actions/core';
|
||||
import NotImplementedException from './error/not-implemented-exception';
|
||||
import System from './system';
|
||||
@@ -5,7 +6,7 @@ import Versioning from './versioning';
|
||||
import { validVersionTagInputs, invalidVersionTagInputs } from './__data__/versions';
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Versioning', () => {
|
||||
@@ -39,7 +40,9 @@ describe('Versioning', () => {
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const matchInputUsingGrep = async (input: string) => {
|
||||
const output = await System.run('sh', undefined, {
|
||||
input: Buffer.from(`echo '${input}' | grep -E '${Versioning.grepCompatibleInputVersionRegex}'`),
|
||||
input: Buffer.from(
|
||||
`echo '${input}' | grep -E '${Versioning.grepCompatibleInputVersionRegex}'`,
|
||||
),
|
||||
silent: true,
|
||||
});
|
||||
|
||||
@@ -50,30 +53,39 @@ describe('Versioning', () => {
|
||||
expect(await matchInputUsingGrep(input)).toStrictEqual(input);
|
||||
});
|
||||
|
||||
it.concurrent.each(invalidVersionTagInputs)(`rejects non-version tag input '%s'`, async (input) => {
|
||||
await expect(async () => matchInputUsingGrep(input)).rejects.toThrowError(/^Failed to run/);
|
||||
});
|
||||
it.concurrent.each(invalidVersionTagInputs)(
|
||||
`rejects non-version tag input '%s'`,
|
||||
async (input) => {
|
||||
await expect(async () => matchInputUsingGrep(input)).rejects.toThrowError(/^Failed to run/);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('branch', () => {
|
||||
it('returns headRef when set', () => {
|
||||
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue('feature-branch-1');
|
||||
const headReference = vi
|
||||
.spyOn(Versioning, 'headRef', 'get')
|
||||
.mockReturnValue('feature-branch-1');
|
||||
|
||||
expect(Versioning.branch).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');
|
||||
vi.spyOn(Versioning, 'headRef', 'get').mockImplementation(() => undefined);
|
||||
const reference = vi
|
||||
.spyOn(Versioning, 'ref', 'get')
|
||||
.mockReturnValue('refs/heads/feature-branch-2');
|
||||
|
||||
expect(Versioning.branch).toStrictEqual('feature-branch-2');
|
||||
expect(reference).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
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 = vi
|
||||
.spyOn(Versioning, 'headRef', 'get')
|
||||
.mockReturnValue('feature-branch-1');
|
||||
const reference = vi.spyOn(Versioning, 'ref', 'get').mockReturnValue('refs/heads/feature-2');
|
||||
|
||||
expect(Versioning.branch).toStrictEqual('feature-branch-1');
|
||||
expect(headReference).toHaveBeenCalledTimes(1);
|
||||
@@ -81,8 +93,10 @@ describe('Versioning', () => {
|
||||
});
|
||||
|
||||
it('returns undefined when headRef and ref are not set', () => {
|
||||
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockImplementation();
|
||||
const reference = jest.spyOn(Versioning, 'ref', 'get').mockImplementation();
|
||||
const headReference = vi
|
||||
.spyOn(Versioning, 'headRef', 'get')
|
||||
.mockImplementation(() => undefined);
|
||||
const reference = vi.spyOn(Versioning, 'ref', 'get').mockImplementation(() => undefined);
|
||||
|
||||
expect(Versioning.branch).not.toBeDefined();
|
||||
|
||||
@@ -106,16 +120,19 @@ describe('Versioning', () => {
|
||||
describe('logging git diff', () => {
|
||||
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(Versioning, 'parseSemanticVersion')
|
||||
.mockResolvedValue({ match: '', tag: 'mocktag', commits: 'abcdef', hash: '75822BCAF' });
|
||||
const logDiffSpy = jest.spyOn(Versioning, 'logDiff');
|
||||
const gitSpy = jest.spyOn(System, 'run').mockImplementation();
|
||||
vi.spyOn(core, 'getInput').mockReturnValue('true');
|
||||
vi.spyOn(Versioning, 'isShallow').mockResolvedValue(true);
|
||||
vi.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
|
||||
vi.spyOn(Versioning, 'fetch').mockResolvedValue();
|
||||
vi.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(true);
|
||||
vi.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
|
||||
match: '',
|
||||
tag: 'mocktag',
|
||||
commits: 'abcdef',
|
||||
hash: '75822BCAF',
|
||||
});
|
||||
const logDiffSpy = vi.spyOn(Versioning, 'logDiff');
|
||||
const gitSpy = vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
|
||||
await Versioning.generateSemanticVersion();
|
||||
|
||||
@@ -157,12 +174,16 @@ describe('Versioning', () => {
|
||||
|
||||
describe('determineBuildVersion', () => {
|
||||
test.each(['somethingRandom'])('throws for invalid strategy %s', async (strategy) => {
|
||||
await expect(Versioning.determineBuildVersion(strategy, '')).rejects.toThrowErrorMatchingSnapshot();
|
||||
await expect(
|
||||
Versioning.determineBuildVersion(strategy, ''),
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
describe('opt out strategy', () => {
|
||||
it("returns 'none'", async () => {
|
||||
await expect(Versioning.determineBuildVersion('None', 'v1.0')).resolves.toMatchInlineSnapshot(`"none"`);
|
||||
await expect(
|
||||
Versioning.determineBuildVersion('None', 'v1.0'),
|
||||
).resolves.toMatchInlineSnapshot(`"none"`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,23 +191,31 @@ 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(
|
||||
Versioning.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 = vi
|
||||
.spyOn(Versioning, 'generateSemanticVersion')
|
||||
.mockResolvedValue('1.3.37');
|
||||
|
||||
await expect(Versioning.determineBuildVersion('Semantic', '')).resolves.toStrictEqual('1.3.37');
|
||||
await expect(Versioning.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 = vi
|
||||
.spyOn(Versioning, 'generateTagVersion')
|
||||
.mockResolvedValue('0.1');
|
||||
|
||||
await expect(Versioning.determineBuildVersion('Tag', '')).resolves.toStrictEqual('0.1');
|
||||
expect(generateTagVersion).toHaveBeenCalledTimes(1);
|
||||
@@ -197,22 +226,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);
|
||||
vi.spyOn(Versioning, 'strategies', 'get').mockReturnValue({ [strategy]: strategy });
|
||||
await expect(Versioning.determineBuildVersion(strategy, '')).rejects.toThrowError(
|
||||
NotImplementedException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTagVersion', () => {
|
||||
it('removes the v', async () => {
|
||||
jest.spyOn(Versioning, 'getTag').mockResolvedValue('v1.3.37');
|
||||
vi.spyOn(Versioning, 'getTag').mockResolvedValue('v1.3.37');
|
||||
await expect(Versioning.generateTagVersion()).resolves.toStrictEqual('1.3.37');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSemanticVersion', () => {
|
||||
it('returns the named parts', async () => {
|
||||
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('v0.1-2-g12345678');
|
||||
vi.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('v0.1-2-g12345678');
|
||||
|
||||
await expect(Versioning.parseSemanticVersion()).resolves.toMatchObject({
|
||||
tag: '0.1',
|
||||
@@ -222,7 +253,7 @@ describe('Versioning', () => {
|
||||
});
|
||||
|
||||
it('throws when no match could be made', async () => {
|
||||
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
|
||||
vi.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
|
||||
|
||||
await expect(Versioning.parseSemanticVersion()).toMatchObject({});
|
||||
});
|
||||
@@ -231,7 +262,7 @@ describe('Versioning', () => {
|
||||
describe('getVersionDescription', () => {
|
||||
it('returns the commands output', async () => {
|
||||
const runOutput = 'someValue';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.getVersionDescription()).resolves.toStrictEqual(runOutput);
|
||||
});
|
||||
});
|
||||
@@ -239,27 +270,27 @@ describe('Versioning', () => {
|
||||
describe('isShallow', () => {
|
||||
it('returns true when the repo is shallow', async () => {
|
||||
const runOutput = 'true\n';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.isShallow()).resolves.toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when the repo is not shallow', async () => {
|
||||
const runOutput = 'false\n';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.isShallow()).resolves.toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
it('awaits the command', async () => {
|
||||
jest.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
jest.spyOn(System, 'run').mockImplementation();
|
||||
vi.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
await expect(Versioning.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();
|
||||
vi.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
const gitFetch = vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
|
||||
await expect(Versioning.fetch()).resolves.not.toThrow();
|
||||
expect(gitFetch).toHaveBeenCalledTimes(1);
|
||||
@@ -268,12 +299,12 @@ describe('Versioning', () => {
|
||||
|
||||
describe('generateSemanticVersion', () => {
|
||||
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({
|
||||
vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
|
||||
vi.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(true);
|
||||
vi.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(2);
|
||||
vi.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
|
||||
match: '0.1-2-g1b345678',
|
||||
tag: '0.1',
|
||||
commits: '2',
|
||||
@@ -284,19 +315,19 @@ describe('Versioning', () => {
|
||||
});
|
||||
|
||||
it('throws when dirty', async () => {
|
||||
jest.spyOn(System, 'run').mockImplementation();
|
||||
jest.spyOn(core, 'info').mockImplementation(() => {});
|
||||
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(true);
|
||||
vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(Versioning, 'isDirty').mockResolvedValue(true);
|
||||
await expect(Versioning.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);
|
||||
vi.spyOn(System, 'run').mockResolvedValue('');
|
||||
vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
vi.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
|
||||
vi.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(false);
|
||||
vi.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(commits);
|
||||
|
||||
await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual(`0.0.${commits}`);
|
||||
});
|
||||
@@ -305,13 +336,13 @@ describe('Versioning', () => {
|
||||
describe('isDirty', () => {
|
||||
it('returns true when there are files listed', async () => {
|
||||
const runOutput = 'file.ext\nfile2.ext';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.isDirty()).resolves.toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when there is no output', async () => {
|
||||
const runOutput = '';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.isDirty()).resolves.toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -319,7 +350,7 @@ describe('Versioning', () => {
|
||||
describe('getTag', () => {
|
||||
it('returns the commands output', async () => {
|
||||
const runOutput = 'v1.0';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.getTag()).resolves.toStrictEqual(runOutput);
|
||||
});
|
||||
});
|
||||
@@ -327,20 +358,20 @@ describe('Versioning', () => {
|
||||
describe('hasAnyVersionTags', () => {
|
||||
it('returns false when the command returns 0', async () => {
|
||||
const runOutput = '0';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when the command returns >= 0', async () => {
|
||||
const runOutput = '9';
|
||||
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
vi.spyOn(System, 'run').mockResolvedValue(runOutput);
|
||||
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalNumberOfCommits', () => {
|
||||
it('returns a number from the command', async () => {
|
||||
jest.spyOn(System, 'run').mockResolvedValue('9');
|
||||
vi.spyOn(System, 'run').mockResolvedValue('9');
|
||||
await expect(Versioning.getTotalNumberOfCommits()).resolves.toStrictEqual(9);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,9 @@ export default class Versioning {
|
||||
static async determineBuildVersion(strategy: string, inputVersion: string): Promise<string> {
|
||||
// Validate input
|
||||
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
|
||||
throw new ValidationError(`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`);
|
||||
throw new ValidationError(
|
||||
`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (strategy) {
|
||||
@@ -122,7 +124,9 @@ export default class Versioning {
|
||||
|
||||
// Ensure 3 digits (commits should always be patch level)
|
||||
const [major, minor, patch] = `${tag}.${commits}`.split('.');
|
||||
const threeDigitVersion = /^\d+$/.test(patch) ? `${major}.${minor}.${patch}` : `${major}.0.${minor}`;
|
||||
const threeDigitVersion = /^\d+$/.test(patch)
|
||||
? `${major}.${minor}.${patch}`
|
||||
: `${major}.0.${minor}`;
|
||||
|
||||
core.info(`Found semantic version ${threeDigitVersion} for ${this.branch}@${hash}`);
|
||||
|
||||
@@ -168,7 +172,9 @@ export default class Versioning {
|
||||
}
|
||||
}
|
||||
|
||||
core.warning(`Failed to parse git describe output or version can not be determined through: "${description}".`);
|
||||
core.warning(
|
||||
`Failed to parse git describe output or version can not be determined through: "${description}".`,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -207,7 +213,9 @@ export default class Versioning {
|
||||
* identifies the current commit.
|
||||
*/
|
||||
static async getVersionDescription() {
|
||||
const versionTags = (await this.git(['tag', '--list', '--merged', 'HEAD', '--sort=-creatordate']))
|
||||
const versionTags = (
|
||||
await this.git(['tag', '--list', '--merged', 'HEAD', '--sort=-creatordate'])
|
||||
)
|
||||
.split('\n')
|
||||
.filter((tag) => new RegExp(this.grepCompatibleInputVersionRegex).test(tag));
|
||||
|
||||
@@ -218,7 +226,9 @@ export default class Versioning {
|
||||
}
|
||||
|
||||
const latestVersionTag = versionTags[0];
|
||||
const commitsCount = (await this.git(['rev-list', `${latestVersionTag}..HEAD`, '--count'])).trim();
|
||||
const commitsCount = (
|
||||
await this.git(['rev-list', `${latestVersionTag}..HEAD`, '--count'])
|
||||
).trim();
|
||||
const commitHash = (await this.git(['rev-parse', '--short', 'HEAD'])).trim();
|
||||
|
||||
return `${latestVersionTag}-${commitsCount}-g${commitHash}`;
|
||||
@@ -253,7 +263,9 @@ export default class Versioning {
|
||||
*/
|
||||
static async hasAnyVersionTags() {
|
||||
const numberOfTagsAsString = await System.run('sh', undefined, {
|
||||
input: Buffer.from(`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`),
|
||||
input: Buffer.from(
|
||||
`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`,
|
||||
),
|
||||
cwd: Input.projectPath,
|
||||
silent: false,
|
||||
});
|
||||
|
||||
30
src/test/setup.ts
Normal file
30
src/test/setup.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { afterEach, beforeEach } from 'vitest';
|
||||
|
||||
// Fail tests when console.error / console.warn etc are called from
|
||||
// production code under test. Mirrors the jest-fail-on-console behaviour
|
||||
// the previous jest setup enforced. Tests can opt-out by replacing the
|
||||
// method with vi.spyOn(console, 'error') for the duration of that test.
|
||||
const original = {
|
||||
log: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
assert: console.assert,
|
||||
};
|
||||
|
||||
const fail = (level: 'log' | 'warn' | 'error' | 'assert', args: unknown[]) => {
|
||||
throw new Error(
|
||||
`console.${level} was called with: ${args.map(String).join(' ')}\n` +
|
||||
`Tests must use vi.spyOn(console, '${level}') if console output is expected.`,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
console.log = (...args: unknown[]) => fail('log', args);
|
||||
console.warn = (...args: unknown[]) => fail('warn', args);
|
||||
console.error = (...args: unknown[]) => fail('error', args);
|
||||
console.assert = ((...args: unknown[]) => fail('assert', args)) as typeof console.assert;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.assign(console, original);
|
||||
});
|
||||
Reference in New Issue
Block a user