Sub-issue of #116. Hardened spec — do not re-derive decisions. Mirrors az pipelines create from the Azure CLI and the Python implementation at azure-dev-ops-cli-extension/azure-dev-ops-cli-extension/azure-dev-ops/azure-devops/azext_devops/dev/pipelines/pipeline_create.py#L43-L141.
Note: The Python implementation is heavily interactive (prompts the user for template selection, parameter values, etc.). The azdo Go port scopes this down to the non-interactive subset — i.e., the flags the user must pass to drive the command without prompts. The interactive flow is intentionally out of scope (the azdo CLI is non-interactive by design).
Command Description
Create a new YAML-based Azure Pipeline in a project. The command resolves the repository (by URL, by name, or by auto-detection from the local git checkout), wires up the build definition, and (optionally) triggers the first run. Returns the created BuildDefinition (when --skip-first-run is set) or the first Build run (when the first run is allowed to proceed).
POST https://dev.azure.com/{organization}/{project}/_apis/build/definitions?api-version=7.1
POST https://dev.azure.com/{organization}/{project}/_apis/build/builds?api-version=7.1 (only when not skipping the first run)
Locked Decisions (do not re-derive)
| # |
Decision |
Rationale |
| 1 |
Use the vendored SDK build.Client.CreateDefinition and build.Client.QueueBuild (not raw HTTP). Mocks already generated at internal/mocks/build_client_mock.go:167 (CreateDefinition) and :1227 (QueueBuild). |
Consistent with build list (#211) and build show (#241). |
| 2 |
The pipeline is identified post-creation by its auto-generated id. Required CLI inputs: --name (string), --repository (string, optional if --repository-type and --branch imply local git detection), --branch (string, optional only if detect is on and the local git repo has a tracked branch), --yml-path (string, optional — defaults to azure-pipelines.yml in the repo root). |
Mirrors az pipelines create ergonomics. |
| 3 |
The positional [ORGANIZATION/]PROJECT is parsed via util.ParseProjectScope. |
Consistent with build list and build show. |
| 4 |
--repository-type enum: tfsgit (Azure Repos) or github. Required when --repository is given as a short name (e.g. SampleOrg/SampleRepo or SampleRepoName); auto-detected from the URL when --repository is a URL. |
Mirrors az pipelines create ergonomics. |
| 5 |
Optional: --description (string), --service-connection (string, GitHub service connection id), --queue-id (int), --skip-first-run (bool, default false — first run is allowed), --folder-path (string). |
Mirrors Python pipeline_create parameters. |
| 6 |
Default output is a table that auto-detects the row shape (transform_pipeline_or_run_table_output): renders the created BuildDefinition if --skip-first-run is set, otherwise the first Build run. JSON output via --json passes the raw SDK type to opts.exporter.Write. |
Mirrors transform_pipeline_or_run_table_output from _format.py. |
| 7 |
No confirmation prompt. Create is non-destructive. |
Mirrors az pipelines create. |
| 8 |
No new SDK client, no new helper, no new package beyond internal/cmd/pipelines/create. |
Mandate: minimal code. |
| 9 |
Mocks for CreateDefinition and QueueBuild are already generated. Do not regenerate. |
Verified. |
| 10 |
The Python implementation's interactive template-selection flow (prompts the user to pick a YAML template and renders it via the CIX client) is out of scope. The Go port expects the YAML to already exist in the repo and only requires --yml-path to point at it. If --yml-path is omitted, the SDK defaults to azure-pipelines.yml. |
The azdo CLI is non-interactive by design; mirroring interactive UX is a no-go. |
Command Signature
azdo pipelines create [ORGANIZATION/]PROJECT
--name NAME (string, required)
--repository REPOSITORY (string: URL, name, or Owner/Repo)
--branch BRANCH (string: refs/heads/main, main, refs/pull/1/merge, refs/tags/X)
--repository-type tfsgit|github (enum, default: tfsgit)
--yml-path YAML_PATH (string, default: azure-pipelines.yml)
[--description DESCRIPTION]
[--service-connection SERVICE_CONNECTION_ID] (GitHub only)
[--queue-id QUEUE_ID] (int)
[--folder-path FOLDER_PATH] (string, e.g. "user1/production")
[--skip-first-run] (bool)
[--json ...]
cobra.ExactArgs(1) — args[0] → project scope (via util.ParseProjectScope).
--name is required; --repository and --branch are required unless the local git context can satisfy them.
Flags
| Flag |
Maps to |
Notes |
--name (str, required) |
BuildDefinition.Name |
Unique within project+folder |
--repository (str) |
BuildDefinition.Repository.{Id,Name,Url} |
URL or name; auto-resolved |
--branch (str) |
BuildDefinition.Repository.DefaultBranch and Build.SourceBranch |
Normalized by SDK |
--repository-type (enum) |
BuildDefinition.Repository.Type |
tfsgit or github |
--yml-path (str) |
BuildDefinition.Process.YamlFilename |
Default azure-pipelines.yml |
--description (str) |
BuildDefinition.Description |
Optional |
--service-connection (str) |
BuildDefinition.Repository.Properties.ConnectedServiceId |
GitHub only |
--queue-id (int) |
BuildDefinition.Queue.Id |
Override default queue |
--folder-path (str) |
BuildDefinition.Path |
Folder path within the project |
--skip-first-run (bool) |
post-create behavior |
If true, returns the BuildDefinition; if false, queues and returns the first Build run |
--json / --jq / --template |
util.AddJSONFlags |
JSON export |
JSON Output Contract
Pass the raw SDK type to opts.exporter.Write. The shape is *build.BuildDefinition when --skip-first-run is set, otherwise *build.Build. No view struct is required (per the show-sibling convention from #203 Decision 7 / #205 Decision 11).
Table Output Contract
Mirrors transform_pipeline_or_run_table_output from _format.py. The output auto-detects the row shape by sniffing for a buildNumber field:
- If
buildNumber is present (a Build), renders Run ID, Number, Status, Result, Pipeline ID, Pipeline Name, Source Branch, Queued Time, Reason.
- Otherwise (a
BuildDefinition), renders ID, Path, Name, [Draft?], Status, Default Queue.
Command Wiring
- Package path:
internal/cmd/pipelines/create
- Files:
create.go — NewCmd(ctx util.CmdContext) *cobra.Command + createOptions + runCreate
create_test.go — table-driven gomock tests
- Update
internal/cmd/pipelines/pipelines.go to add create.NewCmd(ctx) as a top-level leaf (cmd.AddCommand(...)). Update the Example block.
- Higher-level parents must already remain wired:
pipelines → create (top-level).
API Surface
Reuse the already-vendored client. No new SDK clients or mocks required.
build.Client.CreateDefinition → Definitions - Create (REST 7.1)
build.Client.QueueBuild → Builds - Queue (REST 7.1)
build.CreateDefinitionArgs struct: {Definition *BuildDefinition, Project *string}.
build.QueueBuildArgs struct: {Build *Build, Project *string}.
build.BuildDefinition model and build.BuildRepository model — see vendor/.../build/models.go.
Mocks for CreateDefinition (:167) and QueueBuild (:1227) are already generated. No mock regeneration needed.
Reference Existing Patterns
References
Sub-issue of #116. Hardened spec — do not re-derive decisions. Mirrors
az pipelines createfrom the Azure CLI and the Python implementation atazure-dev-ops-cli-extension/azure-dev-ops-cli-extension/azure-dev-ops/azure-devops/azext_devops/dev/pipelines/pipeline_create.py#L43-L141.Command Description
Create a new YAML-based Azure Pipeline in a project. The command resolves the repository (by URL, by name, or by auto-detection from the local git checkout), wires up the build definition, and (optionally) triggers the first run. Returns the created
BuildDefinition(when--skip-first-runis set) or the firstBuildrun (when the first run is allowed to proceed).Locked Decisions (do not re-derive)
build.Client.CreateDefinitionandbuild.Client.QueueBuild(not raw HTTP). Mocks already generated atinternal/mocks/build_client_mock.go:167(CreateDefinition) and:1227(QueueBuild).build list(#211) andbuild show(#241).id. Required CLI inputs:--name(string),--repository(string, optional if--repository-typeand--branchimply local git detection),--branch(string, optional only ifdetectis on and the local git repo has a tracked branch),--yml-path(string, optional — defaults toazure-pipelines.ymlin the repo root).az pipelines createergonomics.[ORGANIZATION/]PROJECTis parsed viautil.ParseProjectScope.build listandbuild show.--repository-typeenum:tfsgit(Azure Repos) orgithub. Required when--repositoryis given as a short name (e.g.SampleOrg/SampleRepoorSampleRepoName); auto-detected from the URL when--repositoryis a URL.az pipelines createergonomics.--description(string),--service-connection(string, GitHub service connection id),--queue-id(int),--skip-first-run(bool, default false — first run is allowed),--folder-path(string).pipeline_createparameters.transform_pipeline_or_run_table_output): renders the createdBuildDefinitionif--skip-first-runis set, otherwise the firstBuildrun. JSON output via--jsonpasses the raw SDK type toopts.exporter.Write.transform_pipeline_or_run_table_outputfrom_format.py.az pipelines create.internal/cmd/pipelines/create.CreateDefinitionandQueueBuildare already generated. Do not regenerate.--yml-pathto point at it. If--yml-pathis omitted, the SDK defaults toazure-pipelines.yml.azdoCLI is non-interactive by design; mirroring interactive UX is a no-go.Command Signature
cobra.ExactArgs(1)—args[0]→ project scope (viautil.ParseProjectScope).--nameis required;--repositoryand--branchare required unless the local git context can satisfy them.Flags
--name(str, required)BuildDefinition.Name--repository(str)BuildDefinition.Repository.{Id,Name,Url}--branch(str)BuildDefinition.Repository.DefaultBranchandBuild.SourceBranch--repository-type(enum)BuildDefinition.Repository.Typetfsgitorgithub--yml-path(str)BuildDefinition.Process.YamlFilenameazure-pipelines.yml--description(str)BuildDefinition.Description--service-connection(str)BuildDefinition.Repository.Properties.ConnectedServiceId--queue-id(int)BuildDefinition.Queue.Id--folder-path(str)BuildDefinition.Path--skip-first-run(bool)BuildDefinition; if false, queues and returns the firstBuildrun--json/--jq/--templateutil.AddJSONFlagsJSON Output Contract
Pass the raw SDK type to
opts.exporter.Write. The shape is*build.BuildDefinitionwhen--skip-first-runis set, otherwise*build.Build. No view struct is required (per the show-sibling convention from #203 Decision 7 / #205 Decision 11).Table Output Contract
Mirrors
transform_pipeline_or_run_table_outputfrom_format.py. The output auto-detects the row shape by sniffing for abuildNumberfield:buildNumberis present (aBuild), rendersRun ID, Number, Status, Result, Pipeline ID, Pipeline Name, Source Branch, Queued Time, Reason.BuildDefinition), rendersID, Path, Name, [Draft?], Status, Default Queue.Command Wiring
internal/cmd/pipelines/createcreate.go—NewCmd(ctx util.CmdContext) *cobra.Command+createOptions+runCreatecreate_test.go— table-driven gomock testsinternal/cmd/pipelines/pipelines.goto addcreate.NewCmd(ctx)as a top-level leaf (cmd.AddCommand(...)). Update theExampleblock.pipelines→create(top-level).API Surface
Reuse the already-vendored client. No new SDK clients or mocks required.
build.Client.CreateDefinition→ Definitions - Create (REST 7.1)build.Client.QueueBuild→ Builds - Queue (REST 7.1)build.CreateDefinitionArgsstruct:{Definition *BuildDefinition, Project *string}.build.QueueBuildArgsstruct:{Build *Build, Project *string}.build.BuildDefinitionmodel andbuild.BuildRepositorymodel — seevendor/.../build/models.go.Mocks for
CreateDefinition(:167) andQueueBuild(:1227) are already generated. No mock regeneration needed.Reference Existing Patterns
azure-dev-ops-cli-extension/azure-dev-ops-cli-extension/azure-dev-ops/azure-devops/azext_devops/dev/pipelines/pipeline_create.py#L43-L141—pipeline_createPython implementation.azure-dev-ops-cli-extension/azure-dev-ops-cli-extension/azure-dev-ops/azure-devops/azext_devops/dev/pipelines/commands.py#L75—g.command('create', 'pipeline_create', table_transformer=transform_pipeline_or_run_table_output).azure-dev-ops-cli-extension/azure-dev-ops-cli-extension/azure-dev-ops/azure-devops/azext_devops/dev/pipelines/_format.py—transform_pipeline_or_run_table_output(auto-detecting transformer).internal/cmd/pipelines/variablegroup/create/create.go— closest create sibling (project-scoped, has its own umbrella feat: Introduceazdo pipelines variable-groupsubgroup #117). Provides the modern create-command pattern.internal/cmd/pipelines/variablegroup/list/list.go— primary list reference for the modern list pattern, JSON view struct, table printer.createdoes not use--max-items; the table emits exactly one row.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture.internal/mocks/build_client_mock.go:167— mock forCreateDefinition(already generated).internal/mocks/build_client_mock.go:1227— mock forQueueBuild(already generated).internal/azdo/factory.go:61—ClientFactory().Build(...)accessor (reuse).References
azext_devops/dev/pipelines/pipeline_create.pyazext_devops/dev/pipelines/commands.pyazext_devops/dev/pipelines/_format.py