update
This commit is contained in:
13
.cnb.yaml
Normal file
13
.cnb.yaml
Normal 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
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
node_modules
|
||||
19
package.json
Normal file
19
package.json
Normal 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
55
pnpm-lock.yaml
generated
Normal 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
56
src/cnb-core.ts
Normal 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
9
src/index.ts
Normal 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
189
src/workspace.ts
Normal 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
15781
swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
14
test/common.ts
Normal file
14
test/common.ts
Normal 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);
|
||||
Reference in New Issue
Block a user