feat: 添加 Repo 和 User 模块,增强 CNBCore 功能

This commit is contained in:
2025-12-15 17:08:39 +08:00
parent 5e43eb2db7
commit 52ccf115fb
9 changed files with 390 additions and 32 deletions

View File

@@ -1,56 +1,134 @@
export type CNBCoreOptions<T = {}> = {
token: string;
cookie?: string;
} & T;
export type RequestOptions = {
url?: string;
method?: string;
data?: Record<string, any>;
body?: any;
params?: Record<string, any>;
headers?: Record<string, any>;
useCookie?: boolean;
useOrigin?: boolean;
};
export class CNBCore {
baseURL = 'https://api.cnb.cool';
token: string;
cookie?: string;
constructor(options: CNBCoreOptions) {
this.token = options.token;
this.cookie = options.cookie;
}
async request({ url, method = 'GET', data, params }: { url: string, method?: string, data?: Record<string, any>, params?: Record<string, any> }): Promise<any> {
const headers: Record<string, string> = {
async request({ url, method = 'GET', data, params, headers, body, useCookie, useOrigin }: RequestOptions): Promise<any> {
const defaultHeaders: Record<string, string> = {
'Content-Type': 'application/json',
'Accept': 'application/json, application/vnd.cnb.api+json, application/vnd.cnb.web+json',
'Authorization': `Bearer ${this.token}`,
// 'Accept': 'application/json, application/vnd.cnb.api+json, application/vnd.cnb.web+json',
'Accept': 'application/json',
};
if (this.token) {
defaultHeaders['Authorization'] = `Bearer ${this.token}`;
}
if (params) {
const queryString = new URLSearchParams(params).toString();
url += `?${queryString}`;
defaultHeaders['Accept'] = 'application/json';
}
const response = await fetch(url, {
const _headers = { ...defaultHeaders, ...headers };
let _body = undefined;
if (data) {
_body = JSON.stringify(data);
}
if (body) {
_body = body;
}
if (!_headers.Authorization) {
delete _headers.Authorization;
}
if (useCookie) {
_headers['Cookie'] = this.cookie || "";
delete _headers.Authorization;
}
console.log('Request URL:', url, data, _headers);
const response = await fetch(url || '', {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
headers: _headers,
body: _body,
});
const res = (data: any, message?: string) => {
if (useOrigin) {
return data;
}
return {
code: 200,
message: message || 'success',
data,
};
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Request failed with status ${response.status}: ${errorText}`);
if (useOrigin)
throw new Error(`Request failed with status ${response.status}: ${errorText}`);
return res(null, `Request failed with status ${response.status}: ${errorText}`);
}
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return response.json();
const values = await response.json();
return res(values);
} else {
return response.text();
const text = await response.text();
return res(text);
}
}
get<T = any>({ url, params }: { url: string, params?: Record<string, any> }): Promise<T> {
const fullUrl = new URL(url, this.baseURL).toString();
return this.request({ url: fullUrl, method: 'GET', params });
makeUrl(url?: string): string {
if (url && url.startsWith('http')) {
return url;
}
return new URL(url || '', this.baseURL).toString();
}
post<T = any>({ url, data }: { url: string, data?: Record<string, any> }): Promise<T> {
const fullUrl = new URL(url, this.baseURL).toString();
return this.request({ url: fullUrl, method: 'POST', data });
get<T = any>({ url, ...REST }: RequestOptions): Promise<T> {
const fullUrl = this.makeUrl(url);
return this.request({ url: fullUrl, method: 'GET', ...REST });
}
put<T = any>({ url, data }: { url: string, data?: Record<string, any> }): Promise<T> {
const fullUrl = new URL(url, this.baseURL).toString();
return this.request({ url: fullUrl, method: 'PUT', data });
post<T = any>({ url, ...REST }: RequestOptions): Promise<T> {
const fullUrl = this.makeUrl(url);
return this.request({ url: fullUrl, method: 'POST', ...REST });
}
delete<T = any>({ url, data }: { url: string, data?: Record<string, any> }): Promise<T> {
const fullUrl = new URL(url, this.baseURL).toString();
return this.request({ url: fullUrl, method: 'DELETE', data });
put<T = any>({ url, ...REST }: RequestOptions): Promise<T> {
const fullUrl = this.makeUrl(url);
return this.request({ url: fullUrl, method: 'PUT', ...REST });
}
}
delete<T = any>({ url, ...REST }: RequestOptions): Promise<T> {
const fullUrl = this.makeUrl(url);
return this.request({ url: fullUrl, method: 'DELETE', ...REST });
}
patch<T = any>({ url, ...REST }: RequestOptions): Promise<T> {
const fullUrl = this.makeUrl(url);
return this.request({ url: fullUrl, method: 'PATCH', ...REST });
}
/**
* 通过 PUT 请求上传文件内容
* @param data 包含 URL、token 和文件内容
* @returns 上传结果
*/
async putFile(data: { url: string, token: string, content: string | Buffer }): Promise<any> {
return this.request({
url: data.url,
method: 'PUT',
body: data.content,
headers: {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/octet-stream'
}
});
}
}
export type Result<T = any> = {
code: number;
message: string;
data: T
};

View File

@@ -1,9 +1,10 @@
import { CNBCore } from "./cnb-core";
import { CNBCore, CNBCoreOptions } from "./cnb-core";
import { Workspace } from "./workspace";
type CNBOptions = CNBCoreOptions<{}>;
export class CNB extends CNBCore {
workspace: Workspace;
constructor(token: string) {
super({ token });
this.workspace = new Workspace(token);
constructor(options: CNBOptions) {
super({ token: options.token });
this.workspace = new Workspace(options.token);
}
}

161
src/repo/index.ts Normal file
View File

@@ -0,0 +1,161 @@
import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core";
type RepoOptions = CNBCoreOptions<{
group?: string;
}>
export class Repo extends CNBCore {
group: string;
constructor(options: RepoOptions) {
super({ token: options.token, cookie: options.cookie });
this.group = options.group || '';
}
/**
* 创建代码仓库
* @param group e.g. my-group
* @param data
* @returns
*/
createRepo(data: CreateRepoData): Promise<any> {
const group = this.group || '';
const url = `/${group}/-/repos`;
let postData: CreateRepoData = {
...data,
description: data.description || '',
name: data.name,
license: data.license || 'Unlicense',
visibility: data.visibility || 'private',
};
return this.post({ url, data: postData });
}
async createCommit(repo: string, data: CreateCommitData): Promise<any> {
const group = this.group || '';
const commitList = await this.getCommitList(repo, {
page: 1,
page_size: 1,
}, { useOrigin: true }).catch((err) => {
console.error("Error fetching commit list:", err);
return []
});
const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined;
if (!data.parent_commit_sha && preCommitSha) {
data.parent_commit_sha = preCommitSha;
}
const url = `https://cnb.cool/${group}/${repo}/-/git/commits`;
const postData: CreateCommitData = {
...data,
base_branch: data.base_branch || 'refs/heads/main',
message: data.message || 'commit from cnb sdk',
parent_commit_sha: data.parent_commit_sha,
files: data.files || [],
new_branch: data.new_branch || 'refs/heads/main',
};
if (!postData.parent_commit_sha) {
delete postData.parent_commit_sha;
delete postData.base_branch;
}
return this.post({ url, data: postData, useCookie: true, });
}
createBlobs(repo: string, data: { content: string, encoding?: 'utf-8' | 'base64' }): Promise<any> {
const group = this.group || '';
const url = `/${group}/${repo}/-/git/blobs`;
const postData = {
content: data.content,
encoding: data.encoding || 'utf-8',
};
return this.post({ url, data: postData });
}
uploadFiles(repo: string, data: { ext?: any, name?: string, path?: string, size?: number }): Promise<any> {
const group = this.group || '';
const url = `/${group}/${repo}/-/upload/files`
return this.post({ url, data });
}
getCommitList(repo: string, params: { author?: string, commiter?: string, page?: number, page_size?: number, sha?: string, since?: string, until?: string }, opts?: RequestOptions): Promise<any> {
const group = this.group || '';
const url = `/${group}/${repo}/-/git/commits`;
return this.get({ url, params, ...opts });
}
getRepoList(params: {
desc?: boolean;
filter_type?: 'private' | 'public' | 'secret';
flags?: 'KnowledgeBase';
order_by?: 'created_at' | 'last_updated_at' | 'stars' | 'slug_path' | 'forks';
page?: number;
page_size?: number;
role?: 'owner' | 'maintainer' | 'developer' | 'reporter' | 'guest';
search?: string;
status?: 'active' | 'archived';
}): Promise<Result<RepoItem[]>> {
const url = '/user/repos';
let _params = {
...params,
page: params.page || 1,
page_size: params.page_size || 999,
}
return this.get({ url, params: _params });
}
}
type CreateRepoData = {
description: string;
license?: 'MIT' | 'Apache-2.0' | 'GPL-3.0' | 'Unlicense';
name: string;
visibility: 'private' | 'public' | 'secret';
}
type CreateCommitData = {
base_branch?: string; // "refs/heads/main"
new_branch?: string; // "refs/heads/main"
message?: string;
parent_commit_sha?: string;
files?: Array<{
content: string;
path: string;
encoding?: 'raw' | 'utf-8' | 'base64';
is_delete?: boolean;
is_executable?: boolean;
}>;
}
type RepoItem = {
id: string;
name: string;
freeze: boolean;
status: number;
visibility_level: 'Public' | 'Private' | 'Secret';
flags: string;
created_at: string;
updated_at: string;
description: string;
site: string;
topics: string;
license: string;
display_module: {
activity: boolean;
contributors: boolean;
release: boolean;
};
star_count: number;
fork_count: number;
mark_count: number;
last_updated_at: string | null;
web_url: string;
path: string;
tags: string[] | null;
open_issue_count: number;
open_pull_request_count: number;
languages: {
language: string;
color: string;
};
second_languages: {
language: string;
color: string;
};
last_update_username: string;
last_update_nickname: string;
access: string;
stared: boolean;
star_time: string;
pinned: boolean;
pinned_time: string;
}

19
src/user/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import { CNBCore, CNBCoreOptions } from "../cnb-core";
export class User extends CNBCore {
constructor(options: CNBCoreOptions<{}>) {
super({ token: options.token, cookie: options.cookie });
}
/**
* 获取当前用户信息
* @returns
*/
getCurrentUser(): Promise<any> {
const url = `https://cnb.cool/user`;
return this.get({
url,
useCookie: true,
});
}
}