uloop-cli 0.56.0 → 0.57.0
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 +680 -16
- package/dist/cli.bundle.cjs.map +4 -4
- package/package.json +7 -6
- package/src/__tests__/cli-e2e.test.ts +40 -0
- package/src/cli.ts +13 -1
- package/src/commands/launch.ts +125 -0
- package/src/default-tools.json +1 -1
- package/src/skills/skill-definitions/cli-only/uloop-launch/Skill/SKILL.md +51 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uloop-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
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",
|
|
@@ -41,23 +41,24 @@
|
|
|
41
41
|
"provenance": true
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"commander": "
|
|
45
|
-
"
|
|
44
|
+
"commander": "14.0.2",
|
|
45
|
+
"launch-unity": "0.12.0",
|
|
46
|
+
"semver": "7.7.3"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@eslint/js": "9.39.2",
|
|
49
|
-
"tsx": "4.21.0",
|
|
50
50
|
"@types/jest": "30.0.0",
|
|
51
51
|
"@types/node": "25.0.2",
|
|
52
|
-
"@types/semver": "
|
|
52
|
+
"@types/semver": "7.7.1",
|
|
53
53
|
"esbuild": "0.27.1",
|
|
54
54
|
"eslint": "9.39.2",
|
|
55
|
-
"eslint-config-prettier": "
|
|
55
|
+
"eslint-config-prettier": "10.1.8",
|
|
56
56
|
"eslint-plugin-prettier": "5.5.4",
|
|
57
57
|
"eslint-plugin-security": "3.0.1",
|
|
58
58
|
"jest": "30.2.0",
|
|
59
59
|
"prettier": "3.7.4",
|
|
60
60
|
"ts-jest": "29.4.6",
|
|
61
|
+
"tsx": "4.21.0",
|
|
61
62
|
"typescript": "5.9.3",
|
|
62
63
|
"typescript-eslint": "8.49.0"
|
|
63
64
|
}
|
|
@@ -482,6 +482,46 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
482
482
|
});
|
|
483
483
|
});
|
|
484
484
|
|
|
485
|
+
describe('launch', () => {
|
|
486
|
+
it('should display launch command help', () => {
|
|
487
|
+
const { stdout, exitCode } = runCli('launch --help');
|
|
488
|
+
|
|
489
|
+
expect(exitCode).toBe(0);
|
|
490
|
+
expect(stdout).toContain('Launch Unity project');
|
|
491
|
+
expect(stdout).toContain('--restart');
|
|
492
|
+
expect(stdout).toContain('--platform');
|
|
493
|
+
expect(stdout).toContain('--max-depth');
|
|
494
|
+
expect(stdout).toContain('--add-unity-hub');
|
|
495
|
+
expect(stdout).toContain('--favorite');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should detect already running Unity and focus window', () => {
|
|
499
|
+
// Unity is already running for this test suite, so launch should detect it
|
|
500
|
+
const { stdout, exitCode } = runCli(`launch "${UNITY_PROJECT_ROOT}"`);
|
|
501
|
+
|
|
502
|
+
expect(exitCode).toBe(0);
|
|
503
|
+
expect(stdout).toContain('Unity process already running');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should fail gracefully when project not found', () => {
|
|
507
|
+
const { stdout, stderr, exitCode } = runCli('launch /nonexistent/path/to/project');
|
|
508
|
+
|
|
509
|
+
expect(exitCode).not.toBe(0);
|
|
510
|
+
// Error message should mention project not found or version file not found
|
|
511
|
+
const output = stderr || stdout;
|
|
512
|
+
expect(output).toMatch(/not found|does not appear to be a Unity project/i);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should search for Unity project from current directory', () => {
|
|
516
|
+
// This test runs from Unity project root, so it should find the project
|
|
517
|
+
const { stdout, exitCode } = runCli('launch');
|
|
518
|
+
|
|
519
|
+
expect(exitCode).toBe(0);
|
|
520
|
+
// Should either find and focus existing Unity or report no Unity found
|
|
521
|
+
expect(stdout).toMatch(/Unity process already running|Selected project/);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
485
525
|
// Domain Reload tests must run last to avoid affecting other tests
|
|
486
526
|
describe('compile --force-recompile (Domain Reload)', () => {
|
|
487
527
|
it('should support --force-recompile option', () => {
|
package/src/cli.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from './tool-cache.js';
|
|
30
30
|
import { pascalToKebabCase } from './arg-parser.js';
|
|
31
31
|
import { registerSkillsCommand } from './skills/skills-command.js';
|
|
32
|
+
import { registerLaunchCommand } from './commands/launch.js';
|
|
32
33
|
import { VERSION } from './version.js';
|
|
33
34
|
import { findUnityProjectRoot } from './project-root.js';
|
|
34
35
|
|
|
@@ -36,7 +37,15 @@ interface CliOptions extends GlobalOptions {
|
|
|
36
37
|
[key: string]: unknown;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const BUILTIN_COMMANDS = [
|
|
40
|
+
const BUILTIN_COMMANDS = [
|
|
41
|
+
'list',
|
|
42
|
+
'sync',
|
|
43
|
+
'completion',
|
|
44
|
+
'update',
|
|
45
|
+
'fix',
|
|
46
|
+
'skills',
|
|
47
|
+
'launch',
|
|
48
|
+
] as const;
|
|
40
49
|
|
|
41
50
|
const program = new Command();
|
|
42
51
|
|
|
@@ -94,6 +103,9 @@ program
|
|
|
94
103
|
// Register skills subcommand
|
|
95
104
|
registerSkillsCommand(program);
|
|
96
105
|
|
|
106
|
+
// Register launch subcommand
|
|
107
|
+
registerLaunchCommand(program);
|
|
108
|
+
|
|
97
109
|
/**
|
|
98
110
|
* Register a tool as a CLI command dynamically.
|
|
99
111
|
*/
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command for launching Unity projects.
|
|
3
|
+
* Integrates launch-unity library into uloop CLI.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// CLI commands output to console by design
|
|
7
|
+
/* eslint-disable no-console */
|
|
8
|
+
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
findUnityProjectBfs,
|
|
14
|
+
getUnityVersion,
|
|
15
|
+
launch,
|
|
16
|
+
findRunningUnityProcess,
|
|
17
|
+
focusUnityProcess,
|
|
18
|
+
killRunningUnity,
|
|
19
|
+
handleStaleLockfile,
|
|
20
|
+
ensureProjectEntryAndUpdate,
|
|
21
|
+
updateLastModifiedIfExists,
|
|
22
|
+
LaunchResolvedOptions,
|
|
23
|
+
} from 'launch-unity';
|
|
24
|
+
|
|
25
|
+
interface LaunchCommandOptions {
|
|
26
|
+
restart?: boolean;
|
|
27
|
+
platform?: string;
|
|
28
|
+
maxDepth?: string;
|
|
29
|
+
addUnityHub?: boolean;
|
|
30
|
+
favorite?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function registerLaunchCommand(program: Command): void {
|
|
34
|
+
program
|
|
35
|
+
.command('launch')
|
|
36
|
+
.description('Launch Unity project with matching Editor version')
|
|
37
|
+
.argument('[project-path]', 'Path to Unity project')
|
|
38
|
+
.option('-r, --restart', 'Kill running Unity and restart')
|
|
39
|
+
.option('-p, --platform <platform>', 'Build target (e.g., Android, iOS)')
|
|
40
|
+
.option('--max-depth <n>', 'Search depth when project-path is omitted', '3')
|
|
41
|
+
.option('-a, --add-unity-hub', 'Add to Unity Hub (does not launch)')
|
|
42
|
+
.option('-f, --favorite', 'Add to Unity Hub as favorite (does not launch)')
|
|
43
|
+
.action(async (projectPath: string | undefined, options: LaunchCommandOptions) => {
|
|
44
|
+
await runLaunchCommand(projectPath, options);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseMaxDepth(value: string | undefined): number {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return 3;
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseInt(value, 10);
|
|
53
|
+
if (Number.isNaN(parsed)) {
|
|
54
|
+
console.error(`Error: Invalid --max-depth value: "${value}". Must be an integer.`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runLaunchCommand(
|
|
61
|
+
projectPath: string | undefined,
|
|
62
|
+
options: LaunchCommandOptions,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const maxDepth = parseMaxDepth(options.maxDepth);
|
|
65
|
+
|
|
66
|
+
let resolvedProjectPath: string | undefined = projectPath ? resolve(projectPath) : undefined;
|
|
67
|
+
|
|
68
|
+
if (!resolvedProjectPath) {
|
|
69
|
+
const searchRoot = process.cwd();
|
|
70
|
+
const depthInfo = maxDepth === -1 ? 'unlimited' : String(maxDepth);
|
|
71
|
+
console.log(
|
|
72
|
+
`No project-path provided. Searching under ${searchRoot} (max-depth: ${depthInfo})...`,
|
|
73
|
+
);
|
|
74
|
+
const found = findUnityProjectBfs(searchRoot, maxDepth);
|
|
75
|
+
if (!found) {
|
|
76
|
+
console.error(`Error: Unity project not found under ${searchRoot}.`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.log(`Selected project: ${found}`);
|
|
80
|
+
resolvedProjectPath = found;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const unityVersion = getUnityVersion(resolvedProjectPath);
|
|
84
|
+
|
|
85
|
+
const unityHubOnlyMode = options.addUnityHub === true || options.favorite === true;
|
|
86
|
+
if (unityHubOnlyMode) {
|
|
87
|
+
console.log(`Detected Unity version: ${unityVersion}`);
|
|
88
|
+
console.log(`Project Path: ${resolvedProjectPath}`);
|
|
89
|
+
const now = new Date();
|
|
90
|
+
await ensureProjectEntryAndUpdate(
|
|
91
|
+
resolvedProjectPath,
|
|
92
|
+
unityVersion,
|
|
93
|
+
now,
|
|
94
|
+
options.favorite === true,
|
|
95
|
+
);
|
|
96
|
+
console.log('Unity Hub entry updated.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.restart === true) {
|
|
101
|
+
await killRunningUnity(resolvedProjectPath);
|
|
102
|
+
} else {
|
|
103
|
+
const runningProcess = await findRunningUnityProcess(resolvedProjectPath);
|
|
104
|
+
if (runningProcess) {
|
|
105
|
+
console.log(
|
|
106
|
+
`Unity process already running for project: ${resolvedProjectPath} (PID: ${runningProcess.pid})`,
|
|
107
|
+
);
|
|
108
|
+
await focusUnityProcess(runningProcess.pid);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await handleStaleLockfile(resolvedProjectPath);
|
|
114
|
+
|
|
115
|
+
const resolved: LaunchResolvedOptions = {
|
|
116
|
+
projectPath: resolvedProjectPath,
|
|
117
|
+
platform: options.platform,
|
|
118
|
+
unityArgs: [],
|
|
119
|
+
unityVersion,
|
|
120
|
+
};
|
|
121
|
+
launch(resolved);
|
|
122
|
+
|
|
123
|
+
const now = new Date();
|
|
124
|
+
await updateLastModifiedIfExists(resolvedProjectPath, now);
|
|
125
|
+
}
|
package/src/default-tools.json
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uloop-launch
|
|
3
|
+
description: "Launch Unity project with matching Editor version via uloop CLI. Use when you need to: (1) Open a Unity project with the correct Editor version, (2) Restart Unity to apply changes, (3) Switch build target when launching."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uloop launch
|
|
7
|
+
|
|
8
|
+
Launch Unity Editor with the correct version for a project.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uloop launch [project-path] [options]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Parameters
|
|
17
|
+
|
|
18
|
+
| Parameter | Type | Description |
|
|
19
|
+
|-----------|------|-------------|
|
|
20
|
+
| `project-path` | string | Path to Unity project (optional, searches current directory if omitted) |
|
|
21
|
+
| `-r, --restart` | boolean | Kill running Unity and restart |
|
|
22
|
+
| `-p, --platform <P>` | string | Build target (e.g., StandaloneOSX, Android, iOS) |
|
|
23
|
+
| `--max-depth <N>` | number | Search depth when project-path is omitted (default: 3, -1 for unlimited) |
|
|
24
|
+
| `-a, --add-unity-hub` | boolean | Add to Unity Hub only (does not launch) |
|
|
25
|
+
| `-f, --favorite` | boolean | Add to Unity Hub as favorite (does not launch) |
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Search for Unity project in current directory and launch
|
|
31
|
+
uloop launch
|
|
32
|
+
|
|
33
|
+
# Launch specific project
|
|
34
|
+
uloop launch /path/to/project
|
|
35
|
+
|
|
36
|
+
# Restart Unity (kill existing and relaunch)
|
|
37
|
+
uloop launch -r
|
|
38
|
+
|
|
39
|
+
# Launch with build target
|
|
40
|
+
uloop launch -p Android
|
|
41
|
+
|
|
42
|
+
# Add project to Unity Hub without launching
|
|
43
|
+
uloop launch -a
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Output
|
|
47
|
+
|
|
48
|
+
- Prints detected Unity version
|
|
49
|
+
- Prints project path
|
|
50
|
+
- If Unity is already running, focuses the existing window
|
|
51
|
+
- If launching, opens Unity in background
|
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.
|
|
7
|
+
export const VERSION = '0.57.0'; // x-release-please-version
|