From 4db144a31a1f11b0200e0be90e11a3fbcd892df6 Mon Sep 17 00:00:00 2001 From: James Devine Date: Sat, 16 May 2026 22:21:27 +0100 Subject: [PATCH] fix(configure): accept bare org name for --org Previously, do-aw configure --org myorg would build malformed ADO API URLs because the value was used verbatim as the organization URL. When no usable ADO git remote is present (e.g. when the pipeline source lives in GitHub but runs in Azure DevOps), this made --definition-ids unusable without forcing users to pass the full https://dev.azure.com/myorg URL. Add a ormalize_org_url helper that: - prefixes the canonical https://dev.azure.com/ host when a bare org name is passed - rewrites the legacy {org}.visualstudio.com form to dev.azure.com/{org}`n- trims whitespace and trailing slashes Apply it in both branches of esolve_ado_context (override-on-ADO-remote and explicit-no-remote), update the --org CLI help text and docs/cli.md, and add unit tests covering bare names, full URLs, trailing slashes, legacy hosts, and whitespace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/cli.md | 2 +- src/configure.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 3 +- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index ef07ed50..f0ddee39 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -37,7 +37,7 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg - `configure` - Detect agentic pipelines in a local repository and update the `GITHUB_TOKEN` pipeline variable on their Azure DevOps build definitions - `--token ` / `GITHUB_TOKEN` env var - The new GITHUB_TOKEN value (prompted if omitted) - - `--org ` - Override: Azure DevOps organization URL (inferred from git remote by default) + - `--org ` - Override: Azure DevOps organization URL (e.g. `https://dev.azure.com/myorg`) or just the org name (e.g. `myorg`, auto-prefixed to the canonical URL). Inferred from git remote by default. - `--project ` - Override: Azure DevOps project name (inferred from git remote by default) - `--pat ` / `AZURE_DEVOPS_EXT_PAT` env var - PAT for ADO API authentication (prompted if omitted) - `--path ` - Path to the repository root (defaults to current directory) diff --git a/src/configure.rs b/src/configure.rs index 253f9788..c70e2834 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -652,6 +652,33 @@ async fn resolve_auth(pat: Option<&str>) -> Result { } } +/// Normalize a `--org` value to a full ADO organization URL. +/// +/// Users commonly pass just the org name (e.g. `myorg`) instead of the full +/// URL (`https://dev.azure.com/myorg`). Accept both forms by prefixing the +/// canonical `https://dev.azure.com/` host when the input has no scheme. +/// +/// Also accepts the legacy `{org}.visualstudio.com` form and rewrites it to +/// the modern `dev.azure.com/{org}` form for consistency with `parse_ado_remote`. +pub fn normalize_org_url(org: &str) -> String { + let trimmed = org.trim().trim_end_matches('/'); + + // Bare org name: no scheme, no dots — assume it's just the org. + if !trimmed.contains("://") && !trimmed.contains('/') && !trimmed.contains('.') { + return format!("https://dev.azure.com/{}", trimmed); + } + + // Legacy `https://{org}.visualstudio.com` → `https://dev.azure.com/{org}`. + if let Ok(url) = url::Url::parse(trimmed) + && let Some(host) = url.host_str() + && let Some(org) = host.strip_suffix(".visualstudio.com") + { + return format!("https://dev.azure.com/{}", org); + } + + trimmed.to_string() +} + /// Resolves the ADO context from the git remote (best-effort) with CLI overrides. /// Falls back to explicit `--org`/`--project` when the remote is absent or non-ADO. async fn resolve_ado_context( @@ -677,7 +704,7 @@ async fn resolve_ado_context( // Git remote parsed — apply overrides (Some(mut ctx), org, project) => { if let Some(org) = org { - ctx.org_url = org.to_string(); + ctx.org_url = normalize_org_url(org); } if let Some(project) = project { ctx.project = project.to_string(); @@ -688,7 +715,7 @@ async fn resolve_ado_context( (None, Some(org), Some(project)) => { info!("No ADO git remote; using --org and --project"); Ok(AdoContext { - org_url: org.to_string(), + org_url: normalize_org_url(org), project: project.to_string(), repo_name: String::new(), }) @@ -922,6 +949,52 @@ mod tests { assert!(parse_ado_remote("not-a-url").is_err()); } + // ==================== Org URL normalization ==================== + + #[test] + fn normalize_org_url_accepts_bare_name() { + assert_eq!( + normalize_org_url("myorg"), + "https://dev.azure.com/myorg" + ); + } + + #[test] + fn normalize_org_url_preserves_full_url() { + assert_eq!( + normalize_org_url("https://dev.azure.com/myorg"), + "https://dev.azure.com/myorg" + ); + } + + #[test] + fn normalize_org_url_strips_trailing_slash() { + assert_eq!( + normalize_org_url("https://dev.azure.com/myorg/"), + "https://dev.azure.com/myorg" + ); + } + + #[test] + fn normalize_org_url_rewrites_legacy_visualstudio() { + assert_eq!( + normalize_org_url("https://myorg.visualstudio.com"), + "https://dev.azure.com/myorg" + ); + assert_eq!( + normalize_org_url("https://myorg.visualstudio.com/"), + "https://dev.azure.com/myorg" + ); + } + + #[test] + fn normalize_org_url_trims_whitespace() { + assert_eq!( + normalize_org_url(" myorg "), + "https://dev.azure.com/myorg" + ); + } + // ==================== Fuzzy name matching ==================== fn make_def(id: u64, name: &str) -> DefinitionSummary { diff --git a/src/main.rs b/src/main.rs index 676a86a3..2cc3f75a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,7 +118,8 @@ enum Commands { /// The new GITHUB_TOKEN value (defaults to GITHUB_TOKEN env var; prompted if omitted) #[arg(long, env = "GITHUB_TOKEN")] token: Option, - /// Override: Azure DevOps organization URL (inferred from git remote by default) + /// Override: Azure DevOps organization (URL like `https://dev.azure.com/myorg`, + /// or just the org name `myorg`). Inferred from git remote by default. #[arg(long)] org: Option, /// Override: Azure DevOps project name (inferred from git remote by default)