Skip to content

feat: Implement azdo pipelines folder delete command #264

Description

@tmeckel

Sub-issue of #262. Hardened spec — do not re-derive decisions.

Command Description

Delete a build definition folder (and all its definitions and builds) at PATH under PROJECT. Mirrors az pipelines folder delete.

Locked Decisions

# Decision Rationale
1 Project-scoped, with embedded path target. Folders are owned by projects.
2 Use: "delete [ORGANIZATION/]PROJECT/PATH". Mirrors variable-group delete's Use: "delete [ORGANIZATION/]PROJECT/GROUP" (internal/cmd/pipelines/variablegroup/delete/delete.go:34).
3 Aliases: []string{"d", "del", "rm"}. Per AGENTS.md destructive command convention.
4 cobra.ExactArgs(1). One positional target.
5 Parse the single positional with util.ParseProjectPathTargetWithDefaultOrganization(ctx, args[0], ...). The wrapper is added as part of the create sub-issue's implementation (or this one — whichever is implemented first). No existing helper supports the 3-segment form.
6 Destructive: requires confirmation unless --yes is provided. Prompt text: This will delete all pipelines in this folder. Are you sure you want to delete this folder?. Mirrors the AzDO Extension's pipeline_folder_delete confirmation. On cancellation, return util.ErrCancel.
7 Plain output: print Deleted folder <PROJECT>/<PATH> to stdout on success. Mirrors variable-group delete.
8 JSON output: N/A. DeleteFolder returns error only — no body.
9 Uses vendored build.Client.DeleteFolder. Vendor verified at vendor/.../v7/build/client.go:757.
10 No new mock needed. internal/mocks/build_client_mock.go:254-265 already mocks DeleteFolder.
11 Predecessors: must be implemented after the create sub-issue (so the ParseProjectPathTargetWithDefaultOrganization wrapper exists), but the two can be filed in either order.
12 No go mod tidy / go mod vendor / scripts/generate_mocks.sh work.

Command Signature

var deleteCmd = &cobra.Command{
    Use:     "delete [ORGANIZATION/]PROJECT/PATH",
    Aliases: []string{"d", "del", "rm"},
    Short:   "Delete a folder.",
    Long: `Delete a build definition folder at PATH under PROJECT.

Mirrors 'az pipelines folder delete'. The folder, all build definitions
in it, and all builds for those definitions are deleted. This action is
not reversible.`,
    Args: cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        return runDelete(cmd.Context(), opts, args[0])
    },
}

Flags

Flag Type Default Required Description
--yes bool false no Skip the confirmation prompt.
--organization string $AZDO_ORGANIZATION or default config no Azure DevOps organization. Omit to use the default.
--project string "" no Project name or ID. Omit to use the project from the positional target.

JSON Output Contract

N/A. DeleteFolder returns error only.

Table Output Contract

N/A. On success, print one line to stdout:

Deleted folder <PROJECT>/<PATH>

Command Wiring

  1. Create internal/cmd/pipelines/folder/delete/delete.go with package delete, factory func NewCmd(ctx util.CmdContext) *cobra.Command.
  2. Add import "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/folder/delete" to internal/cmd/pipelines/folder/folder.go.
  3. Register in folder.go with cmd.AddCommand(delete.NewCmd(ctx)).
  4. Use the util.Prompter from ctx.IOStreams() (or ctx.Prompter() if the codebase has a centralized prompter) to ask for confirmation. The existing pattern in internal/cmd/pipelines/variablegroup/delete/delete.go is the precedent.

API Surface

Vendored SDK call (from vendor/.../v7/build/client.go:757):

args := build.DeleteFolderArgs{
    Project: &opts.project,
    Path:    &opts.path,
}
if err := client.DeleteFolder(ctx, args); err != nil {
    return fmt.Errorf("failed to delete folder %s: %w", opts.path, err)
}

The vendored SDK enforces args.Project != "" and args.Path != nil — will return ArgumentNilOrEmptyError / ArgumentNilError if missing. No extra validation needed in our wrapper.

Reference Existing Patterns

  • internal/cmd/pipelines/variablegroup/delete/delete.go — closest sibling. Mirrors the Use, the Aliases, the confirmation prompt, the success message, and the destructive Delete call.
  • internal/cmd/pipelines/folder/create/create.go — closest sibling for the parsing of [ORGANIZATION/]PROJECT/PATH.

TDD Test Plan

Test name Scenario Expected
TestNewCmd_delete Inspect the command struct. Use == "delete [ORGANIZATION/]PROJECT/PATH", Aliases == ["d", "del", "rm"], Args == cobra.ExactArgs(1).
Test_runDelete_success_withYes Mock DeleteFolder returns nil. opts.yes = true. Returns nil error; prints Deleted folder MyProject/Foo to stdout.
Test_runDelete_success_confirmed Mock DeleteFolder returns nil. opts.yes = false. Prompter is stubbed to return true. Returns nil error; prints Deleted folder MyProject/Foo to stdout.
Test_runDelete_cancelled Prompter is stubbed to return false. Returns util.ErrCancel. The SDK is not called.
Test_runDelete_APIError Mock returns errors.New("boom"). opts.yes = true. Returns wrapped error including the path.
Test_runDelete_missingPath User runs azdo pipelines folder delete with no args. Cobra args validation returns the standard "accepts 1 arg(s)" error.

References

  • Vendored SDK: vendor/.../v7/build/client.go:757 (DeleteFolder), :779 (DeleteFolderArgs).
  • Mock: internal/mocks/build_client_mock.go:254-265 (DeleteFolder).
  • Internal client accessor: internal/azdo/factory.go:61ClientFactory().Build(ctx, organization).
  • Azure CLI reference: https://learn.microsoft.com/en-us/cli/azure/pipelines/folder?view=azure-cli-latest#az-pipelines-folder-delete
  • Azure DevOps REST API: Folders — Delete (DELETE, route id a906531b-d2da-4f55-bda7-f3e676cc50d9, API version 7.1-preview.2). Deleting a folder also deletes its definitions and builds.
  • Umbrella: Introduce azdo pipelines folder command group #262.
  • Pattern sibling: internal/cmd/pipelines/variablegroup/delete/delete.go (destructive confirmation, success message, Delete SDK call).
  • Predecessor: the create sub-issue (so the ParseProjectPathTargetWithDefaultOrganization wrapper exists).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions