diff --git a/cli/internal/run/real_run.go b/cli/internal/run/real_run.go index 1f8845dab4a11..b3d8eb77fb197 100644 --- a/cli/internal/run/real_run.go +++ b/cli/internal/run/real_run.go @@ -216,7 +216,13 @@ func RealRun( if isGrouped { outWriter = logBuffer.StdoutWriter() - errWriter = logBuffer.StderrWriter() + if rs.Opts.runOpts.IsGithubActions { + // If we're running on Github Actions, force everything to stdout + // so as not to have out-of-order log lines + errWriter = outWriter + } else { + errWriter = logBuffer.StderrWriter() + } } var spacesLogBuffer *threadsafeOutputBuffer @@ -280,6 +286,14 @@ func RealRun( // Assign tasks after execution runSummary.RunSummary.Tasks = taskSummaries + terminal := base.UI + if rs.Opts.runOpts.IsGithubActions { + terminal = &cli.PrefixedUi{ + Ui: terminal, + ErrorPrefix: "::error::", + WarnPrefix: "::warn::", + } + } for _, err := range errs { if errors.As(err, &exitCodeErr) { // If a process gets killed via a signal, Go reports it's exit code as -1. @@ -296,7 +310,7 @@ func RealRun( // We hit some error, it shouldn't be exit code 0 exitCode = 1 } - base.UI.Error(err.Error()) + terminal.Error(err.Error()) } // When continue on error is enabled don't register failed tasks as errors @@ -399,14 +413,14 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas Ui: ui, OutputPrefix: prettyPrefix, InfoPrefix: prettyPrefix, - ErrorPrefix: prettyPrefix, + ErrorPrefix: prettyPrefix + "ERROR: ", WarnPrefix: prettyPrefix, } if ec.rs.Opts.runOpts.IsGithubActions { ui.Output(fmt.Sprintf("::group::%s", packageTask.OutputPrefix(ec.isSinglePackage))) - prefixedUI.WarnPrefix = "::warn::" - prefixedUI.ErrorPrefix = "::error::" + prefixedUI.WarnPrefix = "[WARN] " + prefixedUI.ErrorPrefix = "[ERROR] " defer func() { // We don't use the prefixedUI here because the prefix in this case would include // the ::group::, and we explicitly want to close the github group @@ -544,17 +558,25 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas // If it wasn't a ChildExit, and something else went wrong, we don't have an exitCode tracer(runsummary.TargetBuildFailed, err, nil) } - + taskIDDisplay := packageTask.TaskID + if ec.isSinglePackage { + taskIDDisplay = packageTask.Task + } + taskErr := &TaskError{ + cause: err, + taskIDDisplay: taskIDDisplay, + } // If there was an error, flush the buffered output taskCache.OnError(prefixedUI, progressLogger) progressLogger.Error(fmt.Sprintf("Error: command finished with error: %v", err)) if !ec.rs.Opts.runOpts.ContinueOnError { - prefixedUI.Error(fmt.Sprintf("ERROR: command finished with error: %s", err)) + prefixedUI.Error(fmt.Sprintf("command finished with error: %s", err)) ec.processes.Close() // We're not continuing, stop graph traversal - err = core.StopExecution(err) + err = core.StopExecution(taskErr) } else { prefixedUI.Warn("command finished with error, but continuing...") + err = taskErr } return taskExecutionSummary, err @@ -580,3 +602,16 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas progressLogger.Debug("done", "status", "complete", "duration", taskExecutionSummary.Duration) return taskExecutionSummary, nil } + +// TaskError wraps an error encountered running the given task +type TaskError struct { + cause error + taskIDDisplay string +} + +// Unwrap allows for interoperation with standard library error wrapping +func (te *TaskError) Unwrap() error { return te.cause } + +func (te *TaskError) Error() string { + return fmt.Sprintf("%v: %v", te.taskIDDisplay, te.cause) +} diff --git a/turborepo-tests/integration/tests/ordered/github.t b/turborepo-tests/integration/tests/ordered/github.t index 96ee43cb7eafa..a2e205f179ade 100644 --- a/turborepo-tests/integration/tests/ordered/github.t +++ b/turborepo-tests/integration/tests/ordered/github.t @@ -49,9 +49,9 @@ Verify that errors are grouped properly npm ERR! Error: command failed npm ERR! in workspace: util npm ERR\! at location: (.*)/packages/util (re) - ::error::ERROR: command finished with error: command \((.*)/packages/util\) npm run fail exited \(1\) (re) + \[ERROR\] command finished with error: command \((.*)/packages/util\) npm run fail exited \(1\) (re) ::endgroup:: - command \(.*/packages/util\) npm run fail exited \(1\) (re) + ::error::util#fail: command \(.*/packages/util\) npm run fail exited \(1\) (re) Tasks: 0 successful, 1 total Cached: 0 cached, 1 total diff --git a/turborepo-tests/integration/tests/run-logging/errors-only.t b/turborepo-tests/integration/tests/run-logging/errors-only.t index 0b7e38192c5e5..b33b93aa4dd2f 100644 --- a/turborepo-tests/integration/tests/run-logging/errors-only.t +++ b/turborepo-tests/integration/tests/run-logging/errors-only.t @@ -49,7 +49,7 @@ Setup app-a:builderror: npm ERR! in workspace: app-a app-a:builderror: npm ERR! at location: .* (re) app-a:builderror: ERROR: command finished with error: command .* npm run builderror exited \(1\) (re) - command .* npm run builderror exited \(1\) (re) + app-a#builderror: command .* npm run builderror exited \(1\) (re) Tasks: 0 successful, 1 total Cached: 0 cached, 1 total @@ -79,7 +79,7 @@ Setup app-a:builderror2: npm ERR! in workspace: app-a app-a:builderror2: npm ERR! at location: .* (re) app-a:builderror2: ERROR: command finished with error: command .* npm run builderror2 exited \(1\) (re) - command .* npm run builderror2 exited \(1\) (re) + app-a#builderror2: command .* npm run builderror2 exited \(1\) (re) Tasks: 0 successful, 1 total Cached: 0 cached, 1 total diff --git a/turborepo-tests/integration/tests/run/continue.t b/turborepo-tests/integration/tests/run/continue.t index f27d049565e8e..4af9a97d8ff78 100644 --- a/turborepo-tests/integration/tests/run/continue.t +++ b/turborepo-tests/integration/tests/run/continue.t @@ -16,7 +16,7 @@ Run without --continue some-lib:build: npm ERR! in workspace: some-lib some-lib:build: npm ERR! at location: (.*)/apps/some-lib (re) some-lib:build: ERROR: command finished with error: command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) - command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) + some-lib#build: command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) Tasks: 0 successful, 1 total Cached: 0 cached, 1 total @@ -41,7 +41,7 @@ Run without --continue, and with only errors. some-lib:build: npm ERR! in workspace: some-lib some-lib:build: npm ERR! at location: (.*)/apps/some-lib (re) some-lib:build: ERROR: command finished with error: command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) - command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) + some-lib#build: command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) Tasks: 0 successful, 1 total Cached: 0 cached, 1 total @@ -76,8 +76,8 @@ Run with --continue other-app:build: npm ERR! in workspace: other-app other-app:build: npm ERR! at location: (.*)/apps/other-app (re) other-app:build: command finished with error, but continuing... - command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) - command \((.*)/apps/other-app\) npm run build exited \(1\) (re) + some-lib#build: command \((.*)/apps/some-lib\) npm run build exited \(1\) (re) + other-app#build: command \((.*)/apps/other-app\) npm run build exited \(1\) (re) Tasks: 1 successful, 3 total Cached: 0 cached, 3 total diff --git a/turborepo-tests/integration/tests/run/one-script-error.t b/turborepo-tests/integration/tests/run/one-script-error.t index 41be7a86757cb..60737526468ba 100644 --- a/turborepo-tests/integration/tests/run/one-script-error.t +++ b/turborepo-tests/integration/tests/run/one-script-error.t @@ -24,7 +24,7 @@ Note that npm reports any failed script as exit code 1, even though we "exit 2" my-app:error: npm ERR! in workspace: my-app my-app:error: npm ERR! at location: .*apps/my-app (re) my-app:error: ERROR: command finished with error: command \(.*apps/my-app\) npm run error exited \(1\) (re) - command \(.*apps/my-app\) npm run error exited \(1\) (re) + my-app#error: command \(.*apps/my-app\) npm run error exited \(1\) (re) Tasks: 1 successful, 2 total Cached: 0 cached, 2 total @@ -55,7 +55,7 @@ Make sure error isn't cached my-app:error: npm ERR! in workspace: my-app my-app:error: npm ERR! at location: .*apps/my-app (re) my-app:error: ERROR: command finished with error: command \(.*apps/my-app\) npm run error exited \(1\) (re) - command \(.*apps/my-app\) npm run error exited \(1\) (re) + my-app#error: command \(.*apps/my-app\) npm run error exited \(1\) (re) Tasks: 1 successful, 2 total Cached: 1 cached, 2 total @@ -92,7 +92,7 @@ Make sure error code isn't swallowed with continue my-app:okay2: > echo 'working' my-app:okay2: my-app:okay2: working - command \((.*)/apps/my-app\) npm run error exited \(1\) (re) + my-app#error: command \((.*)/apps/my-app\) npm run error exited \(1\) (re) Tasks: 2 successful, 3 total Cached: 1 cached, 3 total