feat(bitbucket-server): convert to typescript (#3756)

This commit is contained in:
Michael Kriese 2019-05-20 15:08:18 +02:00 коммит произвёл Rhys Arkins
Родитель 154f776ceb
Коммит cc52c20533
13 изменённых файлов: 385 добавлений и 296 удалений

Просмотреть файл

@ -32,6 +32,13 @@ module.exports = {
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'@typescript-eslint/explicit-member-accessibility': 0,
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/interface-name-prefix': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'no-underscore-dangle': 0,
},
settings: {
'import/resolver': {

Просмотреть файл

@ -1,15 +1,18 @@
const got = require('got');
const URL = require('url');
const hostRules = require('../../util/host-rules');
import got from 'got';
import URL from 'url';
import * as hostRules from '../../util/host-rules';
import { IGotApiOptions, IGotApi } from '../common';
let cache = {};
let cache: Renovate.IDict<got.Response<any>> = {};
const platform = 'bitbucket-server';
let endpoint;
let endpoint: string;
async function get(path, options) {
async function get(path: string, options: IGotApiOptions & got.GotJSONOptions) {
const host = URL.parse(path).host || URL.parse(endpoint).host;
const opts = {
const opts: IGotApiOptions &
hostRules.IPlatformConfig &
got.GotJSONOptions = {
// TODO: Move to configurable host rules, or use utils/got
timeout: 60 * 1000,
json: true,
@ -18,7 +21,9 @@ async function get(path, options) {
...options,
};
const url = URL.resolve(endpoint, path);
const method = (opts.method || 'get').toLowerCase();
const method = (
opts.method || /* istanbul ignore next */ 'get'
).toLowerCase();
const useCache = opts.useCache;
if (method === 'get' && useCache !== false && cache[path]) {
logger.trace({ path }, 'Returning cached result');
@ -27,7 +32,10 @@ async function get(path, options) {
opts.headers = {
'user-agent': 'https://github.com/renovatebot/renovate',
'X-Atlassian-Token': 'no-check',
authorization: opts.token ? `Basic ${opts.token}` : undefined,
authorization: opts.token
? /* istanbul ignore next */ `Basic ${opts.token}`
: undefined,
...opts.headers,
};
@ -41,17 +49,19 @@ async function get(path, options) {
const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];
export const api: IGotApi = {} as any;
for (const x of helpers) {
get[x] = (url, opts) =>
(api as any)[x] = (url: string, opts: any) =>
get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
}
get.reset = function reset() {
api.reset = function reset() {
cache = {};
};
get.setEndpoint = e => {
api.setEndpoint = (e: string) => {
endpoint = e;
};
module.exports = get;
export default api;

Просмотреть файл

@ -1,71 +1,42 @@
const url = require('url');
const delay = require('delay');
import url from 'url';
import delay from 'delay';
const api = require('./bb-got-wrapper');
const utils = require('./utils');
const hostRules = require('../../util/host-rules');
const GitStorage = require('../git/storage');
import api from './bb-got-wrapper';
import * as utils from './utils';
import * as hostRules from '../../util/host-rules';
import GitStorage from '../git/storage';
const platform = 'bitbucket-server';
let config = {};
interface BbsConfig {
baseBranch: string;
bbUseDefaultReviewers: boolean;
defaultBranch: string;
fileList: any[];
mergeMethod: string;
owner: string;
prList: any[];
projectKey: string;
repository: string;
repositorySlug: string;
storage: GitStorage;
}
const defaults = {
let config: BbsConfig = {} as any;
const defaults: any = {
platform: 'bitbucket-server',
};
module.exports = {
initPlatform,
getRepos,
cleanRepo,
initRepo,
getRepoStatus,
getRepoForceRebase,
setBaseBranch,
setBranchPrefix,
// Search
getFileList,
// Branch
branchExists,
getAllRenovateBranches,
isBranchStale,
getBranchPr,
getBranchStatus,
getBranchStatusCheck,
setBranchStatus,
deleteBranch,
mergeBranch,
getBranchLastCommitTime,
// issue
findIssue,
ensureIssue,
ensureIssueClosing,
addAssignees,
addReviewers,
deleteLabel,
getIssueList,
// Comments
ensureComment,
ensureCommentRemoval,
// PR
getPrList,
findPr,
createPr,
getPr,
getPrFiles,
updatePr,
mergePr,
getPrBody,
// file
commitFilesToBranch,
getFile,
// commits
getCommitMessages,
// vulnerability alerts
getVulnerabilityAlerts,
};
function initPlatform({ endpoint, username, password }) {
export function initPlatform({
endpoint,
username,
password,
}: {
endpoint: string;
username: string;
password: string;
}) {
if (!endpoint) {
throw new Error('Init: You must configure a Bitbucket Server endpoint');
}
@ -84,13 +55,16 @@ function initPlatform({ endpoint, username, password }) {
}
// Get all repositories that the user has access to
async function getRepos() {
export async function getRepos() {
logger.info('Autodiscovering Bitbucket Server repositories');
try {
const repos = await utils.accumulateValues(
`./rest/api/1.0/repos?permission=REPO_WRITE&state=AVAILABLE`
);
const result = repos.map(r => `${r.project.key.toLowerCase()}/${r.slug}`);
const result = repos.map(
(r: { project: { key: string }; slug: string }) =>
`${r.project.key.toLowerCase()}/${r.slug}`
);
logger.debug({ result }, 'result of getRepos()');
return result;
} catch (err) /* istanbul ignore next */ {
@ -99,23 +73,30 @@ async function getRepos() {
}
}
function cleanRepo() {
export function cleanRepo() {
logger.debug(`cleanRepo()`);
if (config.storage) {
config.storage.cleanRepo();
}
api.reset();
config = {};
config = {} as any;
}
// Initialize GitLab by getting base branch
async function initRepo({
export async function initRepo({
repository,
endpoint,
gitPrivateKey,
gitFs,
localDir,
bbUseDefaultReviewers,
}: {
repository: string;
endpoint: string;
gitPrivateKey?: string;
gitFs?: string;
localDir: string;
bbUseDefaultReviewers?: boolean;
}) {
logger.debug(
`initRepo("${JSON.stringify(
@ -128,7 +109,7 @@ async function initRepo({
api.reset();
const [projectKey, repositorySlug] = repository.split('/');
config = { projectKey, repositorySlug, gitPrivateKey };
config = { projectKey, repositorySlug, gitPrivateKey, repository } as any;
/* istanbul ignore else */
if (bbUseDefaultReviewers !== false) {
@ -137,11 +118,13 @@ async function initRepo({
}
// Always gitFs
const { host, pathname } = url.parse(opts.endpoint);
const { host, pathname } = url.parse(opts!.endpoint!);
const gitUrl = GitStorage.getUrl({
gitFs: opts.endpoint.split(':')[0],
auth: `${opts.username}:${opts.password}`,
host: `${host}${pathname}${pathname.endsWith('/') ? '' : '/'}scm`,
gitFs: opts!.endpoint!.split(':')[0] as any,
auth: `${opts!.username}:${opts!.password}`,
host: `${host}${pathname}${
pathname!.endsWith('/') ? '' : /* istanbul ignore next */ '/'
}scm`,
repository,
});
@ -152,7 +135,7 @@ async function initRepo({
url: gitUrl,
});
const platformConfig = {};
const platformConfig: any = {};
try {
const info = (await api.get(
@ -189,7 +172,7 @@ async function initRepo({
return platformConfig;
}
function getRepoForceRebase() {
export function getRepoForceRebase() {
logger.debug(`getRepoForceRebase()`);
// TODO if applicable
// This function should return true only if the user has enabled a setting on the repo that enforces PRs to be kept up to date with master
@ -198,20 +181,20 @@ function getRepoForceRebase() {
return false;
}
async function setBaseBranch(branchName = config.defaultBranch) {
export async function setBaseBranch(branchName: string = config.defaultBranch) {
config.baseBranch = branchName;
await config.storage.setBaseBranch(branchName);
}
// istanbul ignore next
function setBranchPrefix(branchPrefix) {
export function setBranchPrefix(branchPrefix: string) {
return config.storage.setBranchPrefix(branchPrefix);
}
// Search
// Get full file list
function getFileList(branchName = config.baseBranch) {
export function getFileList(branchName: string = config.baseBranch) {
logger.debug(`getFileList(${branchName})`);
return config.storage.getFileList(branchName);
}
@ -219,33 +202,33 @@ function getFileList(branchName = config.baseBranch) {
// Branch
// Returns true if branch exists, otherwise false
function branchExists(branchName) {
export function branchExists(branchName: string) {
logger.debug(`branchExists(${branchName})`);
return config.storage.branchExists(branchName);
}
// Returns the Pull Request for a branch. Null if not exists.
async function getBranchPr(branchName, refreshCache) {
export async function getBranchPr(branchName: string, refreshCache?: boolean) {
logger.debug(`getBranchPr(${branchName})`);
const existingPr = await findPr(branchName, null, 'open');
const existingPr = await findPr(branchName, undefined, 'open');
return existingPr ? getPr(existingPr.number, refreshCache) : null;
}
function getAllRenovateBranches(branchPrefix) {
export function getAllRenovateBranches(branchPrefix: string) {
logger.debug('getAllRenovateBranches');
return config.storage.getAllRenovateBranches(branchPrefix);
}
function isBranchStale(branchName) {
export function isBranchStale(branchName: string) {
logger.debug(`isBranchStale(${branchName})`);
return config.storage.isBranchStale(branchName);
}
async function commitFilesToBranch(
branchName,
files,
message,
parentBranch = config.baseBranch
export async function commitFilesToBranch(
branchName: string,
files: any[],
message: string,
parentBranch: string = config.baseBranch
) {
logger.debug(
`commitFilesToBranch(${JSON.stringify(
@ -267,12 +250,12 @@ async function commitFilesToBranch(
await getBranchPr(branchName, true);
}
function getFile(filePath, branchName) {
export function getFile(filePath: string, branchName: string) {
logger.debug(`getFile(${filePath}, ${branchName})`);
return config.storage.getFile(filePath, branchName);
}
async function deleteBranch(branchName, closePr = false) {
export async function deleteBranch(branchName: string, closePr = false) {
logger.debug(`deleteBranch(${branchName}, closePr=${closePr})`);
// TODO: coverage
// istanbul ignore next
@ -292,25 +275,28 @@ async function deleteBranch(branchName, closePr = false) {
return config.storage.deleteBranch(branchName);
}
function mergeBranch(branchName) {
export function mergeBranch(branchName: string) {
logger.debug(`mergeBranch(${branchName})`);
return config.storage.mergeBranch(branchName);
}
function getBranchLastCommitTime(branchName) {
export function getBranchLastCommitTime(branchName: string) {
logger.debug(`getBranchLastCommitTime(${branchName})`);
return config.storage.getBranchLastCommitTime(branchName);
}
// istanbul ignore next
function getRepoStatus() {
export function getRepoStatus() {
return config.storage.getRepoStatus();
}
// Returns the combined status for a branch.
// umbrella for status checks
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-build-rest.html#idp2
async function getBranchStatus(branchName, requiredStatusChecks) {
export async function getBranchStatus(
branchName: string,
requiredStatusChecks?: string[] | boolean | null
) {
logger.debug(
`getBranchStatus(${branchName}, requiredStatusChecks=${!!requiredStatusChecks})`
);
@ -344,7 +330,10 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
}
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-build-rest.html#idp2
async function getBranchStatusCheck(branchName, context) {
export async function getBranchStatusCheck(
branchName: string,
context: string
) {
logger.debug(`getBranchStatusCheck(${branchName}, context=${context})`);
const branchCommit = await config.storage.getBranchCommit(branchName);
@ -373,12 +362,12 @@ async function getBranchStatusCheck(branchName, context) {
return null;
}
async function setBranchStatus(
branchName,
context,
description,
state,
targetUrl
export async function setBranchStatus(
branchName: string,
context: string,
description: string,
state: string | null,
targetUrl?: string
) {
logger.debug(`setBranchStatus(${branchName})`);
@ -391,7 +380,7 @@ async function setBranchStatus(
const branchCommit = await config.storage.getBranchCommit(branchName);
try {
const body = {
const body: any = {
key: context,
description,
url: targetUrl || 'https://renovatebot.com',
@ -427,7 +416,7 @@ async function setBranchStatus(
// }
// istanbul ignore next
function findIssue(title) {
export function findIssue(title: string) {
logger.debug(`findIssue(${title})`);
// TODO: Needs implementation
// This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue"
@ -436,7 +425,7 @@ function findIssue(title) {
}
// istanbul ignore next
function ensureIssue(title, body) {
export function ensureIssue(title: string, body: string) {
logger.debug(`ensureIssue(${title}, body={${body}})`);
// TODO: Needs implementation
// This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue"
@ -445,14 +434,14 @@ function ensureIssue(title, body) {
}
// istanbul ignore next
function getIssueList() {
export function getIssueList() {
logger.debug(`getIssueList()`);
// TODO: Needs implementation
return [];
}
// istanbul ignore next
function ensureIssueClosing(title) {
export function ensureIssueClosing(title: string) {
logger.debug(`ensureIssueClosing(${title})`);
// TODO: Needs implementation
// This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue"
@ -460,14 +449,14 @@ function ensureIssueClosing(title) {
}
// eslint-disable-next-line no-unused-vars
function addAssignees(iid, assignees) {
export function addAssignees(iid: number, assignees: string[]) {
logger.debug(`addAssignees(${iid}, ${assignees})`);
// TODO: Needs implementation
// Currently Renovate does "Create PR" and then "Add assignee" as a two-step process, with this being the second step.
// BB Server doesnt support assignees
}
async function addReviewers(prNo, reviewers) {
export async function addReviewers(prNo: number, reviewers: string[]) {
logger.debug(`Adding reviewers ${reviewers} to #${prNo}`);
try {
@ -505,13 +494,13 @@ async function addReviewers(prNo, reviewers) {
}
// eslint-disable-next-line no-unused-vars
function deleteLabel(issueNo, label) {
export function deleteLabel(issueNo: number, label: string) {
logger.debug(`deleteLabel(${issueNo}, ${label})`);
// TODO: Needs implementation
// Only used for the "request Renovate to rebase a PR using a label" feature
}
async function getComments(prNo) {
async function getComments(prNo: number) {
// GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/activities
let comments = await utils.accumulateValues(
`./rest/api/1.0/projects/${config.projectKey}/repos/${
@ -520,15 +509,18 @@ async function getComments(prNo) {
);
comments = comments
.filter(a => a.action === 'COMMENTED' && a.commentAction === 'ADDED')
.map(a => a.comment);
.filter(
(a: { action: string; commentAction: string }) =>
a.action === 'COMMENTED' && a.commentAction === 'ADDED'
)
.map((a: { comment: string }) => a.comment);
logger.debug(`Found ${comments.length} comments`);
return comments;
}
async function addComment(prNo, text) {
async function addComment(prNo: number, text: string) {
// POST /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments
await api.post(
`./rest/api/1.0/projects/${config.projectKey}/repos/${
@ -540,7 +532,7 @@ async function addComment(prNo, text) {
);
}
async function getCommentVersion(prNo, commentId) {
async function getCommentVersion(prNo: number, commentId: number) {
// GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
const { version } = (await api.get(
`./rest/api/1.0/projects/${config.projectKey}/repos/${
@ -551,7 +543,7 @@ async function getCommentVersion(prNo, commentId) {
return version;
}
async function editComment(prNo, commentId, text) {
async function editComment(prNo: number, commentId: number, text: string) {
const version = await getCommentVersion(prNo, commentId);
// PUT /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
@ -565,7 +557,7 @@ async function editComment(prNo, commentId, text) {
);
}
async function deleteComment(prNo, commentId) {
async function deleteComment(prNo: number, commentId: number) {
const version = await getCommentVersion(prNo, commentId);
// DELETE /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
@ -576,16 +568,20 @@ async function deleteComment(prNo, commentId) {
);
}
async function ensureComment(prNo, topic, content) {
export async function ensureComment(
prNo: number,
topic: string | null,
content: string
) {
try {
const comments = await getComments(prNo);
let body;
let commentId;
let commentNeedsUpdating;
let body: string;
let commentId: number | undefined;
let commentNeedsUpdating: boolean | undefined;
if (topic) {
logger.debug(`Ensuring comment "${topic}" in #${prNo}`);
body = `### ${topic}\n\n${content}`;
comments.forEach(comment => {
comments.forEach((comment: { text: string; id: number }) => {
if (comment.text.startsWith(`### ${topic}\n\n`)) {
commentId = comment.id;
commentNeedsUpdating = comment.text !== body;
@ -594,7 +590,7 @@ async function ensureComment(prNo, topic, content) {
} else {
logger.debug(`Ensuring content-only comment in #${prNo}`);
body = `${content}`;
comments.forEach(comment => {
comments.forEach((comment: { text: string; id: number }) => {
if (comment.text === body) {
commentId = comment.id;
commentNeedsUpdating = false;
@ -617,13 +613,12 @@ async function ensureComment(prNo, topic, content) {
}
}
// eslint-disable-next-line no-unused-vars
async function ensureCommentRemoval(prNo, topic) {
export async function ensureCommentRemoval(prNo: number, topic: string) {
try {
logger.debug(`Ensuring comment "${topic}" in #${prNo} is removed`);
const comments = await getComments(prNo);
let commentId;
comments.forEach(comment => {
comments.forEach((comment: { text: string; id: any }) => {
if (comment.text.startsWith(`### ${topic}\n\n`)) {
commentId = comment.id;
}
@ -638,7 +633,8 @@ async function ensureCommentRemoval(prNo, topic) {
// TODO: coverage
// istanbul ignore next
async function getPrList() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function getPrList(_args?: any) {
logger.debug(`getPrList()`);
if (!config.prList) {
const values = await utils.accumulateValues(
@ -657,7 +653,7 @@ async function getPrList() {
// TODO: coverage
// istanbul ignore next
function matchesState(state, desiredState) {
function matchesState(state: string, desiredState: string) {
if (desiredState === 'all') {
return true;
}
@ -669,14 +665,23 @@ function matchesState(state, desiredState) {
// TODO: coverage
// istanbul ignore next
const isRelevantPr = (branchName, prTitle, state) => p =>
const isRelevantPr = (
branchName: string,
prTitle: string | null | undefined,
state: string
) => (p: { branchName: string; title: string; state: string }) =>
p.branchName === branchName &&
(!prTitle || p.title === prTitle) &&
matchesState(p.state, state);
// TODO: coverage
// istanbul ignore next
async function findPr(branchName, prTitle, state = 'all', refreshCache) {
export async function findPr(
branchName: string,
prTitle?: string,
state = 'all',
refreshCache?: boolean
) {
logger.debug(`findPr(${branchName}, "${prTitle}", "${state}")`);
const prList = await getPrList({ refreshCache });
const pr = prList.find(isRelevantPr(branchName, prTitle, state));
@ -690,12 +695,12 @@ async function findPr(branchName, prTitle, state = 'all', refreshCache) {
// Pull Request
async function createPr(
branchName,
title,
description,
labels,
useDefaultBranch
export async function createPr(
branchName: string,
title: string,
description: string,
_labels?: string[] | null,
useDefaultBranch?: boolean
) {
logger.debug(`createPr(${branchName}, title=${title})`);
const base = useDefaultBranch ? config.defaultBranch : config.baseBranch;
@ -716,7 +721,9 @@ async function createPr(
}/reviewers?sourceRefId=refs/heads/${branchName}&targetRefId=refs/heads/${base}&sourceRepoId=${id}&targetRepoId=${id}`
)).body;
reviewers = defReviewers.map(u => ({ user: { name: u.name } }));
reviewers = defReviewers.map((u: { name: string }) => ({
user: { name: u.name },
}));
}
const body = {
@ -770,7 +777,7 @@ async function createPr(
}
// Gets details for a PR
async function getPr(prNo, refreshCache) {
export async function getPr(prNo: number, refreshCache?: boolean) {
logger.debug(`getPr(${prNo})`);
if (!prNo) {
return null;
@ -783,10 +790,12 @@ async function getPr(prNo, refreshCache) {
{ useCache: !refreshCache }
);
const pr = {
const pr: any = {
displayNumber: `Pull Request #${res.body.id}`,
...utils.prInfo(res.body),
reviewers: res.body.reviewers.map(r => r.user.name),
reviewers: res.body.reviewers.map(
(r: { user: { name: any } }) => r.user.name
),
};
if (pr.state === 'open') {
@ -845,7 +854,7 @@ async function getPr(prNo, refreshCache) {
// Return a list of all modified files in a PR
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html
async function getPrFiles(prNo) {
export async function getPrFiles(prNo: number) {
logger.debug(`getPrFiles(${prNo})`);
if (!prNo) {
return [];
@ -857,10 +866,14 @@ async function getPrFiles(prNo) {
config.repositorySlug
}/pull-requests/${prNo}/changes?withComments=false`
);
return values.map(f => f.path.toString);
return values.map((f: { path: string }) => f.path.toString);
}
async function updatePr(prNo, title, description) {
export async function updatePr(
prNo: number,
title: string,
description: string
) {
logger.debug(`updatePr(${prNo}, title=${title})`);
try {
@ -878,7 +891,7 @@ async function updatePr(prNo, title, description) {
title,
description,
version: pr.version,
reviewers: pr.reviewers.map(name => ({ user: { name } })),
reviewers: pr.reviewers.map((name: string) => ({ user: { name } })),
},
}
);
@ -896,7 +909,7 @@ async function updatePr(prNo, title, description) {
}
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp261
async function mergePr(prNo, branchName) {
export async function mergePr(prNo: number, branchName: string) {
logger.debug(`mergePr(${prNo}, ${branchName})`);
// Used for "automerge" feature
try {
@ -927,8 +940,8 @@ async function mergePr(prNo, branchName) {
return true;
}
function getPrBody(input) {
logger.debug(`getPrBody(${(input || '').split('\n')[0]})`);
export function getPrBody(input: string) {
logger.debug(`getPrBody(${input.split('\n')[0]})`);
// Remove any HTML we use
return input
.replace(/<\/?summary>/g, '**')
@ -937,12 +950,12 @@ function getPrBody(input) {
.substring(0, 30000);
}
function getCommitMessages() {
export function getCommitMessages() {
logger.debug(`getCommitMessages()`);
return config.storage.getCommitMessages();
}
function getVulnerabilityAlerts() {
export function getVulnerabilityAlerts() {
logger.debug(`getVulnerabilityAlerts()`);
return [];
}

Просмотреть файл

@ -1,26 +1,28 @@
// SEE for the reference https://github.com/renovatebot/renovate/blob/c3e9e572b225085448d94aa121c7ec81c14d3955/lib/platform/bitbucket/utils.js
const url = require('url');
const api = require('./bb-got-wrapper');
import url from 'url';
import api from './bb-got-wrapper';
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp250
const prStateMapping = {
const prStateMapping: any = {
MERGED: 'merged',
DECLINED: 'closed',
OPEN: 'open',
};
const prInfo = pr => ({
version: pr.version,
number: pr.id,
body: pr.description,
branchName: pr.fromRef.displayId,
title: pr.title,
state: prStateMapping[pr.state],
createdAt: pr.createdDate,
canRebase: false,
});
export function prInfo(pr: any) {
return {
version: pr.version,
number: pr.id,
body: pr.description,
branchName: pr.fromRef.displayId,
title: pr.title,
state: prStateMapping[pr.state],
createdAt: pr.createdDate,
canRebase: false,
};
}
const addMaxLength = (inputUrl, limit = 100) => {
const addMaxLength = (inputUrl: string, limit = 100) => {
const { search, ...parsedUrl } = url.parse(inputUrl, true); // eslint-disable-line @typescript-eslint/no-unused-vars
const maxedUrl = url.format({
...parsedUrl,
@ -29,13 +31,19 @@ const addMaxLength = (inputUrl, limit = 100) => {
return maxedUrl;
};
const accumulateValues = async (reqUrl, method = 'get', options, limit) => {
let accumulator = [];
export async function accumulateValues(
reqUrl: string,
method = 'get',
options?: any,
limit?: number
) {
let accumulator: any = [];
let nextUrl = addMaxLength(reqUrl, limit);
const lowerCaseMethod = method.toLocaleLowerCase();
while (typeof nextUrl !== 'undefined') {
const { body } = await api[lowerCaseMethod](nextUrl, options);
// TODO: fix typing
const { body } = await (api as any)[lowerCaseMethod](nextUrl, options);
accumulator = [...accumulator, ...body.values];
if (body.isLastPage !== false) break;
@ -50,13 +58,4 @@ const accumulateValues = async (reqUrl, method = 'get', options, limit) => {
}
return accumulator;
};
module.exports = {
// buildStates,
prInfo,
accumulateValues,
// files: filesEndpoint,
// isConflicted,
// commitForm,
};
}

37
lib/platform/common.ts Normal file
Просмотреть файл

@ -0,0 +1,37 @@
import got from 'got';
export interface IGotApiOptions {
useCache?: boolean;
body?: any;
}
export interface IGotApi {
get<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
post<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
put<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
patch<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
head<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
delete<T extends object = any>(
url: string,
options?: IGotApiOptions
): Promise<got.Response<T>>;
reset(): void;
setEndpoint(endpoint: string): void;
}

Просмотреть файл

@ -5,6 +5,7 @@ import Git from 'simple-git/promise';
import URL from 'url';
declare module 'fs-extra' {
// eslint-disable-next-line import/prefer-default-export
export function exists(pathLike: string): Promise<boolean>;
}
@ -24,7 +25,9 @@ interface ILocalConfig extends IStorageConfig {
class Storage {
private _config: ILocalConfig = {} as any;
private _git: Git.SimpleGit | undefined;
private _cwd: string | undefined;
private async _resetToBranch(branchName: string) {
@ -50,14 +53,16 @@ class Storage {
async initRepo(args: IStorageConfig) {
debugger;
this.cleanRepo();
let config: ILocalConfig = (this._config = { ...args } as any);
let cwd = (this._cwd = config.localDir);
// eslint-disable-next-line no-multi-assign
const config: ILocalConfig = (this._config = { ...args } as any);
// eslint-disable-next-line no-multi-assign
const cwd = (this._cwd = config.localDir);
this._config.branchExists = {};
logger.info('Initialising git repository into ' + cwd);
const gitHead = join(cwd, '.git/HEAD');
let clone = true;
//TODO: move to private class scope
// TODO: move to private class scope
async function determineBaseBranch(git: Git.SimpleGit) {
// see https://stackoverflow.com/a/44750379/1438522
try {
@ -423,6 +428,7 @@ class Storage {
}
}
// eslint-disable-next-line class-methods-use-this
cleanRepo() {}
static getUrl({

5
lib/types.d.ts поставляемый
Просмотреть файл

@ -11,8 +11,13 @@ declare namespace Renovate {
setMeta(obj: any): void;
}
interface IDict<T> {
[key: string]: T;
}
}
// eslint-disable-next-line no-var, vars-on-top
declare var logger: Renovate.ILogger;
declare interface Error {

Просмотреть файл

@ -1,10 +1,20 @@
import URL from 'url';
//TODO: add known properties
interface IPlatformConfig {
export const defaults: IDict<IPlatformConfig> = {
bitbucket: { name: 'Bitbucket', endpoint: 'https://api.bitbucket.org/' },
'bitbucket-server': { name: 'Bitbucket Server' },
github: { name: 'GitHub', endpoint: 'https://api.github.com/' },
gitlab: { name: 'GitLab', endpoint: 'https://gitlab.com/api/v4/' },
azure: { name: 'Azure DevOps' },
};
// TODO: add known properties
export interface IPlatformConfig {
[prop: string]: any;
name?: string;
endpoint?: string;
token?: string;
}
interface IDict<T> {
[key: string]: T;

Просмотреть файл

@ -154,9 +154,10 @@
"@types/bunyan": "1.8.6",
"@types/convert-hrtime": "2.0.0",
"@types/fs-extra": "7.0.0",
"@types/got": "9.4.4",
"@types/jest": "24.0.13",
"@types/node": "11.13.11",
"@types/tmp": "0.1.0",
"@types/tmp": "0.0.33",
"@typescript-eslint/eslint-plugin": "1.9.0",
"@typescript-eslint/parser": "1.9.0",
"babel-plugin-transform-object-rest-spread": "7.0.0-beta.3",

Просмотреть файл

@ -922,14 +922,6 @@ Object {
}
`;
exports[`platform/bitbucket-server endpoint with no path initRepo() no author 1`] = `
Object {
"isFork": false,
"privateRepo": undefined,
"repoFullName": "repo",
}
`;
exports[`platform/bitbucket-server endpoint with no path initRepo() works 1`] = `
Object {
"isFork": false,
@ -2300,14 +2292,6 @@ Object {
}
`;
exports[`platform/bitbucket-server endpoint with path initRepo() no author 1`] = `
Object {
"isFork": false,
"privateRepo": undefined,
"repoFullName": "repo",
}
`;
exports[`platform/bitbucket-server endpoint with path initRepo() works 1`] = `
Object {
"isFork": false,

Просмотреть файл

@ -1,17 +1,23 @@
const responses = require('./_fixtures/responses');
import responses from './_fixtures/responses';
import { IGotApi } from '../../../lib/platform/common';
import Storage from '../../../lib/platform/git/storage';
type BbsApi = typeof import('../../../lib/platform/bitbucket-server');
describe('platform/bitbucket-server', () => {
Object.entries(responses).forEach(([scenarioName, mockResponses]) => {
describe(scenarioName, () => {
let bitbucket;
let api;
let hostRules;
let GitStorage;
let bitbucket: typeof import('../../../lib/platform/bitbucket-server');
let api: jest.Mocked<IGotApi>;
let hostRules: jest.Mocked<typeof import('../../../lib/util/host-rules')>;
let GitStorage: jest.Mock<Storage> & {
getUrl: jest.MockInstance<any, any>;
};
beforeEach(() => {
// reset module
jest.resetModules();
jest.mock('delay');
jest.mock('got', () => (url, options) => {
jest.mock('got', () => (url: string, options: { method: string }) => {
const { method } = options;
const body = mockResponses[url] && mockResponses[url][method];
if (!body) {
@ -25,32 +31,36 @@ describe('platform/bitbucket-server', () => {
jest.mock('../../../lib/platform/git/storage');
jest.mock('../../../lib/util/host-rules');
hostRules = require('../../../lib/util/host-rules');
api = require('../../../lib/platform/bitbucket-server/bb-got-wrapper');
api = require('../../../lib/platform/bitbucket-server/bb-got-wrapper')
.api;
jest.spyOn(api, 'get');
jest.spyOn(api, 'post');
jest.spyOn(api, 'put');
jest.spyOn(api, 'delete');
bitbucket = require('../../../lib/platform/bitbucket-server');
GitStorage = require('../../../lib/platform/git/storage');
GitStorage.mockImplementation(() => ({
initRepo: jest.fn(),
cleanRepo: jest.fn(),
getFileList: jest.fn(),
branchExists: jest.fn(() => true),
isBranchStale: jest.fn(() => false),
setBaseBranch: jest.fn(),
getBranchLastCommitTime: jest.fn(),
getAllRenovateBranches: jest.fn(),
getCommitMessages: jest.fn(),
getFile: jest.fn(),
commitFilesToBranch: jest.fn(),
mergeBranch: jest.fn(),
deleteBranch: jest.fn(),
getRepoStatus: jest.fn(),
getBranchCommit: jest.fn(
() => '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
),
}));
GitStorage.mockImplementation(
() =>
({
initRepo: jest.fn(),
cleanRepo: jest.fn(),
getFileList: jest.fn(),
branchExists: jest.fn(() => true),
isBranchStale: jest.fn(() => false),
setBaseBranch: jest.fn(),
getBranchLastCommitTime: jest.fn(),
getAllRenovateBranches: jest.fn(),
getCommitMessages: jest.fn(),
getFile: jest.fn(),
commitFilesToBranch: jest.fn(),
mergeBranch: jest.fn(),
deleteBranch: jest.fn(),
getRepoStatus: jest.fn(),
getBranchCommit: jest.fn(
() => '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
),
} as any)
);
const endpoint =
scenarioName === 'endpoint with path'
? 'https://stash.renovatebot.com/vcs/'
@ -75,19 +85,18 @@ describe('platform/bitbucket-server', () => {
function initRepo() {
return bitbucket.initRepo({
repository: 'SOME/repo',
gitAuthor: 'bot@renovateapp.com',
});
} as any);
}
describe('init function', () => {
it('should throw if no endpoint', () => {
expect(() => {
bitbucket.initPlatform({});
bitbucket.initPlatform({} as any);
}).toThrow();
});
it('should throw if no username/password', () => {
expect(() => {
bitbucket.initPlatform({ endpoint: 'endpoint' });
bitbucket.initPlatform({ endpoint: 'endpoint' } as any);
}).toThrow();
});
it('should init', () => {
@ -116,20 +125,12 @@ describe('platform/bitbucket-server', () => {
expect(res).toMatchSnapshot();
});
it('no author', async () => {
expect.assertions(1);
const res = await bitbucket.initRepo({
repository: 'SOME/repo',
});
expect(res).toMatchSnapshot();
});
it('sends the host as the endpoint option', async () => {
expect.assertions(2);
GitStorage.getUrl.mockClear();
await bitbucket.initRepo({
repository: 'SOME/repo',
});
} as any);
expect(GitStorage.getUrl).toHaveBeenCalledTimes(1);
expect(GitStorage.getUrl.mock.calls[0][0]).toHaveProperty(
'host',
@ -167,7 +168,7 @@ describe('platform/bitbucket-server', () => {
describe('getFileList()', () => {
it('sends to gitFs', async () => {
await initRepo();
await bitbucket.branchExists();
await bitbucket.branchExists(undefined as any);
});
});
});
@ -175,7 +176,7 @@ describe('platform/bitbucket-server', () => {
describe('isBranchStale()', () => {
it('sends to gitFs', async () => {
await initRepo();
await bitbucket.isBranchStale();
await bitbucket.isBranchStale(undefined as any);
});
});
@ -197,7 +198,7 @@ describe('platform/bitbucket-server', () => {
it('sends to gitFs', async () => {
expect.assertions(1);
await initRepo();
await bitbucket.commitFilesToBranch('some-branch', [{}]);
await bitbucket.commitFilesToBranch('some-branch', [{}], 'message');
expect(api.get.mock.calls).toMatchSnapshot();
});
});
@ -205,21 +206,21 @@ describe('platform/bitbucket-server', () => {
describe('getFile()', () => {
it('sends to gitFs', async () => {
await initRepo();
await bitbucket.getFile();
await bitbucket.getFile('', '');
});
});
describe('getAllRenovateBranches()', () => {
it('sends to gitFs', async () => {
await initRepo();
await bitbucket.getAllRenovateBranches();
await bitbucket.getAllRenovateBranches('');
});
});
describe('getBranchLastCommitTime()', () => {
it('sends to gitFs', async () => {
await initRepo();
await bitbucket.getBranchLastCommitTime();
await bitbucket.getBranchLastCommitTime('');
});
});
@ -248,9 +249,9 @@ describe('platform/bitbucket-server', () => {
expect.assertions(5);
await initRepo();
await expect(bitbucket.addReviewers(null, ['name'])).rejects.toThrow(
'not-found'
);
await expect(
bitbucket.addReviewers(null as any, ['name'])
).rejects.toThrow('not-found');
await expect(bitbucket.addReviewers(4, ['name'])).rejects.toThrow(
'not-found'
@ -426,7 +427,7 @@ describe('platform/bitbucket-server', () => {
expect.assertions(2);
await initRepo();
expect(
await bitbucket.findPr('userName1/pullRequest1', false)
await bitbucket.findPr('userName1/pullRequest1')
).toBeUndefined();
expect(api.get.mock.calls).toMatchSnapshot();
});
@ -480,7 +481,7 @@ describe('platform/bitbucket-server', () => {
describe('getPr()', () => {
it('returns null for no prNo', async () => {
expect.assertions(2);
expect(await bitbucket.getPr()).toBeNull();
expect(await bitbucket.getPr(undefined as any)).toBeNull();
expect(api.get.mock.calls).toMatchSnapshot();
});
it('gets a PR', async () => {
@ -497,10 +498,10 @@ describe('platform/bitbucket-server', () => {
try {
expect(await bitbucket.getPr(3)).toMatchSnapshot();
global.gitAuthor = { email: 'bot@renovateapp.com' };
global.gitAuthor = { email: 'bot@renovateapp.com', name: 'bot' };
expect(await bitbucket.getPr(5)).toMatchSnapshot();
global.gitAuthor = { email: 'jane@example.com' };
global.gitAuthor = { email: 'jane@example.com', name: 'jane' };
expect(await bitbucket.getPr(5)).toMatchSnapshot();
expect(api.get.mock.calls).toMatchSnapshot();
@ -520,7 +521,7 @@ describe('platform/bitbucket-server', () => {
reviewers: [],
fromRef: {},
},
});
} as any);
expect(await bitbucket.getPr(5)).toMatchSnapshot();
expect(api.get.mock.calls).toMatchSnapshot();
});
@ -529,7 +530,7 @@ describe('platform/bitbucket-server', () => {
describe('getPrFiles()', () => {
it('returns empty files', async () => {
expect.assertions(1);
expect(await bitbucket.getPrFiles(null)).toHaveLength(0);
expect(await bitbucket.getPrFiles(null as any)).toHaveLength(0);
});
it('returns one file', async () => {
@ -554,7 +555,7 @@ describe('platform/bitbucket-server', () => {
await initRepo();
await expect(
bitbucket.updatePr(null, 'title', 'body')
bitbucket.updatePr(null as any, 'title', 'body')
).rejects.toThrow('not-found');
await expect(bitbucket.updatePr(4, 'title', 'body')).rejects.toThrow(
@ -617,9 +618,9 @@ describe('platform/bitbucket-server', () => {
expect.assertions(5);
await initRepo();
await expect(bitbucket.mergePr(null, 'branch')).rejects.toThrow(
'not-found'
);
await expect(
bitbucket.mergePr(null as any, 'branch')
).rejects.toThrow('not-found');
await expect(bitbucket.mergePr(4, 'branch')).rejects.toThrow(
'not-found'
);
@ -700,7 +701,7 @@ describe('platform/bitbucket-server', () => {
inProgress: 0,
failed: 0,
},
});
} as any);
await expect(
bitbucket.getBranchStatus('somebranch', true)
@ -722,7 +723,7 @@ describe('platform/bitbucket-server', () => {
inProgress: 1,
failed: 0,
},
});
} as any);
await expect(
bitbucket.getBranchStatus('somebranch', true)
@ -734,7 +735,7 @@ describe('platform/bitbucket-server', () => {
inProgress: 0,
failed: 0,
},
});
} as any);
await expect(
bitbucket.getBranchStatus('somebranch', true)
@ -753,7 +754,7 @@ describe('platform/bitbucket-server', () => {
inProgress: 1,
failed: 1,
},
});
} as any);
await expect(
bitbucket.getBranchStatus('somebranch', true)
@ -772,11 +773,14 @@ describe('platform/bitbucket-server', () => {
it('throws repository-changed', async () => {
expect.assertions(1);
GitStorage.mockImplementationOnce(() => ({
initRepo: jest.fn(),
branchExists: jest.fn(() => Promise.resolve(false)),
cleanRepo: jest.fn(),
}));
GitStorage.mockImplementationOnce(
() =>
({
initRepo: jest.fn(),
branchExists: jest.fn(() => Promise.resolve(false)),
cleanRepo: jest.fn(),
} as any)
);
await initRepo();
await expect(
bitbucket.getBranchStatus('somebranch', true)
@ -799,7 +803,7 @@ describe('platform/bitbucket-server', () => {
},
],
},
});
} as any);
await expect(
bitbucket.getBranchStatusCheck('somebranch', 'context-2')
@ -822,7 +826,7 @@ describe('platform/bitbucket-server', () => {
},
],
},
});
} as any);
await expect(
bitbucket.getBranchStatusCheck('somebranch', 'context-2')
@ -845,7 +849,7 @@ describe('platform/bitbucket-server', () => {
},
],
},
});
} as any);
await expect(
bitbucket.getBranchStatusCheck('somebranch', 'context-2')
@ -870,7 +874,7 @@ describe('platform/bitbucket-server', () => {
isLastPage: true,
values: [],
},
});
} as any);
await expect(
bitbucket.getBranchStatusCheck('somebranch', 'context-2')
@ -889,28 +893,28 @@ describe('platform/bitbucket-server', () => {
await bitbucket.setBranchStatus(
'somebranch',
'context-2',
null,
null as any,
'success'
);
await bitbucket.setBranchStatus(
'somebranch',
'context-2',
null,
null as any,
'failed'
);
await bitbucket.setBranchStatus(
'somebranch',
'context-2',
null,
null as any,
'failure'
);
await bitbucket.setBranchStatus(
'somebranch',
'context-2',
null,
null as any,
'pending'
);
@ -921,14 +925,14 @@ describe('platform/bitbucket-server', () => {
await bitbucket.setBranchStatus(
'somebranch',
'context-2',
null,
null as any,
'success'
);
await bitbucket.setBranchStatus(
'somebranch',
'context-1',
null,
null as any,
'success'
);

Просмотреть файл

@ -31,26 +31,26 @@ describe('platform', () => {
});
it('has same API for github and gitlab', () => {
const githubMethods = Object.keys(github);
const gitlabMethods = Object.keys(gitlab);
const githubMethods = Object.keys(github).sort();
const gitlabMethods = Object.keys(gitlab).sort();
expect(githubMethods).toMatchObject(gitlabMethods);
});
it('has same API for github and azure', () => {
const githubMethods = Object.keys(github);
const azureMethods = Object.keys(azure);
const githubMethods = Object.keys(github).sort();
const azureMethods = Object.keys(azure).sort();
expect(githubMethods).toMatchObject(azureMethods);
});
it('has same API for github and Bitbucket', () => {
const githubMethods = Object.keys(github);
const bitbucketMethods = Object.keys(bitbucket);
const githubMethods = Object.keys(github).sort();
const bitbucketMethods = Object.keys(bitbucket).sort();
expect(bitbucketMethods).toMatchObject(githubMethods);
});
it('has same API for github and Bitbucket Server', () => {
const githubMethods = Object.keys(github);
const bitbucketMethods = Object.keys(bitbucketServer);
const githubMethods = Object.keys(github).sort();
const bitbucketMethods = Object.keys(bitbucketServer).sort();
expect(bitbucketMethods).toMatchObject(githubMethods);
});
});

Просмотреть файл

@ -521,6 +521,14 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/got@9.4.4":
version "9.4.4"
resolved "https://registry.yarnpkg.com/@types/got/-/got-9.4.4.tgz#78129553f6a41715df601db43532cd0b87a55d3f"
integrity sha512-IGAJokJRE9zNoBdY5csIwN4U5qQn+20HxC0kM+BbUdfTKIXa7bOX/pdhy23NnLBRP8Wvyhx7X5e6EHJs+4d8HA==
dependencies:
"@types/node" "*"
"@types/tough-cookie" "*"
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -578,10 +586,15 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/tmp@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd"
integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==
"@types/tmp@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d"
integrity sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=
"@types/tough-cookie@*":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d"
integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==
"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
version "12.0.9"