Skip to content

Commit 5a42214

Browse files
frostebiteclaude
andcommitted
feat: add official game-ci CLI with build, activate, and orchestrate commands
Introduces a yargs-based CLI entry point (src/cli.ts) distributed as the `game-ci` command. The CLI reuses existing unity-builder modules — Input, BuildParameters, Orchestrator, Docker, MacBuilder — so the same build engine powers both the GitHub Action and the standalone CLI. Commands: build, activate, orchestrate, cache (list/restore/clear), status, version. Closes #812 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9d47543 commit 5a42214

File tree

11 files changed

+1026
-10
lines changed

11 files changed

+1026
-10
lines changed

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"version": "3.0.0",
44
"description": "Build Unity projects for different platforms.",
55
"main": "dist/index.js",
6+
"bin": {
7+
"game-ci": "./lib/cli.js"
8+
},
69
"repository": "git@github.com:game-ci/unity-builder.git",
710
"author": "Webber <webber@takken.io>",
811
"license": "MIT",
@@ -12,6 +15,7 @@
1215
"lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts",
1316
"format": "prettier --write \"src/**/*.{js,ts}\"",
1417
"cli": "yarn ts-node src/index.ts -m cli",
18+
"game-ci": "ts-node src/cli.ts",
1519
"gcp-secrets-tests": "cross-env providerStrategy=aws orchestratorTests=true inputPullCommand=\"gcp-secret-manager\" populateOverride=true pullInputList=UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD yarn test -i -t \"orchestrator\"",
1620
"gcp-secrets-cli": "cross-env orchestratorTests=true USE_IL2CPP=false inputPullCommand=\"gcp-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
1721
"aws-secrets-cli": "cross-env orchestratorTests=true inputPullCommand=\"aws-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
@@ -54,14 +58,16 @@
5458
"ts-md5": "^1.3.1",
5559
"unity-changeset": "^3.1.0",
5660
"uuid": "^9.0.0",
57-
"yaml": "^2.2.2"
61+
"yaml": "^2.2.2",
62+
"yargs": "^18.0.0"
5863
},
5964
"devDependencies": {
6065
"@types/base-64": "^1.0.0",
6166
"@types/jest": "^27.4.1",
6267
"@types/node": "^17.0.23",
6368
"@types/semver": "^7.3.9",
6469
"@types/uuid": "^9.0.0",
70+
"@types/yargs": "^17.0.35",
6571
"@typescript-eslint/parser": "4.8.1",
6672
"@vercel/ncc": "^0.36.1",
6773
"cross-env": "^7.0.3",

src/cli.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env node
2+
3+
import yargs from 'yargs';
4+
// eslint-disable-next-line import/no-unresolved
5+
import { hideBin } from 'yargs/helpers';
6+
import buildCommand from './cli/commands/build';
7+
import activateCommand from './cli/commands/activate';
8+
import orchestrateCommand from './cli/commands/orchestrate';
9+
import cacheCommand from './cli/commands/cache';
10+
import statusCommand from './cli/commands/status';
11+
import versionCommand from './cli/commands/version';
12+
import * as core from '@actions/core';
13+
14+
const cli = yargs(hideBin(process.argv))
15+
.scriptName('game-ci')
16+
.usage('$0 <command> [options]')
17+
.command(buildCommand)
18+
.command(activateCommand)
19+
.command(orchestrateCommand)
20+
.command(cacheCommand)
21+
.command(statusCommand)
22+
.command(versionCommand)
23+
.demandCommand(1, 'You must specify a command. Run game-ci --help for available commands.')
24+
.strict()
25+
.alias('h', 'help')
26+
.epilogue('For more information, visit https://game.ci')
27+
.wrap(Math.min(120, process.stdout.columns || 80));
28+
29+
async function main() {
30+
try {
31+
await cli.parse();
32+
} catch (error: any) {
33+
if (error.name !== 'YError') {
34+
core.error(`Error: ${error.message}`);
35+
}
36+
}
37+
}
38+
39+
main();

src/cli/commands/activate.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { CommandModule } from 'yargs';
2+
import * as core from '@actions/core';
3+
import { mapCliArgumentsToInput, CliArguments } from '../input-mapper';
4+
5+
interface ActivateArguments extends CliArguments {
6+
unityVersion?: string;
7+
unitySerial?: string;
8+
unityLicensingServer?: string;
9+
}
10+
11+
const activateCommand: CommandModule<object, ActivateArguments> = {
12+
command: 'activate',
13+
describe: 'Activate a Unity license',
14+
builder: (yargs) => {
15+
return yargs
16+
.option('unity-version', {
17+
alias: 'unityVersion',
18+
type: 'string',
19+
description: 'Version of Unity to activate',
20+
default: 'auto',
21+
})
22+
.option('unity-licensing-server', {
23+
alias: 'unityLicensingServer',
24+
type: 'string',
25+
description: 'The Unity licensing server address for floating licenses',
26+
default: '',
27+
})
28+
.env('UNITY')
29+
.example(
30+
'UNITY_SERIAL=XXXX-XXXX-XXXX-XXXX game-ci activate',
31+
'Activate Unity using a serial from environment variable',
32+
)
33+
.example(
34+
'game-ci activate --unity-licensing-server http://license-server:8080',
35+
'Activate Unity using a floating license server',
36+
) as any;
37+
},
38+
handler: async (cliArguments) => {
39+
try {
40+
mapCliArgumentsToInput(cliArguments);
41+
42+
const unitySerial = process.env.UNITY_SERIAL;
43+
const unityLicense = process.env.UNITY_LICENSE;
44+
const licensingServer = cliArguments.unityLicensingServer || process.env.UNITY_LICENSING_SERVER || '';
45+
46+
if (licensingServer) {
47+
core.info(`Activating Unity via licensing server: ${licensingServer}`);
48+
core.info('Floating license activation is handled automatically during builds.');
49+
core.info('No manual activation step is needed when using a licensing server.');
50+
51+
return;
52+
}
53+
54+
if (!unitySerial && !unityLicense) {
55+
throw new Error(
56+
'No Unity license found.\n\n' +
57+
'Provide one of the following:\n' +
58+
' - UNITY_SERIAL environment variable (professional license)\n' +
59+
' - UNITY_LICENSE environment variable (personal license file content)\n' +
60+
' - --unity-licensing-server flag (floating license)\n\n' +
61+
'For more information, visit: https://game.ci/docs/github/activation',
62+
);
63+
}
64+
65+
if (unitySerial) {
66+
const maskedSerial = unitySerial.length > 8 ? `${unitySerial.slice(0, 4)}...${unitySerial.slice(-4)}` : '****';
67+
core.info(`Unity serial detected: ${maskedSerial}`);
68+
core.info('License will be activated automatically when running a build.');
69+
} else if (unityLicense) {
70+
core.info('Unity license file detected from UNITY_LICENSE environment variable.');
71+
core.info('License will be activated automatically when running a build.');
72+
}
73+
74+
core.info('\nActivation verified. You can now run: game-ci build --target-platform <platform>');
75+
} catch (error: any) {
76+
core.setFailed(`Activation failed: ${error.message}`);
77+
78+
throw error;
79+
}
80+
},
81+
};
82+
83+
export default activateCommand;

0 commit comments

Comments
 (0)