You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sub-issue of #213. Sibling of #211 (list). Hardened spec — do not re-derive decisions. Mirrors internal/cmd/pr/view/view.go and uses Go text templates via internal/template.Template (the same engine used by azdo pr view).
Command Description
Display the details of a single Azure Pipelines build by integer ID. The command fetches the matching Build via the Builds REST 7.1 endpoint and renders it as a Go text template. The build's SourceVersion (commit SHA), Definition, Queue, and RequestedBy are rendered with their respective child types when present.
GET https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}?api-version=7.1
Locked Decisions (do not re-derive)
#
Decision
Rationale
1
Use the vendored SDK build.Client.GetBuild (not raw HTTP). Mock already generated at internal/mocks/build_client_mock.go:387-402.
Consistent with the list sibling (which uses GetBuilds); the SDK is what the entire build subgroup uses.
2
The build is identified by an integer positional ID (e.g. azdo pipelines build show Fabrikam 12345). The --id flag is not offered.
Mirrors az pipelines build show ergonomics. The ID is a single integer — clean as a positional.
3
ID is required. Non-numeric or zero / negative values rejected with util.FlagErrorf before the SDK call.
Cheap pre-flight validation; REST 4xx is unhelpful.
4
Use the Go text template engine from internal/template/template.go with an //go:embed show.tpl file. Mirror internal/cmd/pr/view/view.go and view.tpl structure: bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender.
The user explicitly requested template rendering like pr/view.
5
Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]PROJECT BUILD_ID", cmd.Aliases: []string{"view", "status"}.
Mirrors the pr/view aliasing pattern but with show as the primary.
6
JSON output passes the raw SDK *build.Build to opts.exporter.Write. No view struct.
Update internal/cmd/pipelines/build/build.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/build/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
Phase 1 — RED (tests first). Mirror setupFakeDeps from internal/cmd/boards/workitem/list/list_test.go:765-844. Add show_test.go with the following table-driven / behaviour tests, all using t.Parallel() and gomock (require for preconditions, assert for verifications):
TestNewCmd_RegistersAsShowLeaf — asserts cmd.Name() == "show", cmd.Aliases contains view and status, cmd.Use starts with show [ORGANIZATION/]PROJECT BUILD_ID.
TestNewCmd_RequiresTwoArgs — runs with []string{"Fabrikam"}; asserts cobra.ExactArgs(2) triggers "project and build id required".
TestRunShow_ClientFactoryError — stubs factory to return error; asserts wrapped error.
TestRunShow_SDKError — stubs SDK to return error; asserts wrapped error.
TestRunShow_OrganizationFromConfigDefault — when scopeArg is Fabrikam (no org), asserts clientFact.Build(ctx, defaultOrg) is called with the configured default.
Phase 2 — GREEN (minimal implementation). Strict reuse rules:
No new helpers beyond the inline parseBuildID(raw string) (int, error) (~5 lines) for the integer-parse + positive check, and formatDuration(start, finish *time.Time) string (~10 lines) for the human-readable duration.
Output split: JSON via opts.exporter.Write(ios, res) passing the raw SDK *Build; template via template.New(...).ExecuteData(templateData{Build: res}).
Debug log at the point of the SDK call: organization, project, buildId.
Target delta: show.go ≤ ~120 LOC, show.tpl ≤ ~50 LOC, show_test.go ≤ ~400 LOC (22 tests), parent build.go +3 LOC, docs/pipelines_build_show.md regenerated via make docs. No changes to list.go or other build siblings.
Tooling and Verification Checklist
Run gofmt / gofumpt on touched files
go test ./internal/cmd/pipelines/build/...
go test ./...
make lint
make docs
Reference Existing Patterns
internal/cmd/pr/view/view.go — primary template-engine reference (Primary: view.go:483-549; viewOptions struct at view.go:23-31; template embed at view.go:33-34).
internal/cmd/pr/view/view.tpl — primary template-file reference (45 lines; same field-bullet style).
Sub-issue of #213. Sibling of #211 (
list). Hardened spec — do not re-derive decisions. Mirrorsinternal/cmd/pr/view/view.goand uses Go text templates viainternal/template.Template(the same engine used byazdo pr view).Command Description
Display the details of a single Azure Pipelines build by integer ID. The command fetches the matching
Buildvia the Builds REST 7.1 endpoint and renders it as a Go text template. The build'sSourceVersion(commit SHA),Definition,Queue, andRequestedByare rendered with their respective child types when present.Locked Decisions (do not re-derive)
build.Client.GetBuild(not raw HTTP). Mock already generated atinternal/mocks/build_client_mock.go:387-402.listsibling (which usesGetBuilds); the SDK is what the entirebuildsubgroup uses.azdo pipelines build show Fabrikam 12345). The--idflag is not offered.az pipelines build showergonomics. The ID is a single integer — clean as a positional.util.FlagErrorfbefore the SDK call.internal/template/template.gowith an//go:embed show.tplfile. Mirrorinternal/cmd/pr/view/view.goandview.tplstructure:bold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.pr/view.view,status. Primary name isshow.cmd.Use: "show [ORGANIZATION/]PROJECT BUILD_ID",cmd.Aliases: []string{"view", "status"}.pr/viewaliasing pattern but withshowas the primary.*build.Buildtoopts.exporter.Write. No view struct.--rawflag dumps the full SDK build withspew.Dumpto stderr for debugging.pr/view --raw.internal/cmd/pipelines/build/show. Reuse SDK call from the vendoredbuildpackage.GetBuildis already generated atinternal/mocks/build_client_mock.go:387-402. Do not regenerate.Command Signature
view,statuscobra.ExactArgs(2)—util.ExactArgs(2, "project and build id required").args[0]→ project scope (viautil.ParseProjectScope);args[1]→ integer BUILD_ID (parsed inRunE).BUILD_IDis the second positional soazdo pipelines build show 12345is rejected.Flags
--raw(bool)spew.Dumpof full SDK build to stderr--json/--jq/--templateutil.AddJSONFlagsJSON Output Contract
Pass the raw
*build.Buildreturned by the SDK (Decision 6).Template Output Contract (
show.tpl)The default template renders (see
internal/cmd/pr/view/view.tplfor the established pattern):Command Wiring
internal/cmd/pipelines/build/showshow.go—NewCmd(ctx util.CmdContext) *cobra.Command+showOptions+runShowshow.tpl— Go text templateshow_test.go— table-driven gomock testsinternal/cmd/pipelines/build/build.goto addshowcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/build/show"andcmd.AddCommand(showcmd.NewCmd(ctx)). Update theExampleblock.pipelines→build→show.API Surface
Reuse the already-vendored client. No new SDK clients required.
build.Client.GetBuild→ Builds - Get (REST 7.1)build.GetBuildArgsstruct:{Project *string, BuildId *int}.internal/template.Templatewithbold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.Mock for
GetBuildis already generated atinternal/mocks/build_client_mock.go:387-402. No mock regeneration needed.Implementation Approach (TDD, reuse-first, minimal)
Phase 1 — RED (tests first). Mirror
setupFakeDepsfrominternal/cmd/boards/workitem/list/list_test.go:765-844. Addshow_test.gowith the following table-driven / behaviour tests, all usingt.Parallel()andgomock(requirefor preconditions,assertfor verifications):TestNewCmd_RegistersAsShowLeaf— assertscmd.Name() == "show",cmd.Aliasescontainsviewandstatus,cmd.Usestarts withshow [ORGANIZATION/]PROJECT BUILD_ID.TestNewCmd_RequiresTwoArgs— runs with[]string{"Fabrikam"}; assertscobra.ExactArgs(2)triggers "project and build id required".TestRunShow_BuildIDMustBeInteger— sets BUILD_ID"abc"; assertsutil.FlagErrorfreturned.TestRunShow_BuildIDMustBePositive— sets BUILD_ID0and-1; assertsutil.FlagErrorfreturned.TestRunShow_BasicCall— sets[Fabrikam, 12345]; stubsbuild.EXPECT().GetBuild(gomock.Any(), gomock.Any()); asserts*args.BuildId == 12345,*args.Project == "Fabrikam".TestRunShow_TemplateOutput_BasicFields— mocks return*Build{Id, BuildNumber, Status, Result, SourceBranch, SourceVersion, Url}; asserts rendered output contains all field labels and values.TestRunShow_TemplateOutput_Hyperlink— asserts theurl:line uses ANSI hyperlink escape sequence.TestRunShow_TemplateOutput_DurationFormatted— mocks returnStartTime: 2024-01-01T12:00:00Z,FinishTime: 2024-01-01T12:02:13Z; assertsduration: 2m 13srendered.TestRunShow_TemplateOutput_NoDuration_NotStarted— mocks returnFinishTime: nil; assertsduration:line is omitted.TestRunShow_TemplateOutput_Tags— mocks returnTags: []string{"release", "nightly"}; asserts both tags rendered.TestRunShow_TemplateOutput_NoTags— mocks return no tags; assertstags:line is omitted.TestRunShow_TemplateOutput_DefinitionAndQueue_Nested— mocks returnDefinition: &BuildDefinition{Name, Id}andQueue: &Pool{Name, Id}; asserts both rendered.TestRunShow_TemplateOutput_ResultOnlyWhenCompleted— mocks returnStatus: "inProgress",Result: ""; assertsresult:line is omitted.TestRunShow_TemplateOutput_ResultShownWhenCompleted— mocks returnStatus: "completed",Result: "succeeded"; assertsresult: succeededrendered.TestRunShow_JSONOutput— sets--json; mocks return build; asserts JSON containsid,buildNumber,status,result,url,sourceBranch,sourceVersion,definition,queue,requestedBy,tags.TestRunShow_RawFlag— sets--raw; assertsspew.Dumpwas invoked.TestRunShow_ProjectScopeParsing— table-driven:myorg/Fabrikam,Fabrikam, invalid ("org/proj/extra"), empty.TestRunShow_InvalidProjectScope— assertsutil.FlagErrorWrapreturned.TestRunShow_ClientFactoryError— stubs factory to return error; asserts wrapped error.TestRunShow_SDKError— stubs SDK to return error; asserts wrapped error.TestRunShow_OrganizationFromConfigDefault— when scopeArg isFabrikam(no org), assertsclientFact.Build(ctx, defaultOrg)is called with the configured default.Phase 2 — GREEN (minimal implementation). Strict reuse rules:
parseBuildID(raw string) (int, error)(~5 lines) for the integer-parse + positive check, andformatDuration(start, finish *time.Time) string(~10 lines) for the human-readable duration.util.ParseProjectScope,util.AddJSONFlags,util.FlagErrorf/FlagErrorWrap,util.ExactArgs,types.GetValue,types.ToPtr,ios.StartProgressIndicator/StopProgressIndicator,iostreams.Testas-is.internal/template.New(...).WithFuncs(...).Parse(show.tpl).ExecuteData(data)— exact same pattern asinternal/cmd/pr/view/view.go:483-549.ios.StartProgressIndicator()+defer ios.StopProgressIndicator(); callios.StopProgressIndicator()immediately before template execution.opts.exporter.Write(ios, res)passing the raw SDK*Build; template viatemplate.New(...).ExecuteData(templateData{Build: res}).Target delta:
show.go≤ ~120 LOC,show.tpl≤ ~50 LOC,show_test.go≤ ~400 LOC (22 tests), parentbuild.go+3 LOC,docs/pipelines_build_show.mdregenerated viamake docs. No changes tolist.goor other build siblings.Tooling and Verification Checklist
gofmt/gofumpton touched filesgo test ./internal/cmd/pipelines/build/...go test ./...make lintmake docsReference Existing Patterns
internal/cmd/pr/view/view.go— primary template-engine reference (Primary:view.go:483-549;viewOptionsstruct atview.go:23-31; template embed atview.go:33-34).internal/cmd/pr/view/view.tpl— primary template-file reference (45 lines; same field-bullet style).internal/cmd/boards/workitem/show/show.go(sibling under feat: Implementazdo boards work-item showcommand #238) — copy structure for the show flow, raw-SDK JSON output, progress lifecycle.internal/cmd/pipelines/build/list/list.go(sibling under feat: Implementazdo pipelines build listcommand #211) — closest sibling; reuse JSON/table split pattern, default columns for--format table.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture; copy structure.internal/mocks/build_client_mock.go:387-402— mock forGetBuild(already generated, do not regenerate).internal/azdo/factory.go:61-67—ClientFactory().Build(...)accessor (reuse).internal/template/template.go— template engine + funcs (reuse, do not reimplement).References