backend-common: implement UrlReader.search for the other providers too
This commit is contained in:
@@ -19,6 +19,7 @@ import { AzureIntegration } from './azure/AzureIntegration';
|
||||
import { BitbucketIntegration } from './bitbucket/BitbucketIntegration';
|
||||
import { GitHubIntegration } from './github/GitHubIntegration';
|
||||
import { GitLabIntegration } from './gitlab/GitLabIntegration';
|
||||
import { defaultScmResolveUrl } from './helpers';
|
||||
import {
|
||||
ScmIntegration,
|
||||
ScmIntegrationRegistry,
|
||||
@@ -83,11 +84,11 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
}
|
||||
|
||||
resolveUrl(options: { url: string; base: string }): string {
|
||||
const resolve = this.byUrl(options.base)?.resolveUrl;
|
||||
if (!resolve) {
|
||||
return new URL(options.url, options.base).toString();
|
||||
const integration = this.byUrl(options.base);
|
||||
if (!integration) {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
|
||||
return resolve(options);
|
||||
return integration.resolveUrl(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ describe('AzureIntegration', () => {
|
||||
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
|
||||
);
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/a.yaml',
|
||||
base:
|
||||
'https://dev.azure.com/organization/project/_git/repository?path=%2Ffolder%2Fcatalog-info.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
|
||||
);
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './a.yaml',
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { AzureIntegration } from './AzureIntegration';
|
||||
export {
|
||||
readAzureIntegrationConfig,
|
||||
readAzureIntegrationConfigs,
|
||||
} from './config';
|
||||
export type { AzureIntegrationConfig } from './config';
|
||||
export {
|
||||
getAzureCommitsUrl,
|
||||
getAzureDownloadUrl,
|
||||
getAzureFileFetchUrl,
|
||||
getAzureRequestOptions,
|
||||
getAzureCommitsUrl,
|
||||
} from './core';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { basicIntegrations } from '../helpers';
|
||||
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import {
|
||||
BitbucketIntegrationConfig,
|
||||
@@ -47,4 +47,8 @@ export class BitbucketIntegration implements ScmIntegration {
|
||||
get config(): BitbucketIntegrationConfig {
|
||||
return this.integrationConfig;
|
||||
}
|
||||
|
||||
resolveUrl(options: { url: string; base: string }): string {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { BitbucketIntegration } from './BitbucketIntegration';
|
||||
export {
|
||||
readBitbucketIntegrationConfig,
|
||||
readBitbucketIntegrationConfigs,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { basicIntegrations } from '../helpers';
|
||||
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import {
|
||||
GitHubIntegrationConfig,
|
||||
@@ -45,4 +45,8 @@ export class GitHubIntegration implements ScmIntegration {
|
||||
get config(): GitHubIntegrationConfig {
|
||||
return this.integrationConfig;
|
||||
}
|
||||
|
||||
resolveUrl(options: { url: string; base: string }): string {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,3 +21,4 @@ export {
|
||||
export type { GitHubIntegrationConfig } from './config';
|
||||
export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core';
|
||||
export { GithubCredentialsProvider } from './GithubCredentialsProvider';
|
||||
export { GitHubIntegration } from './GitHubIntegration';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { basicIntegrations } from '../helpers';
|
||||
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import {
|
||||
GitLabIntegrationConfig,
|
||||
@@ -45,4 +45,8 @@ export class GitLabIntegration implements ScmIntegration {
|
||||
get config(): GitLabIntegrationConfig {
|
||||
return this.integrationConfig;
|
||||
}
|
||||
|
||||
resolveUrl(options: { url: string; base: string }): string {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,3 +20,4 @@ export {
|
||||
} from './config';
|
||||
export type { GitLabIntegrationConfig } from './config';
|
||||
export { getGitLabFileFetchUrl, getGitLabRequestOptions } from './core';
|
||||
export { GitLabIntegration } from './GitLabIntegration';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isValidHost } from './helpers';
|
||||
import { defaultScmResolveUrl, isValidHost } from './helpers';
|
||||
|
||||
describe('isValidHost', () => {
|
||||
it.each([
|
||||
@@ -51,3 +51,69 @@ describe('isValidHost', () => {
|
||||
expect(isValidHost(str)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultScmResolveUrl', () => {
|
||||
it('works for relative paths and retains query params', () => {
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: './b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml',
|
||||
);
|
||||
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: './b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml?at=master',
|
||||
);
|
||||
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: 'b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
it('works for absolute paths and retains query params', () => {
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: '/other/b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/other/b.yaml',
|
||||
);
|
||||
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: '/other/b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/other/b.yaml?at=master',
|
||||
);
|
||||
});
|
||||
|
||||
it('works for full urls and throws away query params', () => {
|
||||
expect(
|
||||
defaultScmResolveUrl({
|
||||
url: 'https://b.com/b.yaml',
|
||||
base:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
|
||||
}),
|
||||
).toBe('https://b.com/b.yaml');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { ScmIntegration, ScmIntegrationsGroup } from './types';
|
||||
|
||||
/** Checks whether the given argument is a valid URL hostname */
|
||||
@@ -51,3 +52,43 @@ export function basicIntegrations<T extends ScmIntegration>(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of ScmIntegration.resolveUrl, that only works with
|
||||
* URL pathname based providers.
|
||||
*/
|
||||
export function defaultScmResolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
}): string {
|
||||
const { url, base } = options;
|
||||
|
||||
// If it is a fully qualified URL - then return it verbatim
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return url;
|
||||
} catch {
|
||||
// ignore intentionally
|
||||
}
|
||||
|
||||
let updated: URL;
|
||||
|
||||
if (url.startsWith('/')) {
|
||||
// If it is an absolute path, move relative to the repo root
|
||||
const { filepath } = parseGitUrl(base);
|
||||
updated = new URL(base);
|
||||
const repoRootPath = updated.pathname
|
||||
.substring(0, updated.pathname.length - filepath.length)
|
||||
.replace(/\/+$/, '');
|
||||
updated.pathname = `${repoRootPath}${url}`;
|
||||
} else {
|
||||
// For relative URLs, just let the default URL constructor handle the
|
||||
// resolving. Note that this essentially will treat the last segment of the
|
||||
// base as a file - NOT a folder - unless the url ends in a slash.
|
||||
updated = new URL(url, base);
|
||||
}
|
||||
|
||||
updated.search = new URL(base).search;
|
||||
return updated.toString();
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ export * from './azure';
|
||||
export * from './bitbucket';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export { defaultScmResolveUrl } from './helpers';
|
||||
export { ScmIntegrations } from './ScmIntegrations';
|
||||
export type { ScmIntegration, ScmIntegrationRegistry } from './types';
|
||||
|
||||
@@ -36,16 +36,21 @@ export interface ScmIntegration {
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Works like the two-argument form of the URL constructor, resolving an
|
||||
* absolute or relative URL in relation to a base URL.
|
||||
* Resolves an absolute or relative URL in relation to a base URL.
|
||||
*
|
||||
* If this method is not implemented, the URL constructor is used instead for
|
||||
* URLs that match this integration.
|
||||
* This method is adapted for use within SCM systems, so relative URLs are
|
||||
* within the context of the root of the hierarchy pointed to by the base
|
||||
* URL.
|
||||
*
|
||||
* For example, if the base URL is `<repo root url>/folder/a.yaml`, i.e.
|
||||
* within the file tree of a certain repo, an absolute path of `/b.yaml` does
|
||||
* not resolve to `https://hostname/b.yaml` but rather to
|
||||
* `<repo root url>/b.yaml` inside the file tree of that same repo.
|
||||
*
|
||||
* @param options.url The (absolute or relative) URL or path to resolve
|
||||
* @param options.base The base URL onto which this resolution happens
|
||||
*/
|
||||
resolveUrl?(options: { url: string; base: string }): string;
|
||||
resolveUrl(options: { url: string; base: string }): string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,8 +88,16 @@ export interface ScmIntegrationRegistry
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
|
||||
/**
|
||||
* Works like the two-argument form of the URL constructor, resolving an
|
||||
* absolute or relative URL in relation to a base URL.
|
||||
* Resolves an absolute or relative URL in relation to a base URL.
|
||||
*
|
||||
* This method is adapted for use within SCM systems, so relative URLs are
|
||||
* within the context of the root of the hierarchy pointed to by the base
|
||||
* URL.
|
||||
*
|
||||
* For example, if the base URL is `<repo root url>/folder/a.yaml`, i.e.
|
||||
* within the file tree of a certain repo, an absolute path of `/b.yaml` does
|
||||
* not resolve to `https://hostname/b.yaml` but rather to
|
||||
* `<repo root url>/b.yaml` inside the file tree of that same repo.
|
||||
*
|
||||
* @param options.url The (absolute or relative) URL or path to resolve
|
||||
* @param options.base The base URL onto which this resolution happens
|
||||
|
||||
Reference in New Issue
Block a user