Skip to content

Commit 5b0434c

Browse files
unity-cli@v1.5.6
- updated GitHub annotation handling - added unit tests - added docs - bump deps
1 parent 1dac07c commit 5b0434c

5 files changed

Lines changed: 143 additions & 55 deletions

File tree

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ A powerful command line utility for the Unity Game Engine. Automate Unity projec
2828
- [Run Unity Editor Commands](#run-unity-editor-commands)
2929
- [Unity Package Manager](#unity-package-manager)
3030
- [Sign a Unity Package](#sign-a-unity-package)
31+
- [Logging](#logging)
32+
- [Local cli](#local-cli)
33+
- [Github Actions](#github-actions)
3134

3235
## Features
3336

@@ -288,3 +291,37 @@ unity-cli run --unity-project <path-to-project> -quit -batchmode -executeMethod
288291
```bash
289292
unity-cli sign-package --package <path-to-package-folder> --email <your-email> --password <your-password> --organization <your-organization-id>
290293
```
294+
295+
## Logging
296+
297+
### Local cli
298+
299+
`unity-cli` keeps regular terminal runs simple:
300+
301+
- Writes everything to `stdout` with ANSI colors (yellow warnings, red errors) so you can scan logs quickly.
302+
- `startGroup`/`endGroup` just print headers and content, and don't include any foldouts or collapsing behavior and is meant for CI environments only.
303+
304+
### Github Actions
305+
306+
When `GITHUB_ACTIONS=true`, the logger emits GitHub workflow commands automatically:
307+
308+
- Defaults to `info` level; add `--verbose` (or temporarily set `ACTIONS_STEP_DEBUG=true`) to surface `debug` lines.
309+
- `Logger.annotate(...)` escapes `%`, `\r`, and `\n`, then includes `file`, `line`, `endLine`, `col`, `endColumn`, and `title` metadata so annotations are clickable in the Checks UI.
310+
- `startGroup`/`endGroup` become `::group::` / `::endgroup::` blocks.
311+
- Helper methods (`CI_mask`, `CI_setEnvironmentVariable`, `CI_setOutput`, `CI_appendWorkflowSummary`) write to the corresponding GitHub-provided files, so secrets stay masked and workflow outputs update automatically.
312+
313+
The same command line you run locally therefore produces colorized console output on your machine and rich annotations once it runs inside Actions.
314+
315+
### Additional CI Environments
316+
317+
At the moment, only GitHub Actions is supported for enhanced logging. If you would like to see support for additional CI environments, please open a pull request or feature request on the GitHub repository.
318+
319+
#### Roadmap
320+
321+
- [ ] Support Azure DevOps logging commands
322+
- [ ] Support GitLab CI logging commands
323+
- [ ] Support Bitbucket Pipelines logging commands
324+
- [ ] Support Jenkins logging commands
325+
- [ ] Support CircleCI logging commands
326+
- [ ] Support Travis CI logging commands
327+
- [ ] Support TeamCity logging commands

package-lock.json

Lines changed: 12 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rage-against-the-pixel/unity-cli",
3-
"version": "1.5.5",
3+
"version": "1.5.6",
44
"description": "A command line utility for the Unity Game Engine.",
55
"author": "RageAgainstThePixel",
66
"license": "MIT",
@@ -49,7 +49,7 @@
4949
},
5050
"dependencies": {
5151
"@electron/asar": "^4.0.1",
52-
"@rage-against-the-pixel/unity-releases-api": "^1.0.3",
52+
"@rage-against-the-pixel/unity-releases-api": "^1.0.4",
5353
"commander": "^14.0.2",
5454
"glob": "^11.1.0",
5555
"semver": "^7.7.3",

src/logging.ts

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -152,56 +152,55 @@ export class Logger {
152152

153153
switch (this._ci) {
154154
case 'GITHUB_ACTIONS': {
155-
var level: string;
156-
switch (logLevel) {
157-
case LogLevel.CI:
158-
case LogLevel.INFO:
159-
case LogLevel.DEBUG: {
160-
level = 'notice';
161-
break;
162-
}
163-
case LogLevel.WARN: {
164-
level = 'warning';
165-
break;
166-
}
167-
case LogLevel.ERROR: {
168-
level = 'error';
169-
break;
170-
}
171-
}
155+
const level = {
156+
[LogLevel.CI]: 'notice',
157+
[LogLevel.INFO]: 'notice',
158+
[LogLevel.DEBUG]: 'notice',
159+
[LogLevel.WARN]: 'warning',
160+
[LogLevel.ERROR]: 'error',
161+
}[logLevel] ?? 'notice';
172162

173-
let parts: string[] = [];
174-
175-
if (file !== undefined && file.length > 0) {
176-
parts.push(`file=${file}`);
177-
}
163+
const parts: string[] = [];
164+
const appendPart = (key: string, value?: string | number): void => {
165+
if (value === undefined || value === null) { return; }
166+
const stringValue = value.toString();
167+
if (stringValue.length === 0) { return; }
168+
parts.push(`${key}=${this.escapeGitHubCommandValue(stringValue)}`);
169+
};
178170

171+
appendPart('file', file);
179172
if (line !== undefined && line > 0) {
180-
parts.push(`line=${line}`);
173+
appendPart('line', line);
181174
}
182-
183175
if (endLine !== undefined && endLine > 0) {
184-
parts.push(`endLine=${endLine}`);
176+
appendPart('endLine', endLine);
185177
}
186-
187178
if (column !== undefined && column > 0) {
188-
parts.push(`col=${column}`);
179+
appendPart('col', column);
189180
}
190-
191181
if (endColumn !== undefined && endColumn > 0) {
192-
parts.push(`endColumn=${endColumn}`);
193-
}
194-
195-
if (title !== undefined && title.length > 0) {
196-
parts.push(`title=${title}`);
182+
appendPart('endColumn', endColumn);
197183
}
184+
appendPart('title', title);
198185

199-
annotation = `::${level} ${parts.join(',')}::${message}`;
186+
const metadata = parts.length > 0 ? ` ${parts.join(',')}` : '';
187+
annotation = `::${level}${metadata}::${this.escapeGitHubCommandValue(message)}`;
200188
break;
201189
}
202190
}
203191

204-
process.stdout.write(`${annotation}\n`);
192+
if (annotation.length > 0) {
193+
process.stdout.write(`${annotation}\n`);
194+
} else {
195+
this.log(logLevel, message);
196+
}
197+
}
198+
199+
private escapeGitHubCommandValue(value: string): string {
200+
return value
201+
.replace(/%/g, '%25')
202+
.replace(/\r/g, '%0D')
203+
.replace(/\n/g, '%0A');
205204
}
206205

207206
private shouldLog(level: LogLevel): boolean {

tests/logging.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
type LoggingModule = typeof import('../src/logging');
2+
3+
describe('Logger annotate', () => {
4+
const ORIGINAL_ENV = process.env;
5+
6+
const loadLoggingModule = (): LoggingModule => {
7+
return require('../src/logging') as LoggingModule;
8+
};
9+
10+
beforeEach(() => {
11+
jest.resetModules();
12+
process.env = { ...ORIGINAL_ENV };
13+
});
14+
15+
afterEach(() => {
16+
process.env = ORIGINAL_ENV;
17+
jest.restoreAllMocks();
18+
});
19+
20+
it('formats annotations correctly for GitHub Actions', () => {
21+
process.env.GITHUB_ACTIONS = 'true';
22+
const logging = loadLoggingModule();
23+
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
24+
25+
logging.Logger.instance.annotate(
26+
logging.LogLevel.ERROR,
27+
'Line one\nLine two',
28+
'src/file.ts',
29+
7,
30+
undefined,
31+
undefined,
32+
undefined,
33+
'Build failed'
34+
);
35+
36+
expect(writeSpy).toHaveBeenCalledWith('::error file=src/file.ts,line=7,title=Build failed::Line one%0ALine two\n');
37+
});
38+
39+
it('omits metadata spacing when none is provided', () => {
40+
process.env.GITHUB_ACTIONS = 'true';
41+
const logging = loadLoggingModule();
42+
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
43+
44+
logging.Logger.instance.annotate(logging.LogLevel.INFO, 'Hello world');
45+
46+
expect(writeSpy).toHaveBeenCalledWith('::notice::Hello world\n');
47+
});
48+
49+
it('falls back to standard logging when annotations are unavailable', () => {
50+
delete process.env.GITHUB_ACTIONS;
51+
const logging = loadLoggingModule();
52+
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
53+
54+
logging.Logger.instance.annotate(logging.LogLevel.INFO, 'Hello local');
55+
56+
expect(writeSpy).toHaveBeenCalledWith('Hello local\n');
57+
});
58+
});

0 commit comments

Comments
 (0)