uloop-cli 0.63.0 → 0.64.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uloop-cli",
3
- "version": "0.63.0",
3
+ "version": "0.64.1",
4
4
  "//version": "x-release-please-version",
5
5
  "description": "CLI tool for Unity Editor communication via uLoopMCP",
6
6
  "main": "dist/cli.bundle.cjs",
@@ -7,7 +7,12 @@
7
7
  * @jest-environment node
8
8
  */
9
9
 
10
- import { execSync, ExecSyncOptionsWithStringEncoding } from 'child_process';
10
+ import {
11
+ execSync,
12
+ ExecSyncOptionsWithStringEncoding,
13
+ spawnSync,
14
+ SpawnSyncOptionsWithStringEncoding,
15
+ } from 'child_process';
11
16
  import { join } from 'path';
12
17
 
13
18
  const CLI_PATH = join(__dirname, '../..', 'dist/cli.bundle.cjs');
@@ -21,6 +26,13 @@ const EXEC_OPTIONS: ExecSyncOptionsWithStringEncoding = {
21
26
  stdio: ['pipe', 'pipe', 'pipe'],
22
27
  };
23
28
 
29
+ const SPAWN_OPTIONS: SpawnSyncOptionsWithStringEncoding = {
30
+ encoding: 'utf-8',
31
+ timeout: 60000,
32
+ cwd: UNITY_PROJECT_ROOT,
33
+ stdio: 'pipe',
34
+ };
35
+
24
36
  const INTERVAL_MS = 1500;
25
37
  const DOMAIN_RELOAD_RETRY_MS = 3000;
26
38
  const DOMAIN_RELOAD_MAX_RETRIES = 3;
@@ -54,6 +66,15 @@ function runCli(args: string): { stdout: string; stderr: string; exitCode: numbe
54
66
  }
55
67
  }
56
68
 
69
+ function runCliParts(args: string[]): { stdout: string; stderr: string; exitCode: number } {
70
+ const result = spawnSync('node', [CLI_PATH, ...args], SPAWN_OPTIONS);
71
+ return {
72
+ stdout: result.stdout ?? '',
73
+ stderr: result.stderr ?? '',
74
+ exitCode: result.status ?? 1,
75
+ };
76
+ }
77
+
57
78
  function runCliWithRetry(args: string): { stdout: string; stderr: string; exitCode: number } {
58
79
  for (let attempt = 0; attempt < DOMAIN_RELOAD_MAX_RETRIES; attempt++) {
59
80
  const result = runCli(args);
@@ -72,12 +93,44 @@ function runCliWithRetry(args: string): { stdout: string; stderr: string; exitCo
72
93
  return runCli(args);
73
94
  }
74
95
 
96
+ function runCliWithRetryParts(args: string[]): {
97
+ stdout: string;
98
+ stderr: string;
99
+ exitCode: number;
100
+ } {
101
+ for (let attempt = 0; attempt < DOMAIN_RELOAD_MAX_RETRIES; attempt++) {
102
+ const result = runCliParts(args);
103
+ const output = result.stderr || result.stdout;
104
+
105
+ if (result.exitCode === 0 || !isDomainReloadError(output)) {
106
+ return result;
107
+ }
108
+
109
+ if (attempt < DOMAIN_RELOAD_MAX_RETRIES - 1) {
110
+ sleepSync(DOMAIN_RELOAD_RETRY_MS);
111
+ }
112
+ }
113
+
114
+ return runCliParts(args);
115
+ }
116
+
75
117
  function runCliJson<T>(args: string): T {
76
118
  const { stdout, stderr, exitCode } = runCliWithRetry(args);
77
119
  if (exitCode !== 0) {
78
120
  throw new Error(`CLI failed with exit code ${exitCode}: ${stderr || stdout}`);
79
121
  }
80
- return JSON.parse(stdout) as T;
122
+
123
+ const trimmedOutput = stdout.trim();
124
+ const jsonStartByLine = trimmedOutput.lastIndexOf('\n{');
125
+ const jsonStart = jsonStartByLine >= 0 ? jsonStartByLine + 1 : trimmedOutput.indexOf('{');
126
+ const jsonEnd = trimmedOutput.lastIndexOf('}');
127
+
128
+ if (jsonStart < 0 || jsonEnd < 0 || jsonEnd < jsonStart) {
129
+ throw new Error(`JSON payload not found in CLI output: ${trimmedOutput}`);
130
+ }
131
+
132
+ const jsonPayload = trimmedOutput.slice(jsonStart, jsonEnd + 1);
133
+ return JSON.parse(jsonPayload) as T;
81
134
  }
82
135
 
83
136
  describe('CLI E2E Tests (requires running Unity)', () => {
@@ -97,6 +150,7 @@ describe('CLI E2E Tests (requires running Unity)', () => {
97
150
  describe('get-logs', () => {
98
151
  const TEST_LOG_MENU_PATH = 'uLoopMCP/Debug/LogGetter Tests/Output Test Logs';
99
152
  const MENU_ITEM_WAIT_MS = 1000;
153
+ const ERROR_FAMILY_PREFIX = 'CliE2EErrorFamily';
100
154
 
101
155
  function setupTestLogs(): void {
102
156
  runCliWithRetry('clear-console');
@@ -107,6 +161,38 @@ describe('CLI E2E Tests (requires running Unity)', () => {
107
161
  sleepSync(MENU_ITEM_WAIT_MS);
108
162
  }
109
163
 
164
+ function setupErrorFamilyLogs(token: string): void {
165
+ runCliWithRetry('clear-console');
166
+ const code = [
167
+ 'using UnityEngine;',
168
+ 'using System;',
169
+ `Debug.LogError("${ERROR_FAMILY_PREFIX}_Error_${token}");`,
170
+ `Debug.LogException(new InvalidOperationException("${ERROR_FAMILY_PREFIX}_Exception_${token}"));`,
171
+ `Debug.LogAssertion("${ERROR_FAMILY_PREFIX}_Assert_${token}");`,
172
+ `Debug.LogWarning("${ERROR_FAMILY_PREFIX}_Warning_${token}");`,
173
+ ].join(' ');
174
+ const result = runCliWithRetryParts(['execute-dynamic-code', '--code', code]);
175
+ if (result.exitCode !== 0) {
176
+ throw new Error(`execute-dynamic-code failed: ${result.stderr || result.stdout}`);
177
+ }
178
+ sleepSync(MENU_ITEM_WAIT_MS);
179
+ }
180
+
181
+ function setupAssertTextLogs(token: string): void {
182
+ runCliWithRetry('clear-console');
183
+ const code = [
184
+ 'using UnityEngine;',
185
+ `Debug.Log("Please assert your identity ${token}");`,
186
+ `Debug.LogWarning("All assertions passed ${token}");`,
187
+ `Debug.LogError("${ERROR_FAMILY_PREFIX}_ErrorOnly_${token}");`,
188
+ ].join(' ');
189
+ const result = runCliWithRetryParts(['execute-dynamic-code', '--code', code]);
190
+ if (result.exitCode !== 0) {
191
+ throw new Error(`execute-dynamic-code failed: ${result.stderr || result.stdout}`);
192
+ }
193
+ sleepSync(MENU_ITEM_WAIT_MS);
194
+ }
195
+
110
196
  it('should retrieve test logs after executing Output Test Logs menu item', () => {
111
197
  setupTestLogs();
112
198
 
@@ -163,6 +249,64 @@ describe('CLI E2E Tests (requires running Unity)', () => {
163
249
  expect(messages.some((m) => m.includes('This is an error log'))).toBe(true);
164
250
  });
165
251
 
252
+ it('should filter by lowercase log type error', () => {
253
+ setupTestLogs();
254
+
255
+ const result = runCliJson<{ Logs: Array<{ Type: string; Message: string }> }>(
256
+ 'get-logs --log-type error',
257
+ );
258
+
259
+ expect(result.Logs.length).toBeGreaterThan(0);
260
+ for (const log of result.Logs) {
261
+ expect(log.Type).toBe('Error');
262
+ }
263
+ const messages = result.Logs.map((log) => log.Message);
264
+ expect(messages.some((m) => m.includes('This is an error log'))).toBe(true);
265
+ });
266
+
267
+ it('should include error and exception logs in Error filter', () => {
268
+ const token = `${Date.now()}`;
269
+ setupErrorFamilyLogs(token);
270
+
271
+ const result = runCliJson<{ Logs: Array<{ Type: string; Message: string }> }>(
272
+ `get-logs --log-type Error --search-text "${token}" --max-count 20`,
273
+ );
274
+
275
+ expect(result.Logs.length).toBeGreaterThanOrEqual(2);
276
+ for (const log of result.Logs) {
277
+ expect(log.Type).toBe('Error');
278
+ }
279
+
280
+ const messages = result.Logs.map((log) => log.Message);
281
+ expect(messages.some((m) => m.includes(`${ERROR_FAMILY_PREFIX}_Error_${token}`))).toBe(true);
282
+ expect(messages.some((m) => m.includes(`${ERROR_FAMILY_PREFIX}_Exception_${token}`))).toBe(
283
+ true,
284
+ );
285
+ expect(messages.some((m) => m.includes(`${ERROR_FAMILY_PREFIX}_Warning_${token}`))).toBe(
286
+ false,
287
+ );
288
+ });
289
+
290
+ it('should not include plain assert text logs in Error filter', () => {
291
+ const token = `${Date.now()}`;
292
+ setupAssertTextLogs(token);
293
+
294
+ const result = runCliJson<{ Logs: Array<{ Type: string; Message: string }> }>(
295
+ `get-logs --log-type Error --search-text "${token}" --max-count 20`,
296
+ );
297
+
298
+ for (const log of result.Logs) {
299
+ expect(log.Type).toBe('Error');
300
+ }
301
+
302
+ const messages = result.Logs.map((log) => log.Message);
303
+ expect(messages.some((m) => m.includes(`${ERROR_FAMILY_PREFIX}_ErrorOnly_${token}`))).toBe(
304
+ true,
305
+ );
306
+ expect(messages.some((m) => m.includes(`Please assert your identity ${token}`))).toBe(false);
307
+ expect(messages.some((m) => m.includes(`All assertions passed ${token}`))).toBe(false);
308
+ });
309
+
166
310
  it('should search logs by text', () => {
167
311
  setupTestLogs();
168
312
 
@@ -487,7 +631,7 @@ describe('CLI E2E Tests (requires running Unity)', () => {
487
631
  const { stdout, exitCode } = runCli('launch --help');
488
632
 
489
633
  expect(exitCode).toBe(0);
490
- expect(stdout).toContain('Launch Unity project');
634
+ expect(stdout).toContain('Open a Unity project');
491
635
  expect(stdout).toContain('--restart');
492
636
  expect(stdout).toContain('--platform');
493
637
  expect(stdout).toContain('--max-depth');
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.63.0",
2
+ "version": "0.64.1",
3
3
  "tools": [
4
4
  {
5
5
  "name": "compile",
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * This file exists to avoid bundling the entire package.json into the CLI bundle.
5
5
  * This version is automatically updated by release-please.
6
6
  */
7
- export const VERSION = '0.63.0'; // x-release-please-version
7
+ export const VERSION = '0.64.1'; // x-release-please-version