diff --git a/packages/integration/src/azure/AzureUrl.ts b/packages/integration/src/azure/AzureUrl.ts index 2a0c082831..add03b7876 100644 --- a/packages/integration/src/azure/AzureUrl.ts +++ b/packages/integration/src/azure/AzureUrl.ts @@ -14,14 +14,77 @@ * limitations under the License. */ +const VERSION_PREFIX_GIT_BRANCH = 'GB'; + export class AzureUrl { /** * Parses an azure URL as copied from the browser address bar. * * Throws an error if the URL is not a valid azure repo URL. */ - static fromRepoUrl(_repoUrl: string): AzureUrl { - throw new Error('not implemented'); + static fromRepoUrl(repoUrl: string): AzureUrl { + const url = new URL(repoUrl); + + let owner; + let project; + let repo; + + const parts = url.pathname.split('/').map(part => decodeURIComponent(part)); + if (parts[2] === '_git') { + owner = parts[1]; + project = repo = parts[3]; + } else if (parts[3] === '_git') { + owner = parts[1]; + project = parts[2]; + repo = parts[4]; + } + + if (!owner || !project || !repo) { + throw new Error('Azure URL must point to a git repository'); + } + + const path = url.searchParams.get('path') ?? undefined; + + let ref; + const version = url.searchParams.get('version'); + if (version) { + const prefix = version.slice(0, 2); + if (prefix !== 'GB') { + throw new Error('Azure URL version must point to a git branch'); + } + ref = version.slice(2); + } + + return new AzureUrl(url.origin, owner, project, repo, path, ref); + } + + #origin: string; + #owner: string; + #project: string; + #repo: string; + #path?: string; + #ref?: string; + + private constructor( + origin: string, + owner: string, + project: string, + repo: string, + path?: string, + ref?: string, + ) { + this.#origin = origin; + this.#owner = owner; + this.#project = project; + this.#repo = repo; + this.#path = path; + this.#ref = ref; + } + + #baseUrl(...parts: string[]): URL { + const url = new URL(this.#origin); + url.pathname = parts.map(part => encodeURIComponent(part)).join('/'); + return url; } /** @@ -30,7 +93,21 @@ export class AzureUrl { * Throws an error if the URL is not a valid azure repo URL. */ toRepoUrl(): string { - throw new Error('not implemented'); + let url; + if (this.#project === this.#repo) { + url = this.#baseUrl(this.#owner, '_git', this.#repo); + } else { + url = this.#baseUrl(this.#owner, this.#project, '_git', this.#repo); + } + + if (this.#path) { + url.searchParams.set('path', this.#path); + } + if (this.#ref) { + url.searchParams.set('version', VERSION_PREFIX_GIT_BRANCH + this.#ref); + } + + return url.toString(); } /** @@ -39,7 +116,29 @@ export class AzureUrl { * Throws an error if the URL does not point to a file. */ toFileUrl(): string { - throw new Error('not implemented'); + if (!this.#path) { + throw new Error( + 'Azure URL must point to a specific path to be able to download a file', + ); + } + + const url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + url.searchParams.set('api-version', '6.0'); + url.searchParams.set('path', this.#path); + + if (this.#ref) { + url.searchParams.set('version', this.#ref); + } + + return url.toString(); } /** @@ -48,7 +147,27 @@ export class AzureUrl { * Throws an error if the URL does not point to a repo. */ toArchiveUrl(): string { - throw new Error('not implemented'); + const url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + url.searchParams.set('recursionLevel', 'full'); + url.searchParams.set('download', 'true'); + url.searchParams.set('api-version', '6.0'); + + if (this.#path) { + url.searchParams.set('scopePath', this.#path); + } + if (this.#ref) { + url.searchParams.set('version', this.#ref); + } + + return url.toString(); } /** @@ -57,41 +176,56 @@ export class AzureUrl { * Throws an error if the URL does not point to a commit. */ toCommitsUrl(): string { - throw new Error('not implemented'); + const url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'commits', + ); + url.searchParams.set('api-version', '6.0'); + + if (this.#ref) { + url.searchParams.set('searchCriteria.itemVersion.version', this.#ref); + } + + return url.toString(); } /** * Returns the name of the owner, a user or an organization. */ getOwner(): string { - throw new Error('not implemented'); + return this.#owner; } /** * Returns the name of the project. */ getProject(): string { - throw new Error('not implemented'); + return this.#project; } /** * Returns the name of the repo. */ getRepo(): string { - throw new Error('not implemented'); + return this.#repo; } /** * Returns the file path within the repo if the URL contains one. */ getPath(): string | undefined { - throw new Error('not implemented'); + return this.#path; } /** * Returns the git ref in the repo if the URL contains one. */ getRef(): string | undefined { - throw new Error('not implemented'); + return this.#ref; } }