From 4976dcb9197ff7ea37cce23e6fe703ad8374e459 Mon Sep 17 00:00:00 2001 From: Codex CLI Date: Wed, 17 Jun 2026 13:50:05 +0000 Subject: [PATCH 1/4] =?UTF-8?q?build(deps):=20=F0=9F=8F=97=EF=B8=8F=20upda?= =?UTF-8?q?te=20golang.org/x=20dependencies=20and=20vendor=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .typos.toml | 1 + go.mod | 6 +- go.sum | 6 ++ vendor/golang.org/x/sys/unix/ztypes_linux.go | 76 +++++++++++++++++++ .../golang.org/x/sys/unix/ztypes_linux_386.go | 4 + .../x/sys/unix/ztypes_linux_amd64.go | 4 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 4 + .../x/sys/unix/ztypes_linux_arm64.go | 4 + .../x/sys/unix/ztypes_linux_loong64.go | 4 + .../x/sys/unix/ztypes_linux_mips.go | 4 + .../x/sys/unix/ztypes_linux_mips64.go | 4 + .../x/sys/unix/ztypes_linux_mips64le.go | 4 + .../x/sys/unix/ztypes_linux_mipsle.go | 4 + .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 4 + .../x/sys/unix/ztypes_linux_ppc64.go | 4 + .../x/sys/unix/ztypes_linux_ppc64le.go | 4 + .../x/sys/unix/ztypes_linux_riscv64.go | 4 + .../x/sys/unix/ztypes_linux_s390x.go | 4 + .../x/sys/unix/ztypes_linux_sparc64.go | 4 + vendor/modules.txt | 6 +- 20 files changed, 149 insertions(+), 6 deletions(-) diff --git a/.typos.toml b/.typos.toml index 953a8693..7597807f 100644 --- a/.typos.toml +++ b/.typos.toml @@ -9,6 +9,7 @@ # ignore-vcs = false extend-exclude = [ "**/.mise.toml", + "vendor/**", ] [default] diff --git a/go.mod b/go.mod index e361089b..9466a9ef 100644 --- a/go.mod +++ b/go.mod @@ -30,9 +30,9 @@ require ( github.com/zalando/go-keyring v0.2.8 go.uber.org/mock v0.6.0 go.uber.org/zap v1.28.0 - golang.org/x/sys v0.45.0 - golang.org/x/term v0.43.0 - golang.org/x/text v0.37.0 + golang.org/x/sys v0.46.0 + golang.org/x/term v0.44.0 + golang.org/x/text v0.38.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 1a72c9ef..432f2831 100644 --- a/go.sum +++ b/go.sum @@ -165,16 +165,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index d11d5b96..526a0d5f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -6397,3 +6397,79 @@ const ( MPOL_PREFERRED_MANY = 0x5 MPOL_WEIGHTED_INTERLEAVE = 0x6 ) + +const ( + GPIO_V2_GET_LINEINFO_IOCTL = 0xc100b405 + GPIO_V2_GET_LINE_IOCTL = 0xc250b407 + GPIO_V2_LINE_GET_VALUES_IOCTL = 0xc010b40e + GPIO_V2_LINE_SET_VALUES_IOCTL = 0xc010b40f + GPIO_V2_GET_LINEINFO_WATCH_IOCTL = 0xc100b406 + GPIO_GET_LINEINFO_UNWATCH_IOCTL = 0xc004b40c +) +const ( + GPIO_V2_LINE_ATTR_ID_FLAGS = 0x1 + GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES = 0x2 + GPIO_V2_LINE_ATTR_ID_DEBOUNCE = 0x3 + GPIO_V2_LINE_CHANGED_REQUESTED = 0x1 + GPIO_V2_LINE_CHANGED_RELEASED = 0x2 + GPIO_V2_LINE_CHANGED_CONFIG = 0x3 + GPIO_V2_LINE_EVENT_RISING_EDGE = 0x1 + GPIO_V2_LINE_EVENT_FALLING_EDGE = 0x2 +) + +type GPIOChipInfo struct { + Name [32]byte + Label [32]byte + Lines uint32 +} +type GPIOV2LineValues struct { + Bits uint64 + Mask uint64 +} +type GPIOV2LineAttribute struct { + Id uint32 + _ uint32 + Flags uint64 +} +type GPIOV2LineConfigAttribute struct { + Attr GPIOV2LineAttribute + Mask uint64 +} +type GPIOV2LineConfig struct { + Flags uint64 + Num_attrs uint32 + _ [5]uint32 + Attrs [10]GPIOV2LineConfigAttribute +} +type GPIOV2LineRequest struct { + Offsets [64]uint32 + Consumer [32]byte + Config GPIOV2LineConfig + Num_lines uint32 + Event_buffer_size uint32 + _ [5]uint32 + Fd int32 +} +type GPIOV2LineInfo struct { + Name [32]byte + Consumer [32]byte + Offset uint32 + Num_attrs uint32 + Flags uint64 + Attrs [10]GPIOV2LineAttribute + _ [4]uint32 +} +type GPIOV2LineInfoChanged struct { + Info GPIOV2LineInfo + Timestamp_ns uint64 + Event_type uint32 + _ [5]uint32 +} +type GPIOV2LineEvent struct { + Timestamp_ns uint64 + Id uint32 + Offset uint32 + Seqno uint32 + Line_seqno uint32 + _ [6]uint32 +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index 97ef790d..aede1de7 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -711,3 +711,7 @@ type SysvShmDesc struct { _ uint32 _ uint32 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index 90b50da6..bb3bc4dc 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -725,3 +725,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index acda1368..1fdf4c51 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -705,3 +705,7 @@ type SysvShmDesc struct { _ uint32 _ uint32 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index ef7a99e1..063e6f0b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -704,3 +704,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go index 966063df..9cf836c7 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go @@ -705,3 +705,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index dc53b20b..1d222fcb 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -710,3 +710,7 @@ type SysvShmDesc struct { Ctime_high uint16 _ uint16 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 9ad0aa8c..912cc4ab 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -707,3 +707,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 29d55493..1e358ef3 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -707,3 +707,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index a4d9e158..df59f32f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -710,3 +710,7 @@ type SysvShmDesc struct { Ctime_high uint16 _ uint16 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go index f8a29777..29355aa0 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go @@ -718,3 +718,7 @@ type SysvShmDesc struct { _ uint32 _ [4]byte } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index 4158d6c4..c6083a15 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -713,3 +713,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 1035af49..6321cc76 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -713,3 +713,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index 2297125d..b44f402f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -792,3 +792,7 @@ const ( RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE = 0x6 RISCV_HWPROBE_WHICH_CPUS = 0x1 ) + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index 8481e9bd..b22c795a 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -727,3 +727,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go index a6828a03..0b18075b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go @@ -708,3 +708,7 @@ type SysvShmDesc struct { _ uint64 _ uint64 } + +const ( + GPIO_GET_CHIPINFO_IOCTL = 0x4044b401 +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 9cab5585..c5024c0c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -252,15 +252,15 @@ go.yaml.in/yaml/v3 golang.org/x/net/html golang.org/x/net/html/atom golang.org/x/net/idna -# golang.org/x/sys v0.45.0 +# golang.org/x/sys v0.46.0 ## explicit; go 1.25.0 golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.43.0 +# golang.org/x/term v0.44.0 ## explicit; go 1.25.0 golang.org/x/term -# golang.org/x/text v0.37.0 +# golang.org/x/text v0.38.0 ## explicit; go 1.25.0 golang.org/x/text/cases golang.org/x/text/encoding From ac8ff0b4f878ad2f4d1fd603578d0f2fe7d36b09 Mon Sep 17 00:00:00 2001 From: Codex CLI Date: Wed, 17 Jun 2026 19:58:39 +0000 Subject: [PATCH 2/4] =?UTF-8?q?feat(boards):=20=E2=9C=A8add=20create=20com?= =?UTF-8?q?mand=20for=20project=20iterations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../boards/iteration/project/create/create.go | 231 ++++++++++++++++++ .../cmd/boards/iteration/project/project.go | 2 + 2 files changed, 233 insertions(+) create mode 100644 internal/cmd/boards/iteration/project/create/create.go diff --git a/internal/cmd/boards/iteration/project/create/create.go b/internal/cmd/boards/iteration/project/create/create.go new file mode 100644 index 00000000..0207772c --- /dev/null +++ b/internal/cmd/boards/iteration/project/create/create.go @@ -0,0 +1,231 @@ +package create + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/MakeNowJust/heredoc/v2" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/workitemtracking" + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/tmeckel/azdo-cli/internal/cmd/boards/shared" + "github.com/tmeckel/azdo-cli/internal/cmd/util" + "github.com/tmeckel/azdo-cli/internal/types" +) + +type createOptions struct { + scopeArg string + name string + path string + startDate string + finishDate string + attributes []string + exporter util.Exporter +} + +func NewCmd(ctx util.CmdContext) *cobra.Command { + opts := &createOptions{} + + cmd := &cobra.Command{ + Use: "create [ORGANIZATION/]PROJECT", + Short: "Create an iteration (sprint) in a project.", + Example: heredoc.Doc(` + # Create a top-level iteration + azdo boards iteration project create Fabrikam --name "Sprint 1" + + # Schedule a sprint with start and finish dates + azdo boards iteration project create Fabrikam \ + --name "Sprint 2" --start-date 2025-01-06 --finish-date 2025-01-19 + + # Create a nested iteration under an existing release + azdo boards iteration project create myorg/Fabrikam --name "Sprint 2" --path "Release 2025" + + # Set a custom attribute alongside the dates + azdo boards iteration project create Fabrikam \ + --name "Sprint 1" --start-date 2025-01-06 --finish-date 2025-01-19 \ + --attributes goal="Ship login" + + # Emit JSON + azdo boards iteration project create Fabrikam --name "Sprint 1" --json + `), + Aliases: []string{"c", "cr"}, + Args: util.ExactArgs(1, "project argument required"), + RunE: func(cmd *cobra.Command, args []string) error { + opts.scopeArg = args[0] + return runCreate(ctx, opts) + }, + } + + cmd.Flags().StringVar(&opts.name, "name", "", "Name of the new iteration (required).") + cmd.Flags().StringVar(&opts.path, "path", "", "Parent iteration path under /Iteration. Omit to create at the project root.") + cmd.Flags().StringVar(&opts.startDate, "start-date", "", "Iteration start date (RFC 3339 or YYYY-MM-DD).") + cmd.Flags().StringVar(&opts.finishDate, "finish-date", "", "Iteration finish date (RFC 3339 or YYYY-MM-DD).") + cmd.Flags().StringSliceVar(&opts.attributes, "attributes", nil, "Custom attribute in key=value form. Repeatable. start-date/finish-date win on key conflict.") + _ = cmd.MarkFlagRequired("name") + util.AddJSONFlags(cmd, &opts.exporter, []string{ + "id", "identifier", "name", "path", "structureType", "hasChildren", "attributes", "url", "_links", + }) + + return cmd +} + +func runCreate(ctx util.CmdContext, opts *createOptions) error { + ios, err := ctx.IOStreams() + if err != nil { + return err + } + + ios.StartProgressIndicator() + defer ios.StopProgressIndicator() + if parts := strings.Split(strings.TrimSpace(opts.scopeArg), "/"); len(parts) > 2 { + return util.FlagErrorf("invalid project scope %q: expected [ORGANIZATION/]PROJECT", opts.scopeArg) + } + + scope, err := util.ParseProjectScope(ctx, opts.scopeArg) + if err != nil { + return util.FlagErrorWrap(err) + } + + name := strings.TrimSpace(opts.name) + if name == "" { + return util.FlagErrorf("--name must not be empty") + } + + parentPath, err := shared.BuildClassificationPath(scope.Project, true, "Iteration", opts.path) + if err != nil { + return util.FlagErrorf("invalid --path: %w", err) + } + + attrs, err := buildAttributes(opts) + if err != nil { + return err + } + + postedNode := &workitemtracking.WorkItemClassificationNode{ + Name: types.ToPtr(name), + } + if len(attrs) > 0 { + postedNode.Attributes = &attrs + } + + args := workitemtracking.CreateOrUpdateClassificationNodeArgs{ + PostedNode: postedNode, + Project: types.ToPtr(scope.Project), + StructureGroup: types.ToPtr(workitemtracking.TreeStructureGroupValues.Iterations), + } + if parentPath != "" { + args.Path = types.ToPtr(parentPath) + } + + zap.L().Debug( + "creating iteration", + zap.String("organization", scope.Organization), + zap.String("project", scope.Project), + zap.String("name", name), + zap.String("parentPath", parentPath), + zap.Int("attributeCount", len(attrs)), + ) + + wit, err := ctx.ClientFactory().WorkItemTracking(ctx.Context(), scope.Organization) + if err != nil { + return fmt.Errorf("failed to get classification client: %w", err) + } + + res, err := wit.CreateOrUpdateClassificationNode(ctx.Context(), args) + if err != nil { + return fmt.Errorf("failed to create iteration: %w", err) + } + + ios.StopProgressIndicator() + + if opts.exporter != nil { + return opts.exporter.Write(ios, res) + } + + tp, err := ctx.Printer("list") + if err != nil { + return err + } + tp.AddColumns("ID", "NAME", "PATH", "START DATE", "FINISH DATE", "HAS CHILDREN") + tp.AddField(strconv.Itoa(types.GetValue(res.Id, 0))) + tp.AddField(types.GetValue(res.Name, "")) + tp.AddField(shared.NormalizeClassificationPath(types.GetValue(res.Path, ""))) + tp.AddField(formatAttributeDate(res.Attributes, "startDate")) + tp.AddField(formatAttributeDate(res.Attributes, "finishDate")) + tp.AddField(strconv.FormatBool(types.GetValue(res.HasChildren, false))) + tp.EndRow() + return tp.Render() +} + +// buildAttributes assembles iteration attributes. Start/finish flags win over --attributes. +func buildAttributes(opts *createOptions) (map[string]any, error) { + attrs := make(map[string]any) + var startTime *time.Time + var finishTime *time.Time + + if raw := strings.TrimSpace(opts.startDate); raw != "" { + t, err := parseStrictDate(raw) + if err != nil { + return nil, util.FlagErrorf("invalid --start-date: %w", err) + } + start := t.UTC() + startTime = &start + } + if raw := strings.TrimSpace(opts.finishDate); raw != "" { + t, err := parseStrictDate(raw) + if err != nil { + return nil, util.FlagErrorf("invalid --finish-date: %w", err) + } + finish := t.UTC() + finishTime = &finish + } + if startTime != nil && finishTime != nil && finishTime.Before(*startTime) { + return nil, util.FlagErrorf("--finish-date must be on or after --start-date") + } + for _, kv := range opts.attributes { + idx := strings.Index(kv, "=") + if idx <= 0 { + return nil, util.FlagErrorf("invalid --attributes %q: expected key=value", kv) + } + key := strings.TrimSpace(kv[:idx]) + if key == "" { + return nil, util.FlagErrorf("invalid --attributes %q: empty key", kv) + } + if _, reserved := attrs[key]; reserved { + continue + } + attrs[key] = kv[idx+1:] + } + if startTime != nil { + attrs["startDate"] = startTime.Format(time.RFC3339) + } + if finishTime != nil { + attrs["finishDate"] = finishTime.Format(time.RFC3339) + } + + return attrs, nil +} + +func parseStrictDate(raw string) (time.Time, error) { + if strings.Contains(raw, "T") { + return time.Parse(time.RFC3339, raw) + } + return time.Parse("2006-01-02", raw) +} + +func formatAttributeDate(attrs *map[string]any, key string) string { + if attrs == nil { + return "" + } + raw, ok := (*attrs)[key] + if !ok || raw == nil { + return "" + } + if s, ok := raw.(string); ok { + return s + } + return fmt.Sprintf("%v", raw) +} diff --git a/internal/cmd/boards/iteration/project/project.go b/internal/cmd/boards/iteration/project/project.go index f4690ec9..f476ee4b 100644 --- a/internal/cmd/boards/iteration/project/project.go +++ b/internal/cmd/boards/iteration/project/project.go @@ -3,6 +3,7 @@ package project import ( "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" + "github.com/tmeckel/azdo-cli/internal/cmd/boards/iteration/project/create" "github.com/tmeckel/azdo-cli/internal/cmd/boards/iteration/project/list" "github.com/tmeckel/azdo-cli/internal/cmd/util" ) @@ -22,6 +23,7 @@ func NewCmd(ctx util.CmdContext) *cobra.Command { }, } + cmd.AddCommand(create.NewCmd(ctx)) cmd.AddCommand(list.NewCmd(ctx)) return cmd From a1cb23c222d29a82438aa1d0410894ccbe11a19a Mon Sep 17 00:00:00 2001 From: Codex CLI Date: Wed, 17 Jun 2026 20:14:44 +0000 Subject: [PATCH 3/4] =?UTF-8?q?test(boards):=20=F0=9F=A7=AAadd=20tests=20f?= =?UTF-8?q?or=20create=20command=20for=20project=20iterations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iteration/project/create/create_test.go | 513 ++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 internal/cmd/boards/iteration/project/create/create_test.go diff --git a/internal/cmd/boards/iteration/project/create/create_test.go b/internal/cmd/boards/iteration/project/create/create_test.go new file mode 100644 index 00000000..7c46b570 --- /dev/null +++ b/internal/cmd/boards/iteration/project/create/create_test.go @@ -0,0 +1,513 @@ +package create + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "strings" + "testing" + + "github.com/google/uuid" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/workitemtracking" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/tmeckel/azdo-cli/internal/cmd/util" + "github.com/tmeckel/azdo-cli/internal/iostreams" + "github.com/tmeckel/azdo-cli/internal/mocks" + "github.com/tmeckel/azdo-cli/internal/printer" + "github.com/tmeckel/azdo-cli/internal/types" +) + +type fakeCreateDeps struct { + ctrl *gomock.Controller + cmd *mocks.MockCmdContext + clientFact *mocks.MockClientFactory + wit *mocks.MockWorkItemTrackingClient + stdout *bytes.Buffer + org string +} + +func setupFakeDeps(t *testing.T, organization string) *fakeCreateDeps { + t.Helper() + + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + io, _, out, _ := iostreams.Test() + io.SetStdoutTTY(false) + io.SetStderrTTY(false) + + deps := &fakeCreateDeps{ + ctrl: ctrl, + cmd: mocks.NewMockCmdContext(ctrl), + clientFact: mocks.NewMockClientFactory(ctrl), + wit: mocks.NewMockWorkItemTrackingClient(ctrl), + stdout: out, + org: organization, + } + + deps.cmd.EXPECT().IOStreams().Return(io, nil).AnyTimes() + deps.cmd.EXPECT().Context().Return(context.Background()).AnyTimes() + deps.cmd.EXPECT().ClientFactory().Return(deps.clientFact).AnyTimes() + + tp, err := printer.NewTablePrinter(out, false, 200) + require.NoError(t, err) + deps.cmd.EXPECT().Printer("list").Return(tp, nil).AnyTimes() + + return deps +} + +func setupFakeDepsWithDefaultOrg(t *testing.T, defaultOrg string) *fakeCreateDeps { + t.Helper() + + deps := setupFakeDeps(t, defaultOrg) + cfg := mocks.NewMockConfig(deps.ctrl) + auth := mocks.NewMockAuthConfig(deps.ctrl) + + deps.cmd.EXPECT().Config().Return(cfg, nil).AnyTimes() + cfg.EXPECT().Authentication().Return(auth).AnyTimes() + auth.EXPECT().GetDefaultOrganization().Return(defaultOrg, nil).AnyTimes() + + return deps +} + +func minimalCreatedNode() *workitemtracking.WorkItemClassificationNode { + attrs := map[string]any{ + "startDate": "2025-01-06T00:00:00Z", + "finishDate": "2025-01-19T00:00:00Z", + } + id := 42 + hasChildren := true + name := "Sprint 1" + path := "Fabrikam\\Iteration\\Sprint 1" + return &workitemtracking.WorkItemClassificationNode{ + Id: &id, + Name: &name, + Path: &path, + HasChildren: &hasChildren, + Attributes: &attrs, + } +} + +func TestNewCmd_RegistersAsCreateLeaf(t *testing.T) { + t.Parallel() + + cmd := NewCmd(nil) + + assert.Equal(t, "create", cmd.Name()) + assert.Equal(t, []string{"c", "cr"}, cmd.Aliases) + assert.True(t, strings.HasPrefix(cmd.Use, "create [ORGANIZATION/]PROJECT")) +} + +func TestNewCmd_NameFlagRequired(t *testing.T) { + t.Parallel() + + cmd := NewCmd(nil) + cmd.SetArgs([]string{"Fabrikam"}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + + err := cmd.Execute() + + require.Error(t, err) + assert.Contains(t, err.Error(), "name") +} + +func TestRunCreate_EmptyNameFlag(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: " "} + + err := runCreate(deps.cmd, opts) + + requireFlagError(t, err, "--name") +} + +func TestRunCreate_RootLevelCreate(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode) + assert.Nil(t, args.Path) + assert.Equal(t, workitemtracking.TreeStructureGroupValues.Iterations, *args.StructureGroup) + assert.Equal(t, "iterations", string(*args.StructureGroup)) + assert.Equal(t, "Fabrikam", *args.Project) + assert.Equal(t, "Sprint 1", *args.PostedNode.Name) + assert.Nil(t, args.PostedNode.Id) + assert.Nil(t, args.PostedNode.Attributes) +} + +func TestRunCreate_NestedPathCreate(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 2", path: "Release 2025"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.Path) + assert.Equal(t, "Release%202025", *args.Path) +} + +func TestRunCreate_PathNormalizationStripsProjectAndIteration(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 2", path: "Fabrikam/Iteration/Release 2025/Sprint 1"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.Path) + assert.Equal(t, "Release%202025/Sprint%201", *args.Path) +} + +func TestRunCreate_PathURLEscaping(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 2", path: "My Sprint/Sub Sprint"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.Path) + assert.Equal(t, "My%20Sprint/Sub%20Sprint", *args.Path) +} + +func TestRunCreate_StartDateOnly(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1", startDate: "2025-01-06"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode.Attributes) + assert.Equal(t, "2025-01-06T00:00:00Z", (*args.PostedNode.Attributes)["startDate"]) + assert.NotContains(t, *args.PostedNode.Attributes, "finishDate") +} + +func TestRunCreate_FinishDateOnly(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1", finishDate: "2025-01-19T00:00:00Z"} + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode.Attributes) + assert.Equal(t, "2025-01-19T00:00:00Z", (*args.PostedNode.Attributes)["finishDate"]) + assert.NotContains(t, *args.PostedNode.Attributes, "startDate") +} + +func TestRunCreate_BothDates_RFC3339(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{ + scopeArg: "org/Fabrikam", + name: "Sprint 1", + startDate: "2025-01-06T00:00:00Z", + finishDate: "2025-01-19T00:00:00Z", + } + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode.Attributes) + assert.Equal(t, "2025-01-06T00:00:00Z", (*args.PostedNode.Attributes)["startDate"]) + assert.Equal(t, "2025-01-19T00:00:00Z", (*args.PostedNode.Attributes)["finishDate"]) +} + +func TestRunCreate_DateFlags_InvalidFormat(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1", startDate: "yesterday"} + + err := runCreate(deps.cmd, opts) + + requireFlagError(t, err, "invalid --start-date") +} + +func TestRunCreate_DateFlags_FinishBeforeStart(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{ + scopeArg: "org/Fabrikam", + name: "Sprint 1", + startDate: "2025-01-19", + finishDate: "2025-01-06", + } + + err := runCreate(deps.cmd, opts) + + requireFlagError(t, err, "--finish-date must be on or after --start-date") +} + +func TestRunCreate_AttributesFlag_Merged(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{ + scopeArg: "org/Fabrikam", + name: "Sprint 1", + attributes: []string{"goal=Ship", "team=Alpha"}, + } + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode.Attributes) + assert.Equal(t, "Ship", (*args.PostedNode.Attributes)["goal"]) + assert.Equal(t, "Alpha", (*args.PostedNode.Attributes)["team"]) +} + +func TestRunCreate_AttributesFlag_StartDateWins(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{ + scopeArg: "org/Fabrikam", + name: "Sprint 1", + startDate: "2025-01-06", + attributes: []string{"startDate=2024-12-01"}, + } + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + + require.NoError(t, err) + require.NotNil(t, args.PostedNode.Attributes) + assert.Equal(t, "2025-01-06T00:00:00Z", (*args.PostedNode.Attributes)["startDate"]) +} + +func TestRunCreate_AttributesFlag_InvalidFormat(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + attributes []string + want string + }{ + {name: "empty key", attributes: []string{"=value"}, want: `invalid --attributes "=value": expected key=value`}, + {name: "missing equals", attributes: []string{"novalue"}, want: `invalid --attributes "novalue": expected key=value`}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1", attributes: tc.attributes} + + err := runCreate(deps.cmd, opts) + + requireFlagError(t, err, tc.want) + }) + } +} + +func TestRunCreate_ProjectScopeParsing(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + scopeArg string + org string + project string + wantErr string + defaultOrg string + }{ + {name: "organization and project", scopeArg: "org/proj", org: "org", project: "proj"}, + {name: "project uses default organization", scopeArg: "proj", org: "default-org", project: "proj", defaultOrg: "default-org"}, + {name: "too many segments", scopeArg: "org/proj/extra", wantErr: "expected"}, + {name: "empty scope", scopeArg: "", wantErr: "expected"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var deps *fakeCreateDeps + if tc.defaultOrg != "" { + deps = setupFakeDepsWithDefaultOrg(t, tc.defaultOrg) + } else { + deps = setupFakeDeps(t, tc.org) + } + opts := &createOptions{scopeArg: tc.scopeArg, name: "Sprint 1"} + + if tc.wantErr != "" { + err := runCreate(deps.cmd, opts) + requireFlagError(t, err, tc.wantErr) + return + } + + args, err := captureCreateArgs(t, deps, opts, minimalCreatedNode()) + require.NoError(t, err) + assert.Equal(t, tc.project, *args.Project) + }) + } +} + +func TestRunCreate_ClientFactoryError(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + io, _, _, _ := iostreams.Test() + cmd := mocks.NewMockCmdContext(ctrl) + clientFact := mocks.NewMockClientFactory(ctrl) + + cmd.EXPECT().IOStreams().Return(io, nil).AnyTimes() + cmd.EXPECT().Context().Return(context.Background()).AnyTimes() + cmd.EXPECT().ClientFactory().Return(clientFact).AnyTimes() + clientFact.EXPECT().WorkItemTracking(gomock.Any(), "org").Return(nil, errors.New("boom")) + + err := runCreate(cmd, &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1"}) + + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to get classification client") +} + +func TestRunCreate_SDKError(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1"} + deps.clientFact.EXPECT().WorkItemTracking(gomock.Any(), "org").Return(deps.wit, nil).AnyTimes() + deps.wit.EXPECT().CreateOrUpdateClassificationNode(gomock.Any(), gomock.Any()).Return(nil, errors.New("boom")) + + err := runCreate(deps.cmd, opts) + + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to create iteration") +} + +func TestRunCreate_TableOutput_AllColumns(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1"} + deps.clientFact.EXPECT().WorkItemTracking(gomock.Any(), "org").Return(deps.wit, nil).AnyTimes() + deps.wit.EXPECT().CreateOrUpdateClassificationNode(gomock.Any(), gomock.Any()).Return(minimalCreatedNode(), nil) + + err := runCreate(deps.cmd, opts) + + require.NoError(t, err) + lines := strings.Split(strings.TrimSpace(deps.stdout.String()), "\n") + require.Len(t, lines, 1) + fields := strings.Split(lines[0], "\t") + assert.Equal(t, []string{ + "42", + "Sprint 1", + "Fabrikam/Iteration/Sprint 1", + "2025-01-06T00:00:00Z", + "2025-01-19T00:00:00Z", + "true", + }, fields) +} + +func TestRunCreate_JSONOutput(t *testing.T) { + t.Parallel() + + deps := setupFakeDeps(t, "org") + opts := &createOptions{scopeArg: "org/Fabrikam", name: "Sprint 1", exporter: util.NewJSONExporter()} + identifier := uuid.New() + path := "Fabrikam\\Iteration\\Sprint 1" + jsonNode := &workitemtracking.WorkItemClassificationNode{ + Id: types.ToPtr(42), + Identifier: &identifier, + Name: types.ToPtr("Sprint 1"), + Path: &path, + HasChildren: types.ToPtr(true), + Attributes: &map[string]any{"startDate": "2025-01-06T00:00:00Z", "finishDate": "2025-01-19T00:00:00Z"}, + StructureType: types.ToPtr(workitemtracking.TreeNodeStructureTypeValues.Iteration), + Url: types.ToPtr("https://dev.azure.com/org/Fabrikam/_apis/wit/classificationNodes/iterations/42"), + Links: map[string]any{ + "self": map[string]any{"href": "https://dev.azure.com/org/Fabrikam/_apis/wit/classificationNodes/iterations/42"}, + }, + } + deps.clientFact.EXPECT().WorkItemTracking(gomock.Any(), "org").Return(deps.wit, nil) + deps.wit.EXPECT().CreateOrUpdateClassificationNode(gomock.Any(), gomock.Any()).Return(jsonNode, nil) + + err := runCreate(deps.cmd, opts) + + require.NoError(t, err) + var got map[string]any + require.NoError(t, json.Unmarshal(deps.stdout.Bytes(), &got)) + assert.Equal(t, float64(42), got["id"]) + assert.Equal(t, identifier.String(), got["identifier"]) + assert.Equal(t, "Sprint 1", got["name"]) + assert.Equal(t, "Fabrikam\\Iteration\\Sprint 1", got["path"]) + assert.Equal(t, true, got["hasChildren"]) + assert.Equal(t, "https://dev.azure.com/org/Fabrikam/_apis/wit/classificationNodes/iterations/42", got["url"]) + assert.Equal(t, "iteration", got["structureType"]) + attrs, ok := got["attributes"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "2025-01-06T00:00:00Z", attrs["startDate"]) + assert.Equal(t, "2025-01-19T00:00:00Z", attrs["finishDate"]) + links, ok := got["_links"].(map[string]any) + require.True(t, ok) + assert.Contains(t, links, "self") +} + +func TestRunCreate_OrganizationFromConfigDefault(t *testing.T) { + t.Parallel() + + deps := setupFakeDepsWithDefaultOrg(t, "default-org") + opts := &createOptions{scopeArg: "Fabrikam", name: "Sprint 1"} + var got workitemtracking.CreateOrUpdateClassificationNodeArgs + deps.clientFact.EXPECT().WorkItemTracking(gomock.Any(), "default-org").Return(deps.wit, nil) + deps.wit.EXPECT().CreateOrUpdateClassificationNode(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, args workitemtracking.CreateOrUpdateClassificationNodeArgs) (*workitemtracking.WorkItemClassificationNode, error) { + got = args + return minimalCreatedNode(), nil + }, + ) + + err := runCreate(deps.cmd, opts) + + require.NoError(t, err) + assert.Equal(t, "Fabrikam", *got.Project) +} + +func captureCreateArgs(t *testing.T, deps *fakeCreateDeps, opts *createOptions, response *workitemtracking.WorkItemClassificationNode) (workitemtracking.CreateOrUpdateClassificationNodeArgs, error) { + t.Helper() + + var got workitemtracking.CreateOrUpdateClassificationNodeArgs + deps.clientFact.EXPECT().WorkItemTracking(gomock.Any(), deps.org).Return(deps.wit, nil) + deps.wit.EXPECT().CreateOrUpdateClassificationNode(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, args workitemtracking.CreateOrUpdateClassificationNodeArgs) (*workitemtracking.WorkItemClassificationNode, error) { + got = args + return response, nil + }, + ) + + err := runCreate(deps.cmd, opts) + return got, err +} + +func requireFlagError(t *testing.T, err error, substr string) { + t.Helper() + + require.Error(t, err) + var flagErr *util.FlagError + require.ErrorAs(t, err, &flagErr) + assert.Contains(t, err.Error(), substr) +} From 863732305e0c4dc106e6fc5a56974256235499e3 Mon Sep 17 00:00:00 2001 From: Codex CLI Date: Wed, 17 Jun 2026 20:22:49 +0000 Subject: [PATCH 4/4] =?UTF-8?q?docs(boards):=F0=9F=93=84add=20documentatio?= =?UTF-8?q?n=20for=20azdo=20boards=20iteration=20project=20create=20comman?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/azdo_boards_iteration_project.md | 1 + docs/azdo_boards_iteration_project_create.md | 78 ++++++++++++++++++++ docs/azdo_help_reference.md | 21 ++++++ 3 files changed, 100 insertions(+) create mode 100644 docs/azdo_boards_iteration_project_create.md diff --git a/docs/azdo_boards_iteration_project.md b/docs/azdo_boards_iteration_project.md index e81146a9..298fdd66 100644 --- a/docs/azdo_boards_iteration_project.md +++ b/docs/azdo_boards_iteration_project.md @@ -4,6 +4,7 @@ Project-scoped iteration commands. ### Available commands +* [azdo boards iteration project create](./azdo_boards_iteration_project_create.md) * [azdo boards iteration project list](./azdo_boards_iteration_project_list.md) ### ALIASES diff --git a/docs/azdo_boards_iteration_project_create.md b/docs/azdo_boards_iteration_project_create.md new file mode 100644 index 00000000..f040469f --- /dev/null +++ b/docs/azdo_boards_iteration_project_create.md @@ -0,0 +1,78 @@ +## Command `azdo boards iteration project create` + +Create an iteration (sprint) in a project. + +``` +azdo boards iteration project create [ORGANIZATION/]PROJECT [flags] +``` + +### Options + + +* `--attributes` `strings` + + Custom attribute in key=value form. Repeatable. start-date/finish-date win on key conflict. + +* `--finish-date` `string` + + Iteration finish date (RFC 3339 or YYYY-MM-DD). + +* `-q`, `--jq` `expression` + + Filter JSON output using a jq expression + +* `--json` `fields` + + Output JSON with the specified fields. Prefix a field with '-' to exclude it. + +* `--name` `string` + + Name of the new iteration (required). + +* `--path` `string` + + Parent iteration path under /Iteration. Omit to create at the project root. + +* `--start-date` `string` + + Iteration start date (RFC 3339 or YYYY-MM-DD). + +* `-t`, `--template` `string` + + Format JSON output using a Go template; see "azdo help formatting" + + +### ALIASES + +- `c` +- `cr` + +### JSON Fields + +`_links`, `attributes`, `hasChildren`, `id`, `identifier`, `name`, `path`, `structureType`, `url` + +### Examples + +```bash +# Create a top-level iteration +azdo boards iteration project create Fabrikam --name "Sprint 1" + +# Schedule a sprint with start and finish dates +azdo boards iteration project create Fabrikam \ + --name "Sprint 2" --start-date 2025-01-06 --finish-date 2025-01-19 + +# Create a nested iteration under an existing release +azdo boards iteration project create myorg/Fabrikam --name "Sprint 2" --path "Release 2025" + +# Set a custom attribute alongside the dates +azdo boards iteration project create Fabrikam \ + --name "Sprint 1" --start-date 2025-01-06 --finish-date 2025-01-19 \ + --attributes goal="Ship login" + +# Emit JSON +azdo boards iteration project create Fabrikam --name "Sprint 1" --json +``` + +### See also + +* [azdo boards iteration project](./azdo_boards_iteration_project.md) diff --git a/docs/azdo_help_reference.md b/docs/azdo_help_reference.md index 2c9f3467..ae96d598 100644 --- a/docs/azdo_help_reference.md +++ b/docs/azdo_help_reference.md @@ -127,6 +127,27 @@ Aliases prj, p ``` +##### `azdo boards iteration project create [ORGANIZATION/]PROJECT [flags]` + +Create an iteration (sprint) in a project. + +``` + --attributes strings Custom attribute in key=value form. Repeatable. start-date/finish-date win on key conflict. + --finish-date string Iteration finish date (RFC 3339 or YYYY-MM-DD). +-q, --jq expression Filter JSON output using a jq expression + --json fields[=*] Output JSON with the specified fields. Prefix a field with '-' to exclude it. + --name string Name of the new iteration (required). + --path string Parent iteration path under /Iteration. Omit to create at the project root. + --start-date string Iteration start date (RFC 3339 or YYYY-MM-DD). +-t, --template string Format JSON output using a Go template; see "azdo help formatting" +``` + +Aliases + +``` +c, cr +``` + ##### `azdo boards iteration project list [ORGANIZATION/]PROJECT [flags]` List iteration hierarchy for a project.