UNPKG

long-git-cli

Version:

A CLI tool for Git tag management.

335 lines 13.2 kB
"use strict"; /** * Bitbucket API 客户端 * 负责与 Bitbucket API 交互,获取 Pipeline 状态 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BitbucketClient = void 0; const axios_1 = __importDefault(require("axios")); const constants_1 = require("../constants"); /** * Bitbucket API 客户端类 */ class BitbucketClient { /** * 构造函数 * @param username Bitbucket 用户名 * @param token Bitbucket API Token 或 App Password(明文) */ constructor(username, token) { this.username = username; this.token = token; // Bitbucket API Token 和 App Password 都使用 Basic Auth // 用户名作为 username,token 作为 password this.client = axios_1.default.create({ baseURL: constants_1.BITBUCKET_API_BASE_URL, auth: { username: this.username, password: token, }, headers: { "Content-Type": "application/json", }, timeout: 30000, // 30 秒超时 }); } /** * 测试连接 * @returns 连接是否成功 */ async testConnection() { try { // 尝试获取用户的 repositories 来测试连接 // 这个端点支持 API Token 和 App Password const response = await this.client.get(`/user/permissions/repositories`); console.log('Bitbucket 连接测试成功'); return true; } catch (error) { if (axios_1.default.isAxiosError(error)) { const axiosError = error; if (axiosError.response) { console.error('Bitbucket 连接失败:', { status: axiosError.response.status, statusText: axiosError.response.statusText, data: axiosError.response.data, }); // 401 表示认证失败 if (axiosError.response.status === 401) { console.error('认证失败:请检查以下几点:'); console.error('1. 用户名是否正确(应该是 Bitbucket 邮箱或用户名)'); console.error('2. API Token 是否正确(应该以 ATAT 开头)'); console.error('3. API Token 是否已过期'); console.error('4. API Token 是否有正确的 scopes:'); console.error(' - account (或 account:read)'); console.error(' - repository (或 repository:read)'); console.error(' - pullrequest (或 pullrequest:read)'); } // 403 表示权限不足 else if (axiosError.response.status === 403) { console.error('权限不足:API Token 缺少必要的 scopes'); console.error('请确保 Token 有以下权限:'); console.error(' - account (或 account:read)'); console.error(' - repository (或 repository:read)'); console.error(' - pullrequest (或 pullrequest:read)'); } } else if (axiosError.request) { console.error('网络错误:无法连接到 Bitbucket API'); } else { console.error('请求配置错误:', axiosError.message); } } else { console.error('未知错误:', error); } return false; } } /** * 获取指定 tag 的 Pipeline 列表 * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param tag Tag 名称 * @returns Pipeline 列表 */ async getPipelinesByTag(workspace, repoSlug, tag) { try { console.log(`查询 Pipeline: workspace=${workspace}, repo=${repoSlug}, tag=${tag}`); const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/pipelines/`, { params: { // 按 tag 过滤 - 使用 target.ref_name 而不是带引号的 'target.ref_name': tag, // 按创建时间倒序排序 sort: "-created_on", }, }); console.log(`找到 ${response.data.values?.length || 0} 个 Pipeline`); if (response.data.values && response.data.values.length > 0) { console.log(`最新 Pipeline: ${response.data.values[0].uuid}, 状态: ${response.data.values[0].state.name}`); } return response.data.values || []; } catch (error) { this.handleError(error, "获取 Pipeline 列表失败"); throw error; } } /** * 获取 tag 对应的 commit hash * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param tag Tag 名称 * @returns Commit hash */ async getTagCommit(workspace, repoSlug, tag) { try { const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/refs/tags/${tag}`); return response.data.target.hash; } catch (error) { this.handleError(error, `获取 tag ${tag} 的 commit 失败`); throw error; } } /** * 获取 commit 的 build status * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param commitHash Commit hash * @returns Build status 列表 */ async getCommitBuildStatus(workspace, repoSlug, commitHash) { try { console.log(`查询 commit ${commitHash.substring(0, 7)} 的 build status...`); // 使用 /statuses 而不是 /statuses/build 来读取状态 // /statuses/build 是用于创建/更新状态,需要 OAuth // /statuses 是用于读取所有状态,支持 API Token const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/commit/${commitHash}/statuses`); const statuses = response.data.values || []; console.log(`找到 ${statuses.length} 个 status`); if (statuses.length > 0) { statuses.forEach((status) => { console.log(` - ${status.name || status.key}: ${status.state}`); }); } return statuses; } catch (error) { this.handleError(error, "获取 commit build status 失败"); throw error; } } /** * 获取分支的最新 commit hash * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param branch 分支名称 * @returns Commit hash */ async getBranchLatestCommit(workspace, repoSlug, branch) { try { console.log(`获取分支 ${branch} 的最新 commit...`); const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/refs/branches/${branch}`); const commitHash = response.data.target.hash; console.log(`分支 ${branch} 最新 commit: ${commitHash.substring(0, 7)}`); return commitHash; } catch (error) { this.handleError(error, `获取分支 ${branch} 的最新 commit 失败`); throw error; } } /** * 创建 tag(通过 API) * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param tagName Tag 名称 * @param commitHash Commit hash(可选,不提供则使用当前 HEAD) * @returns 创建的 tag 信息 */ async createTag(workspace, repoSlug, tagName, commitHash) { try { console.log(`创建 tag: ${tagName}${commitHash ? ` (commit: ${commitHash.substring(0, 7)})` : ''}`); const payload = { name: tagName, target: {} }; if (commitHash) { payload.target.hash = commitHash; } const response = await this.client.post(`/repositories/${workspace}/${repoSlug}/refs/tags`, payload); console.log(`Tag ${tagName} 创建成功`); return response.data; } catch (error) { this.handleError(error, `创建 tag ${tagName} 失败`); throw error; } } /** * 获取仓库的所有 tags * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @returns Tag 列表 */ async getTags(workspace, repoSlug) { try { console.log(`获取仓库 tags: ${workspace}/${repoSlug}`); const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/refs/tags`, { params: { pagelen: 100, // 获取最近 100 个 tags sort: '-target.date', // 按日期倒序 } }); const tags = response.data.values || []; console.log(`找到 ${tags.length} 个 tags`); return tags; } catch (error) { this.handleError(error, "获取 tags 失败"); throw error; } } /** * 获取指定 Pipeline 的状态 * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param pipelineUuid Pipeline UUID * @returns Pipeline 状态 */ async getPipelineStatus(workspace, repoSlug, pipelineUuid) { try { // 移除 UUID 中的大括号(如果有) const cleanUuid = pipelineUuid.replace(/[{}]/g, ""); const response = await this.client.get(`/repositories/${workspace}/${repoSlug}/pipelines/${cleanUuid}`); return response.data; } catch (error) { this.handleError(error, "获取 Pipeline 状态失败"); throw error; } } /** * 等待 Pipeline 完成 * @param workspace Bitbucket workspace * @param repoSlug 仓库 slug * @param tag Tag 名称 * @param options 等待选项 * @returns 最终的 Pipeline 状态 */ async waitForPipelineCompletion(workspace, repoSlug, tag, options) { const pollInterval = options?.pollInterval || constants_1.BITBUCKET_POLL_INTERVAL; const timeout = options?.timeout || constants_1.BITBUCKET_TIMEOUT; const onProgress = options?.onProgress; const startTime = Date.now(); // 首先获取该 tag 的 Pipeline const pipelines = await this.getPipelinesByTag(workspace, repoSlug, tag); if (pipelines.length === 0) { throw new Error(`未找到 tag "${tag}" 的 Pipeline`); } // 获取最新的 Pipeline const pipeline = pipelines[0]; const pipelineUuid = pipeline.uuid; // 轮询 Pipeline 状态 while (true) { // 检查超时 if (Date.now() - startTime > timeout) { throw new Error(`Pipeline 监听超时(${timeout / 1000}秒),tag: ${tag}`); } // 获取当前状态 const status = await this.getPipelineStatus(workspace, repoSlug, pipelineUuid); // 调用进度回调 if (onProgress) { onProgress(status); } // 检查是否完成 const stateName = status.state.name; if (stateName === "SUCCESSFUL" || stateName === "FAILED" || stateName === "STOPPED") { return status; } // 等待下一次轮询 await this.sleep(pollInterval); } } /** * 休眠指定毫秒数 * @param ms 毫秒数 */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * 处理错误 * @param error 错误对象 * @param message 错误消息 */ handleError(error, message) { if (axios_1.default.isAxiosError(error)) { const axiosError = error; if (axiosError.response) { // 服务器返回错误响应 console.error(`${message}: ${axiosError.response.status} - ${JSON.stringify(axiosError.response.data)}`); } else if (axiosError.request) { // 请求已发送但没有收到响应 console.error(`${message}: 网络错误,无法连接到 Bitbucket API`); } else { // 请求配置错误 console.error(`${message}: ${axiosError.message}`); } } else { console.error(`${message}:`, error); } } } exports.BitbucketClient = BitbucketClient; //# sourceMappingURL=bitbucket-client.js.map