feat: devops-support - #13
Conversation
Auto-detect the hosting provider from the origin remote URL (dev.azure.com / *.visualstudio.com -> Azure DevOps, else GitHub) with a bflow.hosting.provider git-config override. PRs on Azure DevOps are created via the az CLI (azure-devops extension); the PR web URL is built from the parsed org/project/repo since az's repository.webUrl is unreliable. No new dependencies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds Azure DevOps support to bflow by auto-detecting the hosting provider from the origin remote URL (with a git-config override) and routing PR creation/auth checks through the appropriate CLI (gh or az).
Changes:
- Introduces hosting provider detection (
originURL parsing +bflow.hosting.provideroverride). - Adds an Azure DevOps hosting backend using the Azure CLI +
azure-devopsextension. - Refactors hosting initialization and URL opening to be provider-agnostic.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/common/mod.rs | Extends MockGit to support remote_url() for provider detection tests and flows. |
| src/menu.rs | Simplifies Up-arrow selection logic using saturating_sub. |
| src/main.rs | Adds provider detection + dynamic hosting backend creation and preflight checks. |
| src/hosting/mod.rs | Adds provider modules and centralizes browser URL opening as a default trait method. |
| src/hosting/github.rs | Removes provider-specific URL opening and adds Default impl. |
| src/hosting/devops.rs | New Azure DevOps implementation using az repos pr ... commands. |
| src/hosting/detect.rs | New provider detection logic based on origin remote and config override. |
| src/git/mod.rs | Extends Git trait and GitCli with remote_url() for detection. |
| README.md | Documents Azure DevOps requirements, provider detection, and template fallback behavior. |
| .claude/skills/bflow/SKILL.md | Updates operational guidance to include Azure DevOps CLI commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn check_auth(&self) -> Result<()> { | ||
| // Explicit extension check first: it also prevents az's interactive | ||
| // dynamic-install prompt from firing inside a non-tty command later. | ||
| self.run_az(&["extension".into(), "show".into(), "--name".into(), "azure-devops".into()]) | ||
| .map_err(|_| "Azure DevOps CLI extension is missing. Run 'az extension add --name azure-devops'.".to_string())?; | ||
| self.run_az(&["account".into(), "show".into()]).map(|_| ()) | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed in 287f27d. check_auth now probes actual repo access with az repos show (scoped to the detected org/project/repo) instead of az account show. This passes for both az login sessions and PAT auth via az devops login / AZURE_DEVOPS_EXT_PAT, and it also verifies the user can reach the repo itself. README and the bflow skill doc now mention PAT auth.
| /// Open a URL in the OS default browser (platform dispatch, provider-agnostic). | ||
| pub fn open_in_browser(url: &str) -> Result<()> { | ||
| use std::process::Command; | ||
| #[cfg(target_os = "macos")] | ||
| let result = Command::new("open").arg(url).output(); | ||
| #[cfg(target_os = "windows")] | ||
| let result = Command::new("cmd").args(["/C", "start", "", url]).output(); | ||
| #[cfg(target_os = "linux")] | ||
| let result = Command::new("xdg-open").arg(url).output(); | ||
| result.map_err(|e| format!("Failed to open URL: {e}"))?; | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Fixed in 287f27d. open_in_browser now defines result as an Err on targets outside macOS/Windows/Linux, so it compiles everywhere and reports the platform as unsupported at runtime. It also checks the child exit status and surfaces stderr when the opener returns non-zero.
Code reviewFound 2 issues:
If beans-gitflow/src/hosting/devops.rs Lines 129 to 134 in 34efd59
Both methods are structurally identical (spawn beans-gitflow/src/hosting/github.rs Lines 12 to 23 in 34efd59 beans-gitflow/src/hosting/devops.rs Lines 31 to 42 in 34efd59 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
| if selected > 0 { | ||
| selected -= 1; | ||
| } | ||
| selected = selected.saturating_sub(1); |
There was a problem hiding this comment.
This one-line refactor of show_select's Up-arrow handler (if selected > 0 { selected -= 1; } → selected = selected.saturating_sub(1);) is behaviorally identical, but show_select is a generic terminal-menu widget completely unrelated to the Azure DevOps hosting feature this PR delivers — no call site or type in src/hosting/** or src/main.rs's create_hosting() touches it.
Per CLAUDE.md's Minimal Impact rule ("Changes should only touch what's necessary"), drive-by cleanups like this should be split into a separate commit/PR so the feature diff stays scoped. Not a bug — pure scope concern.
Reference:
Lines 167 to 171 in 34efd59
There was a problem hiding this comment.
Reverted in 287f27d. The saturating_sub cleanup came from a clippy suggestion, but it is unrelated to this feature, so src/menu.rs is back to the develop version and the diff stays scoped. The cleanup can go in a separate chore PR.
- Extract shared run_cli helper in hosting/mod.rs; run_gh/run_az delegate to it (DRY) - check_auth (Azure DevOps): propagate the real az error instead of swallowing it, and probe repo access via 'az repos show' instead of 'az account show' so PAT auth (az devops login) passes preflight - open_in_browser: compile-safe fallback for unsupported platforms and surface non-zero exit statuses as errors - Revert unrelated menu.rs cleanup to keep the feature diff scoped - Sync README.md and bflow SKILL.md auth wording (PAT support) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both issues from the review are fixed in 287f27d. 1. check_auth error swallowing The extension check now appends the real az error to the hint instead of discarding it, matching the pattern from ea3216c. The 2. Duplicated run_gh / run_az Extracted a shared Also reverted the unrelated |
No description provided.