Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rage-against-the-pixel/unity-cli",
"version": "1.0.0",
"version": "1.0.1",
"description": "A command line utility for the Unity Game Engine.",
"author": "RageAgainstThePixel",
"license": "MIT",
Expand All @@ -23,6 +23,7 @@
"build": "tsc",
"dev": "tsc --watch",
"link": "npm link",
"unlink": "npm unlink @rage-against-the-pixel/unity-cli",
"tests": "jest --roots tests"
},
"dependencies": {
Expand All @@ -44,4 +45,4 @@
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
}
}
20 changes: 13 additions & 7 deletions src/android-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import fs from 'fs';
import os from 'os';
import path from 'path';
import { spawn } from 'child_process';
import { Logger } from './logging';
import { UnityEditor } from './unity-editor';
import {
ReadFileContents,
ResolveGlobToPath
} from './utilities';
import {
Logger,
LogLevel
} from './logging';

const logger = Logger.instance;

Expand Down Expand Up @@ -132,8 +129,10 @@ async function execSdkManager(sdkManagerPath: string, javaPath: string, args: st
env: { ...process.env, JAVA_HOME: javaPath }
});

process.once('SIGINT', () => child.kill('SIGINT'));
process.once('SIGTERM', () => child.kill('SIGTERM'));
const sigintHandler = () => child.kill('SIGINT');
const sigtermHandler = () => child.kill('SIGTERM');
process.once('SIGINT', sigintHandler);
process.once('SIGTERM', sigtermHandler);
child.stdout.on('data', (data: Buffer) => {
const chunk = data.toString();
output += chunk;
Expand All @@ -150,9 +149,16 @@ async function execSdkManager(sdkManagerPath: string, javaPath: string, args: st
output += chunk;
process.stderr.write(chunk);
});
child.on('error', (error: Error) => reject(error));
child.on('error', (error: Error) => {
process.stdout.write('\n');
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGTERM', sigtermHandler);
reject(error);
});
child.on('close', (code: number | null) => {
process.stdout.write('\n');
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGTERM', sigtermHandler);
resolve(code === null ? 0 : code);
});
});
Expand Down
14 changes: 12 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node

import 'source-map-support/register';
import * as fs from 'fs';
import * as os from 'os';
import { Command } from 'commander';
import { readFileSync } from 'fs';
import path, { join } from 'path';
import { LicenseType, LicensingClient } from './license-client';
import { PromptForSecretInput } from './utilities';
Expand All @@ -14,8 +14,18 @@ import { UnityProject } from './unity-project';
import { CheckAndroidSdkInstalled } from './android-sdk';
import { UnityEditor } from './unity-editor';

// export public API
export * from './license-client';
export * from './utilities';
export * from './unity-hub';
export * from './logging';
export * from './unity-version';
export * from './unity-project';
export * from './android-sdk';
export * from './unity-editor';

const pkgPath = join(__dirname, '..', 'package.json');
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const program = new Command();

program.name('unity-cli')
Expand Down
42 changes: 37 additions & 5 deletions src/license-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ export enum LicenseType {
}

export class LicensingClient {
private unityHub: UnityHub = new UnityHub();
private readonly unityHub: UnityHub = new UnityHub();
private readonly logger: Logger = Logger.instance;

private licenseClientPath: string | undefined;
private licenseVersion: string | undefined;
private logger: Logger = Logger.instance;

/**
* Creates an instance of LicensingClient.
* @param licenseVersion The license version to use (e.g., '4.x', '5.x', '6.x'). If undefined, defaults to '6.x'.
*/
constructor(licenseVersion: string | undefined = undefined) {
this.licenseVersion = licenseVersion;
}
Expand Down Expand Up @@ -245,13 +250,22 @@ export class LicensingClient {
stdio: ['ignore', 'pipe', 'pipe']
});

process.once('SIGINT', () => child.kill('SIGINT'));
process.once('SIGTERM', () => child.kill('SIGTERM'));
const sigintHandler = () => child.kill('SIGINT');
const sigtermHandler = () => child.kill('SIGTERM');
process.once('SIGINT', sigintHandler);
process.once('SIGTERM', sigtermHandler);
child.stdout.on('data', processOutput);
child.stderr.on('data', processOutput);
child.on('error', (error) => reject(error));
child.on('error', (error) => {
process.stdout.write('\n');
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGTERM', sigtermHandler);
reject(error);
});
child.on('close', (code) => {
process.stdout.write('\n');
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGTERM', sigtermHandler);
resolve(code === null ? 0 : code);
});
});
Expand Down Expand Up @@ -283,10 +297,22 @@ export class LicensingClient {
});
}

/**
* Displays the version of the licensing client to the console.
*/
public async Version(): Promise<void> {
await this.exec(['--version']);
}

/**
* Activates a Unity license.
* @param licenseType The type of license to activate.
* @param servicesConfig The services config path for floating licenses.
* @param serial The license serial number.
* @param username The Unity ID username.
* @param password The Unity ID password.
* @throws Error if activation fails or required parameters are missing.
*/
public async Activate(licenseType: LicenseType, servicesConfig: string | undefined = undefined, serial: string | undefined = undefined, username: string | undefined = undefined, password: string | undefined = undefined): Promise<void> {
let activeLicenses = await this.showEntitlements();

Expand Down Expand Up @@ -356,6 +382,12 @@ export class LicensingClient {
}
}

/**
* Deactivates a Unity license.
* @param licenseType The type of license to deactivate.
* @returns A promise that resolves when the license is deactivated.
* @throws Error if deactivation fails.
*/
public async Deactivate(licenseType: LicenseType): Promise<void> {
if (licenseType === LicenseType.floating) {
return;
Expand Down
6 changes: 2 additions & 4 deletions src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ export enum LogLevel {

export class Logger {
public logLevel: LogLevel = LogLevel.INFO;
private _ci: string | undefined;
static instance: Logger = new Logger();
private readonly _ci: string | undefined;
static readonly instance: Logger = new Logger();

private constructor() {
if (process.env.GITHUB_ACTIONS) {
this._ci = 'GITHUB_ACTIONS';
this.logLevel = process.env.ACTIONS_STEP_DEBUG === 'true' ? LogLevel.DEBUG : LogLevel.CI;
}

Logger.instance = this;
}

/**
Expand Down
54 changes: 21 additions & 33 deletions src/unity-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as fs from 'fs';
import * as path from 'path';
import { Logger } from './logging';
import {
getArgumentValueAsString,
killChildProcesses,
GetArgumentValueAsString,
KillChildProcesses,
ProcInfo,
readPidFile,
tryKillProcess
ReadPidFile,
TryKillProcess
} from './utilities';
import {
spawn,
Expand All @@ -19,21 +19,25 @@ export interface EditorCommand {
}

export class UnityEditor {
public editorRootPath: string;
public readonly editorRootPath: string;

private readonly logger: Logger = Logger.instance;
private readonly autoAddNoGraphics: boolean;

private procInfo: ProcInfo | undefined;
private pidFile: string;
private logger: Logger = Logger.instance;
private autoAddNoGraphics: boolean;

constructor(public editorPath: string) {
/**
* Initializes a new instance of the UnityEditor class.
* @param editorPath The path to the Unity Editor installation.
* @throws Will throw an error if the editor path is invalid or not executable.
*/
constructor(public readonly editorPath: string) {
if (!fs.existsSync(editorPath)) {
throw new Error(`The Unity Editor path does not exist: ${editorPath}`);
}

fs.accessSync(editorPath, fs.constants.X_OK);
this.editorRootPath = UnityEditor.GetEditorRootPath(editorPath);
this.pidFile = path.join(process.env.RUNNER_TEMP || process.env.USERPROFILE || '.', '.unity', 'unity-editor-process-id.txt');

const match = editorPath.match(/(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/);

Expand Down Expand Up @@ -117,6 +121,7 @@ export class UnityEditor {
isCancelled = true;
await this.tryKillEditorProcess();
};

process.once('SIGINT', onCancel);
process.once('SIGTERM', onCancel);
let exitCode: number | undefined;
Expand All @@ -133,7 +138,10 @@ export class UnityEditor {
exitCode = 1;
}
} finally {
process.removeListener('SIGINT', onCancel);
process.removeListener('SIGTERM', onCancel);
this.logger.endGroup();

if (!isCancelled) {
await this.tryKillEditorProcess();

Expand Down Expand Up @@ -188,7 +196,7 @@ export class UnityEditor {
command.args.push('-logFile', this.GenerateLogFilePath(command.projectPath));
}

const logPath: string = getArgumentValueAsString('-logFile', command.args);
const logPath: string = GetArgumentValueAsString('-logFile', command.args);

let unityProcess: ChildProcessByStdio<null, null, null>;

Expand Down Expand Up @@ -225,26 +233,6 @@ export class UnityEditor {

onPid({ pid: processId, ppid: process.pid, name: this.editorPath });
this.logger.debug(`Unity process started with pid: ${processId}`);
// make sure the directory for the PID file exists
const pidDir = path.dirname(this.pidFile);

if (!fs.existsSync(pidDir)) {
fs.mkdirSync(pidDir, { recursive: true });
} else {
try {
var existingProcInfo = await readPidFile(this.pidFile);
if (existingProcInfo) {
const killedPid = await tryKillProcess(existingProcInfo);
if (killedPid) {
this.logger.warn(`Killed existing Unity process with pid: ${killedPid}`);
}
}
} catch {
// PID file does not exist, continue
}
}
// Write the PID to the PID file
fs.writeFileSync(this.pidFile, String(processId));
const logPollingInterval = 100; // milliseconds
// Wait for log file to appear
while (!fs.existsSync(logPath)) {
Expand Down Expand Up @@ -318,8 +306,8 @@ export class UnityEditor {

private async tryKillEditorProcess(): Promise<void> {
if (this.procInfo) {
await tryKillProcess(this.procInfo);
await killChildProcesses(this.procInfo);
await TryKillProcess(this.procInfo);
await KillChildProcesses(this.procInfo);
} else {
this.logger.debug('No Unity process info available to kill.');
}
Expand Down
Loading
Loading