DevOS v1 + Phase 2: unified resource model, infra ops, website deployment, AI chat ops
- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages - PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs) - Auth/RBAC/workspace isolation, credential vault (AES-256-GCM) - Servers/SSH, Docker discovery & lifecycle, Job Center - Websites & deployments (git provider credential clone, env, health verification) - Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring - Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools) - AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals) - Notes: Vditor WYSIWYG markdown editor, non-blocking autosave - Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@devos/api-client",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devos/types": "workspace:*",
|
||||
"@devos/auth": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* # @devos/api-client
|
||||
* 浏览器端 API 客户端:统一的 fetch 封装,自动附带 JWT 与工作区头,
|
||||
* 归一化错误结构(与后端 error contract 对齐)。
|
||||
*/
|
||||
|
||||
import type { ApiErrorBody } from "@devos/types";
|
||||
|
||||
/** # 客户端配置 **/
|
||||
export interface ApiClientOptions {
|
||||
/** # API 基础地址,默认同源 /api/v1 **/
|
||||
baseUrl?: string;
|
||||
/** # 令牌提供器(返回当前 JWT 或 null) **/
|
||||
getToken: () => string | null;
|
||||
/** # 工作区 ID 提供器(返回当前工作区或 null) **/
|
||||
getWorkspaceId?: () => string | null;
|
||||
/** # 401 时的回调(例如跳转登录) **/
|
||||
onUnauthorized?: () => void;
|
||||
}
|
||||
|
||||
/** # API 错误(客户端侧归一化) **/
|
||||
export class ApiError extends Error {
|
||||
/** # 错误码 **/
|
||||
code: string;
|
||||
/** # HTTP 状态码 **/
|
||||
status: number;
|
||||
/** # 请求 ID **/
|
||||
requestId: string;
|
||||
constructor(status: number, code: string, message: string, requestId: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
/** # API 客户端 **/
|
||||
export class ApiClient {
|
||||
/** # 基础地址 **/
|
||||
private baseUrl: string;
|
||||
private getToken: () => string | null;
|
||||
private getWorkspaceId?: () => string | null;
|
||||
private onUnauthorized?: () => void;
|
||||
|
||||
constructor(options: ApiClientOptions) {
|
||||
this.baseUrl = (options.baseUrl ?? "/api/v1").replace(/\/$/, "");
|
||||
this.getToken = options.getToken;
|
||||
this.getWorkspaceId = options.getWorkspaceId;
|
||||
this.onUnauthorized = options.onUnauthorized;
|
||||
}
|
||||
|
||||
/** # 发起请求
|
||||
* @param method HTTP 方法
|
||||
* @param path 相对路径(不含 /api/v1)
|
||||
* @param body 请求体(自动 JSON 序列化)
|
||||
* @param query 查询参数
|
||||
* @returns 解析后的 JSON 响应
|
||||
*/
|
||||
async request<T>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
query?: Record<string, string | number | boolean | undefined | null>,
|
||||
): Promise<T> {
|
||||
const url = new URL(this.baseUrl + path, window.location.origin);
|
||||
if (query) {
|
||||
for (const [k, v] of Object.entries(query)) {
|
||||
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
||||
}
|
||||
}
|
||||
const headers: Record<string, string> = {};
|
||||
const token = this.getToken();
|
||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||
const wid = this.getWorkspaceId?.();
|
||||
if (wid) headers["X-Workspace-Id"] = wid;
|
||||
if (body !== undefined) headers["Content-Type"] = "application/json";
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
method,
|
||||
headers,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
let code = "INTERNAL_ERROR";
|
||||
let message = res.statusText;
|
||||
let requestId = "";
|
||||
let parsed: ApiErrorBody | null = null;
|
||||
try {
|
||||
parsed = (await res.json()) as ApiErrorBody;
|
||||
} catch {
|
||||
/* 非 JSON 错误体 */
|
||||
}
|
||||
if (parsed?.error) {
|
||||
code = parsed.error.code;
|
||||
message = parsed.error.message;
|
||||
requestId = parsed.error.requestId;
|
||||
}
|
||||
if (res.status === 401) this.onUnauthorized?.();
|
||||
throw new ApiError(res.status, code, message, requestId);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
get<T>(path: string, query?: Record<string, string | number | boolean | undefined | null>) {
|
||||
return this.request<T>("GET", path, undefined, query);
|
||||
}
|
||||
post<T>(path: string, body?: unknown) {
|
||||
return this.request<T>("POST", path, body);
|
||||
}
|
||||
patch<T>(path: string, body?: unknown) {
|
||||
return this.request<T>("PATCH", path, body);
|
||||
}
|
||||
delete<T>(path: string) {
|
||||
return this.request<T>("DELETE", path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@devos/auth",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devos/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* # @devos/auth
|
||||
* 认证相关的纯逻辑实现:HS256 JWT 签名/校验、密码哈希/校验。
|
||||
* 设计原则:零第三方依赖、算法固定(防止 alg 混淆)、常量时间比较。
|
||||
*/
|
||||
|
||||
import { createHmac, randomBytes, randomUUID, timingSafeEqual, scrypt as scryptCb } from "node:crypto";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
/** # scrypt Promise 封装 **/
|
||||
const scrypt = promisify(scryptCb) as (
|
||||
password: string,
|
||||
salt: Buffer,
|
||||
keylen: number,
|
||||
) => Promise<Buffer>;
|
||||
|
||||
/** # base64url 编码
|
||||
* @param input 待编码字符串
|
||||
* @returns base64url 字符串(无填充)
|
||||
*/
|
||||
function b64url(input: Buffer | string): string {
|
||||
return Buffer.from(input).toString("base64url");
|
||||
}
|
||||
|
||||
/** # base64url 解码 */
|
||||
function b64urlDecode(input: string): Buffer {
|
||||
return Buffer.from(input, "base64url");
|
||||
}
|
||||
|
||||
/** # JWT 载荷结构 **/
|
||||
export interface JwtPayload {
|
||||
/** # 用户 ID **/
|
||||
sub: string;
|
||||
/** # 登录邮箱 **/
|
||||
email: string;
|
||||
/** # 登录时激活的工作区 ID **/
|
||||
wid: string | null;
|
||||
/** # 令牌唯一 ID **/
|
||||
jti: string;
|
||||
/** # 签发时间(秒) **/
|
||||
iat: number;
|
||||
/** # 过期时间(秒) **/
|
||||
exp: number;
|
||||
}
|
||||
|
||||
/** # 签发选项 **/
|
||||
export interface SignOptions {
|
||||
/** # 有效期(秒),默认 12 小时 **/
|
||||
expiresInSec?: number;
|
||||
/** # 当前时间(秒),用于测试注入 **/
|
||||
nowSec?: number;
|
||||
}
|
||||
|
||||
/** # 签发 HS256 JWT
|
||||
* @param payload 业务载荷(不含 iat/exp/jti)
|
||||
* @param secret 应用密钥
|
||||
* @param options 签发选项
|
||||
* @returns JWT 字符串
|
||||
*/
|
||||
export function signJwt(
|
||||
payload: Omit<JwtPayload, "iat" | "exp" | "jti">,
|
||||
secret: string,
|
||||
options: SignOptions = {},
|
||||
): string {
|
||||
const now = options.nowSec ?? Math.floor(Date.now() / 1000);
|
||||
const expiresInSec = options.expiresInSec ?? 12 * 3600;
|
||||
const header = { alg: "HS256", typ: "JWT" };
|
||||
const body: JwtPayload = { ...payload, jti: randomUUID(), iat: now, exp: now + expiresInSec };
|
||||
const head = b64url(JSON.stringify(header));
|
||||
const data = b64url(JSON.stringify(body));
|
||||
const sig = createHmac("sha256", secret).update(`${head}.${data}`).digest("base64url");
|
||||
return `${head}.${data}.${sig}`;
|
||||
}
|
||||
|
||||
/** # 校验结果 **/
|
||||
export type VerifyResult = { ok: true; payload: JwtPayload } | { ok: false; reason: string };
|
||||
|
||||
/** # 校验 HS256 JWT
|
||||
* 安全约束:仅接受 alg=HS256 的令牌;签名使用常量时间比较;校验 exp 过期。
|
||||
* @param token JWT 字符串
|
||||
* @param secret 应用密钥
|
||||
* @returns 校验结果
|
||||
*/
|
||||
export function verifyJwt(token: string, secret: string, nowSec?: number): VerifyResult {
|
||||
const parts = token.split(".");
|
||||
if (parts.length !== 3) return { ok: false, reason: "MALFORMED" };
|
||||
const [head, data, sig] = parts;
|
||||
let header: { alg?: string };
|
||||
try {
|
||||
header = JSON.parse(b64urlDecode(head).toString("utf8"));
|
||||
} catch {
|
||||
return { ok: false, reason: "MALFORMED" };
|
||||
}
|
||||
if (header.alg !== "HS256") return { ok: false, reason: "BAD_ALG" };
|
||||
const expected = createHmac("sha256", secret).update(`${head}.${data}`).digest();
|
||||
let given: Buffer;
|
||||
try {
|
||||
given = b64urlDecode(sig);
|
||||
} catch {
|
||||
return { ok: false, reason: "MALFORMED" };
|
||||
}
|
||||
if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
|
||||
return { ok: false, reason: "BAD_SIGNATURE" };
|
||||
}
|
||||
let payload: JwtPayload;
|
||||
try {
|
||||
payload = JSON.parse(b64urlDecode(data).toString("utf8"));
|
||||
} catch {
|
||||
return { ok: false, reason: "MALFORMED" };
|
||||
}
|
||||
const now = nowSec ?? Math.floor(Date.now() / 1000);
|
||||
if (typeof payload.exp !== "number" || payload.exp <= now) return { ok: false, reason: "EXPIRED" };
|
||||
return { ok: true, payload };
|
||||
}
|
||||
|
||||
/** # 密码哈希
|
||||
* Bun 运行时下使用 argon2id;否则回退 scrypt。
|
||||
* @param password 明文密码
|
||||
* @returns 哈希字符串
|
||||
*/
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const bun = (globalThis as Record<string, unknown>).Bun as typeof Bun | undefined;
|
||||
if (bun?.password) {
|
||||
return bun.password.hash(password, { algorithm: "argon2id" });
|
||||
}
|
||||
const salt = randomBytes(16);
|
||||
const key = await scrypt(password, salt, 32);
|
||||
return `scrypt$${salt.toString("hex")}$${key.toString("hex")}`;
|
||||
}
|
||||
|
||||
/** # 校验密码
|
||||
* @param password 明文密码
|
||||
* @param hash 存储的哈希
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
if (hash.startsWith("scrypt$")) {
|
||||
const [, saltHex, keyHex] = hash.split("$");
|
||||
const key = await scrypt(password, Buffer.from(saltHex, "hex"), 32);
|
||||
const expected = Buffer.from(keyHex, "hex");
|
||||
return key.length === expected.length && timingSafeEqual(key, expected);
|
||||
}
|
||||
const bun = (globalThis as Record<string, unknown>).Bun as typeof Bun | undefined;
|
||||
if (bun?.password) {
|
||||
return bun.password.verify(password, hash);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** # 生成随机令牌(如 API 临时凭证) */
|
||||
export function randomToken(bytes = 32): string {
|
||||
return randomBytes(bytes).toString("hex");
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"types": [
|
||||
"node",
|
||||
"bun-types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@devos/types",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* # @devos/types
|
||||
* 前后端共享的领域类型与常量定义。
|
||||
* 该包不依赖任何运行时库,仅类型与枚举。
|
||||
*/
|
||||
|
||||
/** # 资源类型枚举
|
||||
* DevOS 统一资源模型支持的资源类型(V1 子集 + 预留)。
|
||||
*/
|
||||
export const RESOURCE_TYPES = [
|
||||
"SERVER",
|
||||
"DOMAIN",
|
||||
"WEBSITE",
|
||||
"REPOSITORY",
|
||||
"CERTIFICATE",
|
||||
"DATABASE",
|
||||
"CONTAINER",
|
||||
"EMAIL_ACCOUNT",
|
||||
"PROJECT",
|
||||
"TASK",
|
||||
"NOTE",
|
||||
"DOCUMENT",
|
||||
"BACKUP",
|
||||
"DEPLOYMENT",
|
||||
"MONITOR",
|
||||
"CREDENTIAL",
|
||||
"AUTOMATION",
|
||||
"AI_RUN",
|
||||
] as const;
|
||||
export type ResourceType = (typeof RESOURCE_TYPES)[number];
|
||||
|
||||
/** # 资源状态枚举 */
|
||||
export const RESOURCE_STATUSES = [
|
||||
"PENDING",
|
||||
"ACTIVE",
|
||||
"ERROR",
|
||||
"OFFLINE",
|
||||
"ARCHIVED",
|
||||
"UNKNOWN",
|
||||
] as const;
|
||||
export type ResourceStatus = (typeof RESOURCE_STATUSES)[number];
|
||||
|
||||
/** # 工作区角色 */
|
||||
export const WORKSPACE_ROLES = ["OWNER", "ADMIN", "MEMBER", "VIEWER"] as const;
|
||||
export type WorkspaceRole = (typeof WORKSPACE_ROLES)[number];
|
||||
|
||||
/** # 风险等级 */
|
||||
export const RISK_LEVELS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
|
||||
export type RiskLevel = (typeof RISK_LEVELS)[number];
|
||||
|
||||
/** # 审计主体类型 */
|
||||
export const ACTOR_TYPES = ["USER", "SYSTEM", "AGENT", "MCP"] as const;
|
||||
export type ActorType = (typeof ACTOR_TYPES)[number];
|
||||
|
||||
/** # 任务状态(job 状态机) */
|
||||
export const JOB_STATUSES = [
|
||||
"PENDING",
|
||||
"RUNNING",
|
||||
"SUCCEEDED",
|
||||
"FAILED",
|
||||
"CANCELLED",
|
||||
"DEAD",
|
||||
] as const;
|
||||
export type JobStatus = (typeof JOB_STATUSES)[number];
|
||||
|
||||
/** # 部署状态 */
|
||||
export const DEPLOYMENT_STATUSES = [
|
||||
"QUEUED",
|
||||
"RUNNING",
|
||||
"SUCCEEDED",
|
||||
"FAILED",
|
||||
"CANCELLED",
|
||||
] as const;
|
||||
export type DeploymentStatus = (typeof DEPLOYMENT_STATUSES)[number];
|
||||
|
||||
/** # 触发器类型 */
|
||||
export const TRIGGER_TYPES = ["MANUAL", "CRON", "EVENT", "WEBHOOK"] as const;
|
||||
export type TriggerType = (typeof TRIGGER_TYPES)[number];
|
||||
|
||||
/** # 规范事件名常量
|
||||
* 事件命名遵循 `<resource>.<verb>` / `<domain>.<verb>` 约定。
|
||||
*/
|
||||
export const EVENTS = {
|
||||
deploymentCreated: "deployment.created",
|
||||
deploymentStarted: "deployment.started",
|
||||
deploymentSuccess: "deployment.success",
|
||||
deploymentFailed: "deployment.failed",
|
||||
serverOnline: "server.online",
|
||||
serverOffline: "server.offline",
|
||||
websiteDown: "website.down",
|
||||
websiteRecovered: "website.recovered",
|
||||
containerRestarted: "container.restarted",
|
||||
taskCreated: "task.created",
|
||||
taskCompleted: "task.completed",
|
||||
} as const;
|
||||
export type EventName = (typeof EVENTS)[keyof typeof EVENTS] | (string & {});
|
||||
|
||||
/** # API 错误码 */
|
||||
export const ERROR_CODES = {
|
||||
VALIDATION_ERROR: "VALIDATION_ERROR",
|
||||
UNAUTHORIZED: "UNAUTHORIZED",
|
||||
FORBIDDEN: "FORBIDDEN",
|
||||
RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND",
|
||||
CONFLICT: "CONFLICT",
|
||||
RATE_LIMITED: "RATE_LIMITED",
|
||||
INTERNAL_ERROR: "INTERNAL_ERROR",
|
||||
UPSTREAM_FAILURE: "UPSTREAM_FAILURE",
|
||||
TIMEOUT: "TIMEOUT",
|
||||
} as const;
|
||||
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
||||
|
||||
/** # API 统一错误结构 */
|
||||
export interface ApiErrorBody {
|
||||
error: {
|
||||
code: ErrorCode | string;
|
||||
message: string;
|
||||
requestId: string;
|
||||
details?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/** # 资源通用视图(API 返回) */
|
||||
export interface ResourceView {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
type: ResourceType;
|
||||
name: string;
|
||||
provider: string | null;
|
||||
status: ResourceStatus;
|
||||
metadata: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** # 服务器视图 */
|
||||
export interface ServerView {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
authType: string;
|
||||
provider: string;
|
||||
status: ResourceStatus;
|
||||
osInfo: string | null;
|
||||
lastSeenAt: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** # 容器视图 */
|
||||
export interface ContainerView {
|
||||
id: string;
|
||||
containerId: string;
|
||||
name: string;
|
||||
image: string;
|
||||
state: string;
|
||||
ports: Record<string, unknown> | null;
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
/** # 网站视图 */
|
||||
export interface WebsiteView {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
primaryDomain: string | null;
|
||||
serverId: string | null;
|
||||
repositoryUrl: string | null;
|
||||
deployType: string;
|
||||
port: number;
|
||||
healthPath: string;
|
||||
status: ResourceStatus;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** # 部署视图 */
|
||||
export interface DeploymentView {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
websiteId: string;
|
||||
status: DeploymentStatus;
|
||||
trigger: string;
|
||||
progress: number;
|
||||
commit: string | null;
|
||||
createdAt: string;
|
||||
finishedAt: string | null;
|
||||
}
|
||||
|
||||
/** # 域名视图 */
|
||||
export interface DomainView {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
status: ResourceStatus;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** # 域名记录视图 */
|
||||
export interface DomainRecordView {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
value: string;
|
||||
ttl: number;
|
||||
proxied: boolean;
|
||||
}
|
||||
|
||||
/** # 健康检查响应 */
|
||||
export interface HealthView {
|
||||
status: "ok";
|
||||
service: string;
|
||||
version: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
/** # 认证用户视图 */
|
||||
export interface AuthUserView {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
activeWorkspaceId: string | null;
|
||||
workspaces: { id: string; name: string; slug: string; role: WorkspaceRole }[];
|
||||
}
|
||||
|
||||
// ============ Phase 2 常量 ============
|
||||
|
||||
/** # 规范关系类型(有向,Phase 2 标准化) **/
|
||||
export const RELATION_TYPES = {
|
||||
projectHasRepository: "PROJECT_HAS_REPOSITORY",
|
||||
projectHasWebsite: "PROJECT_HAS_WEBSITE",
|
||||
projectHasServer: "PROJECT_HAS_SERVER",
|
||||
projectHasTask: "PROJECT_HAS_TASK",
|
||||
projectHasNote: "PROJECT_HAS_NOTE",
|
||||
projectHasDomain: "PROJECT_HAS_DOMAIN",
|
||||
websiteDeployedToServer: "WEBSITE_DEPLOYED_TO_SERVER",
|
||||
websiteUsesDomain: "WEBSITE_USES_DOMAIN",
|
||||
websiteHasDeployment: "WEBSITE_HAS_DEPLOYMENT",
|
||||
websiteHasMonitor: "WEBSITE_HAS_MONITOR",
|
||||
websiteHasCertificate: "WEBSITE_HAS_CERTIFICATE",
|
||||
deploymentFromRepository: "DEPLOYMENT_FROM_REPOSITORY",
|
||||
deploymentToServer: "DEPLOYMENT_TO_SERVER",
|
||||
serverHasContainer: "SERVER_HAS_CONTAINER",
|
||||
} as const;
|
||||
export type RelationType = (typeof RELATION_TYPES)[keyof typeof RELATION_TYPES];
|
||||
|
||||
/** # 任务状态 **/
|
||||
export const TASK_STATUSES = ["BACKLOG", "TODO", "DOING", "REVIEW", "DONE"] as const;
|
||||
export type TaskStatus = (typeof TASK_STATUSES)[number];
|
||||
|
||||
/** # 任务优先级 **/
|
||||
export const TASK_PRIORITIES = ["LOW", "MEDIUM", "HIGH", "URGENT"] as const;
|
||||
export type TaskPriority = (typeof TASK_PRIORITIES)[number];
|
||||
|
||||
/** # 部署环境 **/
|
||||
export const ENVIRONMENTS = ["development", "staging", "production"] as const;
|
||||
export type DeployEnvironment = (typeof ENVIRONMENTS)[number];
|
||||
|
||||
/** # Git 提供商 **/
|
||||
export const GIT_PROVIDERS = ["GITHUB", "GITLAB", "GITEA"] as const;
|
||||
export type GitProvider = (typeof GIT_PROVIDERS)[number];
|
||||
|
||||
/** # 新增动作类型(自动化 Phase F) **/
|
||||
export const AUTOMATION_ACTION_TYPES = [
|
||||
"LOG",
|
||||
"WEBHOOK",
|
||||
"DEPLOY_WEBSITE",
|
||||
"CONTAINER_RESTART",
|
||||
"TASK_CREATE",
|
||||
"NOTE_CREATE",
|
||||
"NOTIFICATION_SEND",
|
||||
] as const;
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@devos/ui",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devos/types": "workspace:*",
|
||||
"antd": "^5.22.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user