diff --git a/tools/installer/modules/custom-module-manager.js b/tools/installer/modules/custom-module-manager.js index 482c4dc43..9d95b9101 100644 --- a/tools/installer/modules/custom-module-manager.js +++ b/tools/installer/modules/custom-module-manager.js @@ -73,6 +73,56 @@ class CustomModuleManager { }; } + // Azure DevOps URL: https://dev.azure.com/{org}/{project}/_git/{repo} + // Also supports legacy: https://{org}.visualstudio.com/{project}/_git/{repo} + const adoModernMatch = trimmed.match( + /^https?:\/\/(dev\.azure\.com)\/([^/]+)\/([^/]+)\/_git\/([^/.]+?)(?:\.git)?(\/.*)?$/, + ); + const adoLegacyMatch = + !adoModernMatch && + trimmed.match( + /^https?:\/\/([^/]+\.visualstudio\.com)\/([^/]+)\/_git\/([^/.]+?)(?:\.git)?(\/.*)?$/, + ); + const adoMatch = adoModernMatch || adoLegacyMatch; + if (adoMatch) { + let host, org, project, repo, remainder; + if (adoModernMatch) { + [, host, org, project, repo, remainder] = adoModernMatch; + } else { + // Legacy: org is in the hostname, path is /{project}/_git/{repo} + [, host, project, repo, remainder] = adoLegacyMatch; + org = null; + } + + const cloneUrl = adoModernMatch + ? `https://${host}/${org}/${project}/_git/${repo}` + : `https://${host}/${project}/_git/${repo}`; + let subdir = null; + + if (remainder) { + // Azure DevOps uses ?path=/subdir or /path/subdir patterns + const subdirMatch = remainder.match(/^\/(.+)$/); + if (subdirMatch) { + subdir = subdirMatch[1].replace(/\/$/, ''); + } + } + + const cacheKey = adoModernMatch + ? `${host}/${org}/${project}/${repo}` + : `${host}/${project}/${repo}`; + + return { + type: 'url', + cloneUrl, + subdir, + localPath: null, + cacheKey, + displayName: `${project}/${repo}`, + isValid: true, + error: null, + }; + } + // HTTPS URL: https://host/owner/repo[/tree/branch/subdir][.git] const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/); if (httpsMatch) {