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
102 changes: 102 additions & 0 deletions cli/common/clicore/error_aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package clicore

import (
"io"

clierrors "github.com/hatayama/unity-cli-loop/common/errors"
"github.com/hatayama/unity-cli-loop/common/unityipc"
)

const (
ErrorCodeInvalidArgument = clierrors.ErrorCodeInvalidArgument
ErrorCodeUnityNotReachable = clierrors.ErrorCodeUnityNotReachable
ErrorCodeUnityStartupTimeout = clierrors.ErrorCodeUnityStartupTimeout
ErrorCodeUnityProcessExitTimeout = clierrors.ErrorCodeUnityProcessExitTimeout
ErrorCodeCLIUpdateRequired = clierrors.ErrorCodeCLIUpdateRequired
ErrorCodeToolDisabled = clierrors.ErrorCodeToolDisabled
ErrorCodeCompileWaitTimeout = clierrors.ErrorCodeCompileWaitTimeout
ErrorCodeControlPlayModeWaitTimeout = clierrors.ErrorCodeControlPlayModeWaitTimeout
ErrorCodeControlPlayModeCompileErrors = clierrors.ErrorCodeControlPlayModeCompileErrors
ErrorCodePausePointNotEnabled = clierrors.ErrorCodePausePointNotEnabled
ErrorCodePausePointWaitTimeout = clierrors.ErrorCodePausePointWaitTimeout
ErrorCodePausePointExpired = clierrors.ErrorCodePausePointExpired
ErrorCodePausePointCleared = clierrors.ErrorCodePausePointCleared
ErrorCodeInternalError = clierrors.ErrorCodeInternalError

ErrorPhaseArgumentParsing = clierrors.ErrorPhaseArgumentParsing
ErrorPhaseProjectResolve = clierrors.ErrorPhaseProjectResolve
ErrorPhaseDispatch = clierrors.ErrorPhaseDispatch
ErrorPhaseConnection = clierrors.ErrorPhaseConnection
ErrorPhaseResponseWaiting = clierrors.ErrorPhaseResponseWaiting
ErrorPhaseCompileWaiting = clierrors.ErrorPhaseCompileWaiting
ErrorPhaseExecution = clierrors.ErrorPhaseExecution
)

type (
CLIError = clierrors.CLIError
CLIErrorEnvelope = clierrors.CLIErrorEnvelope
ErrorContext = clierrors.ErrorContext
ArgumentError = clierrors.ArgumentError
UnityServerNotRespondingError = clierrors.UnityServerNotRespondingError
)

func WriteErrorEnvelope(writer io.Writer, err CLIError) {
clierrors.WriteErrorEnvelope(writer, err)
}

func WriteClassifiedError(writer io.Writer, err error, context ErrorContext) {
clierrors.WriteClassifiedError(writer, err, context)
}

func WriteToolFailure(writer io.Writer, err error, outcome unityipc.UnitySendOutcome, context ErrorContext) {
clierrors.WriteToolFailure(writer, err, outcome, context)
}

func ClassifyError(err error, context ErrorContext) CLIError {
return clierrors.ClassifyError(err, context)
}

func MissingValueArgumentError(option string) *ArgumentError {
return clierrors.MissingValueArgumentError(option)
}

func InvalidValueArgumentError(option string, received string, expectedType string) *ArgumentError {
return clierrors.InvalidValueArgumentError(option, received, expectedType)
}

func InternalCLIError(message string, context ErrorContext) CLIError {
return clierrors.InternalCLIError(message, context)
}

func IsTransportDisconnectError(err error) bool {
return clierrors.IsTransportDisconnectError(err)
}

func IsFinalResponseTimeoutError(err error) bool {
return clierrors.IsFinalResponseTimeoutError(err)
}

func RPCDataType(data map[string]any) string {
return clierrors.RPCDataType(data)
}

func UnknownCommandError(command string, cache ToolsCache, context ErrorContext) CLIError {
return clierrors.UnknownCommandError(command, availableCommandNames(cache), context)
}

func availableCommandNames(cache ToolsCache) []string {
seen := map[string]bool{}
names := []string{}
for _, name := range NativeCommandNamesForCompletion() {
seen[name] = true
names = append(names, name)
}
for _, tool := range cache.Tools {
if seen[tool.Name] {
continue
}
seen[tool.Name] = true
names = append(names, tool.Name)
}
return names
}
14 changes: 14 additions & 0 deletions cli/common/clicore/error_aliases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package clicore

import "testing"

func TestAvailableCommandNamesIncludesBuiltIns(t *testing.T) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
// Verifies the clicore compatibility wrapper still contributes built-in commands.
names := availableCommandNames(ToolsCache{})
expectedBuiltIns := []string{"launch", "list", "sync", "focus-window", "wait-for-pause-point", "pause-point-status", "skills", "completion", "install", "update", "uninstall"}
for index, expected := range expectedBuiltIns {
if names[index] != expected {
t.Fatalf("built-in command mismatch: %#v", names)
}
}
}
34 changes: 1 addition & 33 deletions cli/common/clicore/server_not_responding.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,6 @@
package clicore

import (
"fmt"

"github.com/hatayama/unity-cli-loop/common/project"
)

// UnityServerNotRespondingError reports that a running Unity Editor accepted a
// connection but its Unity CLI Loop server did not respond in time. Both the
// dispatcher's launch-readiness wait and the runner's connection retry loop
// construct this error, and the shared CORE classifiers turn it into a CLIError.
type UnityServerNotRespondingError struct {
ProjectRoot string
Endpoint string
Cause error
}

func (err UnityServerNotRespondingError) Error() string {
if err.Cause != nil {
return fmt.Sprintf("Unity is running but the Unity CLI Loop server is not responding: %s", err.Cause)
}
return "Unity is running but the Unity CLI Loop server is not responding"
}

func (err UnityServerNotRespondingError) Unwrap() error {
return err.Cause
}

func (err UnityServerNotRespondingError) causeText() string {
if err.Cause == nil {
return ""
}
return err.Cause.Error()
}
import "github.com/hatayama/unity-cli-loop/common/project"

func resolveProjectEndpointAddress(projectRoot string) string {
connection, err := project.ResolveConnection(projectRoot, projectRoot)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import "fmt"

Expand All @@ -16,7 +16,7 @@ func (err *ArgumentError) Error() string {
}

func (err *ArgumentError) ToCLIError(context ErrorContext) CLIError {
command := FirstNonEmpty(err.Command, context.Command)
command := firstNonEmpty(err.Command, context.Command)
details := map[string]any{}
if err.Option != "" {
details["Option"] = err.Option
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"encoding/json"
Expand Down Expand Up @@ -51,7 +51,7 @@ func serverBusyStatusDetailsFromError(err CLIError) serverBusyStatusDetails {
isPaused, hasIsPaused := rpcBoolData(data, "isPaused")
return serverBusyStatusDetails{
runningToolName: rpcStringData(data, "runningToolName"),
requestedToolName: FirstNonEmpty(rpcStringData(data, "requestedToolName"), err.Command),
requestedToolName: firstNonEmpty(rpcStringData(data, "requestedToolName"), err.Command),
isPlaying: isPlaying,
hasIsPlaying: hasIsPlaying,
isPaused: isPaused,
Expand All @@ -77,7 +77,7 @@ func optionalBool(hasValue bool, value bool) *bool {

func unityServerBusyMessage(fallback string, data map[string]any, requestedCommand string) string {
runningToolName := rpcStringData(data, "runningToolName")
requestedToolName := FirstNonEmpty(rpcStringData(data, "requestedToolName"), requestedCommand)
requestedToolName := firstNonEmpty(rpcStringData(data, "requestedToolName"), requestedCommand)
if runningToolName == "" || requestedToolName == "" {
return fallback
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import "github.com/hatayama/unity-cli-loop/common/unityipc"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"encoding/json"
Expand Down Expand Up @@ -244,7 +244,7 @@ func unityServerNotRespondingAfterDispatchError(err UnityServerNotRespondingErro
Message: "Unity is running for this project, but the Unity CLI Loop server did not acknowledge the dispatched request.",
Retryable: true,
SafeToRetry: false,
ProjectRoot: FirstNonEmpty(context.ProjectRoot, err.ProjectRoot),
ProjectRoot: firstNonEmpty(context.ProjectRoot, err.ProjectRoot),
Command: context.Command,
NextActions: []string{
"Check Unity Console logs and project state because Unity may have received the request.",
Expand All @@ -258,7 +258,7 @@ func unityServerNotRespondingAfterDispatchError(err UnityServerNotRespondingErro
}
}

func UnknownCommandError(command string, cache ToolsCache, context ErrorContext) CLIError {
func UnknownCommandError(command string, availableCommands []string, context ErrorContext) CLIError {
return CLIError{
ErrorCode: errorCodeUnknownCommand,
Phase: ErrorPhaseDispatch,
Expand All @@ -272,28 +272,11 @@ func UnknownCommandError(command string, cache ToolsCache, context ErrorContext)
"Run `uloop sync` if the local tool cache may be stale.",
},
Details: map[string]any{
"AvailableCommands": availableCommandNames(cache),
"AvailableCommands": availableCommands,
},
}
}

func availableCommandNames(cache ToolsCache) []string {
seen := map[string]bool{}
names := []string{}
for _, name := range NativeCommandNamesForCompletion() {
seen[name] = true
names = append(names, name)
}
for _, tool := range cache.Tools {
if seen[tool.Name] {
continue
}
seen[tool.Name] = true
names = append(names, tool.Name)
}
return names
}

func isSafeRetryCommand(command string) bool {
switch command {
case "list", "sync", "get-version", "get-logs", "get-tool-details":
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"encoding/json"
Expand Down Expand Up @@ -70,7 +70,7 @@ func unityServerNotRespondingCLIError(err UnityServerNotRespondingError, context
Message: "Unity is running for this project, but the Unity CLI Loop server is not responding.",
Retryable: true,
SafeToRetry: true,
ProjectRoot: FirstNonEmpty(context.ProjectRoot, err.ProjectRoot),
ProjectRoot: firstNonEmpty(context.ProjectRoot, err.ProjectRoot),
Command: context.Command,
NextActions: []string{
"Wait and retry; Unity may be starting, importing assets, compiling, or reloading scripts.",
Expand All @@ -91,7 +91,7 @@ func connectionAttemptCLIError(err *unityipc.ConnectionAttemptError, context Err
Message: "The Unity CLI Loop server is not reachable for this project.",
Retryable: true,
SafeToRetry: true,
ProjectRoot: FirstNonEmpty(context.ProjectRoot, err.ProjectRoot),
ProjectRoot: firstNonEmpty(context.ProjectRoot, err.ProjectRoot),
Command: context.Command,
NextActions: []string{
"If Unity is closed, run `uloop launch`.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"bytes"
Expand Down Expand Up @@ -417,7 +417,7 @@ func TestWriteToolFailureClassifiesAcceptedResponseTimeout(t *testing.T) {
func TestUnknownCommandErrorIncludesAvailableCommands(t *testing.T) {
cliErr := UnknownCommandError(
"missing",
ToolsCache{Tools: []ToolDefinition{{Name: "compile"}}},
[]string{"launch", "compile"},
ErrorContext{ProjectRoot: "/tmp/MyProject"},
)

Expand Down Expand Up @@ -470,16 +470,6 @@ func TestClassifyConnectionAttemptUsesContextProjectRootFallback(t *testing.T) {
}
}

func TestAvailableCommandNamesIncludesBuiltIns(t *testing.T) {
names := availableCommandNames(ToolsCache{})
expectedBuiltIns := []string{"launch", "list", "sync", "focus-window", "wait-for-pause-point", "pause-point-status", "skills", "completion", "install", "update"}
for index, expected := range expectedBuiltIns {
if names[index] != expected {
t.Fatalf("built-in command mismatch: %#v", names)
}
}
}

func TestSafeRetryCommand(t *testing.T) {
if !isSafeRetryCommand("get-logs") {
t.Fatal("get-logs should be safe to retry")
Expand Down
33 changes: 33 additions & 0 deletions cli/common/errors/server_not_responding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package clierrors

import (
"fmt"
)

// UnityServerNotRespondingError reports that a running Unity Editor accepted a
// connection but its Unity CLI Loop server did not respond in time. Both the
// dispatcher's launch-readiness wait and the runner's connection retry loop
// construct this error, and the shared CORE classifiers turn it into a CLIError.
type UnityServerNotRespondingError struct {
ProjectRoot string
Endpoint string
Cause error
}

func (err UnityServerNotRespondingError) Error() string {
if err.Cause != nil {
return fmt.Sprintf("Unity is running but the Unity CLI Loop server is not responding: %s", err.Cause)
}
return "Unity is running but the Unity CLI Loop server is not responding"
}

func (err UnityServerNotRespondingError) Unwrap() error {
return err.Cause
}

func (err UnityServerNotRespondingError) causeText() string {
if err.Cause == nil {
return ""
}
return err.Cause.Error()
}
10 changes: 10 additions & 0 deletions cli/common/errors/string_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package clierrors

func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"errors"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package clicore
package clierrors

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion cli/dispatcher/shared-inputs-stamp.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"schemaVersion": 1,
"sharedInputsHash": "b58452f9c0f12c73da40ccdda7b87ee77e64b99a"
"sharedInputsHash": "4ffd3b6d8156471cab11096b0f52f3696937bdba"
}
2 changes: 1 addition & 1 deletion cli/project-runner/shared-inputs-stamp.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"schemaVersion": 1,
"sharedInputsHash": "bdaecfb852b88de8634ae849eddd5e1b0c0f064e"
"sharedInputsHash": "552a3bbd8757b2357b7fca535453fdf2e416577c"
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ var sharedCommonPackageRoots = []string{
// intentionally excluded because they never ship in release binaries.
"cli/common/clicontract/",
"cli/common/clicore/",
"cli/common/errors/",
"cli/common/project/",
"cli/common/skills/",
"cli/common/tools/",
Expand Down
1 change: 1 addition & 0 deletions scripts/stamp-release-inputs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ list_shared_common_inputs() {
cli/common/go.sum \
'cli/common/clicontract/' \
'cli/common/clicore/' \
'cli/common/errors/' \
'cli/common/project/' \
'cli/common/skills/' \
'cli/common/tools/' \
Expand Down
Loading