Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,13 @@ unity-cli console --clear

가장 강력한 명령어입니다. Unity Editor 런타임에서 임의의 C# 코드를 실행합니다. UnityEngine, UnityEditor, ECS 및 로드된 모든 어셈블리에 접근 가능합니다. 일회성 조회나 수정을 위해 커스텀 도구를 만들 필요가 없습니다.

`return`으로 결과를 받습니다. 주요 namespace는 기본 포함. 프로젝트 전용 타입(예: `Unity.Entities`)만 `--usings`로 추가합니다.
`return`으로 결과를 받습니다. 주요 namespace는 기본 포함. 프로젝트 전용 타입(예: `Unity.Entities`)만 `--usings`로 추가합니다. `--usings`는 콤마로 여러 namespace를 받을 수 있고 반복 지정도 가능합니다.

```bash
unity-cli exec "return Application.dataPath;"
unity-cli exec "return EditorSceneManager.GetActiveScene().name;"
unity-cli exec "return World.All.Count;" --usings Unity.Entities
unity-cli exec "return World.All.Count;" --usings Unity.Entities --usings Unity.Mathematics

# stdin으로 파이프하면 shell escaping 문제 없음
echo 'Debug.Log("hello"); return null;' | unity-cli exec
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,13 @@ unity-cli console --clear

Run arbitrary C# code inside the Unity Editor at runtime. This is the most powerful command — it gives you full access to UnityEngine, UnityEditor, ECS, and every loaded assembly. No need to write a custom tool for one-off queries or mutations.

Use `return` to get output. Common namespaces are included by default. Add `--usings` only for project-specific types (e.g. `Unity.Entities`). The csc compiler and dotnet runtime are auto-detected; if detection fails, specify manually with `--csc <path>` or `--dotnet <path>`.
Use `return` to get output. Common namespaces are included by default. Add `--usings` only for project-specific types (e.g. `Unity.Entities`). `--usings` accepts comma-separated namespaces and can be repeated. The csc compiler and dotnet runtime are auto-detected; if detection fails, specify manually with `--csc <path>` or `--dotnet <path>`.

```bash
unity-cli exec "return Application.dataPath;"
unity-cli exec "return EditorSceneManager.GetActiveScene().name;"
unity-cli exec "return World.All.Count;" --usings Unity.Entities
unity-cli exec "return World.All.Count;" --usings Unity.Entities --usings Unity.Mathematics

# Pipe via stdin to avoid shell escaping issues
echo 'Debug.Log("hello"); return null;' | unity-cli exec
Expand Down
40 changes: 32 additions & 8 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,34 +333,41 @@ func buildParams(args []string, base map[string]interface{}) (map[string]interfa
}

var positional []string
flags := map[string]string{}
flags := map[string][]string{}
for i := 0; i < len(args); i++ {
a := args[i]
if strings.HasPrefix(a, "--") {
key := a[2:]
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
flags[key] = args[i+1]
flags[key] = append(flags[key], args[i+1])
i++
} else {
flags[key] = "true"
flags[key] = append(flags[key], "true")
}
} else {
positional = append(positional, a)
}
}

if raw, ok := flags["params"]; ok {
if values, ok := flags["params"]; ok && len(values) > 0 {
raw := values[len(values)-1]
if jsonErr := json.Unmarshal([]byte(raw), &params); jsonErr != nil {
return nil, fmt.Errorf("invalid JSON in --params: %w", jsonErr)
}
}
for k, v := range flags {
for k, values := range flags {
if k == "params" {
continue
}
if _, exists := params[k]; exists {
continue
}
if k == "usings" {
params[k] = normalizeUsings(values)
continue
}

v := values[len(values)-1]
if n, err := strconv.Atoi(v); err == nil {
params[k] = n
} else if v == "true" {
Expand All @@ -379,6 +386,22 @@ func buildParams(args []string, base map[string]interface{}) (map[string]interfa
return params, nil
}

func normalizeUsings(values []string) []string {
var result []string
seen := map[string]bool{}
for _, value := range values {
for _, item := range strings.Split(value, ",") {
using := strings.TrimSpace(item)
if using == "" || seen[using] {
continue
}
seen[using] = true
result = append(result, using)
}
}
return result
}

func rejectRemovedFlags(args []string) error {
for _, arg := range args {
if arg == "--port" || strings.HasPrefix(arg, "--port=") {
Expand Down Expand Up @@ -450,7 +473,7 @@ Console:
Execute C#:
exec "<code>" Run C# code in Unity (return required for output)
echo '<code>' | exec Pipe code via stdin (avoids shell escaping)
exec "<code>" --usings x,y Add extra using directives
exec "<code>" --usings x,y Add extra using directives (repeatable)

Examples:
exec "Time.time"
Expand Down Expand Up @@ -573,7 +596,7 @@ UnityEditor, and all loaded assemblies.
Use 'return' to get output. Add --usings for types outside default namespaces.

Options:
--usings <ns1,ns2> Add extra using directives
--usings <ns1,ns2> Add extra using directives (comma-separated or repeatable)
--csc <path> Path to csc compiler (csc.dll or csc.exe). Auto-detected if omitted.
--dotnet <path> Path to dotnet runtime. Auto-detected if omitted.

Expand All @@ -588,10 +611,11 @@ Examples:
echo 'return EditorSceneManager.GetActiveScene().name;' | unity-cli exec
echo 'Debug.Log("hello"); return null;' | unity-cli exec
unity-cli exec "return World.All.Count;" --usings Unity.Entities
unity-cli exec "return World.All.Count;" --usings Unity.Entities --usings Unity.Mathematics

Stdin:
Pipe code via stdin to avoid shell escaping issues.
echo '<code>' | unity-cli exec [--usings ns1,ns2]
echo '<code>' | unity-cli exec [--usings ns1,ns2] [--usings ns3]

Notes:
- Use 'return' for output, 'return null;' for void operations
Expand Down
25 changes: 25 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,31 @@ func TestBuildParams_StringParsing(t *testing.T) {
}
}

func TestBuildParams_UsingsParsing(t *testing.T) {
p, err := buildParams([]string{
"--usings", "UnityEditor.Build.Profile, Accelix.Editor.Tools.BuildManager",
"--usings", "Unity.Entities",
"--usings", "Unity.Entities",
}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

got, ok := p["usings"].([]string)
if !ok {
t.Fatalf("expected usings []string, got %T: %v", p["usings"], p["usings"])
}

want := []string{
"UnityEditor.Build.Profile",
"Accelix.Editor.Tools.BuildManager",
"Unity.Entities",
}
if !sliceEqual(got, want) {
t.Errorf("expected usings=%v, got %v", want, got)
}
}

func TestBuildParams_BaseParams(t *testing.T) {
p, err := buildParams([]string{"--depth", "5"}, map[string]interface{}{"action": "hierarchy"})
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,13 @@ func DiscoverInstance(project string) (*Instance, error) {
}

func normalizeProjectPath(path string) string {
normalized := strings.TrimRight(filepath.ToSlash(path), "/")
normalized := filepath.Clean(path)
if filepath.IsAbs(normalized) {
if resolved, err := filepath.EvalSymlinks(normalized); err == nil {
normalized = resolved
}
}
normalized = strings.TrimRight(filepath.ToSlash(normalized), "/")
if runtime.GOOS == "windows" {
normalized = strings.ToLower(normalized)
}
Expand Down
2 changes: 1 addition & 1 deletion unity-connector/Editor/Tools/ExecuteCsharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class Parameters
[ToolParameter("C# code to execute. Use 'return' for output.", Required = true)]
public string Code { get; set; }

[ToolParameter("Additional using directives (comma-separated, e.g. Unity.Entities,Unity.Mathematics)")]
[ToolParameter("Additional using directives (comma-separated or repeated CLI --usings, e.g. Unity.Entities,Unity.Mathematics)")]
public string[] Usings { get; set; }

[ToolParameter("Path to csc compiler (csc.dll or csc.exe). Auto-detected if omitted.")]
Expand Down