This commit is contained in:
2025-12-04 20:05:56 +08:00
parent 47fe4c1343
commit 57660d2d9c
13 changed files with 16138 additions and 0 deletions

13
.cnb.yaml Normal file
View File

@@ -0,0 +1,13 @@
# .cnb.yml
$:
vscode:
- docker:
image: docker.cnb.cool/kevisual/node24:latest
services:
- vscode
- docker
imports: https://cnb.cool/kevisual/env/-/blob/main/env.yml
# 开发环境启动后会执行的任务
# stages:
# - name: pnpm install
# script: pnpm install

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
node_modules

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "cnb",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.24.0",
"type": "module",
"devDependencies": {
"@types/bun": "^1.3.3",
"@types/node": "^24.10.1",
"dotenv": "^17.2.3"
}
}

55
pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,55 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@types/bun':
specifier: ^1.3.3
version: 1.3.3
'@types/node':
specifier: ^24.10.1
version: 24.10.1
dotenv:
specifier: ^17.2.3
version: 17.2.3
packages:
'@types/bun@1.3.3':
resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==}
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
bun-types@1.3.3:
resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==}
dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
snapshots:
'@types/bun@1.3.3':
dependencies:
bun-types: 1.3.3
'@types/node@24.10.1':
dependencies:
undici-types: 7.16.0
bun-types@1.3.3:
dependencies:
'@types/node': 24.10.1
dotenv@17.2.3: {}
undici-types@7.16.0: {}

56
src/cnb-core.ts Normal file
View File

@@ -0,0 +1,56 @@
export type CNBCoreOptions<T = {}> = {
token: string;
} & T;
export class CNBCore {
baseURL = 'https://api.cnb.cool';
token: string;
constructor(options: CNBCoreOptions) {
this.token = options.token;
}
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> = {
'Content-Type': 'application/json',
'Accept': 'application/json, application/vnd.cnb.api+json, application/vnd.cnb.web+json',
'Authorization': `Bearer ${this.token}`,
};
if (params) {
const queryString = new URLSearchParams(params).toString();
url += `?${queryString}`;
}
const response = await fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Request failed with status ${response.status}: ${errorText}`);
}
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return response.json();
} else {
return response.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 });
}
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 });
}
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 });
}
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 });
}
}

9
src/index.ts Normal file
View File

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

189
src/workspace.ts Normal file
View File

@@ -0,0 +1,189 @@
/**
* @title 工作空间管理模块
* @description 提供云原生构建工作空间的管理功能,包括列表查询、创建、更新和删除工作空间,支持多种过滤条件和分页选项
* @tags workspace, cloud-native, development-environment, cnb, api
* @createdAt 2025-12-04
*/
import { CNBCore } from "./cnb-core";
/**
* 工作空间列表查询参数
*/
export interface ListParams {
/** Git 分支名称,例如 "main" */
branch?: string;
/** 查询结束时间,格式 YYYY-MM-DD HH:mm:ssZZ,例如 2024-12-01 00:00:00+0800 */
end?: string;
/** 分页页码,默认为 1 */
page?: number;
/** 分页大小,默认 20,最大 100 */
pageSize?: number;
/** 仓库路径,例如 "groupname/reponame" */
slug?: string;
/** 查询开始时间,格式 YYYY-MM-DD HH:mm:ssZZ,例如 2024-12-01 00:00:00+0800 */
start?: string;
/** 开发环境状态,running: 开发环境已启动,closed: 开发环境已关闭 */
status?: 'running' | 'closed';
}
export class Workspace extends CNBCore {
constructor(token: string) {
super({ token });
}
/**
* 删除我的云原生开发环境
* @param params 删除参数pipelineId 和 sn 二选一,优先使用 pipelineId
*/
async deleteWorkspace(params: { pipelineId?: string; sn?: string }): Promise<{ code: number; message: string }> {
const data: { pipelineId?: string; sn?: string } = {};
if (params.pipelineId) {
data.pipelineId = params.pipelineId;
} else if (params.sn) {
data.sn = params.sn;
} else {
throw new Error('pipelineId 和 sn 必须提供其中一个');
}
return this.post({ url: '/workspace/delete', data });
}
/** 获取我的云原生开发环境列表 */
async list(params?: ListParams): Promise<WorkspaceResult> {
return this.get({ url: '/workspace/list', params });
}
/**
* 停止我的云原生开发环境
* @param params 停止参数pipelineId 和 sn 二选一,优先使用 pipelineId
*/
async stopWorkspace(params: { pipelineId?: string; sn?: string }): Promise<{ buildLogUrl: string; message: string; sn: string }> {
const data: { pipelineId?: string; sn?: string } = {};
if (params.pipelineId) {
data.pipelineId = params.pipelineId;
} else if (params.sn) {
data.sn = params.sn;
} else {
throw new Error('pipelineId 和 sn 必须提供其中一个');
}
return this.post({ url: '/workspace/stop', data });
}
/**
* 根据流水线 SN 查询云原生开发访问地址
* @param repo 仓库路径例如groupname/reponame
* @param sn 流水线构建号
*/
async getDetail(repo: string, sn: string): Promise<{
codebuddy: string;
codebuddycn: string;
cursor: string;
jetbrains: Record<string, string>;
jumpUrl: string;
remoteSsh: string;
ssh: string;
vscode: string;
'vscode-insiders': string;
webide: string;
}> {
return this.get({ url: `/${repo}/-/workspace/detail/${sn}` });
}
/**
* 启动云原生开发环境,已存在环境则直接打开,否则重新创建开发环境
* @param repo 仓库路径例如groupname/reponame
* @param params 启动参数
* @returns 返回启动结果,包含以下字段:
*/
async startWorkspace(repo: string, params: { branch?: string; ref?: string }): Promise<{
/** 仅新创建开发环境时返回,表示创建开发环境的流水线日志地址 */
buildLogUrl?: string;
/** 仅新创建开发环境时返回,表示创建开发环境的提示信息 */
message?: string;
/** 仅新创建开发环境时返回,表示创建开发环境的流水线 sn */
sn?: string;
/** 如果存在开发环境,则返回 WebIDE 访问 url如果不存在开发环境则返回启动云原生开发的 loading 页面 url 地址 */
url: string;
}> {
const data: { branch?: string; ref?: string } = {};
if (params.branch) {
data.branch = params.branch;
}
if (params.ref) {
data.ref = params.ref;
}
return this.post({ url: `/${repo}/-/workspace/start`, data });
}
}
export type ResultList<T> = {
hasMore: boolean;
list: T[];
pageInfo: {
page: number;
pageSize: number;
};
total: number;
}
/**
* 工作空间信息接口
*/
export interface WorkspaceInfo {
/** 分支名例如main */
branch: string;
/** 备份的 commit 数 */
commit_count: number;
/** 开发环境创建时间例如2024-12-02T03:20:22.000Z */
create_time: string;
/** 开发环境持续时间单位ms非实时更新 */
duration: number;
/** 备份的文件数 */
file_count: number;
/** 备份的文件列表,仅前五个备份文件相对路径 */
file_list: string;
/** 环境销毁时远程最新的 commit short hash */
latest_sha: string;
/** 创建环境的子流水线 id */
pipeline_id: string;
/** 备份的 stash 数 */
remote_stash_count: number;
/** 仓库地址 */
repo_url: string;
/** 恢复备份代码的流水线 id如果有值表示备份代码已被恢复重建环境时会恢复备份代码 */
restore_id: string;
/** 仓库路径例如groupname/reponame */
slug: string;
/** 创建开发环境的流水线 sn */
sn: string;
/** 开发环境是否支持 ssh 链接 */
ssh: boolean;
/** 工作区状态running: 开发环境已启动closed开发环境已关闭 */
status: string;
/** 开发环境默认工作区路径 */
workspace: string;
}
/**
* 工作空间列表响应结果
*/
export type WorkspaceResult = {
/** 是否有更多数据 */
hasMore: boolean;
/** 工作空间列表 */
list: WorkspaceInfo[];
/** 分页信息 */
pageInfo: {
/** 当前页码 */
page: number;
/** 每页大小 */
pageSize: number;
};
/** 总数量 */
total: number;
}

15781
swagger.json Normal file

File diff suppressed because it is too large Load Diff

14
test/common.ts Normal file
View File

@@ -0,0 +1,14 @@
import { CNB } from "../src";
import dotenv from "dotenv";
dotenv.config();
const cnb = new CNB(process.env.CNB_TOKEN || "");
const worksaceList = await cnb.workspace.list();
// console.log("worksaceList", worksaceList);
const sn = 'cnb-0eg-1jbkjj615-001'
const worksace = await cnb.workspace.getDetail('kevisual/node24', sn)
console.log("worksace", worksace);