UNPKG

@sleavely/bitbucket

Version:

Client and utils for Bitbucket Cloud REST APIs

270 lines (267 loc) 11 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { BitbucketClient: () => BitbucketClient }); module.exports = __toCommonJS(src_exports); // src/BitbucketClient.ts var import_form_data = __toESM(require("form-data")); var import_got = __toESM(require("got")); var import_path = require("path"); var BitbucketClient = class { client; constructor(opts) { if (!opts.auth) { throw new Error("Missing auth params"); } this.client = import_got.default.extend({ prefixUrl: "https://api.bitbucket.org/2.0/", headers: { Accept: "application/json" }, responseType: "json", ...opts.auth }); } extend(gotOpts) { this.client = this.client.extend(gotOpts); } // #region User async getCurrentUser() { return await this.client("user").json(); } // #endregion // #region Workspaces async listWorkspaces() { const { values } = await this.client("user/permissions/workspaces").json(); return values.map(({ permission, workspace }) => { return { ...workspace, permission }; }); } async getWorkspace(workspace) { return await this.client(`workspaces/${workspace}}`).json(); } // #endregion // #region Projects async getProject(workspace, projectKey) { return await this.client(`workspaces/${workspace}/projects/${projectKey}`).json(); } // #endregion // #region Repositories async getRepositoriesByProject(workspace, projectKey, requestPage) { const res = await this.client(`repositories/${workspace}?q=project.key="${projectKey}"${requestPage ? `&page=${requestPage}` : ""}`).json(); const { values, next, page: currentPage } = res; if (next) return [...values, ...await this.getRepositoriesByProject(workspace, projectKey, currentPage + 1)]; return values; } async getRepository(workspace, repoSlug) { return await this.client(`repositories/${workspace}/${repoSlug}`).json(); } /** * @deprecated This only returns repo-level defaults, not inherited ones. Use getEffectiveDefaultReviewers instead. * @see getEffectiveDefaultReviewers */ async getDefaultReviewers(workspace, repoSlug) { const { values } = await this.client(`repositories/${workspace}/${repoSlug}/default-reviewers`).json(); return values; } /** * Adds the specified user to the repository's list of default reviewers. This method is idempotent. Adding a user a second time has no effect. */ async addDefaultReviewer(workspace, repoSlug, targetUser) { return await this.client.put(`repositories/${workspace}/${repoSlug}/default-reviewers/${targetUser}`).json(); } async removeDefaultReviewer(workspace, repoSlug, targetUser) { await this.client.delete(`repositories/${workspace}/${repoSlug}/default-reviewers/${targetUser}`); } /** * Includes both default reviewers defined at the repository level as well as those inherited from its project. */ async getEffectiveDefaultReviewers(workspace, repoSlug) { const { values } = await this.client(`repositories/${workspace}/${repoSlug}/effective-default-reviewers`).json(); return values.map(({ user, reviewer_type }) => ({ ...user, reviewer_type })); } // #endregion // #region Pipelines /** * Gets 10 most recent pipelines that were created */ async getPipelines(workspace, repoSlug) { const { values } = await this.client(`repositories/${workspace}/${repoSlug}/pipelines/?sort=-created_on`).json(); return values; } async getPipeline(workspace, repoSlug, pipelineUuid) { return await this.client(`repositories/${workspace}/${repoSlug}/pipelines/${pipelineUuid}`).json(); } async getCommitStatuses(workspace, repoSlug, commit) { const { values } = await this.client(`repositories/${workspace}/${repoSlug}/commit/${commit}/statuses`).json(); return values; } /** * Returns repository level variables */ async getRepoVariables(workspace, repoSlug) { const { values } = await this.client(`repositories/${workspace}/${repoSlug}/pipelines_config/variables`).json(); return values; } /** * Regardless of whether it exists or not */ async setRepoVariable(workspace, repoSlug, varName, varValue) { const repoVars = await this.getRepoVariables(workspace, repoSlug); const existingVariable = repoVars.find(({ key }) => key === varName); if (!existingVariable) { return await this.createRepoVariable(workspace, repoSlug, varName, varValue); } else { return await this.updateRepoVariable(workspace, repoSlug, existingVariable.uuid, varName, varValue); } } async createRepoVariable(workspace, repoSlug, varName, varValue) { return await this.client.post( `repositories/${workspace}/${repoSlug}/pipelines_config/variables`, { json: { type: "pipeline_variable", key: varName, value: varValue, secured: true } } ).json(); } async updateRepoVariable(workspace, repoSlug, varUuid, varName, varValue) { return await this.client.put( `repositories/${workspace}/${repoSlug}/pipelines_config/variables/${varUuid}`, { json: { type: "pipeline_variable", uuid: varUuid, key: varName, value: varValue, secured: true } } ).json(); } async deleteRepoVariable(workspace, repoSlug, varName) { const repoVars = await this.getRepoVariables(workspace, repoSlug); const variableProps = repoVars.find(({ key }) => key === varName); if (!variableProps) return null; return await this.client.delete(`repositories/${workspace}/${repoSlug}/pipelines_config/variables/${variableProps.uuid}`); } // #endregion // #region Pull Requests async createPullRequest(workspace, repoSlug, title, sourceBranch, destinationBranch) { const currentUser = await this.getCurrentUser(); const defaultReviewers = (await this.getEffectiveDefaultReviewers(workspace, repoSlug)).filter((reviewer) => currentUser.uuid !== reviewer.uuid); const destination = destinationBranch ? { branch: { name: destinationBranch } } : void 0; return await this.client.post( `repositories/${workspace}/${repoSlug}/pullrequests`, { json: { title, source: { branch: { name: sourceBranch } }, destination, reviewers: defaultReviewers, close_source_branch: true } } ).json(); } // #endregion // #region Files async getFile(workspace, repoSlug, commitOrRef, filePath) { const absolutePath = import_path.posix.resolve("/", filePath); return await this.client.extend({ responseType: "text" })(`repositories/${workspace}/${repoSlug}/src/${commitOrRef}${absolutePath}`).then((res) => res.body).catch((err) => { if (err.response && err.response.statusCode === 404) return null; throw err; }); } /** * @param workspace * @param repoSlug * @param filePath A file path in the repository * @param contents * @param message The commit message * @param author Should be in format of `Joakim Hedlund <contact@joakimhedlund.com>`. Defaults to the current user. * @param branch The name of the branch that the new commit should be created on. When omitted, the commit will be created on top of the main branch * @returns Full commit hash */ async commitFile(workspace, repoSlug, filePath, contents, message = "Posted a file via API", author, branch) { const form = new import_form_data.default(); form.append(filePath, contents); if (message) form.append("message", message); if (author) form.append("author", author); if (branch) form.append("branch", branch); const res = await this.client.post(`repositories/${workspace}/${repoSlug}/src`, { body: form }); const commitHash = res.headers?.location?.split("/")?.at(-1) ?? "<unknown>"; return commitHash; } /** * @param workspace * @param repoSlug * @param filePath A file path in the repository * @param message The commit message * @param author Should be in format of `Joakim Hedlund <contact@joakimhedlund.com>`. Defaults to the current user. * @param branch The name of the branch that the new commit should be created on. When omitted, the commit will be created on top of the main branch * @returns Full commit hash */ async removeFile(workspace, repoSlug, filePath, message = "Removed a file via API", author, branch) { const form = new import_form_data.default(); form.append("files", filePath); if (message) form.append("message", message); if (author) form.append("author", author); if (branch) form.append("branch", branch); const res = await this.client.post(`repositories/${workspace}/${repoSlug}/src`, { body: form }); const commitHash = res.headers?.location?.split("/")?.at(-1) ?? "<unknown>"; return commitHash; } async codeSearch(workspace, query, requestPage) { const queryparams = new URLSearchParams({ search_query: query, fields: "+values.file.commit.repository" }); if (requestPage) queryparams.append("page", requestPage.toString()); const res = await this.client(`workspaces/${workspace}/search/code?${queryparams.toString()}`).json(); const { values, next, page: currentPage } = res; if (next) return [...values, ...await this.codeSearch(workspace, query, currentPage + 1)]; return values; } // #endregion }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BitbucketClient });