feat: 添加 Repo 和 User 模块,增强 CNBCore 功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "cnb",
|
||||
"name": "@kevisual/cnb",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
@@ -7,6 +7,11 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"src",
|
||||
"mod.ts",
|
||||
"agents"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
@@ -16,4 +21,4 @@
|
||||
"@types/node": "^24.10.1",
|
||||
"dotenv": "^17.2.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/cnb-core.ts
126
src/cnb-core.ts
@@ -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
|
||||
};
|
||||
@@ -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
161
src/repo/index.ts
Normal 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
19
src/user/index.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import { CNB } from "../src";
|
||||
import dotenv from "dotenv";
|
||||
import util from 'node:util';
|
||||
dotenv.config();
|
||||
export const cnb = new CNB(process.env.CNB_TOKEN || "");
|
||||
|
||||
export const token = process.env.CNB_TOKEN || "";
|
||||
export const cookie = process.env.CNB_COOKIE || "";
|
||||
console.log("Using CNB_TOKEN:", token.slice(0, 4) + "****", cookie);
|
||||
export const cnb = new CNB({ token, cookie });
|
||||
export const showMore = (obj: any) => {
|
||||
return util.inspect(obj, { showHidden: false, depth: null, colors: true });
|
||||
}
|
||||
// const worksaceList = await cnb.workspace.list({ status: 'running' });
|
||||
|
||||
// console.log("worksaceList", worksaceList);
|
||||
|
||||
69
test/create-repo.ts
Normal file
69
test/create-repo.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Repo } from "../src/repo";
|
||||
|
||||
import { token, showMore, cookie } from "./common.ts";
|
||||
|
||||
const repo = new Repo({ group: "kevisual/demo", token: token, cookie: cookie });
|
||||
|
||||
// const res = await repo.createRepo({
|
||||
// name: "test-cnb-2",
|
||||
// description: "This is my new repository",
|
||||
// visibility: "private",
|
||||
// license: 'MIT',
|
||||
// });
|
||||
|
||||
// console.log("res", showMore(res));
|
||||
|
||||
const repoName = "test-cnb";
|
||||
// const commitList = await repo.getCommitList(repoName, {
|
||||
// page: 1,
|
||||
// page_size: 1,
|
||||
// }).catch((err) => {
|
||||
// console.error("Error fetching commit list:", err);
|
||||
// return []
|
||||
// });
|
||||
// console.log("commitList", showMore(commitList));
|
||||
|
||||
// const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined;
|
||||
|
||||
const commitRes = await repo.createCommit(repoName, {
|
||||
message: "Initial commit3",
|
||||
files: [
|
||||
{ path: "README.md", content: "# Hello World\nThis is my first commit!", encoding: 'raw' },
|
||||
{ path: "a.md", content: "# Hello World\nThis is my first commit2!", encoding: 'raw' },
|
||||
],
|
||||
});
|
||||
|
||||
console.log("commitRes", showMore(commitRes));
|
||||
|
||||
|
||||
|
||||
// const blobRes = await repo.createBlobs(repoName, {
|
||||
// content: Buffer.from("Hello, CNB!").toString("base64"),
|
||||
// encoding: "base64",
|
||||
// });
|
||||
|
||||
// console.log("blobRes", showMore(blobRes));
|
||||
// sha: 4b909f74428c24221a9f795c591f5eb560817f2d
|
||||
|
||||
// const bufferText = Buffer.from("This is a sample file content. 232332");
|
||||
// const bufferSize = Buffer.byteLength(bufferText);
|
||||
// console.log("bufferSize", bufferSize);
|
||||
// const uploadRes = await repo.uploadFiles(repoName, {
|
||||
// name: "sample.txt",
|
||||
// ext: { "a": "b" },
|
||||
// size: Buffer.byteLength(bufferText),
|
||||
// });
|
||||
|
||||
// console.log("uploadRes", showMore(uploadRes));
|
||||
|
||||
// const upload_url = uploadRes.upload_url
|
||||
// const upload_token = uploadRes.token;
|
||||
|
||||
|
||||
// const uploadResponse = await repo.putFile({
|
||||
// url: upload_url,
|
||||
// token: upload_token,
|
||||
// content: bufferText,
|
||||
// });
|
||||
|
||||
// console.log("uploadResponse", showMore(uploadResponse));
|
||||
10
test/repo.ts
Normal file
10
test/repo.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Repo } from "../src/repo";
|
||||
|
||||
import { token, showMore, cookie } from "./common.ts";
|
||||
|
||||
const repo = new Repo({ group: "kevisual/demo", token: token, cookie: cookie });
|
||||
|
||||
|
||||
const listRes = await repo.getRepoList({ page: 1, page_size: 10 });
|
||||
|
||||
console.log("listRes", showMore(listRes));
|
||||
9
test/user.ts
Normal file
9
test/user.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { User } from "../src/user/index";
|
||||
|
||||
import { token, showMore, cookie } from "./common.ts";
|
||||
|
||||
const user = new User({ token: token, cookie: cookie });
|
||||
|
||||
const currentUser = await user.getCurrentUser();
|
||||
|
||||
console.log("currentUser", showMore(currentUser));
|
||||
Reference in New Issue
Block a user