@codefresh-io/cf-git-providers
Version:
An NPM module/CLI for interacting with various git providers
778 lines • 30 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.avatarSize = void 0;
/* eslint-disable max-lines */
const lodash_1 = require("lodash");
const https_1 = require("https");
const types_1 = require("./types");
const helpers_1 = require("../helpers");
const request_retry_1 = require("../helpers/request-retry");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const CFError = require('cf-errors');
const logger = (0, helpers_1.createNewLogger)('codefresh:infra:git-providers:bitbucket-server');
const BITBUCKET_SERVER_RESPONSE_LIMIT = 1000;
const LIMIT_PER_PAGE = 500;
const MAX_PAGES = 20;
const ApiVersions = {
// eslint-disable-next-line @typescript-eslint/naming-convention
V1: 'rest/api/1.0/',
};
const COMMIT_METADATA_PATH = 'com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata';
const statesMap = {
pending: 'INPROGRESS',
running: 'INPROGRESS',
success: 'SUCCESSFUL',
failure: 'FAILED',
error: 'FAILED',
};
const ownerRegex = /(scm\/)?(?<noun>~)?(?<projOrUser>[^/]*)\/(?<repo>[^/]*)$/;
// avatarSize adds avatar URLs for commit authors and specifying the desired size in pixels
exports.avatarSize = 64;
const _extractErrorFromResponse = (response) => {
let message = (0, lodash_1.get)(response, 'body.errors[0].message', '');
const exceptionName = (0, lodash_1.get)(response, 'body.errors[0].exceptionName');
if (exceptionName) {
message = `${message} [${exceptionName}]`;
}
return new types_1.HttpError(response.statusCode, message);
};
const _toBranch = (rawBranch) => ({
name: rawBranch.displayId,
id: rawBranch.id,
commit: {
sha: rawBranch.metadata[COMMIT_METADATA_PATH].id,
commiter_name: rawBranch.metadata[COMMIT_METADATA_PATH].author.name,
message: rawBranch.metadata[COMMIT_METADATA_PATH].message,
url: '',
},
});
const _toRepo = (rawRepo, lastCommit, defaultBranch) => {
const isPersonal = (0, lodash_1.get)(rawRepo, 'project.type') === 'PERSONAL';
let fullName = '';
if (isPersonal) {
fullName = `${(0, lodash_1.get)(rawRepo, 'project.owner.name')}/${rawRepo.slug}`;
}
else {
fullName = `${(0, lodash_1.get)(rawRepo, 'project.name')}/${rawRepo.slug}`;
}
const httpClone = (0, lodash_1.find)((0, lodash_1.get)(rawRepo, 'links.clone', []), (l) => l.name === 'http' || l.name === 'https');
const sshClone = (0, lodash_1.find)((0, lodash_1.get)(rawRepo, 'links.clone', []), (l) => l.name === 'ssh');
return {
id: rawRepo.hierarchyId,
provider: 'bitbucket-server',
name: rawRepo.name,
full_name: fullName,
private: !rawRepo.public,
pushed_at: lastCommit?.committerTimestamp && new Date(lastCommit.committerTimestamp),
open_issues: 0,
clone_url: (0, lodash_1.get)(httpClone, 'href', ''),
ssh_url: (0, lodash_1.get)(sshClone, 'href', ''),
owner: {
login: (0, lodash_1.get)(rawRepo, isPersonal ? 'project.owner.name' : 'project.name', ''),
avatar_url: (0, lodash_1.get)(rawRepo, isPersonal ? 'project.owner.avatarUrl' : 'project.avatarUrl', ''),
creator: null,
},
org: isPersonal ? null : (0, lodash_1.get)(rawRepo, 'project.name', null),
default_branch: (0, lodash_1.get)(defaultBranch, 'displayId', ''),
permissions: {
admin: true, // this value is hard to get with bitbucket, for now use 'true'
},
webUrl: (0, lodash_1.get)(rawRepo, 'links.self[0].href', ''),
};
};
const _toWebhook = (owner, repo, rawWebhook) => {
return {
id: rawWebhook.id,
name: String(rawWebhook.url),
endpoint: rawWebhook.url,
repo: `${owner}/${repo}`,
events: rawWebhook.events,
};
};
const _toUser = (user) => {
return {
login: user.name,
email: user.emailAddress,
avatar_url: user.avatarUrl,
web_url: user.links.self[0].href,
};
};
const getRepoInfo = (fullRepoName) => {
const groups = fullRepoName.match(ownerRegex)?.groups;
if (!ownerRegex.test(fullRepoName) || !groups) {
throw new CFError(`${fullRepoName} is invalid`);
}
return {
noun: groups.noun || '',
owner: groups.projOrUser,
repo: groups.repo
};
};
class BitbucketServer {
baseUrl;
authenticationHeader;
timeout;
agent;
auth;
refreshTokenHandler;
retryConfig;
constructor(opts) {
const apiUrl = opts.apiURL || opts.apiUrl;
if (!apiUrl) {
throw new CFError('Cannot create bitbucket-server provider without api url');
}
this.baseUrl = apiUrl;
if (!this.baseUrl.endsWith('/')) {
this.baseUrl = `${this.baseUrl}/`;
}
this.timeout = opts.timeout || 10000;
this.refreshTokenHandler = opts.refreshTokenHandler;
if (opts.type === 'basic') {
this.authenticationHeader = {
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Basic ${Buffer.from(`${opts.username}:${opts.password}`).toString('base64')}`,
};
}
else {
// eslint-disable-next-line @typescript-eslint/naming-convention
this.authenticationHeader = { Authorization: `Bearer ${opts.password}` };
}
this.auth = {
type: opts.type,
username: opts.username,
password: opts.password,
refreshToken: opts.refreshToken,
};
if (apiUrl.startsWith('https') && (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' || opts.insecure)) {
logger.warn('using insecure mode');
this.agent = new https_1.Agent({ rejectUnauthorized: false });
}
this.retryConfig = opts.retryConfig;
}
async createRepository(opts) {
const isOwner = opts.owner.startsWith('~');
const projectKey = await this.getProjectKey(opts.owner);
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `${isOwner ? 'users' : 'projects'}/${projectKey}/repos?avatarSize=${exports.avatarSize}`,
method: 'POST',
json: true,
data: {
name: opts.repo,
scmId: 'git'
}
}));
if (err) {
throw new CFError({
message: `Failed to create repository ${opts.repo} in ${projectKey}`,
cause: err,
});
}
return _toRepo(res.body);
}
async shouldRefreshToken(requestOptions) {
const validateTokenRequestOptions = (0, lodash_1.cloneDeep)(requestOptions);
validateTokenRequestOptions.qs = {};
validateTokenRequestOptions.url = `${this.baseUrl}${ApiVersions.V1}profile/recent/repos?limit=1`;
const response = await request_retry_1.RpRetry.rpRetry(validateTokenRequestOptions, logger);
switch (true) {
case response.statusCode === 401: return true;
case response.statusCode < 400: return false;
default: throw new Error(`Refresh token error. StatusCode: ${response.statusCode}. Message: ${response.statusMessage}`);
}
}
updateAuth(auth) {
this.auth.password = auth.accessToken;
this.auth.refreshToken = auth.refreshToken;
// eslint-disable-next-line @typescript-eslint/naming-convention
this.authenticationHeader = auth.accessToken ? { Authorization: `Bearer ${auth.accessToken}` } : {};
}
async performAPICall(opts) {
const method = opts.method || 'GET';
const requestHeaders = (0, lodash_1.merge)(opts.noAuth ? {} : this.authenticationHeader, opts.headers || {});
const requestOptions = {
method,
url: `${this.baseUrl}${opts.apiVersion || ApiVersions.V1}${opts.api}`,
qs: opts.qs || {},
headers: requestHeaders,
json: opts.json,
timeout: this.timeout,
body: opts.data,
resolveWithFullResponse: true,
agent: this.agent,
simple: false,
retryConfig: this.retryConfig,
};
logger.debug(`${method} ${requestOptions.url} qs: ${JSON.stringify(requestOptions.qs)}`);
return request_retry_1.RpRetry.rpRetry(requestOptions, logger)
.then(async (res) => {
if (res.statusCode === 401 && this.refreshTokenHandler && this.auth.refreshToken && this.auth.type !== 'basic') {
try {
// eslint-disable-next-line unicorn/no-lonely-if
if (await this.shouldRefreshToken(requestOptions)) {
const auth = await this.refreshTokenHandler(this.auth.refreshToken);
if (!auth) {
throw new Error(`Refresh token error`);
}
this.updateAuth(auth);
requestOptions.headers = (0, lodash_1.merge)(opts.noAuth ? {} : this.authenticationHeader, opts.headers || {});
return request_retry_1.RpRetry.rpRetry(requestOptions, logger);
}
}
catch (error) {
logger.error(error.message);
return Promise.reject(error);
}
}
return Promise.resolve(res);
})
.then((res) => {
const curLogger = res.statusCode >= 400 ? logger.error.bind(logger) : logger.debug.bind(logger);
curLogger(`${method} ${requestOptions.url} qs: ${JSON.stringify(requestOptions.qs)} status: ${res.statusCode}`);
return res;
});
}
// this func is performing all the pagination inside (up to MAX_PAGES, or until predicate is true)
async paginateForResult(opts, predicate) {
let start = 0;
let page = 0;
let isLastPage = false;
const allRes = [];
while (!isLastPage && page < MAX_PAGES) {
opts.qs = (0, lodash_1.merge)(opts.qs, {
start: '' + start,
limit: '' + LIMIT_PER_PAGE,
});
const res = await this.performAPICall(opts);
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed pagination`,
cause: _extractErrorFromResponse(res),
});
}
isLastPage = res.body.isLastPage;
const predicateResult = predicate && (0, lodash_1.find)(res.body.values, predicate);
if (predicateResult) {
return predicateResult; // found the wanted result
}
allRes.push(...res.body.values);
start += LIMIT_PER_PAGE;
page += 1;
}
return predicate ? undefined : allRes;
}
async getProjectKey(projectName) {
if (projectName.startsWith('~')) {
return projectName;
}
const nameExists = await this.verifyProjectKey(projectName);
if (nameExists) {
return projectName;
}
const isUsername = await this.verifyProjectKey('~' + projectName);
if (isUsername) {
return '~' + projectName;
}
const proj = await this.paginateForResult({
api: `projects`,
json: true,
qs: {
name: projectName,
},
}, (project) => project.name === projectName);
if (!proj) {
throw new CFError(`Project "${projectName}" was not found`);
}
return proj.key;
}
async verifyProjectKey(projectKey) {
try {
const res = await this.performAPICall({
api: `projects/${projectKey}`,
json: true,
});
return res.statusCode === 200;
}
catch (error) {
throw new CFError({
message: `Failed to get project with key "${projectKey}"`,
cause: error,
});
}
}
getName() {
return 'bitbucket-server';
}
async fetchRawFile(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const lines = [];
let start = 0;
let page = 0;
let isLastPage = false;
while (!isLastPage && page < MAX_PAGES) {
const res = await this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos/${opts.repo}/browse/${(0, helpers_1.cleanEncodedFilePath)(opts.path, { preserveSlashes: true })}`,
json: true,
qs: {
at: opts.ref,
raw: 'true',
start: '' + start,
limit: '' + LIMIT_PER_PAGE,
},
});
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed to retrieve file: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(res)
});
}
isLastPage = res.body.isLastPage;
lines.push(...(0, lodash_1.map)(res.body.lines, (line) => line.text));
start += LIMIT_PER_PAGE;
page += 1;
}
return lines.join('\n');
}
async getBranch(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [getBranchErr, branch] = await (0, helpers_1.to)(this.paginateForResult({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos/${opts.repo}/branches`,
json: true,
qs: {
filterText: opts.branch,
details: 'true',
},
}, (b) => (0, lodash_1.isEqual)(opts.branch, b.displayId ? b.displayId : (0, lodash_1.last)(b.id.split('/')))));
if (getBranchErr) {
throw new CFError({
message: `Failed to retrieve branch, opts: ${JSON.stringify(opts)}`,
cause: getBranchErr,
});
}
if (!branch) {
throw new CFError(`Failed to retrieve branch: ${JSON.stringify(opts)}, branch not found`);
}
return _toBranch(branch);
}
async getRepository(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [repo, lastCommit, defaultBranch] = await Promise.all([
this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos/${opts.repo}?avatarSize=${exports.avatarSize}`,
json: true,
}),
this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos/${opts.repo}/commits`,
json: true,
}),
this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos/${opts.repo}/branches/default`,
json: true,
}),
]);
if (repo.statusCode >= 400) {
throw new CFError({
message: `Failed to get repository: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(repo),
});
}
if (lastCommit.statusCode >= 400) {
throw new CFError({
message: `Failed to get repository lastest commit: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(lastCommit),
});
}
if (defaultBranch.statusCode >= 400) {
throw new CFError({
message: `Failed to get repository default branch: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(defaultBranch),
});
}
return _toRepo(repo.body, (0, lodash_1.get)(lastCommit.body, 'values[0]', {}), defaultBranch.body);
}
async listBranches(opts) {
try {
const projectKey = await this.getProjectKey(opts.owner);
const branches = await this.paginateForResult({
api: `projects/${projectKey}/repos/${opts.repo}/branches`,
qs: {
details: String(true),
},
json: true,
});
return branches.map(_toBranch);
}
catch (error) {
throw new CFError({
message: `Failed list branches, opts: ${JSON.stringify(opts)}`,
cause: error,
});
}
}
async createBranch() {
throw new Error('Method createBranch not implemented.');
}
async listRepositoriesForOwner() {
throw new Error('Method listRepositoriesForOwner not implemented.');
}
async createRepositoryWebhook(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `projects/${projectKey}/repos/${opts.repo}/webhooks`,
method: 'POST',
json: true,
data: {
events: [
'repo:refs_changed',
'pr:opened',
'pr:merged',
'pr:modified',
'pr:deleted',
'pr:declined',
'pr:reviewer:unapproved',
'pr:reviewer:approved',
'pr:reviewer:needs_work',
'pr:comment:edited',
'pr:comment:added',
'pr:comment:deleted',
],
configuration: {
secret: opts.secret,
},
url: opts.endpoint,
active: true,
},
}));
if (err) {
throw new CFError({
message: `Failed to create repository webhooks: ${JSON.stringify(opts)}`,
cause: err
});
}
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed to create repository webhooks: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(res),
});
}
return _toWebhook(opts.owner, opts.repo, res.body);
}
async listWebhooks(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [getHooksErr, hooks] = await (0, helpers_1.to)(this.paginateForResult({
api: `projects/${projectKey}/repos/${opts.repo}/webhooks`,
json: true,
}));
if (getHooksErr) {
throw new CFError({
message: `Failed to list repository webhooks: ${JSON.stringify(opts)}`,
cause: getHooksErr,
});
}
if (hooks.statusCode >= 400) {
throw new CFError({
message: `Failed to list repository webhooks: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(hooks),
});
}
return hooks.map(_toWebhook.bind(null, opts.owner, opts.repo));
}
async deleteRepositoryWebhook(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `projects/${projectKey}/repos/${opts.repo}/webhooks/${opts.hookId}`,
method: 'DELETE',
json: true,
}));
if (err) {
throw new CFError({
message: `Failed to delete webhook: ${JSON.stringify(opts)}`,
cause: err,
});
}
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed to delete webhook: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(res),
});
}
}
async listRepositoriesWithAffiliation(opts) {
const { limit = 2000 } = opts;
let { page = 1 } = opts;
const pageSize = Math.min(BITBUCKET_SERVER_RESPONSE_LIMIT, limit);
let repoList = [];
do {
const [getReposErr, repos] = await (0, helpers_1.to)(this.paginateReposResult({
api: `repos?avatarSize=${exports.avatarSize}`,
json: true,
qs: {
permission: 'REPO_READ',
},
limit: pageSize,
page,
}));
if (getReposErr) {
throw new CFError({
message: `Failed to list repos: ${JSON.stringify(opts)}`,
cause: getReposErr,
});
}
repoList = [...repoList, ...repos];
if (repos.length < pageSize)
break;
page++;
} while (((page - 1) * pageSize < limit));
return repoList.map(_toRepo);
}
// this func is performing pagination that only return a specific page with limit || LIMIT_PER_PAGE
async paginateReposResult(opts) {
const limit = opts.limit || LIMIT_PER_PAGE;
// bitbucket-server api is 0-based and our api is 1-based
const start = (opts.page - 1) * limit;
opts.qs = (0, lodash_1.merge)(opts.qs, {
start: start.toString(),
limit: limit.toString(),
});
const res = await this.performAPICall(opts);
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed pagination`,
cause: _extractErrorFromResponse(res),
});
}
return res.body.values;
}
async listOrganizations(opts) {
const { limit = 25, page = 0 } = opts;
const start = page * limit;
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `projects`,
qs: {
start: String(start),
limit: String(limit),
},
json: true,
}));
if (err) {
throw new CFError({
message: `Failed to list organization`,
cause: err,
});
}
// no organizations
if (!res.body.values || res.body.values.length == 0) {
return [];
}
return res.body.values.map((project) => project.name);
}
async getRepositoryPermissions(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [reposReadResErr, reposReadRes] = await (0, helpers_1.to)(this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos?permissions=REPO_READ`,
json: true,
}));
if (reposReadResErr) {
throw new CFError({
message: `Failed to get repository permissions: ${opts.owner}/${opts.repo}`,
cause: reposReadResErr,
});
}
const read = !!reposReadRes.body?.values?.find((repo) => repo.name == opts.repo);
const [reposWriteResErr, reposWriteRes] = await (0, helpers_1.to)(this.performAPICall({
apiVersion: ApiVersions.V1,
api: `projects/${projectKey}/repos?permissions=REPO_WRITE`,
json: true,
}));
if (reposWriteResErr) {
throw new CFError({
message: `Failed to get repository permissions: ${opts.owner}/${opts.repo}`,
cause: reposWriteResErr,
});
}
const write = !!reposWriteRes.body?.values?.find((repo) => repo.name == opts.repo);
return {
read,
write
};
}
async listRepositoriesForOrganization() {
throw new Error('Method listRepositoriesForOrganization not implemented.');
}
async createCommitStatus(opts) {
const projectKey = await this.getProjectKey(opts.owner);
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `projects/${projectKey}/repos/${opts.repo}/commits/${opts.sha}/builds`,
method: 'POST',
json: true,
data: {
key: opts.context,
name: opts.context,
description: opts.description,
url: opts.targetUrl,
state: statesMap[opts.state],
},
}));
if (err) {
throw new CFError({
message: `Failed to create commit status: ${JSON.stringify(opts)}`,
cause: err,
});
}
if (res.statusCode >= 400) {
throw new CFError({
message: `Failed to create commit status: ${JSON.stringify(opts)}`,
cause: _extractErrorFromResponse(res),
});
}
}
async getUser(opts) {
let username = opts?.username;
if (!username) {
const res = await this.performAPICall({
api: `application-properties`,
method: 'HEAD',
json: true,
});
if (!res.headers['x-ausername']) {
throw new CFError(`ValidationError: check your token permissions, failed assert read scopes`);
}
username = res.headers['x-ausername'];
}
const [err, res] = await (0, helpers_1.to)(this.performAPICall({
api: `users/${username}?avatarSize=${exports.avatarSize}`,
method: 'GET',
json: true,
}));
if (err) {
throw new CFError({
message: `Failed to get ${username}`,
cause: err,
});
}
if (res.body?.errors) {
throw new CFError(`Failed to get ${username}, err: ${JSON.stringify(res.body.errors)}`);
}
return _toUser(res.body);
}
async getUserByEmail(email) {
const [err, user] = await (0, helpers_1.to)(this.paginateForResult({
api: `users?avatarSize=${exports.avatarSize}`,
json: true,
}, (users) => users.emailAddress === email));
if (err) {
throw new CFError({
message: `Failed to get user connected to email ${email}`,
cause: err,
});
}
return _toUser(user);
}
async getPullRequestFiles() {
throw new Error('Method getPullRequestFiles not implemented.');
}
async getPullRequest() {
throw new Error('Method getPullRequest not implemented.');
}
async searchMergedPullRequestByCommitSha() {
throw new Error('Method searchMergedPullRequestByCommitSha not implemented.');
}
async createPullRequest() {
throw new Error('Method getPullRequest not implemented.');
}
async assertApiScopes(opts) {
if (opts.scopes.includes('admin_repo_hook')) {
await this.assertAdminScope();
return;
}
if ((opts.scopes.includes('repo_write') || opts.scopes.includes('repo_create')) && opts.repoUrl) {
await this.assertWriteScope(opts.repoUrl);
return;
}
if (opts.scopes.includes('repo_read')) {
await this.assertReadScope();
}
}
async validateToken() {
const res = await this.performAPICall({
api: `application-properties`,
method: 'HEAD',
json: true,
});
if (res.statusCode == 401) {
throw new CFError(`ValidationError: token is invalid`);
}
}
skipPermissionsValidation() {
return { skip: false };
}
async assertReadScope() {
const res = await this.performAPICall({
api: `application-properties`,
method: 'HEAD',
json: true,
});
if (!res.headers['x-ausername']) {
throw new CFError(`ValidationError: check your token permissions, failed assert read scopes`);
}
}
async assertWriteScope(repoUrl) {
try {
await (0, helpers_1.assertPatWritePermission)(repoUrl, this.getAuth());
}
catch (error) {
throw new CFError({
message: `ValidationError: check your token permissions, failed assert write scopes`,
cause: error
});
}
}
async assertAdminScope() {
const res = await this.performAPICall({
api: `application-properties`,
method: 'HEAD',
json: true,
});
if (!res.headers['x-ausername']) {
throw new CFError(`ValidationError: check your token permissions, failed assert admin scopes`);
}
const username = res.headers['x-ausername'];
const [projErr, projRes] = await (0, helpers_1.to)(this.performAPICall({
api: `users/${username}/repos`,
method: 'POST',
json: true,
data: {
name: '!Invalid'
}
}));
if (projRes.statusCode !== 400) {
throw new CFError({
message: `ValidationError: failed to get project, check your token permissions, expected Project Admin`,
cause: projErr
});
}
}
toOwnerRepo(fullRepoName) {
const repoInfo = getRepoInfo(fullRepoName);
const repo = repoInfo.repo;
const owner = repoInfo.owner;
const noun = repoInfo.noun;
if (!owner || !repo) {
throw new CFError(`Failed to get repo and owner from repository name ${fullRepoName}`);
}
return [noun + owner, repo];
}
getAuth() {
return { headers: this.authenticationHeader };
}
isTokenMutable() { return false; }
requiresRepoToCheckTokenScopes() { return true; }
useAdminForUserPermission() { return false; }
}
exports.default = BitbucketServer;
//# sourceMappingURL=bitbucket-server.js.map