feat: cleanup parameters part 1/*
parent
bfb847dc81
commit
fa479f532a
|
|
@ -5,6 +5,7 @@ import { CommandFactory } from './commands/command-factory.ts';
|
||||||
import { ArgumentsParser } from './core/cli/arguments-parser.ts';
|
import { ArgumentsParser } from './core/cli/arguments-parser.ts';
|
||||||
import { Environment } from './core/env/environment.ts';
|
import { Environment } from './core/env/environment.ts';
|
||||||
import { EngineDetector } from './core/engine/engine-detector.ts';
|
import { EngineDetector } from './core/engine/engine-detector.ts';
|
||||||
|
import { ParameterOptions } from './model/parameter-options.ts';
|
||||||
|
|
||||||
export class GameCI {
|
export class GameCI {
|
||||||
private readonly env: Environment;
|
private readonly env: Environment;
|
||||||
|
|
@ -20,6 +21,10 @@ export class GameCI {
|
||||||
const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args);
|
const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args);
|
||||||
await configureLogger(verbosity);
|
await configureLogger(verbosity);
|
||||||
|
|
||||||
|
// Todo - set default values for parameters to restore functionality.
|
||||||
|
// Todo - investigate how fitting a CLI lib will be for us
|
||||||
|
// (now that things are starting to be more separated)
|
||||||
|
|
||||||
// Determine which command to run
|
// Determine which command to run
|
||||||
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
|
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
|
||||||
const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands);
|
const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands);
|
||||||
|
|
|
||||||
|
|
@ -43,29 +43,29 @@ class SetupMac {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async setEnvironmentVariables(buildParameters: Parameters, actionFolder: string) {
|
private static async setEnvironmentVariables(parameters: Parameters, actionFolder: string) {
|
||||||
// Need to set environment variables from here because we execute
|
// Need to set environment variables from here because we execute
|
||||||
// the scripts on the host for mac
|
// the scripts on the host for mac
|
||||||
Deno.env.set('ACTION_FOLDER', actionFolder);
|
Deno.env.set('ACTION_FOLDER', actionFolder);
|
||||||
Deno.env.set('UNITY_VERSION', buildParameters.editorVersion);
|
Deno.env.set('UNITY_VERSION', parameters.editorVersion);
|
||||||
Deno.env.set('UNITY_SERIAL', buildParameters.unitySerial);
|
Deno.env.set('UNITY_SERIAL', parameters.unitySerial);
|
||||||
Deno.env.set('PROJECT_PATH', buildParameters.projectPath);
|
Deno.env.set('PROJECT_PATH', parameters.projectPath);
|
||||||
Deno.env.set('BUILD_TARGET', buildParameters.targetPlatform);
|
Deno.env.set('BUILD_TARGET', parameters.targetPlatform);
|
||||||
Deno.env.set('BUILD_NAME', buildParameters.buildName);
|
Deno.env.set('BUILD_NAME', parameters.buildName);
|
||||||
Deno.env.set('BUILD_PATH', buildParameters.buildPath);
|
Deno.env.set('BUILD_PATH', parameters.buildPath);
|
||||||
Deno.env.set('BUILD_FILE', buildParameters.buildFile);
|
Deno.env.set('BUILD_FILE', parameters.buildFile);
|
||||||
Deno.env.set('BUILD_METHOD', buildParameters.buildMethod);
|
Deno.env.set('BUILD_METHOD', parameters.buildMethod);
|
||||||
Deno.env.set('VERSION', buildParameters.buildVersion);
|
Deno.env.set('VERSION', parameters.buildVersion);
|
||||||
Deno.env.set('ANDROID_VERSION_CODE', buildParameters.androidVersionCode);
|
Deno.env.set('ANDROID_VERSION_CODE', parameters.androidVersionCode);
|
||||||
Deno.env.set('ANDROID_KEYSTORE_NAME', buildParameters.androidKeystoreName);
|
Deno.env.set('ANDROID_KEYSTORE_NAME', parameters.androidKeystoreName);
|
||||||
Deno.env.set('ANDROID_KEYSTORE_BASE64', buildParameters.androidKeystoreBase64);
|
Deno.env.set('ANDROID_KEYSTORE_BASE64', parameters.androidKeystoreBase64);
|
||||||
Deno.env.set('ANDROID_KEYSTORE_PASS', buildParameters.androidKeystorePass);
|
Deno.env.set('ANDROID_KEYSTORE_PASS', parameters.androidKeystorePass);
|
||||||
Deno.env.set('ANDROID_KEYALIAS_NAME', buildParameters.androidKeyaliasName);
|
Deno.env.set('ANDROID_KEYALIAS_NAME', parameters.androidKeyaliasName);
|
||||||
Deno.env.set('ANDROID_KEYALIAS_PASS', buildParameters.androidKeyaliasPass);
|
Deno.env.set('ANDROID_KEYALIAS_PASS', parameters.androidKeyaliasPass);
|
||||||
Deno.env.set('ANDROID_TARGET_SDK_VERSION', buildParameters.androidTargetSdkVersion);
|
Deno.env.set('ANDROID_TARGET_SDK_VERSION', parameters.androidTargetSdkVersion);
|
||||||
Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', buildParameters.androidSdkManagerParameters);
|
Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', parameters.androidSdkManagerParameters);
|
||||||
Deno.env.set('CUSTOM_PARAMETERS', buildParameters.customParameters);
|
Deno.env.set('CUSTOM_PARAMETERS', parameters.customParameters);
|
||||||
Deno.env.set('CHOWN_FILES_TO', buildParameters.chownFilesTo);
|
Deno.env.set('CHOWN_FILES_TO', parameters.chownFilesTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,7 @@ describe('BuildParameters', () => {
|
||||||
async (targetPlatform) => {
|
async (targetPlatform) => {
|
||||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||||
expect(Parameters.create()).resolves.toEqual(
|
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.exe` }));
|
||||||
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -103,18 +101,14 @@ describe('BuildParameters', () => {
|
||||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||||
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
|
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
|
||||||
expect(Parameters.create()).resolves.toEqual(
|
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.apk` }));
|
||||||
expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => {
|
test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => {
|
||||||
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
|
||||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
|
||||||
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);
|
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);
|
||||||
expect(Parameters.create()).resolves.toEqual(
|
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.aab` }));
|
||||||
expect.objectContaining({ buildFile: `${targetPlatform}.aab` }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the build method', async () => {
|
it('returns the build method', async () => {
|
||||||
|
|
@ -156,9 +150,7 @@ describe('BuildParameters', () => {
|
||||||
it('returns the android target sdk version', async () => {
|
it('returns the android target sdk version', async () => {
|
||||||
const mockValue = 'AndroidApiLevelAuto';
|
const mockValue = 'AndroidApiLevelAuto';
|
||||||
jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
|
jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
|
||||||
expect(Parameters.create()).resolves.toEqual(
|
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidTargetSdkVersion: mockValue }));
|
||||||
expect.objectContaining({ androidTargetSdkVersion: mockValue }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the custom parameters', async () => {
|
it('returns the custom parameters', async () => {
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,43 @@ import { path, fsSync as fs } from '../dependencies.ts';
|
||||||
import System from './system.ts';
|
import System from './system.ts';
|
||||||
|
|
||||||
class Docker {
|
class Docker {
|
||||||
static async run(image, parameters, silent = false) {
|
static async run(image, parameters) {
|
||||||
log.warning('running docker process for', process.platform, silent);
|
log.warning('running docker process for', process.platform);
|
||||||
let command = '';
|
let command = '';
|
||||||
switch (process.platform) {
|
switch (Deno.build.os) {
|
||||||
|
case 'windows': {
|
||||||
|
// Todo: check if docker daemon is set for Windows or Linux containers.
|
||||||
|
command = await this.getWindowsCommand(image, parameters);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'linux':
|
case 'linux':
|
||||||
|
case 'darwin': {
|
||||||
command = await this.getLinuxCommand(image, parameters);
|
command = await this.getLinuxCommand(image, parameters);
|
||||||
break;
|
break;
|
||||||
case 'win32':
|
}
|
||||||
command = await this.getWindowsCommand(image, parameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const test = await System.newRun(`docker`, command.replace(/\s\s+/, ' ').split(' '), { silent, verbose: true });
|
try {
|
||||||
log.error('test', test);
|
const test = await System.run(command, { attach: true });
|
||||||
|
log.warning('test', test);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('docker: image operating system "windows" cannot be used on this platform')) {
|
||||||
|
throw new Error(String.dedent`
|
||||||
|
Docker daemon is not set to run Windows containers.
|
||||||
|
|
||||||
|
To enable the Hyper-V container backend run:
|
||||||
|
Enable-WindowsOptionalFeature -Online -FeatureName $("Microsoft-Hyper-V", "Containers") -All
|
||||||
|
|
||||||
|
To switch the docker daemon to run Windows containers run:
|
||||||
|
& $Env:ProgramFiles\\Docker\\Docker\\DockerCli.exe -SwitchDaemon .
|
||||||
|
|
||||||
|
For more information see:
|
||||||
|
https://docs.microsoft.com/en-us/virtualization/windowscontainers/quick-start/set-up-environment?tabs=dockerce#prerequisites
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getLinuxCommand(image, parameters): string {
|
static async getLinuxCommand(image, parameters): string {
|
||||||
|
|
@ -28,8 +52,8 @@ class Docker {
|
||||||
|
|
||||||
return String.dedent`
|
return String.dedent`
|
||||||
docker run \
|
docker run \
|
||||||
--workdir /github/workspace \
|
|
||||||
--rm \
|
--rm \
|
||||||
|
--workdir /github/workspace \
|
||||||
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
||||||
--env UNITY_SERIAL \
|
--env UNITY_SERIAL \
|
||||||
--env GITHUB_WORKSPACE=/github/workspace \
|
--env GITHUB_WORKSPACE=/github/workspace \
|
||||||
|
|
@ -51,25 +75,23 @@ class Docker {
|
||||||
static async getWindowsCommand(image: any, parameters: any): string {
|
static async getWindowsCommand(image: any, parameters: any): string {
|
||||||
const { workspace, actionFolder, unitySerial, gitPrivateToken, cliStoragePath } = parameters;
|
const { workspace, actionFolder, unitySerial, gitPrivateToken, cliStoragePath } = parameters;
|
||||||
|
|
||||||
// Todo - get this to work on a non-github runner local machine
|
// Note: the equals sign (=) is needed in Powershell.
|
||||||
// Note: difference between `docker run` and `run`
|
|
||||||
return String.dedent`run ${image} powershell c:/steps/entrypoint.ps1`;
|
|
||||||
return String.dedent`
|
return String.dedent`
|
||||||
docker run \
|
docker run \
|
||||||
--workdir /github/workspace \
|
|
||||||
--rm \
|
--rm \
|
||||||
|
--workdir="c:/github/workspace" \
|
||||||
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
||||||
--env UNITY_SERIAL="${unitySerial}" \
|
--env UNITY_SERIAL="${unitySerial}" \
|
||||||
--env GITHUB_WORKSPACE=c:/github/workspace \
|
--env GITHUB_WORKSPACE=c:/github/workspace \
|
||||||
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
|
--env GIT_PRIVATE_TOKEN="${gitPrivateToken}" \
|
||||||
--volume "${workspace}":"c:/github/workspace" \
|
--volume="${workspace}":"c:/github/workspace" \
|
||||||
--volume "${cliStoragePath}/registry-keys":"c:/registry-keys" \
|
--volume="${cliStoragePath}/registry-keys":"c:/registry-keys" \
|
||||||
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
|
--volume="C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
|
||||||
--volume "C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \
|
--volume="C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \
|
||||||
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
|
--volume="C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
|
||||||
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
|
--volume="${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
|
||||||
--volume "${actionFolder}/platforms/windows":"c:/steps" \
|
--volume="${actionFolder}/platforms/windows":"c:/steps" \
|
||||||
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \
|
--volume="${actionFolder}/BlankProject":"c:/BlankProject" \
|
||||||
${image} \
|
${image} \
|
||||||
powershell c:/steps/entrypoint.ps1
|
powershell c:/steps/entrypoint.ps1
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,12 @@ class Input {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Todo - read something from environment instead and make that into a parameter, then use that.
|
||||||
public static githubInputEnabled: boolean = true;
|
public static githubInputEnabled: boolean = true;
|
||||||
|
|
||||||
// Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess.
|
// Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess.
|
||||||
public getInput(query: string) {
|
public get(query: string) {
|
||||||
if (this && this.arguments) {
|
if (this && this.arguments) {
|
||||||
const value = this.arguments.get(query);
|
|
||||||
|
|
||||||
if (log.isVeryVerbose) log.debug('arg', query, '=', value);
|
|
||||||
|
|
||||||
return this.arguments.get(query);
|
return this.arguments.get(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,25 +61,25 @@ class Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get region(): string {
|
public get region(): string {
|
||||||
return this.getInput('region') || 'eu-west-2';
|
return this.get('region');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get githubRepo() {
|
public get githubRepo() {
|
||||||
return this.getInput('GITHUB_REPOSITORY') || this.getInput('GITHUB_REPO') || undefined;
|
return this.get('GITHUB_REPOSITORY') || this.get('GITHUB_REPO') || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get branch() {
|
public get branch() {
|
||||||
if (this.getInput(`GITHUB_REF`)) {
|
if (this.get(`GITHUB_REF`)) {
|
||||||
return this.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
|
return this.get(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
|
||||||
} else if (this.getInput('branch')) {
|
} else if (this.get('branch')) {
|
||||||
return this.getInput('branch').replace('/head', '');
|
return this.get('branch').replace('/head', '');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cloudRunnerBuilderPlatform() {
|
public get cloudRunnerBuilderPlatform() {
|
||||||
const input = this.getInput('cloudRunnerBuilderPlatform');
|
const input = this.get('cloudRunnerBuilderPlatform');
|
||||||
if (input) {
|
if (input) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
@ -94,55 +91,55 @@ class Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get gitSha() {
|
public get gitSha() {
|
||||||
if (this.getInput(`GITHUB_SHA`)) {
|
if (this.get(`GITHUB_SHA`)) {
|
||||||
return this.getInput(`GITHUB_SHA`);
|
return this.get(`GITHUB_SHA`);
|
||||||
} else if (this.getInput(`GitSHA`)) {
|
} else if (this.get(`GitSHA`)) {
|
||||||
return this.getInput(`GitSHA`);
|
return this.get(`GitSHA`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get runNumber() {
|
public get runNumber() {
|
||||||
return this.getInput('GITHUB_RUN_NUMBER') || '0';
|
return this.get('GITHUB_RUN_NUMBER') || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get targetPlatform() {
|
public get targetPlatform() {
|
||||||
return this.getInput('targetPlatform') || Platform.default;
|
return this.get('targetPlatform') || Platform.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unityVersion() {
|
public get unityVersion() {
|
||||||
return this.getInput('unityVersion') || 'auto';
|
return this.get('unityVersion') || 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unityEmail() {
|
public get unityEmail() {
|
||||||
return this.getInput('unityEmail') || '';
|
return this.get('unityEmail') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unityPassword() {
|
public get unityPassword() {
|
||||||
return this.getInput('unityPassword') || '';
|
return this.get('unityPassword') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unityLicense() {
|
public get unityLicense() {
|
||||||
return this.getInput('unityLicense') || '';
|
return this.get('unityLicense') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unityLicenseFile() {
|
public get unityLicenseFile() {
|
||||||
return this.getInput('unityLicenseFile') || '';
|
return this.get('unityLicenseFile') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get unitySerial() {
|
public get unitySerial() {
|
||||||
return this.getInput('unitySerial') || '';
|
return this.get('unitySerial') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get usymUploadAuthToken() {
|
public get usymUploadAuthToken() {
|
||||||
return this.getInput('usymUploadAuthToken') || '';
|
return this.get('usymUploadAuthToken') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get customImage() {
|
public get customImage() {
|
||||||
return this.getInput('customImage') || '';
|
return this.get('customImage') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get projectPath() {
|
public get projectPath() {
|
||||||
let input = this.getInput('projectPath');
|
let input = this.get('projectPath');
|
||||||
|
|
||||||
// Todo - remove hardcoded test project reference
|
// Todo - remove hardcoded test project reference
|
||||||
const isTestProject =
|
const isTestProject =
|
||||||
|
|
@ -156,172 +153,169 @@ class Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get buildName() {
|
public get buildName() {
|
||||||
return this.getInput('buildName') || this.targetPlatform;
|
return this.get('buildName');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get buildsPath() {
|
public get buildsPath() {
|
||||||
return this.getInput('buildsPath') || 'build';
|
return this.get('buildsPath') || 'build';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get buildMethod() {
|
public get buildMethod() {
|
||||||
return this.getInput('buildMethod') || ''; // Processed in docker file
|
return this.get('buildMethod') || ''; // Processed in docker file
|
||||||
}
|
}
|
||||||
|
|
||||||
public get customParameters() {
|
public get customParameters() {
|
||||||
return this.getInput('customParameters') || '';
|
return this.get('customParameters') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get versioningStrategy() {
|
public get versioningStrategy() {
|
||||||
return this.getInput('versioning') || 'Semantic';
|
return this.get('versioning') || 'Semantic';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get specifiedVersion() {
|
public get specifiedVersion() {
|
||||||
return this.getInput('version') || '';
|
return this.get('version') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidVersionCode() {
|
public get androidVersionCode() {
|
||||||
return this.getInput('androidVersionCode');
|
return this.get('androidVersionCode');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidAppBundle() {
|
public get androidAppBundle() {
|
||||||
const input = this.getInput('androidAppBundle') || false;
|
const input = this.get('androidAppBundle') || false;
|
||||||
|
|
||||||
return input === 'true';
|
return input === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidKeystoreName() {
|
public get androidKeystoreName() {
|
||||||
return this.getInput('androidKeystoreName') || '';
|
return this.get('androidKeystoreName') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidKeystoreBase64() {
|
public get androidKeystoreBase64() {
|
||||||
return this.getInput('androidKeystoreBase64') || '';
|
return this.get('androidKeystoreBase64') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidKeystorePass() {
|
public get androidKeystorePass() {
|
||||||
return this.getInput('androidKeystorePass') || '';
|
return this.get('androidKeystorePass') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidKeyaliasName() {
|
public get androidKeyaliasName() {
|
||||||
return this.getInput('androidKeyaliasName') || '';
|
return this.get('androidKeyaliasName') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidKeyaliasPass() {
|
public get androidKeyaliasPass() {
|
||||||
return this.getInput('androidKeyaliasPass') || '';
|
return this.get('androidKeyaliasPass') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get androidTargetSdkVersion() {
|
public get androidTargetSdkVersion() {
|
||||||
return this.getInput('androidTargetSdkVersion') || '';
|
return this.get('androidTargetSdkVersion') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get sshAgent() {
|
public get sshAgent() {
|
||||||
return this.getInput('sshAgent') || '';
|
return this.get('sshAgent') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get gitPrivateToken() {
|
public get gitPrivateToken() {
|
||||||
return core.getInput('gitPrivateToken') || false;
|
return this.get('gitPrivateToken') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get customJob() {
|
public get customJob() {
|
||||||
return this.getInput('customJob') || '';
|
return this.get('customJob') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public customJobHooks() {
|
public customJobHooks() {
|
||||||
return this.getInput('customJobHooks') || '';
|
return this.get('customJobHooks') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public cachePushOverrideCommand() {
|
public cachePushOverrideCommand() {
|
||||||
return this.getInput('cachePushOverrideCommand') || '';
|
return this.get('cachePushOverrideCommand') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public cachePullOverrideCommand() {
|
public cachePullOverrideCommand() {
|
||||||
return this.getInput('cachePullOverrideCommand') || '';
|
return this.get('cachePullOverrideCommand') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInputFromOverrideList() {
|
public readInputFromOverrideList() {
|
||||||
return this.getInput('readInputFromOverrideList') || '';
|
return this.get('readInputFromOverrideList') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInputOverrideCommand() {
|
public readInputOverrideCommand() {
|
||||||
return this.getInput('readInputOverrideCommand') || '';
|
return this.get('readInputOverrideCommand') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cloudRunnerBranch() {
|
public get cloudRunnerBranch() {
|
||||||
return this.getInput('cloudRunnerBranch') || 'cloud-runner-develop';
|
return this.get('cloudRunnerBranch') || 'cloud-runner-develop';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get chownFilesTo() {
|
public get chownFilesTo() {
|
||||||
return this.getInput('chownFilesTo') || '';
|
return this.get('chownFilesTo') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get allowDirtyBuild() {
|
public get allowDirtyBuild() {
|
||||||
const input = this.getInput('allowDirtyBuild');
|
const input = this.get('allowDirtyBuild');
|
||||||
log.debug('input === ', input);
|
log.debug('input === ', input);
|
||||||
|
|
||||||
return input || false === true;
|
return input || false === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get postBuildSteps() {
|
public get postBuildSteps() {
|
||||||
return this.getInput('postBuildSteps') || '';
|
return this.get('postBuildSteps') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get preBuildSteps() {
|
public get preBuildSteps() {
|
||||||
return this.getInput('preBuildSteps') || '';
|
return this.get('preBuildSteps') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get awsBaseStackName() {
|
public get awsBaseStackName() {
|
||||||
return this.getInput('awsBaseStackName') || 'game-ci';
|
return this.get('awsBaseStackName') || 'game-ci';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cloudRunnerCpu() {
|
public get cloudRunnerCpu() {
|
||||||
return this.getInput('cloudRunnerCpu');
|
return this.get('cloudRunnerCpu');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cloudRunnerMemory() {
|
public get cloudRunnerMemory() {
|
||||||
return this.getInput('cloudRunnerMemory');
|
return this.get('cloudRunnerMemory');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kubeConfig() {
|
public get kubeConfig() {
|
||||||
return this.getInput('kubeConfig') || '';
|
return this.get('kubeConfig') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kubeVolume() {
|
public get kubeVolume() {
|
||||||
return this.getInput('kubeVolume') || '';
|
return this.get('kubeVolume') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kubeVolumeSize() {
|
public get kubeVolumeSize() {
|
||||||
return this.getInput('kubeVolumeSize') || '5Gi';
|
return this.get('kubeVolumeSize') || '5Gi';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kubeStorageClass(): string {
|
public get kubeStorageClass(): string {
|
||||||
return this.getInput('kubeStorageClass') || '';
|
return this.get('kubeStorageClass') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get checkDependencyHealthOverride(): string {
|
public get checkDependencyHealthOverride(): string {
|
||||||
return this.getInput('checkDependencyHealthOverride') || '';
|
return this.get('checkDependencyHealthOverride') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get startDependenciesOverride(): string {
|
public get startDependenciesOverride(): string {
|
||||||
return this.getInput('startDependenciesOverride') || '';
|
return this.get('startDependenciesOverride') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cacheKey(): string {
|
public get cacheKey(): string {
|
||||||
return this.getInput('cacheKey') || Input.branch;
|
return this.get('cacheKey') || Input.branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cloudRunnerTests(): boolean {
|
public get cloudRunnerTests(): boolean {
|
||||||
return this.getInput(`cloudRunnerTests`) || false;
|
return this.get(`cloudRunnerTests`) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use Parameter.toEnvFormat
|
||||||
|
*/
|
||||||
public static toEnvVarFormat(input: string) {
|
public static toEnvVarFormat(input: string) {
|
||||||
if (input.toUpperCase() === input) {
|
if (input.toUpperCase() === input) return input;
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return input
|
return input.replace(/([\da-z])([A-Z])/g, '$1_$2').toUpperCase();
|
||||||
.replace(/([A-Z])/g, ' $1')
|
|
||||||
.trim()
|
|
||||||
.toUpperCase()
|
|
||||||
.replace(/ /g, '_');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import Versioning from './versioning.ts';
|
||||||
import { GitRepoReader } from './input-readers/git-repo.ts';
|
import { GitRepoReader } from './input-readers/git-repo.ts';
|
||||||
import { CommandInterface } from '../commands/command/command-interface.ts';
|
import { CommandInterface } from '../commands/command/command-interface.ts';
|
||||||
import { Environment } from '../core/env/environment.ts';
|
import { Environment } from '../core/env/environment.ts';
|
||||||
|
import { Parameter } from './parameter.ts';
|
||||||
|
|
||||||
class Parameters {
|
class Parameters {
|
||||||
private command: CommandInterface;
|
private command: CommandInterface;
|
||||||
|
|
@ -64,85 +65,70 @@ class Parameters {
|
||||||
public cloudRunnerBuilderPlatform!: string | undefined;
|
public cloudRunnerBuilderPlatform!: string | undefined;
|
||||||
public isCliMode!: boolean;
|
public isCliMode!: boolean;
|
||||||
|
|
||||||
|
private defaults: Partial<Parameters> = {
|
||||||
|
region: 'eu-west-2',
|
||||||
|
};
|
||||||
|
|
||||||
private readonly input: Input;
|
private readonly input: Input;
|
||||||
private readonly env: Environment;
|
private readonly env: Environment;
|
||||||
|
|
||||||
constructor(input: Input, env: Environment) {
|
constructor(input: Input, env: Environment) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
|
||||||
|
// Todo - ~/.gameci should hold a config with default settings, like cloud region = 'eu-west-2'
|
||||||
|
// this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(query, useDefault = true) {
|
||||||
|
const defaultValue = useDefault ? this.default(query) : undefined;
|
||||||
|
const value = this.input.get(query) || this.env.get(Parameter.toUpperSnakeCase(query)) || defaultValue;
|
||||||
|
|
||||||
|
if (log.isVeryVerbose) log.debug('Argument:', query, '=', value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private default(query) {
|
||||||
|
return this.defaults[query];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async parse(): Promise<Parameters> {
|
public async parse(): Promise<Parameters> {
|
||||||
const cliStoragePath = `${getHomeDir()}/.game-ci`;
|
const cliStoragePath = `${getHomeDir()}/.game-ci`;
|
||||||
|
const targetPlatform = this.input.get('targetPlatform');
|
||||||
|
const buildsPath = this.input.get('buildsPath');
|
||||||
|
const projectPath = this.get('projectPath');
|
||||||
|
const unityVersion = this.get('unityVersion');
|
||||||
|
const versioningStrategy = this.get('versioningStrategy');
|
||||||
|
const specifiedVersion = this.get('specifiedVersion');
|
||||||
|
const allowDirtyBuild = this.get('allowDirtyBuild');
|
||||||
|
const androidTargetSdkVersion = this.get('androidTargetSdkVersion');
|
||||||
|
|
||||||
const buildFile = Parameters.parseBuildFile(
|
const buildName = this.input.get('buildName') || targetPlatform;
|
||||||
this.input.buildName,
|
const buildFile = Parameters.parseBuildFile(buildName, targetPlatform, this.get('androidAppBundle'));
|
||||||
this.input.targetPlatform,
|
const buildPath = `${buildsPath}/${targetPlatform}`;
|
||||||
this.input.androidAppBundle,
|
const editorVersion = UnityVersioning.determineUnityVersion(projectPath, unityVersion);
|
||||||
);
|
const buildVersion = await Versioning.determineBuildVersion(versioningStrategy, specifiedVersion, allowDirtyBuild);
|
||||||
log.debug('buildFile:', buildFile);
|
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.get('androidVersionCode'));
|
||||||
const editorVersion = UnityVersioning.determineUnityVersion(this.input.projectPath, this.input.unityVersion);
|
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(androidTargetSdkVersion);
|
||||||
log.info('Detected editorVersion', editorVersion);
|
|
||||||
const buildVersion = await Versioning.determineBuildVersion(
|
|
||||||
this.input.versioningStrategy,
|
|
||||||
this.input.specifiedVersion,
|
|
||||||
this.input.allowDirtyBuild,
|
|
||||||
);
|
|
||||||
log.debug('buildVersion', buildVersion);
|
|
||||||
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.input.androidVersionCode);
|
|
||||||
log.debug('androidVersionCode', androidVersionCode);
|
|
||||||
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(
|
|
||||||
this.input.androidTargetSdkVersion,
|
|
||||||
);
|
|
||||||
log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
|
|
||||||
|
|
||||||
// Commandline takes precedence over environment variables
|
|
||||||
const unityEmail = this.input.unityEmail || this.env.get('UNITY_EMAIL');
|
|
||||||
const unityPassword = this.input.unityPassword || this.env.get('UNITY_PASSWORD');
|
|
||||||
const unityLicense = this.input.unityLicense || this.env.get('UNITY_LICENSE');
|
|
||||||
const unityLicenseFile = this.input.unityLicenseFile || this.env.get('UNITY_LICENSE_FILE');
|
|
||||||
let unitySerial = this.input.unitySerial || this.env.get('UNITY_SERIAL');
|
|
||||||
|
|
||||||
// For Windows, we need to use the serial from the license file
|
|
||||||
if (!unitySerial && this.input.githubInputEnabled) {
|
|
||||||
// No serial was present, so it is a personal license that we need to convert
|
|
||||||
if (!unityLicense) {
|
|
||||||
throw new Error(String.dedent`
|
|
||||||
Missing Unity License File and no Serial was found. If this is a personal license,
|
|
||||||
make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
|
|
||||||
a Unity serial number inside the UNITY_SERIAL variable.
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
unitySerial = this.getSerialFromLicenseFile(this.env.UNITY_LICENSE);
|
|
||||||
} else {
|
|
||||||
unitySerial = this.env.UNITY_SERIAL!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const branch = (await Versioning.getCurrentBranch()) || (await GitRepoReader.GetBranch());
|
const branch = (await Versioning.getCurrentBranch()) || (await GitRepoReader.GetBranch());
|
||||||
log.info(`branch: "${branch}"`);
|
|
||||||
|
|
||||||
const projectPath = this.input.projectPath;
|
|
||||||
log.info(`projectPath: "${projectPath}"`);
|
|
||||||
|
|
||||||
const targetPlatform = this.input.targetPlatform;
|
|
||||||
log.info(`targetPlatform: "${targetPlatform}"`);
|
|
||||||
|
|
||||||
const parameters = {
|
const parameters = {
|
||||||
|
branch,
|
||||||
|
unityEmail: this.get('unityEmail'),
|
||||||
|
unityPassword: this.get('unityPassword'),
|
||||||
|
unityLicense: this.get('unityLicense'),
|
||||||
|
unityLicenseFile: this.get('unityLicenseFile'),
|
||||||
|
unitySerial: this.getUnitySerial(),
|
||||||
cliStoragePath,
|
cliStoragePath,
|
||||||
editorVersion,
|
editorVersion,
|
||||||
customImage: this.input.customImage,
|
customImage: this.get('customImage'),
|
||||||
unityEmail,
|
usymUploadAuthToken: this.get('usymUploadAuthToken'),
|
||||||
unityPassword,
|
runnerTempPath: this.env.get('RUNNER_TEMP'),
|
||||||
unityLicense,
|
|
||||||
unityLicenseFile,
|
|
||||||
unitySerial,
|
|
||||||
usymUploadAuthToken: this.input.usymUploadAuthToken || this.env.get('USYM_UPLOAD_AUTH_TOKEN'),
|
|
||||||
runnerTempPath: this.env.RUNNER_TEMP,
|
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
projectPath,
|
projectPath,
|
||||||
buildName: this.input.buildName,
|
buildName,
|
||||||
buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`,
|
buildPath,
|
||||||
buildFile,
|
buildFile,
|
||||||
buildMethod: this.input.buildMethod,
|
buildMethod: this.input.buildMethod,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
|
|
@ -152,14 +138,13 @@ class Parameters {
|
||||||
androidKeystorePass: this.input.androidKeystorePass,
|
androidKeystorePass: this.input.androidKeystorePass,
|
||||||
androidKeyaliasName: this.input.androidKeyaliasName,
|
androidKeyaliasName: this.input.androidKeyaliasName,
|
||||||
androidKeyaliasPass: this.input.androidKeyaliasPass,
|
androidKeyaliasPass: this.input.androidKeyaliasPass,
|
||||||
androidTargetSdkVersion: this.input.androidTargetSdkVersion,
|
androidTargetSdkVersion,
|
||||||
androidSdkManagerParameters,
|
androidSdkManagerParameters,
|
||||||
customParameters: this.input.customParameters,
|
customParameters: this.get('customParameters'),
|
||||||
sshAgent: this.input.sshAgent,
|
sshAgent: this.input.sshAgent,
|
||||||
gitPrivateToken: this.input.gitPrivateToken,
|
gitPrivateToken: this.get('gitPrivateToken'),
|
||||||
chownFilesTo: this.input.chownFilesTo,
|
chownFilesTo: this.input.chownFilesTo,
|
||||||
customJob: this.input.customJob,
|
customJob: this.input.customJob,
|
||||||
branch,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const commandParameterOverrides = await this.command.parseParameters(this.input, parameters);
|
const commandParameterOverrides = await this.command.parseParameters(this.input, parameters);
|
||||||
|
|
@ -183,7 +168,27 @@ class Parameters {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSerialFromLicenseFile(license) {
|
static getUnitySerial() {
|
||||||
|
let unitySerial = this.get('unitySerial');
|
||||||
|
|
||||||
|
if (!unitySerial && this.env.getOS() === 'windows') {
|
||||||
|
const unityLicense = this.get('unityLicense');
|
||||||
|
|
||||||
|
unitySerial = this.getSerialFromLicense(unityLicense);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unitySerial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSerialFromLicense(license) {
|
||||||
|
if (!license) {
|
||||||
|
throw new Error(String.dedent`
|
||||||
|
Missing Unity License File and no Unity Serial was found. If this is a personal license,
|
||||||
|
make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
|
||||||
|
a Unity serial number inside the UNITY_SERIAL variable.
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
const startKey = `<DeveloperData Value="`;
|
const startKey = `<DeveloperData Value="`;
|
||||||
const endKey = `"/>`;
|
const endKey = `"/>`;
|
||||||
const startIndex = license.indexOf(startKey) + startKey.length;
|
const startIndex = license.indexOf(startKey) + startKey.length;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export interface RunOptions {
|
export interface RunOptions {
|
||||||
pwd: string;
|
pwd: string;
|
||||||
|
attach: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class System {
|
class System {
|
||||||
|
|
@ -7,7 +8,7 @@ class System {
|
||||||
* Run any command as if you're typing in shell.
|
* Run any command as if you're typing in shell.
|
||||||
* Make sure it's Windows/MacOS/Ubuntu compatible or has alternative commands.
|
* Make sure it's Windows/MacOS/Ubuntu compatible or has alternative commands.
|
||||||
*
|
*
|
||||||
* Intended to always be silent and capture the output.
|
* Intended to always be silent and capture the output, unless attach is passed.
|
||||||
*
|
*
|
||||||
* @returns {string} output of the command on success or failure
|
* @returns {string} output of the command on success or failure
|
||||||
*
|
*
|
||||||
|
|
@ -28,11 +29,15 @@ class System {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async shellRun(command: string, options: RunOptions = {}): Promise<string> {
|
static async shellRun(command: string, options: RunOptions = {}): Promise<string> {
|
||||||
return System.newRun('sh', ['-c', command]);
|
const { attach } = options;
|
||||||
|
|
||||||
|
return attach ? System.runAndAttach('sh', ['-c', command]) : System.runAndCapture('sh', ['-c', command]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async powershellRun(command: string, options: RunOptions = {}): Promise<string> {
|
static async powershellRun(command: string, options: RunOptions = {}): Promise<string> {
|
||||||
return System.newRun('powershell', [command]);
|
const { attach } = options;
|
||||||
|
|
||||||
|
return attach ? System.runAndAttach('powershell', [command]) : System.runAndCapture('powershell', [command]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,7 +56,7 @@ class System {
|
||||||
*
|
*
|
||||||
* @deprecated not really deprecated, but please use System.run instead because this method will be made private.
|
* @deprecated not really deprecated, but please use System.run instead because this method will be made private.
|
||||||
*/
|
*/
|
||||||
public static async newRun(command, args: string | string[] = []): Promise<string> {
|
public static async runAndCapture(command, args: string | string[] = []): Promise<string> {
|
||||||
if (!Array.isArray(args)) args = [args];
|
if (!Array.isArray(args)) args = [args];
|
||||||
|
|
||||||
const argsString = args.join(' ');
|
const argsString = args.join(' ');
|
||||||
|
|
@ -92,6 +97,26 @@ class System {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output stdout and stderr to the terminal and attach to the process.
|
||||||
|
*
|
||||||
|
* Note that the return signature is slightly different from runAndCapture, because we don't have stderrOutput.
|
||||||
|
*
|
||||||
|
* Todo - it would be nice to pipe the output to both stdout and capture it in the result object, but this doesn't seem possible yet.
|
||||||
|
*/
|
||||||
|
private static async runAndAttach(command, args: string | string[] = []): Promise<string> {
|
||||||
|
if (!Array.isArray(args)) args = [args];
|
||||||
|
|
||||||
|
const process = Deno.run({ cmd: [command, ...args] });
|
||||||
|
const status = await process.status();
|
||||||
|
|
||||||
|
process.close();
|
||||||
|
|
||||||
|
if (!status.success) throw new Error(`Command failed with code ${status.code}`);
|
||||||
|
|
||||||
|
return { status, output: 'runAndAttach has access to the output stream' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default System;
|
export default System;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue