long-git-cli
Version:
A CLI tool for Git tag management.
335 lines • 13.2 kB
JavaScript
;
/**
* 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