diff --git a/cli/integration_tests/basic_monorepo/run_summary/discovery.t b/cli/integration_tests/basic_monorepo/run_summary/discovery.t new file mode 100644 index 0000000000000..79e629f03a6d0 --- /dev/null +++ b/cli/integration_tests/basic_monorepo/run_summary/discovery.t @@ -0,0 +1,21 @@ +Setup + $ . ${TESTDIR}/../../setup.sh + $ . ${TESTDIR}/../setup.sh $(pwd) + $ rm -rf .turbo/runs + + $ ${TURBO} run build --summarize=true --filter=my-app + \xe2\x80\xa2 Packages in scope: my-app (esc) + \xe2\x80\xa2 Running build in 1 packages (esc) + \xe2\x80\xa2 Remote caching disabled (esc) + my-app:build: cache miss, executing 2f192ed93e20f940 + my-app:build: + my-app:build: > build + my-app:build: > echo 'building' + my-app:build: + my-app:build: building + + Tasks: 1 successful, 1 total + Cached: 0 cached, 1 total + Time:\s*[\.0-9]+m?s (re) + Summary: .+\.turbo\/runs\/[a-zA-Z0-9]+.json (re) + \ No newline at end of file diff --git a/cli/internal/run/real_run.go b/cli/internal/run/real_run.go index 595fdadc73507..ba0c11aaac2c7 100644 --- a/cli/internal/run/real_run.go +++ b/cli/internal/run/real_run.go @@ -166,7 +166,7 @@ func RealRun( } } - runSummary.Close(exitCode, base.RepoRoot) + runSummary.Close(exitCode) if exitCode != 0 { return &process.ChildExit{ diff --git a/cli/internal/run/run.go b/cli/internal/run/run.go index 2d43446e28302..5acb3d3a94cbc 100644 --- a/cli/internal/run/run.go +++ b/cli/internal/run/run.go @@ -352,6 +352,7 @@ func (r *run) run(ctx gocontext.Context, targets []string) error { summary := runsummary.NewRunSummary( startAt, r.base.UI, + r.base.RepoRoot, rs.Opts.runOpts.singlePackage, rs.Opts.runOpts.profile, r.base.TurboVersion, diff --git a/cli/internal/runsummary/format_execution_summary.go b/cli/internal/runsummary/format_execution_summary.go index 647bddb3fd032..961cc265a49c3 100644 --- a/cli/internal/runsummary/format_execution_summary.go +++ b/cli/internal/runsummary/format_execution_summary.go @@ -37,11 +37,34 @@ func (rsm *Meta) printExecutionSummary() { ui.Warn("No tasks were executed as part of this run.") } - ui.Output("") // Clear the line + ui.Output("") // Clear the line + spacer := " " // 4 chars + + var lines []string + + // The only difference between these two branches is that when there is a run summary + // we print the path to that file and we adjust the whitespace in the printed text so it aligns. + // We could just always align to account for the summary line, but that would require a whole + // bunch of test output assertions to change. + if rsm.getPath().FileExists() { + lines = []string{ + util.Sprintf("${BOLD} Tasks:${BOLD_GREEN}%s%v successful${RESET}${GRAY}, %v total${RESET}", spacer, successful, attempted), + util.Sprintf("${BOLD} Cached:%s%v cached${RESET}${GRAY}, %v total${RESET}", spacer, cached, attempted), + util.Sprintf("${BOLD} Time:%s%v${RESET} %v${RESET}", spacer, duration, maybeFullTurbo), + util.Sprintf("${BOLD}Summary:%s%s${RESET}", spacer, rsm.getPath()), + } + } else { + lines = []string{ + util.Sprintf("${BOLD} Tasks:${BOLD_GREEN} %v successful${RESET}${GRAY}, %v total${RESET}", successful, attempted), + util.Sprintf("${BOLD}Cached: %v cached${RESET}${GRAY}, %v total${RESET}", cached, attempted), + util.Sprintf("${BOLD} Time: %v${RESET} %v${RESET}", duration, maybeFullTurbo), + } + } + + // Print the real thing + for _, line := range lines { + ui.Output(line) + } - // The whitespaces in the string are to align the UI in a nice way. - ui.Output(util.Sprintf("${BOLD} Tasks:${BOLD_GREEN} %v successful${RESET}${GRAY}, %v total${RESET}", successful, attempted)) - ui.Output(util.Sprintf("${BOLD}Cached: %v cached${RESET}${GRAY}, %v total${RESET}", cached, attempted)) - ui.Output(util.Sprintf("${BOLD} Time: %v${RESET} %v${RESET}", duration, maybeFullTurbo)) ui.Output("") } diff --git a/cli/internal/runsummary/run_summary.go b/cli/internal/runsummary/run_summary.go index 250cf6e5ada03..9c012921c7d83 100644 --- a/cli/internal/runsummary/run_summary.go +++ b/cli/internal/runsummary/run_summary.go @@ -31,6 +31,7 @@ const tasksEndpoint = "/v0/spaces/%s/runs/%s/tasks" type Meta struct { RunSummary *RunSummary ui cli.Ui + repoRoot turbopath.AbsoluteSystemPath // used to write run summary singlePackage bool shouldSave bool apiClient *client.APIClient @@ -59,6 +60,7 @@ type singlePackageRunSummary struct { func NewRunSummary( startAt time.Time, terminal cli.Ui, + repoRoot turbopath.AbsoluteSystemPath, singlePackage bool, profile string, turboVersion string, @@ -81,6 +83,7 @@ func NewRunSummary( GlobalHashSummary: globalHashSummary, }, ui: terminal, + repoRoot: repoRoot, singlePackage: singlePackage, shouldSave: shouldSave, apiClient: apiClient, @@ -88,8 +91,16 @@ func NewRunSummary( } } +// getPath returns a path to where the runSummary is written. +// The returned path will always be relative to the dir passsed in. +// We don't do a lot of validation, so `../../` paths are allowed. +func (rsm *Meta) getPath() turbopath.AbsoluteSystemPath { + filename := fmt.Sprintf("%s.json", rsm.RunSummary.ID) + return rsm.repoRoot.UntypedJoin(filepath.Join(".turbo", "runs"), filename) +} + // Close wraps up the RunSummary at the end of a `turbo run`. -func (rsm *Meta) Close(exitCode int, dir turbopath.AbsoluteSystemPath) { +func (rsm *Meta) Close(exitCode int) { rsm.RunSummary.ExecutionSummary.exitCode = exitCode rsm.RunSummary.ExecutionSummary.endedAt = time.Now() @@ -102,19 +113,24 @@ func (rsm *Meta) Close(exitCode int, dir turbopath.AbsoluteSystemPath) { // are all the same thng, we should use a strategy similar to cache save/upload to // do this in parallel. - rsm.printExecutionSummary() - + // Otherwise, attempt to save the summary + // Warn on the error, but we don't need to throw an error if rsm.shouldSave { - if err := rsm.save(dir); err != nil { + if err := rsm.save(); err != nil { rsm.ui.Warn(fmt.Sprintf("Error writing run summary: %v", err)) } + } + rsm.printExecutionSummary() + + if rsm.shouldSave { if rsm.spaceID != "" && rsm.apiClient.IsLinked() { if err := rsm.record(); err != nil { rsm.ui.Warn(fmt.Sprintf("Error recording Run to Vercel: %v", err)) } } } + } // TrackTask makes it possible for the consumer to send information about the execution of a task. @@ -123,7 +139,7 @@ func (summary *RunSummary) TrackTask(taskID string) (func(outcome executionEvent } // Save saves the run summary to a file -func (rsm *Meta) save(dir turbopath.AbsoluteSystemPath) error { +func (rsm *Meta) save() error { json, err := rsm.FormatJSON() if err != nil { return err @@ -131,10 +147,7 @@ func (rsm *Meta) save(dir turbopath.AbsoluteSystemPath) error { // summaryPath will always be relative to the dir passsed in. // We don't do a lot of validation, so `../../` paths are allowed - summaryPath := dir.UntypedJoin( - filepath.Join(".turbo", "runs"), - fmt.Sprintf("%s.json", rsm.RunSummary.ID), - ) + summaryPath := rsm.getPath() if err := summaryPath.EnsureDir(); err != nil { return err