Modules/businessdev.ALbuild.Feeds/Public/ConvertFrom-BcRemoteUrl.ps1

function ConvertFrom-BcRemoteUrl {
    <#
    .SYNOPSIS
        Derives the Azure DevOps organization and project from a git remote URL.
 
    .DESCRIPTION
        The org/project a project-scoped Universal feed needs used to be parsed with a single regex that
        only matched the HTTPS remote format (dev.azure.com/{org}/{project}/_git/...). Azure Pipelines
        checks out exactly that way, so the pipeline worked; an SSH-based checkout (our agents / any SSH
        clone) uses the SSH v3 form (git@ssh.dev.azure.com:v3/{org}/{project}/{repo}), which the HTTPS
        regex does NOT match - the project came back empty, the Universal download ran org-scoped, the
        project-scoped feed was not found, and the failure surfaced much later as AL1024 on publish.
 
        This function recognizes every common Azure DevOps remote format so org/project resolve the same
        regardless of how the repo was cloned:
          * HTTPS https://[user@]dev.azure.com/{org}/{project}/_git/{repo}
          * Legacy HTTPS https://{org}.visualstudio.com/{project}/_git/{repo}
          * SSH v3 [user@]ssh.dev.azure.com:v3/{org}/{project}/{repo}
          * SSH host-alias <alias>:v3/{org}/{project}/{repo} (a ~/.ssh/config Host alias)
 
        Returns empty strings for both when nothing is recognized (e.g. a non-Azure-DevOps remote), so the
        caller can fall back to org-scoped behaviour without a hard failure.
 
    .PARAMETER RemoteUrl
        The output of `git remote get-url origin` (or any git remote URL).
 
    .OUTPUTS
        PSCustomObject with Organization (a full https://dev.azure.com/{org}/ URL, or '') and Project (the
        URL-decoded project name, or ''). The project name is URL-encoded in every remote format
        (e.g. 'Dynamics%20365%20...') and is decoded here.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param([string] $RemoteUrl)

    $result = [PSCustomObject]@{ Organization = ''; Project = '' }
    if ([string]::IsNullOrWhiteSpace($RemoteUrl)) { return $result }

    $u = $RemoteUrl.Trim()
    $org = ''; $proj = ''
    switch -Regex ($u) {
        # HTTPS: https://[user@]dev.azure.com/{org}/{project}/_git/{repo}
        'dev\.azure\.com/([^/]+)/([^/]+)/_git/' { $org = $matches[1]; $proj = $matches[2]; break }
        # Legacy HTTPS: https://{org}.visualstudio.com/{project}/_git/{repo}
        '://([^./@]+)\.visualstudio\.com/([^/]+)/_git/' { $org = $matches[1]; $proj = $matches[2]; break }
        # SSH v3 (real host ssh.dev.azure.com OR a Host alias): [user@]<host>:v3/{org}/{project}[/repo]
        ':v3/([^/]+)/([^/]+)' { $org = $matches[1]; $proj = $matches[2]; break }
    }

    if ($org) {
        $result.Organization = if ($org -match '^https?://') { $org } else { "https://dev.azure.com/$org/" }
        # The project name is URL-encoded in every format (e.g. 'Dynamics%20365%20...').
        $result.Project = [uri]::UnescapeDataString($proj)
    }
    return $result
}