integrations: move common integration concerns to a separate package (#3295)

This commit is contained in:
Fredrik Adelöw
2020-11-16 16:15:09 +01:00
committed by GitHub
parent 04c0698df8
commit 7b37e68348
25 changed files with 1086 additions and 403 deletions
+1
View File
@@ -32,6 +32,7 @@
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.2.0",
"@backstage/integration": "^0.1.0",
"@backstage/test-utils": "^0.1.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
@@ -14,49 +14,27 @@
* limitations under the License.
*/
import {
AzureIntegrationConfig,
readAzureIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import { Config } from '@backstage/config';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
type Options = {
// TODO: added here for future support, but we only allow dev.azure.com for now
host: string;
token?: string;
};
function readConfig(config: Config): Options[] {
const optionsArr = Array<Options>();
const providerConfigs =
config.getOptionalConfigArray('integrations.azure') ?? [];
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'dev.azure.com';
const token = providerConfig.getOptionalString('token');
optionsArr.push({ host, token });
}
// As a convenience we always make sure there's at least an unauthenticated
// reader for public azure repos.
if (!optionsArr.some(p => p.host === 'dev.azure.com')) {
optionsArr.push({ host: 'dev.azure.com' });
}
return optionsArr;
}
export class AzureUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(options => {
const configs = readAzureIntegrationConfigs(
config.getOptionalConfigArray('integrations.azure') ?? [],
);
return configs.map(options => {
const reader = new AzureUrlReader(options);
const predicate = (url: URL) => url.host === options.host;
return { reader, predicate };
});
};
constructor(private readonly options: Options) {
constructor(private readonly options: AzureIntegrationConfig) {
if (options.host !== 'dev.azure.com') {
throw Error(
`Azure integration currently only supports 'dev.azure.com', tried to use host '${options.host}'`,
@@ -14,24 +14,22 @@
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { BitbucketIntegrationConfig } from '@backstage/integration';
import {
BitbucketUrlReader,
getApiRequestOptions,
getApiUrl,
ProviderConfig,
readConfig,
} from './BitbucketUrlReader';
describe('BitbucketUrlReader', () => {
describe('getApiRequestOptions', () => {
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -44,13 +42,13 @@ describe('BitbucketUrlReader', () => {
});
it('insert basic auth when needed', () => {
const withUsernameAndPassword: ProviderConfig = {
const withUsernameAndPassword: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
username: 'some-user',
appPassword: 'my-secret',
};
const withoutUsernameAndPassword: ProviderConfig = {
const withoutUsernameAndPassword: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -67,11 +65,11 @@ describe('BitbucketUrlReader', () => {
describe('getApiUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: BitbucketIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for Bitbucket Cloud', () => {
const config: ProviderConfig = {
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
};
@@ -87,7 +85,7 @@ describe('BitbucketUrlReader', () => {
);
});
it('happy path for Bitbucket Server', () => {
const config: ProviderConfig = {
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.mycompany.net',
apiBaseUrl: 'https://bitbucket.mycompany.net/rest/api/1.0',
};
@@ -104,66 +102,6 @@ describe('BitbucketUrlReader', () => {
});
});
describe('readConfig', () => {
function config(
providers: {
host: string;
apiBaseUrl?: string;
token?: string;
username?: string;
password?: string;
}[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
integrations: { bitbucket: providers },
},
},
]);
}
it('adds a default Bitbucket Cloud entry when missing', () => {
const output = readConfig(config([]));
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
it('injects the correct Bitbucket Cloud API base URL when missing', () => {
const output = readConfig(config([{ host: 'bitbucket.org' }]));
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
it('rejects custom targets with no base URLs', () => {
expect(() =>
readConfig(config([{ host: 'bitbucket.mycompany.net' }])),
).toThrow(
"Bitbucket integration for 'bitbucket.mycompany.net' must configure an explicit apiBaseUrl",
);
});
it('rejects funky configs', () => {
expect(() => readConfig(config([{ host: 7 } as any]))).toThrow(/host/);
expect(() => readConfig(config([{ token: 7 } as any]))).toThrow(/token/);
expect(() =>
readConfig(config([{ host: 'bitbucket.org', apiBaseUrl: 7 } as any])),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(config([{ host: 'bitbucket.org', token: 7 } as any])),
).toThrow(/token/);
});
});
describe('implementation', () => {
it('rejects unknown targets', async () => {
const processor = new BitbucketUrlReader({
@@ -14,57 +14,18 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import {
BitbucketIntegrationConfig,
readBitbucketIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUri from 'git-url-parse';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
const DEFAULT_BASE_URL = 'https://api.bitbucket.org/2.0';
/**
* The configuration parameters for a single Bitbucket API provider.
*/
export type ProviderConfig = {
/**
* The host of the target that this matches on, e.g. "bitbucket.com"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.bitbucket.org/2.0",
* with no trailing slash.
*
* May be omitted specifically for Bitbucket Cloud; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The authorization token to use for requests to a Bitbucket Server provider.
*
* See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html
*
* If no token is specified, anonymous access is used.
*/
token?: string;
/**
* The username to use for requests to Bitbucket Cloud (bitbucket.org).
*/
username?: string;
/**
* Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords.
*
* See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/
*/
appPassword?: string;
};
export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
export function getApiRequestOptions(
provider: BitbucketIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {};
if (provider.token) {
@@ -84,7 +45,10 @@ export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
// Converts for example
// from: https://bitbucket.org/orgname/reponame/src/master/file.yaml
// to: https://api.bitbucket.org/2.0/repositories/orgname/reponame/src/master/file.yaml
export function getApiUrl(target: string, provider: ProviderConfig): URL {
export function getApiUrl(
target: string,
provider: BitbucketIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
if (
@@ -115,74 +79,39 @@ export function getApiUrl(target: string, provider: ProviderConfig): URL {
}
}
export function readConfig(config: Config): ProviderConfig[] {
const providers: ProviderConfig[] = [];
const providerConfigs =
config.getOptionalConfigArray('integrations.bitbucket') ?? [];
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'bitbucket.org';
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
const token = providerConfig.getOptionalString('token');
const username = providerConfig.getOptionalString('username');
const appPassword = providerConfig.getOptionalString('appPassword');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === 'bitbucket.org') {
apiBaseUrl = DEFAULT_BASE_URL;
}
if (!apiBaseUrl) {
throw new Error(
`Bitbucket integration for '${host}' must configure an explicit apiBaseUrl`,
);
}
if (!token && username && !appPassword) {
throw new Error(
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
);
}
providers.push({
host,
apiBaseUrl,
token,
username,
appPassword,
});
}
// If no explicit bitbucket.org provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.host === 'bitbucket.org')) {
providers.push({
host: 'bitbucket.org',
apiBaseUrl: DEFAULT_BASE_URL,
});
}
return providers;
}
/**
* A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as
* the one exposed by Bitbucket Cloud itself.
*/
export class BitbucketUrlReader implements UrlReader {
private config: ProviderConfig;
private readonly config: BitbucketIntegrationConfig;
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(provider => {
const configs = readBitbucketIntegrationConfigs(
config.getOptionalConfigArray('integrations.bitbucket') ?? [],
);
return configs.map(provider => {
const reader = new BitbucketUrlReader(provider);
const predicate = (url: URL) => url.host === provider.host;
return { reader, predicate };
});
};
constructor(config: ProviderConfig) {
constructor(config: BitbucketIntegrationConfig) {
const { host, apiBaseUrl, token, username, appPassword } = config;
if (!apiBaseUrl) {
throw new Error(
`Bitbucket integration for '${host}' must configure an explicit apiBaseUrl`,
);
}
if (!token && username && !appPassword) {
throw new Error(
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
);
}
this.config = config;
}
@@ -15,20 +15,19 @@
*/
import { ConfigReader } from '@backstage/config';
import { setupServer } from 'msw/node';
import { GitHubIntegrationConfig } from '@backstage/integration';
import { msw } from '@backstage/test-utils';
import fs from 'fs';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import path from 'path';
import {
getApiRequestOptions,
getApiUrl,
getRawRequestOptions,
getRawUrl,
GithubUrlReader,
ProviderConfig,
readConfig,
} from './GithubUrlReader';
import fs from 'fs';
import path from 'path';
import { ReadTreeResponseFactory } from './tree';
const treeResponseFactory = ReadTreeResponseFactory.create({
@@ -38,19 +37,19 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
describe('GithubUrlReader', () => {
describe('getApiRequestOptions', () => {
it('sets the correct API version', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect((getApiRequestOptions(config).headers as any).Accept).toEqual(
'application/vnd.github.v3.raw',
);
});
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: GitHubIntegrationConfig = {
host: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: GitHubIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -65,12 +64,12 @@ describe('GithubUrlReader', () => {
describe('getRawRequestOptions', () => {
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: GitHubIntegrationConfig = {
host: '',
rawBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: GitHubIntegrationConfig = {
host: '',
rawBaseUrl: '',
};
@@ -85,12 +84,12 @@ describe('GithubUrlReader', () => {
describe('getApiUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
};
@@ -117,7 +116,7 @@ describe('GithubUrlReader', () => {
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'ghe.mycompany.net',
apiBaseUrl: 'https://ghe.mycompany.net/api/v3',
};
@@ -136,12 +135,12 @@ describe('GithubUrlReader', () => {
describe('getRawUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getRawUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
};
@@ -158,7 +157,7 @@ describe('GithubUrlReader', () => {
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'ghe.mycompany.net',
rawBaseUrl: 'https://ghe.mycompany.net/raw',
};
@@ -173,60 +172,6 @@ describe('GithubUrlReader', () => {
});
});
describe('readConfig', () => {
function config(
providers: { host: string; apiBaseUrl?: string; token?: string }[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
integrations: { github: providers },
},
},
]);
}
it('adds a default GitHub entry when missing', () => {
const output = readConfig(config([]));
expect(output).toEqual([
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('injects the correct GitHub API base URL when missing', () => {
const output = readConfig(config([{ host: 'github.com' }]));
expect(output).toEqual([
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('rejects custom targets with no base URLs', () => {
expect(() => readConfig(config([{ host: 'ghe.company.com' }]))).toThrow(
"GitHub integration for 'ghe.company.com' must configure an explicit apiBaseUrl and rawBaseUrl",
);
});
it('rejects funky configs', () => {
expect(() => readConfig(config([{ host: 7 } as any]))).toThrow(/host/);
expect(() => readConfig(config([{ token: 7 } as any]))).toThrow(/token/);
expect(() =>
readConfig(config([{ host: 'github.com', apiBaseUrl: 7 } as any])),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(config([{ host: 'github.com', token: 7 } as any])),
).toThrow(/token/);
});
});
describe('implementation', () => {
it('rejects unknown targets', async () => {
const processor = new GithubUrlReader(
@@ -271,6 +216,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -293,6 +239,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -308,6 +255,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -14,59 +14,25 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import {
GitHubIntegrationConfig,
readGitHubIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUri from 'git-url-parse';
import { Readable } from 'stream';
import { InputError, NotFoundError } from '../errors';
import { ReadTreeResponseFactory } from './tree';
import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
UrlReader,
ReadTreeOptions,
} from './types';
import { ReadTreeResponseFactory } from './tree';
/**
* The configuration parameters for a single GitHub API provider.
*/
export type ProviderConfig = {
/**
* The host of the target that this matches on, e.g. "github.com"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.github.com",
* with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The base URL of the raw fetch endpoint of this provider, e.g.
* "https://raw.githubusercontent.com", with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
rawBaseUrl?: string;
/**
* The authorization token to use for requests to this provider.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
export function getApiRequestOptions(
provider: GitHubIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
@@ -80,7 +46,9 @@ export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
};
}
export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
export function getRawRequestOptions(
provider: GitHubIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {};
if (provider.token) {
@@ -95,7 +63,10 @@ export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
// Converts for example
// from: https://github.com/a/b/blob/branchname/path/to/c.yaml
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname
export function getApiUrl(target: string, provider: ProviderConfig): URL {
export function getApiUrl(
target: string,
provider: GitHubIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
@@ -120,7 +91,10 @@ export function getApiUrl(target: string, provider: ProviderConfig): URL {
// Converts for example
// from: https://github.com/a/b/blob/branchname/c.yaml
// to: https://raw.githubusercontent.com/a/b/branchname/c.yaml
export function getRawUrl(target: string, provider: ProviderConfig): URL {
export function getRawUrl(
target: string,
provider: GitHubIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
@@ -142,60 +116,16 @@ export function getRawUrl(target: string, provider: ProviderConfig): URL {
}
}
export function readConfig(config: Config): ProviderConfig[] {
const providers: ProviderConfig[] = [];
const providerConfigs =
config.getOptionalConfigArray('integrations.github') ?? [];
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'github.com';
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
let rawBaseUrl = providerConfig.getOptionalString('rawBaseUrl');
const token = providerConfig.getOptionalString('token');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === 'github.com') {
apiBaseUrl = 'https://api.github.com';
}
if (rawBaseUrl) {
rawBaseUrl = rawBaseUrl.replace(/\/+$/, '');
} else if (host === 'github.com') {
rawBaseUrl = 'https://raw.githubusercontent.com';
}
if (!apiBaseUrl && !rawBaseUrl) {
throw new Error(
`GitHub integration for '${host}' must configure an explicit apiBaseUrl and rawBaseUrl`,
);
}
providers.push({ host, apiBaseUrl, rawBaseUrl, token });
}
// If no explicit github.com provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.host === 'github.com')) {
providers.push({
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
});
}
return providers;
}
/**
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* the one exposed by GitHub itself.
*/
export class GithubUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
return readConfig(config).map(provider => {
const configs = readGitHubIntegrationConfigs(
config.getOptionalConfigArray('integrations.github') ?? [],
);
return configs.map(provider => {
const reader = new GithubUrlReader(provider, { treeResponseFactory });
const predicate = (url: URL) => url.host === provider.host;
return { reader, predicate };
@@ -203,9 +133,15 @@ export class GithubUrlReader implements UrlReader {
};
constructor(
private readonly config: ProviderConfig,
private readonly config: GitHubIntegrationConfig,
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
) {}
) {
if (!config.apiBaseUrl && !config.rawBaseUrl) {
throw new Error(
`GitHub integration for '${config.host}' must configure an explicit apiBaseUrl and rawBaseUrl`,
);
}
}
async read(url: string): Promise<Buffer> {
const useApi =
@@ -14,48 +14,27 @@
* limitations under the License.
*/
import {
GitLabIntegrationConfig,
readGitLabIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import { Config } from '@backstage/config';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
type Options = {
host: string;
token?: string;
};
function readConfig(config: Config): Options[] {
const optionsArr = Array<Options>();
const providerConfigs =
config.getOptionalConfigArray('integrations.gitlab') ?? [];
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'gitlab.com';
const token = providerConfig.getOptionalString('token');
optionsArr.push({ host, token });
}
// As a convenience we always make sure there's at least an unauthenticated
// reader for public gitlab repos.
if (!optionsArr.some(p => p.host === 'gitlab.com')) {
optionsArr.push({ host: 'gitlab.com' });
}
return optionsArr;
}
export class GitlabUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(options => {
const configs = readGitLabIntegrationConfigs(
config.getOptionalConfigArray('integrations.gitlab') ?? [],
);
return configs.map(options => {
const reader = new GitlabUrlReader(options);
const predicate = (url: URL) => url.host === options.host;
return { reader, predicate };
});
};
constructor(private readonly options: Options) {}
constructor(private readonly options: GitLabIntegrationConfig) {}
async read(url: string): Promise<Buffer> {
// TODO(Rugvip): merged the old GitlabReaderProcessor in here and used
@@ -133,9 +112,9 @@ export class GitlabUrlReader implements UrlReader {
try {
const url = new URL(target);
const branchAndfilePath = url.pathname.split('/-/blob/')[1];
const branchAndFilePath = url.pathname.split('/-/blob/')[1];
const [branch, ...filePath] = branchAndfilePath.split('/');
const [branch, ...filePath] = branchAndFilePath.split('/');
url.pathname = [
'/api/v4/projects',