uloop-cli 1.7.0 → 1.7.3
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/dist/cli.bundle.cjs +352 -53
- package/dist/cli.bundle.cjs.map +4 -4
- package/package.json +6 -6
- package/src/__tests__/busy-state-order.test.ts +120 -0
- package/src/__tests__/cli-project-error.test.ts +45 -0
- package/src/__tests__/cli-update.test.ts +129 -0
- package/src/__tests__/execute-tool.test.ts +67 -2
- package/src/__tests__/package-metadata.test.ts +28 -0
- package/src/__tests__/port-resolver.test.ts +3 -5
- package/src/__tests__/unity-process.test.ts +289 -0
- package/src/cli-project-error.ts +36 -0
- package/src/cli.ts +19 -23
- package/src/default-tools.json +1 -1
- package/src/execute-tool.ts +82 -17
- package/src/port-resolver.ts +6 -6
- package/src/project-root.ts +1 -1
- package/src/unity-process.ts +337 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uloop-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"//version": "x-release-please-version",
|
|
5
5
|
"description": "CLI tool for Unity Editor communication via Unity CLI Loop",
|
|
6
6
|
"main": "dist/cli.bundle.cjs",
|
|
7
7
|
"bin": {
|
|
8
|
-
"uloop": "
|
|
8
|
+
"uloop": "dist/cli.bundle.cjs"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"scripts": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "10.0.1",
|
|
51
51
|
"@types/jest": "30.0.0",
|
|
52
|
-
"@types/node": "25.
|
|
52
|
+
"@types/node": "25.6.0",
|
|
53
53
|
"@types/semver": "7.7.1",
|
|
54
54
|
"esbuild": "0.28.0",
|
|
55
55
|
"eslint": "10.2.0",
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"eslint-plugin-prettier": "5.5.5",
|
|
58
58
|
"eslint-plugin-security": "4.0.0",
|
|
59
59
|
"jest": "30.3.0",
|
|
60
|
-
"knip": "6.3.
|
|
61
|
-
"prettier": "3.8.
|
|
60
|
+
"knip": "6.3.1",
|
|
61
|
+
"prettier": "3.8.2",
|
|
62
62
|
"ts-jest": "29.4.9",
|
|
63
63
|
"typescript": "5.9.3",
|
|
64
|
-
"typescript-eslint": "8.58.
|
|
64
|
+
"typescript-eslint": "8.58.1"
|
|
65
65
|
},
|
|
66
66
|
"overrides": {
|
|
67
67
|
"minimatch": "10.2.4"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const mockResolveUnityPort = jest.fn<Promise<number>, [number | undefined, string | undefined]>();
|
|
2
|
+
const mockValidateProjectPath = jest.fn<string, [string]>();
|
|
3
|
+
const mockFindUnityProjectRoot = jest.fn<string | null, []>();
|
|
4
|
+
const mockExistsSync = jest.fn<boolean, [string]>();
|
|
5
|
+
|
|
6
|
+
jest.mock('../port-resolver.js', () => ({
|
|
7
|
+
resolveUnityPort: (explicitPort?: number, projectPath?: string): Promise<number> =>
|
|
8
|
+
mockResolveUnityPort(explicitPort, projectPath),
|
|
9
|
+
validateProjectPath: (projectPath: string): string => mockValidateProjectPath(projectPath),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock('../project-root.js', () => ({
|
|
13
|
+
findUnityProjectRoot: (): string | null => mockFindUnityProjectRoot(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('fs', () => ({
|
|
17
|
+
existsSync: (path: string): boolean => mockExistsSync(path),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
import { executeToolCommand, listAvailableTools, syncTools } from '../execute-tool.js';
|
|
21
|
+
|
|
22
|
+
describe('busy state detection order', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
mockResolveUnityPort.mockReset();
|
|
25
|
+
mockResolveUnityPort.mockRejectedValue(new Error('RESOLVE_CALLED_BEFORE_BUSY_CHECK'));
|
|
26
|
+
|
|
27
|
+
mockValidateProjectPath.mockReset();
|
|
28
|
+
mockValidateProjectPath.mockReturnValue('/project');
|
|
29
|
+
|
|
30
|
+
mockFindUnityProjectRoot.mockReset();
|
|
31
|
+
mockFindUnityProjectRoot.mockReturnValue('/project');
|
|
32
|
+
|
|
33
|
+
mockExistsSync.mockReset();
|
|
34
|
+
mockExistsSync.mockImplementation((path: string) => path.endsWith('serverstarting.lock'));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('checks busy state before resolving port for tool execution', async () => {
|
|
38
|
+
await expect(executeToolCommand('get-logs', {}, { projectPath: '/project' })).rejects.toThrow(
|
|
39
|
+
'UNITY_SERVER_STARTING',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(mockResolveUnityPort).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('checks busy state before resolving port for list', async () => {
|
|
46
|
+
await expect(listAvailableTools({ projectPath: '/project' })).rejects.toThrow(
|
|
47
|
+
'UNITY_SERVER_STARTING',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(mockResolveUnityPort).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('checks busy state before resolving port for sync', async () => {
|
|
54
|
+
await expect(syncTools({ projectPath: '/project' })).rejects.toThrow('UNITY_SERVER_STARTING');
|
|
55
|
+
|
|
56
|
+
expect(mockResolveUnityPort).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('skips busy state checks when an explicit port is provided for tool execution', async () => {
|
|
60
|
+
mockResolveUnityPort.mockRejectedValue(new Error('EXPLICIT_PORT_RESOLVED'));
|
|
61
|
+
|
|
62
|
+
await expect(executeToolCommand('get-logs', {}, { port: '8711' })).rejects.toThrow(
|
|
63
|
+
'EXPLICIT_PORT_RESOLVED',
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, undefined);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('preserves usage errors before busy state checks for tool execution', async () => {
|
|
70
|
+
mockResolveUnityPort.mockRejectedValue(
|
|
71
|
+
new Error('Cannot specify both --port and --project-path. Use one or the other.'),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await expect(
|
|
75
|
+
executeToolCommand('get-logs', {}, { port: '8711', projectPath: '/project' }),
|
|
76
|
+
).rejects.toThrow('Cannot specify both --port and --project-path. Use one or the other.');
|
|
77
|
+
|
|
78
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, '/project');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('skips busy state checks when an explicit port is provided for list', async () => {
|
|
82
|
+
mockResolveUnityPort.mockRejectedValue(new Error('EXPLICIT_PORT_RESOLVED'));
|
|
83
|
+
|
|
84
|
+
await expect(listAvailableTools({ port: '8711' })).rejects.toThrow('EXPLICIT_PORT_RESOLVED');
|
|
85
|
+
|
|
86
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, undefined);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('preserves usage errors before busy state checks for list', async () => {
|
|
90
|
+
mockResolveUnityPort.mockRejectedValue(
|
|
91
|
+
new Error('Cannot specify both --port and --project-path. Use one or the other.'),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
await expect(listAvailableTools({ port: '8711', projectPath: '/project' })).rejects.toThrow(
|
|
95
|
+
'Cannot specify both --port and --project-path. Use one or the other.',
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, '/project');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('skips busy state checks when an explicit port is provided for sync', async () => {
|
|
102
|
+
mockResolveUnityPort.mockRejectedValue(new Error('EXPLICIT_PORT_RESOLVED'));
|
|
103
|
+
|
|
104
|
+
await expect(syncTools({ port: '8711' })).rejects.toThrow('EXPLICIT_PORT_RESOLVED');
|
|
105
|
+
|
|
106
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, undefined);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('preserves usage errors before busy state checks for sync', async () => {
|
|
110
|
+
mockResolveUnityPort.mockRejectedValue(
|
|
111
|
+
new Error('Cannot specify both --port and --project-path. Use one or the other.'),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
await expect(syncTools({ port: '8711', projectPath: '/project' })).rejects.toThrow(
|
|
115
|
+
'Cannot specify both --port and --project-path. Use one or the other.',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(mockResolveUnityPort).toHaveBeenCalledWith(8711, '/project');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getProjectResolutionErrorLines } from '../cli-project-error.js';
|
|
2
|
+
import { UnityNotRunningError, UnityServerNotRunningError } from '../port-resolver.js';
|
|
3
|
+
import { ProjectMismatchError } from '../project-validator.js';
|
|
4
|
+
|
|
5
|
+
describe('getProjectResolutionErrorLines', () => {
|
|
6
|
+
it('returns not-running guidance for UnityNotRunningError', () => {
|
|
7
|
+
const lines = getProjectResolutionErrorLines(new UnityNotRunningError('/project/root'));
|
|
8
|
+
|
|
9
|
+
expect(lines).toEqual([
|
|
10
|
+
'Error: Unity Editor for this project is not running.',
|
|
11
|
+
'',
|
|
12
|
+
' Project: /project/root',
|
|
13
|
+
'',
|
|
14
|
+
'Start the Unity Editor for this project and try again.',
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns mismatch guidance for ProjectMismatchError', () => {
|
|
19
|
+
const lines = getProjectResolutionErrorLines(
|
|
20
|
+
new ProjectMismatchError('/expected/project', '/connected/project'),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(lines).toEqual([
|
|
24
|
+
'Error: Connected Unity instance belongs to a different project.',
|
|
25
|
+
'',
|
|
26
|
+
' Project: /expected/project',
|
|
27
|
+
' Connected to: /connected/project',
|
|
28
|
+
'',
|
|
29
|
+
'Another Unity instance was found, but it belongs to a different project.',
|
|
30
|
+
'Start the Unity Editor for this project, or use --project-path to specify the target.',
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns server-not-running guidance for UnityServerNotRunningError', () => {
|
|
35
|
+
const lines = getProjectResolutionErrorLines(new UnityServerNotRunningError('/project/root'));
|
|
36
|
+
|
|
37
|
+
expect(lines).toEqual([
|
|
38
|
+
'Error: Unity Editor is running, but Unity CLI Loop server is not.',
|
|
39
|
+
'',
|
|
40
|
+
' Project: /project/root',
|
|
41
|
+
'',
|
|
42
|
+
'Start the server from: Window > Unity CLI Loop > Server',
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
type SpawnArgs = [string, string[], Record<string, unknown>?];
|
|
2
|
+
|
|
3
|
+
const mockSpawn = jest.fn<unknown, SpawnArgs>();
|
|
4
|
+
|
|
5
|
+
jest.mock('child_process', () => ({
|
|
6
|
+
spawn: (...args: SpawnArgs): unknown => mockSpawn(...args),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock(
|
|
10
|
+
'launch-unity',
|
|
11
|
+
() => ({
|
|
12
|
+
orchestrateLaunch: jest.fn(),
|
|
13
|
+
}),
|
|
14
|
+
{ virtual: true },
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
import { getInstalledVersion, updateCli } from '../cli.js';
|
|
18
|
+
|
|
19
|
+
type CloseHandler = (code: number | null) => void;
|
|
20
|
+
type ErrorHandler = (error: Error) => void;
|
|
21
|
+
type DataHandler = (chunk: Buffer) => void;
|
|
22
|
+
|
|
23
|
+
interface MockChildProcess {
|
|
24
|
+
stdout: {
|
|
25
|
+
on: jest.Mock<void, [string, DataHandler]>;
|
|
26
|
+
};
|
|
27
|
+
on: jest.Mock<void, [string, CloseHandler | ErrorHandler]>;
|
|
28
|
+
emitStdout: (chunk: string) => void;
|
|
29
|
+
emitClose: (code: number | null) => void;
|
|
30
|
+
emitError: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createMockChildProcess(): MockChildProcess {
|
|
34
|
+
let closeHandler: CloseHandler | undefined;
|
|
35
|
+
let errorHandler: ErrorHandler | undefined;
|
|
36
|
+
let dataHandler: DataHandler | undefined;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
stdout: {
|
|
40
|
+
on: jest.fn((event: string, handler: DataHandler) => {
|
|
41
|
+
if (event === 'data') {
|
|
42
|
+
dataHandler = handler;
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
on: jest.fn((event: string, handler: CloseHandler | ErrorHandler) => {
|
|
47
|
+
if (event === 'close') {
|
|
48
|
+
closeHandler = handler as CloseHandler;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (event === 'error') {
|
|
52
|
+
errorHandler = handler as ErrorHandler;
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
emitStdout: (chunk: string): void => {
|
|
56
|
+
dataHandler?.(Buffer.from(chunk));
|
|
57
|
+
},
|
|
58
|
+
emitClose: (code: number | null): void => {
|
|
59
|
+
closeHandler?.(code);
|
|
60
|
+
},
|
|
61
|
+
emitError: (error: Error): void => {
|
|
62
|
+
errorHandler?.(error);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('CLI update npm invocation', () => {
|
|
68
|
+
const expectedNpmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
mockSpawn.mockReset();
|
|
72
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
jest.restoreAllMocks();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('gets installed version without enabling shell mode', () => {
|
|
80
|
+
const child = createMockChildProcess();
|
|
81
|
+
const callback = jest.fn();
|
|
82
|
+
mockSpawn.mockReturnValue(child);
|
|
83
|
+
|
|
84
|
+
getInstalledVersion(callback);
|
|
85
|
+
|
|
86
|
+
expect(mockSpawn).toHaveBeenCalledWith(expectedNpmCommand, [
|
|
87
|
+
'list',
|
|
88
|
+
'-g',
|
|
89
|
+
'uloop-cli',
|
|
90
|
+
'--json',
|
|
91
|
+
]);
|
|
92
|
+
expect(mockSpawn.mock.calls[0]).toHaveLength(2);
|
|
93
|
+
|
|
94
|
+
child.emitStdout(JSON.stringify({ dependencies: { 'uloop-cli': { version: '1.8.0' } } }));
|
|
95
|
+
child.emitClose(0);
|
|
96
|
+
|
|
97
|
+
expect(callback).toHaveBeenCalledWith('1.8.0');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('updates the CLI without enabling shell mode', () => {
|
|
101
|
+
const updateChild = createMockChildProcess();
|
|
102
|
+
const listChild = createMockChildProcess();
|
|
103
|
+
mockSpawn.mockReturnValueOnce(updateChild).mockReturnValueOnce(listChild);
|
|
104
|
+
|
|
105
|
+
updateCli();
|
|
106
|
+
|
|
107
|
+
expect(mockSpawn).toHaveBeenNthCalledWith(
|
|
108
|
+
1,
|
|
109
|
+
expectedNpmCommand,
|
|
110
|
+
['install', '-g', 'uloop-cli@latest'],
|
|
111
|
+
{ stdio: 'inherit' },
|
|
112
|
+
);
|
|
113
|
+
const installOptions = mockSpawn.mock.calls[0]?.[2];
|
|
114
|
+
expect(installOptions?.['shell']).toBeUndefined();
|
|
115
|
+
|
|
116
|
+
updateChild.emitClose(0);
|
|
117
|
+
|
|
118
|
+
expect(mockSpawn).toHaveBeenNthCalledWith(2, expectedNpmCommand, [
|
|
119
|
+
'list',
|
|
120
|
+
'-g',
|
|
121
|
+
'uloop-cli',
|
|
122
|
+
'--json',
|
|
123
|
+
]);
|
|
124
|
+
expect(mockSpawn.mock.calls[1]).toHaveLength(2);
|
|
125
|
+
|
|
126
|
+
listChild.emitStdout(JSON.stringify({ dependencies: { 'uloop-cli': { version: '1.7.1' } } }));
|
|
127
|
+
listChild.emitClose(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
diagnoseRetryableProjectConnectionError,
|
|
3
|
+
isTransportDisconnectError,
|
|
4
|
+
} from '../execute-tool.js';
|
|
5
|
+
import { UnityNotRunningError, UnityServerNotRunningError } from '../port-resolver.js';
|
|
3
6
|
import { ProjectMismatchError } from '../project-validator.js';
|
|
4
7
|
|
|
5
8
|
describe('isTransportDisconnectError', () => {
|
|
@@ -35,7 +38,69 @@ describe('isTransportDisconnectError', () => {
|
|
|
35
38
|
expect(isTransportDisconnectError(new UnityNotRunningError('/project'))).toBe(false);
|
|
36
39
|
});
|
|
37
40
|
|
|
41
|
+
it('returns false for UnityServerNotRunningError', () => {
|
|
42
|
+
expect(isTransportDisconnectError(new UnityServerNotRunningError('/project'))).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
38
45
|
it('returns false for ProjectMismatchError', () => {
|
|
39
46
|
expect(isTransportDisconnectError(new ProjectMismatchError('/a', '/b'))).toBe(false);
|
|
40
47
|
});
|
|
41
48
|
});
|
|
49
|
+
|
|
50
|
+
describe('diagnoseRetryableProjectConnectionError', () => {
|
|
51
|
+
it('returns UnityNotRunningError when connection fails and Unity is not running', async () => {
|
|
52
|
+
const error = await diagnoseRetryableProjectConnectionError(
|
|
53
|
+
new Error('Connection error: connect ECONNREFUSED 127.0.0.1:8711'),
|
|
54
|
+
'/project',
|
|
55
|
+
true,
|
|
56
|
+
{
|
|
57
|
+
findRunningUnityProcessForProjectFn: jest.fn().mockResolvedValue(null),
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(error).toBeInstanceOf(UnityNotRunningError);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns UnityServerNotRunningError when Unity is running but server is unavailable', async () => {
|
|
65
|
+
const error = await diagnoseRetryableProjectConnectionError(
|
|
66
|
+
new Error('UNITY_NO_RESPONSE'),
|
|
67
|
+
'/project',
|
|
68
|
+
true,
|
|
69
|
+
{
|
|
70
|
+
findRunningUnityProcessForProjectFn: jest.fn().mockResolvedValue({ pid: 1234 }),
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(error).toBeInstanceOf(UnityServerNotRunningError);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('preserves non-retryable errors', async () => {
|
|
78
|
+
const originalError = new ProjectMismatchError('/expected', '/actual');
|
|
79
|
+
|
|
80
|
+
const error = await diagnoseRetryableProjectConnectionError(originalError, '/project', true, {
|
|
81
|
+
findRunningUnityProcessForProjectFn: jest.fn(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(error).toBe(originalError);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('preserves retryable errors when project diagnosis is disabled', async () => {
|
|
88
|
+
const originalError = new Error('Connection error: connect ECONNREFUSED 127.0.0.1:8711');
|
|
89
|
+
|
|
90
|
+
const error = await diagnoseRetryableProjectConnectionError(originalError, '/project', false, {
|
|
91
|
+
findRunningUnityProcessForProjectFn: jest.fn(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(error).toBe(originalError);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('preserves the original error when OS-level process inspection fails', async () => {
|
|
98
|
+
const originalError = new Error('Connection error: connect ECONNREFUSED 127.0.0.1:8711');
|
|
99
|
+
|
|
100
|
+
const error = await diagnoseRetryableProjectConnectionError(originalError, '/project', true, {
|
|
101
|
+
findRunningUnityProcessForProjectFn: jest.fn().mockRejectedValue(new Error('ps failed')),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(error).toBe(originalError);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Test reads the checked-in manifest through a stable relative path during Jest execution.
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
type PackageManifest = {
|
|
7
|
+
readonly bin?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function loadPackageManifest(): PackageManifest {
|
|
11
|
+
const packageJsonPath = join(__dirname, '..', '..', 'package.json');
|
|
12
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
13
|
+
const packageJsonText = readFileSync(packageJsonPath, 'utf8');
|
|
14
|
+
return JSON.parse(packageJsonText) as PackageManifest;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('package metadata', () => {
|
|
18
|
+
it('avoids bin target prefixes that npm normalizes during publish', () => {
|
|
19
|
+
const packageManifest = loadPackageManifest();
|
|
20
|
+
const binEntries = Object.entries(packageManifest.bin ?? {});
|
|
21
|
+
|
|
22
|
+
expect(binEntries.length).toBeGreaterThan(0);
|
|
23
|
+
|
|
24
|
+
for (const [, binTarget] of binEntries) {
|
|
25
|
+
expect(binTarget).not.toMatch(/^(?:\.{1,2}[\\/]|[\\/])/);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
resolvePortFromUnitySettings,
|
|
6
6
|
validateProjectPath,
|
|
7
7
|
resolveUnityPort,
|
|
8
|
-
UnityNotRunningError,
|
|
9
8
|
} from '../port-resolver.js';
|
|
10
9
|
|
|
11
10
|
describe('resolvePortFromUnitySettings', () => {
|
|
@@ -93,15 +92,14 @@ describe('resolveUnityPort with project settings', () => {
|
|
|
93
92
|
rmSync(tempProjectRoot, { recursive: true });
|
|
94
93
|
});
|
|
95
94
|
|
|
96
|
-
it('
|
|
95
|
+
it('returns port when isServerRunning is false', async () => {
|
|
97
96
|
writeFileSync(
|
|
98
97
|
join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'),
|
|
99
98
|
JSON.stringify({ isServerRunning: false, customPort: 8700 }),
|
|
100
99
|
);
|
|
101
100
|
|
|
102
|
-
await
|
|
103
|
-
|
|
104
|
-
);
|
|
101
|
+
const port = await resolveUnityPort(undefined, tempProjectRoot);
|
|
102
|
+
expect(port).toBe(8700);
|
|
105
103
|
});
|
|
106
104
|
|
|
107
105
|
it('returns port when isServerRunning is true', async () => {
|