From b5c550f5c5b2251ea2353352e68220c2c7851dcf Mon Sep 17 00:00:00 2001 From: hurole <1192163814@qq.com> Date: Sat, 3 Jan 2026 00:54:57 +0800 Subject: [PATCH] feat(project): add workspace directory configuration and management - Add projectDir field to Project model for workspace directory management - Implement workspace directory creation, validation and Git initialization - Add workspace status query endpoint with directory info and Git status - Create GitManager for Git repository operations (clone, branch, commit info) - Add PathValidator for secure path validation and traversal attack prevention - Implement execution queue with concurrency control for build tasks - Refactor project list UI to remove edit/delete actions from cards - Add project settings tab in detail page with edit/delete functionality - Add icons to all tabs (History, Code, Settings) - Implement time formatting with dayjs in YYYY-MM-DD HH:mm:ss format - Display all timestamps using browser's local timezone - Update PipelineRunner to use workspace directory for command execution - Add workspace status card showing directory path, size, Git info - Enhance CreateProjectModal with repository URL validation --- apps/server/controllers/project/dto.ts | 3 + apps/server/controllers/project/index.ts | 70 +- apps/server/generated/internal/class.ts | 4 +- .../generated/internal/prismaNamespace.ts | 1 + .../internal/prismaNamespaceBrowser.ts | 1 + apps/server/generated/models/Project.ts | 40 +- apps/server/libs/execution-queue.ts | 59 +- apps/server/libs/git-manager.ts | 280 ++++++ apps/server/libs/path-validator.ts | 67 ++ apps/server/prisma/data/dev.db | Bin 36864 -> 40960 bytes apps/server/prisma/schema.prisma | 1 + apps/server/runners/pipeline-runner.ts | 240 +++-- apps/web/package.json | 1 + .../project/detail/components/DeployModal.tsx | 3 +- .../detail/components/DeployRecordItem.tsx | 3 +- apps/web/src/pages/project/detail/index.tsx | 228 ++++- apps/web/src/pages/project/detail/service.ts | 29 + .../list/components/CreateProjectModal.tsx | 29 +- .../project/list/components/ProjectCard.tsx | 61 +- apps/web/src/pages/project/list/index.tsx | 43 +- apps/web/src/pages/project/types.ts | 28 + apps/web/src/utils/time.ts | 31 + pnpm-lock.yaml | 866 +++++++++++++++++- 23 files changed, 1859 insertions(+), 229 deletions(-) create mode 100644 apps/server/libs/git-manager.ts create mode 100644 apps/server/libs/path-validator.ts create mode 100644 apps/web/src/utils/time.ts diff --git a/apps/server/controllers/project/dto.ts b/apps/server/controllers/project/dto.ts index cff4ebb..0f1e76e 100644 --- a/apps/server/controllers/project/dto.ts +++ b/apps/server/controllers/project/dto.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { projectDirSchema } from '../../libs/path-validator.js'; /** * 创建项目验证架构 @@ -15,6 +16,8 @@ export const createProjectSchema = z.object({ repository: z.string({ message: '仓库地址必须是字符串', }).url({ message: '请输入有效的仓库地址' }).min(1, { message: '仓库地址不能为空' }), + + projectDir: projectDirSchema, }); /** diff --git a/apps/server/controllers/project/index.ts b/apps/server/controllers/project/index.ts index 3f08fdb..b86338b 100644 --- a/apps/server/controllers/project/index.ts +++ b/apps/server/controllers/project/index.ts @@ -1,8 +1,9 @@ import type { Context } from 'koa'; -import {prisma} from '../../libs/prisma.ts'; +import { prisma } from '../../libs/prisma.ts'; import { log } from '../../libs/logger.ts'; import { BusinessError } from '../../middlewares/exception.ts'; import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts'; +import { GitManager } from '../../libs/git-manager.ts'; import { createProjectSchema, updateProjectSchema, @@ -37,7 +38,7 @@ export class ProjectController { orderBy: { createdAt: 'desc', }, - }) + }), ]); return { @@ -47,7 +48,7 @@ export class ProjectController { limit: query?.limit || 10, total, totalPages: Math.ceil(total / (query?.limit || 10)), - } + }, }; } @@ -67,7 +68,48 @@ export class ProjectController { throw new BusinessError('项目不存在', 1002, 404); } - return project; + // 获取工作目录状态信息 + let workspaceStatus = null; + if (project.projectDir) { + try { + const status = await GitManager.checkWorkspaceStatus( + project.projectDir, + ); + let size = 0; + let gitInfo = null; + + if (status.exists && !status.isEmpty) { + size = await GitManager.getDirectorySize(project.projectDir); + } + + if (status.hasGit) { + gitInfo = await GitManager.getGitInfo(project.projectDir); + } + + workspaceStatus = { + ...status, + size, + gitInfo, + }; + } catch (error) { + log.error( + 'project', + 'Failed to get workspace status for project %s: %s', + project.name, + (error as Error).message, + ); + // 即使获取状态失败,也返回项目信息 + workspaceStatus = { + status: 'error', + error: (error as Error).message, + }; + } + } + + return { + ...project, + workspaceStatus, + }; } // POST /api/projects - 创建项目 @@ -75,18 +117,36 @@ export class ProjectController { async create(ctx: Context) { const validatedData = createProjectSchema.parse(ctx.request.body); + // 检查工作目录是否已被其他项目使用 + const existingProject = await prisma.project.findFirst({ + where: { + projectDir: validatedData.projectDir, + valid: 1, + }, + }); + + if (existingProject) { + throw new BusinessError('该工作目录已被其他项目使用', 1003, 400); + } + const project = await prisma.project.create({ data: { name: validatedData.name, description: validatedData.description || '', repository: validatedData.repository, + projectDir: validatedData.projectDir, createdBy: 'system', updatedBy: 'system', valid: 1, }, }); - log.info('project', 'Created new project: %s', project.name); + log.info( + 'project', + 'Created new project: %s with projectDir: %s', + project.name, + project.projectDir, + ); return project; } diff --git a/apps/server/generated/internal/class.ts b/apps/server/generated/internal/class.ts index 155d138..0a55e73 100644 --- a/apps/server/generated/internal/class.ts +++ b/apps/server/generated/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.0.0", "engineVersion": "0c19ccc313cf9911a90d99d2ac2eb0280c76c513", "activeProvider": "sqlite", - "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel Project {\n id Int @id @default(autoincrement())\n name String\n description String?\n repository String\n // Relations\n deployments Deployment[]\n pipelines Pipeline[]\n\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n username String\n login String\n email String\n avatar_url String?\n active Boolean @default(true)\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String @default(\"system\")\n updatedBy String @default(\"system\")\n}\n\nmodel Pipeline {\n id Int @id @default(autoincrement())\n name String\n description String?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n // Relations\n projectId Int?\n Project Project? @relation(fields: [projectId], references: [id])\n steps Step[]\n}\n\nmodel Step {\n id Int @id @default(autoincrement())\n name String\n order Int\n script String // 执行的脚本命令\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n pipelineId Int\n pipeline Pipeline @relation(fields: [pipelineId], references: [id])\n}\n\nmodel Deployment {\n id Int @id @default(autoincrement())\n branch String\n env String?\n status String // pending, running, success, failed, cancelled\n commitHash String?\n commitMessage String?\n buildLog String?\n sparseCheckoutPaths String? // 稀疏检出路径,用于monorepo项目\n startedAt DateTime @default(now())\n finishedAt DateTime?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n projectId Int\n Project Project? @relation(fields: [projectId], references: [id])\n pipelineId Int\n}\n", + "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel Project {\n id Int @id @default(autoincrement())\n name String\n description String?\n repository String\n projectDir String @unique // 项目工作目录路径(必填)\n // Relations\n deployments Deployment[]\n pipelines Pipeline[]\n\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n username String\n login String\n email String\n avatar_url String?\n active Boolean @default(true)\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String @default(\"system\")\n updatedBy String @default(\"system\")\n}\n\nmodel Pipeline {\n id Int @id @default(autoincrement())\n name String\n description String?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n // Relations\n projectId Int?\n Project Project? @relation(fields: [projectId], references: [id])\n steps Step[]\n}\n\nmodel Step {\n id Int @id @default(autoincrement())\n name String\n order Int\n script String // 执行的脚本命令\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n pipelineId Int\n pipeline Pipeline @relation(fields: [pipelineId], references: [id])\n}\n\nmodel Deployment {\n id Int @id @default(autoincrement())\n branch String\n env String?\n status String // pending, running, success, failed, cancelled\n commitHash String?\n commitMessage String?\n buildLog String?\n sparseCheckoutPaths String? // 稀疏检出路径,用于monorepo项目\n startedAt DateTime @default(now())\n finishedAt DateTime?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n projectId Int\n Project Project? @relation(fields: [projectId], references: [id])\n pipelineId Int\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"repository\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deployments\",\"kind\":\"object\",\"type\":\"Deployment\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelines\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToProject\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"avatar_url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Pipeline\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"PipelineToProject\"},{\"name\":\"steps\",\"kind\":\"object\",\"type\":\"Step\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Step\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"order\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"script\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"pipeline\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Deployment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"branch\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"env\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitMessage\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"buildLog\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sparseCheckoutPaths\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"startedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"repository\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectDir\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deployments\",\"kind\":\"object\",\"type\":\"Deployment\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelines\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToProject\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"avatar_url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Pipeline\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"PipelineToProject\"},{\"name\":\"steps\",\"kind\":\"object\",\"type\":\"Step\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Step\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"order\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"script\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"pipeline\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Deployment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"branch\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"env\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitMessage\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"buildLog\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sparseCheckoutPaths\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"startedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") async function decodeBase64AsWasm(wasmBase64: string): Promise { const { Buffer } = await import('node:buffer') diff --git a/apps/server/generated/internal/prismaNamespace.ts b/apps/server/generated/internal/prismaNamespace.ts index c0d9b1b..2146723 100644 --- a/apps/server/generated/internal/prismaNamespace.ts +++ b/apps/server/generated/internal/prismaNamespace.ts @@ -819,6 +819,7 @@ export const ProjectScalarFieldEnum = { name: 'name', description: 'description', repository: 'repository', + projectDir: 'projectDir', valid: 'valid', createdAt: 'createdAt', updatedAt: 'updatedAt', diff --git a/apps/server/generated/internal/prismaNamespaceBrowser.ts b/apps/server/generated/internal/prismaNamespaceBrowser.ts index 99fbd97..7692541 100644 --- a/apps/server/generated/internal/prismaNamespaceBrowser.ts +++ b/apps/server/generated/internal/prismaNamespaceBrowser.ts @@ -76,6 +76,7 @@ export const ProjectScalarFieldEnum = { name: 'name', description: 'description', repository: 'repository', + projectDir: 'projectDir', valid: 'valid', createdAt: 'createdAt', updatedAt: 'updatedAt', diff --git a/apps/server/generated/models/Project.ts b/apps/server/generated/models/Project.ts index 8181589..9caaedf 100644 --- a/apps/server/generated/models/Project.ts +++ b/apps/server/generated/models/Project.ts @@ -41,6 +41,7 @@ export type ProjectMinAggregateOutputType = { name: string | null description: string | null repository: string | null + projectDir: string | null valid: number | null createdAt: Date | null updatedAt: Date | null @@ -53,6 +54,7 @@ export type ProjectMaxAggregateOutputType = { name: string | null description: string | null repository: string | null + projectDir: string | null valid: number | null createdAt: Date | null updatedAt: Date | null @@ -65,6 +67,7 @@ export type ProjectCountAggregateOutputType = { name: number description: number repository: number + projectDir: number valid: number createdAt: number updatedAt: number @@ -89,6 +92,7 @@ export type ProjectMinAggregateInputType = { name?: true description?: true repository?: true + projectDir?: true valid?: true createdAt?: true updatedAt?: true @@ -101,6 +105,7 @@ export type ProjectMaxAggregateInputType = { name?: true description?: true repository?: true + projectDir?: true valid?: true createdAt?: true updatedAt?: true @@ -113,6 +118,7 @@ export type ProjectCountAggregateInputType = { name?: true description?: true repository?: true + projectDir?: true valid?: true createdAt?: true updatedAt?: true @@ -212,6 +218,7 @@ export type ProjectGroupByOutputType = { name: string description: string | null repository: string + projectDir: string valid: number createdAt: Date updatedAt: Date @@ -247,6 +254,7 @@ export type ProjectWhereInput = { name?: Prisma.StringFilter<"Project"> | string description?: Prisma.StringNullableFilter<"Project"> | string | null repository?: Prisma.StringFilter<"Project"> | string + projectDir?: Prisma.StringFilter<"Project"> | string valid?: Prisma.IntFilter<"Project"> | number createdAt?: Prisma.DateTimeFilter<"Project"> | Date | string updatedAt?: Prisma.DateTimeFilter<"Project"> | Date | string @@ -261,6 +269,7 @@ export type ProjectOrderByWithRelationInput = { name?: Prisma.SortOrder description?: Prisma.SortOrderInput | Prisma.SortOrder repository?: Prisma.SortOrder + projectDir?: Prisma.SortOrder valid?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder @@ -272,6 +281,7 @@ export type ProjectOrderByWithRelationInput = { export type ProjectWhereUniqueInput = Prisma.AtLeast<{ id?: number + projectDir?: string AND?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] OR?: Prisma.ProjectWhereInput[] NOT?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] @@ -285,13 +295,14 @@ export type ProjectWhereUniqueInput = Prisma.AtLeast<{ updatedBy?: Prisma.StringFilter<"Project"> | string deployments?: Prisma.DeploymentListRelationFilter pipelines?: Prisma.PipelineListRelationFilter -}, "id"> +}, "id" | "projectDir"> export type ProjectOrderByWithAggregationInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder description?: Prisma.SortOrderInput | Prisma.SortOrder repository?: Prisma.SortOrder + projectDir?: Prisma.SortOrder valid?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder @@ -312,6 +323,7 @@ export type ProjectScalarWhereWithAggregatesInput = { name?: Prisma.StringWithAggregatesFilter<"Project"> | string description?: Prisma.StringNullableWithAggregatesFilter<"Project"> | string | null repository?: Prisma.StringWithAggregatesFilter<"Project"> | string + projectDir?: Prisma.StringWithAggregatesFilter<"Project"> | string valid?: Prisma.IntWithAggregatesFilter<"Project"> | number createdAt?: Prisma.DateTimeWithAggregatesFilter<"Project"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Project"> | Date | string @@ -323,6 +335,7 @@ export type ProjectCreateInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -337,6 +350,7 @@ export type ProjectUncheckedCreateInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -350,6 +364,7 @@ export type ProjectUpdateInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -364,6 +379,7 @@ export type ProjectUncheckedUpdateInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -378,6 +394,7 @@ export type ProjectCreateManyInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -389,6 +406,7 @@ export type ProjectUpdateManyMutationInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -401,6 +419,7 @@ export type ProjectUncheckedUpdateManyInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -413,6 +432,7 @@ export type ProjectCountOrderByAggregateInput = { name?: Prisma.SortOrder description?: Prisma.SortOrder repository?: Prisma.SortOrder + projectDir?: Prisma.SortOrder valid?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder @@ -430,6 +450,7 @@ export type ProjectMaxOrderByAggregateInput = { name?: Prisma.SortOrder description?: Prisma.SortOrder repository?: Prisma.SortOrder + projectDir?: Prisma.SortOrder valid?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder @@ -442,6 +463,7 @@ export type ProjectMinOrderByAggregateInput = { name?: Prisma.SortOrder description?: Prisma.SortOrder repository?: Prisma.SortOrder + projectDir?: Prisma.SortOrder valid?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder @@ -515,6 +537,7 @@ export type ProjectCreateWithoutPipelinesInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -528,6 +551,7 @@ export type ProjectUncheckedCreateWithoutPipelinesInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -556,6 +580,7 @@ export type ProjectUpdateWithoutPipelinesInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -569,6 +594,7 @@ export type ProjectUncheckedUpdateWithoutPipelinesInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -581,6 +607,7 @@ export type ProjectCreateWithoutDeploymentsInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -594,6 +621,7 @@ export type ProjectUncheckedCreateWithoutDeploymentsInput = { name: string description?: string | null repository: string + projectDir: string valid?: number createdAt?: Date | string updatedAt?: Date | string @@ -622,6 +650,7 @@ export type ProjectUpdateWithoutDeploymentsInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -635,6 +664,7 @@ export type ProjectUncheckedUpdateWithoutDeploymentsInput = { name?: Prisma.StringFieldUpdateOperationsInput | string description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null repository?: Prisma.StringFieldUpdateOperationsInput | string + projectDir?: Prisma.StringFieldUpdateOperationsInput | string valid?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -688,6 +718,7 @@ export type ProjectSelect = runtime.Types.Extensions.GetOmit<"id" | "name" | "description" | "repository" | "valid" | "createdAt" | "updatedAt" | "createdBy" | "updatedBy", ExtArgs["result"]["project"]> +export type ProjectOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "description" | "repository" | "projectDir" | "valid" | "createdAt" | "updatedAt" | "createdBy" | "updatedBy", ExtArgs["result"]["project"]> export type ProjectInclude = { deployments?: boolean | Prisma.Project$deploymentsArgs pipelines?: boolean | Prisma.Project$pipelinesArgs @@ -754,6 +788,7 @@ export type $ProjectPayload readonly description: Prisma.FieldRef<"Project", 'String'> readonly repository: Prisma.FieldRef<"Project", 'String'> + readonly projectDir: Prisma.FieldRef<"Project", 'String'> readonly valid: Prisma.FieldRef<"Project", 'Int'> readonly createdAt: Prisma.FieldRef<"Project", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"Project", 'DateTime'> diff --git a/apps/server/libs/execution-queue.ts b/apps/server/libs/execution-queue.ts index 11fd2d3..969549d 100644 --- a/apps/server/libs/execution-queue.ts +++ b/apps/server/libs/execution-queue.ts @@ -61,12 +61,12 @@ export class ExecutionQueue { const pendingDeployments = await prisma.deployment.findMany({ where: { status: 'pending', - valid: 1 + valid: 1, }, select: { id: true, - pipelineId: true - } + pipelineId: true, + }, }); console.log(`Found ${pendingDeployments.length} pending deployments`); @@ -126,21 +126,25 @@ export class ExecutionQueue { const pendingDeployments = await prisma.deployment.findMany({ where: { status: 'pending', - valid: 1 + valid: 1, }, select: { id: true, - pipelineId: true - } + pipelineId: true, + }, }); - console.log(`Found ${pendingDeployments.length} pending deployments in polling`); + console.log( + `Found ${pendingDeployments.length} pending deployments in polling`, + ); // 检查这些任务是否已经在队列中,如果没有则添加 for (const deployment of pendingDeployments) { // 检查是否已经在运行队列中 if (!runningDeployments.has(deployment.id)) { - console.log(`Adding deployment ${deployment.id} to queue from polling`); + console.log( + `Adding deployment ${deployment.id} to queue from polling`, + ); await this.addTask(deployment.id, deployment.pipelineId); } } @@ -154,7 +158,10 @@ export class ExecutionQueue { * @param deploymentId 部署ID * @param pipelineId 流水线ID */ - public async addTask(deploymentId: number, pipelineId: number): Promise { + public async addTask( + deploymentId: number, + pipelineId: number, + ): Promise { // 检查是否已经在运行队列中 if (runningDeployments.has(deploymentId)) { console.log(`Deployment ${deploymentId} is already queued or running`); @@ -196,7 +203,7 @@ export class ExecutionQueue { } // 添加一个小延迟以避免过度占用资源 - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); } this.isProcessing = false; @@ -207,9 +214,35 @@ export class ExecutionQueue { * @param deploymentId 部署ID * @param pipelineId 流水线ID */ - private async executePipeline(deploymentId: number, pipelineId: number): Promise { + private async executePipeline( + deploymentId: number, + pipelineId: number, + ): Promise { try { - const runner = new PipelineRunner(deploymentId); + // 获取部署信息以获取项目和 projectDir + const deployment = await prisma.deployment.findUnique({ + where: { id: deploymentId }, + include: { + Project: true, + }, + }); + + if (!deployment || !deployment.Project) { + throw new Error( + `Deployment ${deploymentId} or associated project not found`, + ); + } + + if (!deployment.Project.projectDir) { + throw new Error( + `项目 "${deployment.Project.name}" 未配置工作目录,无法执行流水线`, + ); + } + + const runner = new PipelineRunner( + deploymentId, + deployment.Project.projectDir, + ); await runner.run(pipelineId); } catch (error) { console.error('执行流水线失败:', error); @@ -227,7 +260,7 @@ export class ExecutionQueue { } { return { pendingCount: pendingQueue.length, - runningCount: runningDeployments.size + runningCount: runningDeployments.size, }; } } diff --git a/apps/server/libs/git-manager.ts b/apps/server/libs/git-manager.ts new file mode 100644 index 0000000..2946bd1 --- /dev/null +++ b/apps/server/libs/git-manager.ts @@ -0,0 +1,280 @@ +/** + * Git 管理器 + * 封装 Git 操作:克隆、更新、分支切换等 + */ + +import { $ } from 'zx'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { log } from './logger'; + +/** + * 工作目录状态 + */ +export const WorkspaceDirStatus = { + NOT_CREATED: 'not_created', // 目录不存在 + EMPTY: 'empty', // 目录存在但为空 + NO_GIT: 'no_git', // 目录存在但不是 Git 仓库 + READY: 'ready', // 目录存在且包含 Git 仓库 +} as const; + +export type WorkspaceDirStatus = + (typeof WorkspaceDirStatus)[keyof typeof WorkspaceDirStatus]; + +/** + * 工作目录状态信息 + */ +export interface WorkspaceStatus { + status: WorkspaceDirStatus; + exists: boolean; + isEmpty?: boolean; + hasGit?: boolean; +} + +/** + * Git仓库信息 + */ +export interface GitInfo { + branch?: string; + lastCommit?: string; + lastCommitMessage?: string; +} + +/** + * Git管理器类 + */ +export class GitManager { + static readonly TAG = 'GitManager'; + /** + * 检查工作目录状态 + */ + static async checkWorkspaceStatus(dirPath: string): Promise { + try { + // 检查目录是否存在 + const stats = await fs.stat(dirPath); + if (!stats.isDirectory()) { + return { + status: WorkspaceDirStatus.NOT_CREATED, + exists: false, + }; + } + + // 检查目录是否为空 + const files = await fs.readdir(dirPath); + if (files.length === 0) { + return { + status: WorkspaceDirStatus.EMPTY, + exists: true, + isEmpty: true, + }; + } + + // 检查是否包含 .git 目录 + const gitDir = path.join(dirPath, '.git'); + try { + const gitStats = await fs.stat(gitDir); + if (gitStats.isDirectory()) { + return { + status: WorkspaceDirStatus.READY, + exists: true, + isEmpty: false, + hasGit: true, + }; + } + } catch { + return { + status: WorkspaceDirStatus.NO_GIT, + exists: true, + isEmpty: false, + hasGit: false, + }; + } + + return { + status: WorkspaceDirStatus.NO_GIT, + exists: true, + isEmpty: false, + hasGit: false, + }; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return { + status: WorkspaceDirStatus.NOT_CREATED, + exists: false, + }; + } + throw error; + } + } + + /** + * 克隆仓库到指定目录 + * @param repoUrl 仓库URL + * @param dirPath 目标目录 + * @param branch 分支名 + * @param token Gitea access token(可选) + */ + static async cloneRepository( + repoUrl: string, + dirPath: string, + branch: string, + token?: string, + ): Promise { + try { + log.info( + GitManager.TAG, + 'Cloning repository: %s to %s (branch: %s)', + repoUrl, + dirPath, + branch, + ); + + // 如果提供了token,嵌入到URL中 + let cloneUrl = repoUrl; + if (token) { + const url = new URL(repoUrl); + url.username = token; + cloneUrl = url.toString(); + } + + // 使用 zx 执行 git clone(浅克隆) + $.verbose = false; // 禁止打印敏感信息 + await $`git clone --depth 1 --branch ${branch} ${cloneUrl} ${dirPath}`; + $.verbose = true; + + log.info(GitManager.TAG, 'Repository cloned successfully: %s', dirPath); + } catch (error) { + log.error( + GitManager.TAG, + 'Failed to clone repository: %s to %s, error: %s', + repoUrl, + dirPath, + (error as Error).message, + ); + throw new Error(`克隆仓库失败: ${(error as Error).message}`); + } + } + + /** + * 更新已存在的仓库 + * @param dirPath 仓库目录 + * @param branch 目标分支 + */ + static async updateRepository( + dirPath: string, + branch: string, + ): Promise { + try { + log.info( + GitManager.TAG, + 'Updating repository: %s (branch: %s)', + dirPath, + branch, + ); + + $.verbose = false; + // 切换到仓库目录 + const originalCwd = process.cwd(); + process.chdir(dirPath); + + try { + // 获取最新代码 + await $`git fetch --depth 1 origin ${branch}`; + // 切换到目标分支 + await $`git checkout ${branch}`; + // 拉取最新代码 + await $`git pull origin ${branch}`; + + log.info( + GitManager.TAG, + 'Repository updated successfully: %s (branch: %s)', + dirPath, + branch, + ); + } finally { + process.chdir(originalCwd); + $.verbose = true; + } + } catch (error) { + log.error( + GitManager.TAG, + 'Failed to update repository: %s (branch: %s), error: %s', + dirPath, + branch, + (error as Error).message, + ); + throw new Error(`更新仓库失败: ${(error as Error).message}`); + } + } + + /** + * 获取Git仓库信息 + */ + static async getGitInfo(dirPath: string): Promise { + try { + const originalCwd = process.cwd(); + process.chdir(dirPath); + + try { + $.verbose = false; + const branchResult = await $`git branch --show-current`; + const commitResult = await $`git rev-parse --short HEAD`; + const messageResult = await $`git log -1 --pretty=%B`; + $.verbose = true; + + return { + branch: branchResult.stdout.trim(), + lastCommit: commitResult.stdout.trim(), + lastCommitMessage: messageResult.stdout.trim(), + }; + } finally { + process.chdir(originalCwd); + } + } catch (error) { + log.error( + GitManager.TAG, + 'Failed to get git info: %s, error: %s', + dirPath, + (error as Error).message, + ); + return {}; + } + } + + /** + * 创建目录(递归) + */ + static async ensureDirectory(dirPath: string): Promise { + try { + await fs.mkdir(dirPath, { recursive: true }); + log.info(GitManager.TAG, 'Directory created: %s', dirPath); + } catch (error) { + log.error( + GitManager.TAG, + 'Failed to create directory: %s, error: %s', + dirPath, + (error as Error).message, + ); + throw new Error(`创建目录失败: ${(error as Error).message}`); + } + } + + /** + * 获取目录大小 + */ + static async getDirectorySize(dirPath: string): Promise { + try { + const { stdout } = await $`du -sb ${dirPath}`; + const size = Number.parseInt(stdout.split('\t')[0], 10); + return size; + } catch (error) { + log.error( + GitManager.TAG, + 'Failed to get directory size: %s, error: %s', + dirPath, + (error as Error).message, + ); + return 0; + } + } +} diff --git a/apps/server/libs/path-validator.ts b/apps/server/libs/path-validator.ts new file mode 100644 index 0000000..5cc6fb6 --- /dev/null +++ b/apps/server/libs/path-validator.ts @@ -0,0 +1,67 @@ +/** + * 路径验证工具 + * 用于验证项目工作目录路径的合法性 + */ + +import path from 'node:path'; +import { z } from 'zod'; + +/** + * 项目目录路径验证schema + */ +export const projectDirSchema = z + .string() + .min(1, '工作目录路径不能为空') + .refine(path.isAbsolute, '工作目录路径必须是绝对路径') + .refine((v) => !v.includes('..'), '不能包含路径遍历字符') + .refine((v) => !v.includes('~'), '不能包含用户目录符号') + .refine((v) => !/[<>:"|?*\x00-\x1f]/.test(v), '包含非法字符') + .refine((v) => path.normalize(v) === v, '路径格式不规范'); + +/** + * 验证路径格式 + * @param dirPath 待验证的路径 + * @returns 验证结果 + */ +export function validateProjectDir(dirPath: string): { + valid: boolean; + error?: string; +} { + try { + projectDirSchema.parse(dirPath); + return { valid: true }; + } catch (error) { + if (error instanceof z.ZodError) { + return { valid: false, error: error.issues[0].message }; + } + return { valid: false, error: '路径验证失败' }; + } +} + +/** + * 检查路径是否为绝对路径 + */ +export function isAbsolutePath(dirPath: string): boolean { + return path.isAbsolute(dirPath); +} + +/** + * 检查路径是否包含非法字符 + */ +export function hasIllegalCharacters(dirPath: string): boolean { + return /[<>:"|?*\x00-\x1f]/.test(dirPath); +} + +/** + * 检查路径是否包含路径遍历 + */ +export function hasPathTraversal(dirPath: string): boolean { + return dirPath.includes('..') || dirPath.includes('~'); +} + +/** + * 规范化路径 + */ +export function normalizePath(dirPath: string): string { + return path.normalize(dirPath); +} diff --git a/apps/server/prisma/data/dev.db b/apps/server/prisma/data/dev.db index 7f9afae143463bd15e918a7e7691758d05d31574..8c2bc4207dc1ebd6461e8ae81fa0e6cec3aa5f21 100644 GIT binary patch delta 3347 zcmbVOTWr%-7`Bs^+!{Q@wJQuaJPgK?I*#L9n1mSZDx?j{rED5Q#c^CUqfJ5*Mhnx@ zKq;YjW@r(&!b(Rs(NO3pkW$KoCZS1_rd=NxVh=e^tFi5YJ#3SPb{wZAEpCPuC9&lH z{NMNe-~apg)Sp$<-&S-K%;`Z8L<@ftAJvhUElYb4Q{zi|>`z3aC`a@^V1s$2?q|(o z)nB>aEAA@FFGqev!@D%9g^mI=QsD8rx!SUT?_G`!RrsZAu_sWmldDVL)vn!CvSv#O zUcP?a#_|$;-TLB^w{U}eiUHrSelliQZg>J{Skb6es}>d(qRkZ{X7CS1g+^1TR4pth zKpXS0iPB=(A!xE&0b`X?>vr{&=-EC(W^klf|Awd!(O_&LCvDs=RNpl6`c2C$;;r0Yl zi%eJWpD@$2Xwt(*q1X-t+X0K(ifJNCP#sdB-(Jc$uBJ8?l?S;%u&8Qxz*o%`6?4Iz zA)mjf(zlZ_vK~>0kuVzxa*LUElC+bsm`K91k|3N!N~KJY|84c6ygMj%4;#lGVc%eP zAnRp}(B~=(HN~92+E-V@c|)ok<$R4uorfxypk*FESMBj~IckM+KDs%?`E`SjHYBmR znd}ZHP9sV%}}VrZk-NUsh*p10|g^IINU099!d-kf%7-~ zd)(8Uu{jBwi6-TnIyD%q3x>EF=|3m3UYmy)H4#?gd3Cp^+Kn?_H_m^t=LP=Jo~<&~ z+2mQ$PFnaQb@T9m2EwZMuascdR{OjhzKQeuf}W5sP`3={Z`Us7AJ&HC;K^)_A|d=- z)%$84@*-NoU#wfU&Finh1G~MrOVmEimb4}!8)7<`X^SGL(jiOHRs5>GOJ5Q3LtHTQ zf1r7M?>yD4Svth3d4vCaZ&6CD#~TbW)zz|iv?GhuiQp8e2Nve$A#2nP48Lukfp6cp z9NKE(|K9iY#;2bF+MW$P9h`!`FDGTR^i1+)p(Q*1+iu#LjU^q-WC5c2@SqBH3$4@j0Er&qt z1j~O_i@^@<;tz)@nc=6ijVeuuJI5?kdmqu>hr7hLT746a&O_$UE!ZCQMa%iehZlt% zc8AU7CMcHV7*{20#*0F0jIh3r8;rEsmOK|rjGj*OhEu%(AGS=R6#9GMI;e4MWJI+4+OVj%;}U4<6S{iAXqQ-Gdq?to zJhOmFGB^Z6v_rdX>ZWCZCkWC+(dM_e<8W$(?p_eS)|(hPl{^`PrX-t&g!+gR&w4tU z3DW{Gfee_}7ph{}MxBO})VIldlmV)rKSGN3W!XiqQ93N{qA)LDC#No?*hQ*X9 zJQ+Vg;5XwlxC9@@j+D(V#FeF*te*V~EqmK=JjBOkG zVm1CizsZL08-0D6aG&|M!}(vLvu%ZN{KfgZ?szw0|3>GI_Wz(Ac*6ufB?4#cogKZq zca!H*GMCQ5L0HU*C-Sf$uYI#c;?Zz23X0EOQYx9h(`}Z5&lRt0bCQBLdZfDm zaa2j8QDnt&R5S6BNK)A~Iib`gh=dc7@L<&5cY1e6NALFS&qSjxcC`o^S7CB7sSkp-GHnJUwX~i>*s< z@0+l0>wV-A@{n4;(y<&G45TD@tPBf0Z27ajsn%PB`;_W?6y@xjB`qDjJ9m4quTr=wvG_`Og9B$dv&bl!eZi#hyCFYdMc~^5jvp(L4BU2m-0@gMo zAk|O5el{u10`!Dt1tJfE!rXP6l2k9OB<6B3lS*^^(Z-{;91_LnN_5LN#m!jA2v!G#Dr!w?%pbzOhOrRgUVFH){CV&ZG0+;|MfC*p% zm;fe#319-4z}*w5xJdhC#nFC*Alhv#pYhUkn(6lkvcZ1VpJn=kS=Q6<336$Mf_}#9 zPqQ%1ruiV7p)zTIfTlAH6HGH{$WmE0;|((YAkDIVF75ZRK|Y)H`m;d_258RDGQJGW z@#M>2TYY`TLwUTk2mNDd)x`CV&ZG0+;|MfC=0`1WuB6VyEMXZD?r6j;wf;>*s}OSuRSUfq^MOcJs%x&@DrL zdSJSYo=|`TdKxzpD)apPv-1kZ>OS@({ykiV0u>m;fe#319-4 z049J5U;>x`CV&ZSfk4GWI*1)R1}a~*sq^{Tl)d&pO$R-+j|tH1W5Uf24rh9(n_#gU$H%tH%zyvS>OaK$W1TX7n-2TV==d0)E zs+WFUePy9`^(~a3_RG`N#f9q6&g&_E^ZM$W*P9hZH>xwN+Y=0$DcT}zQZ(4sMSQo? zuPds)&JGxhSgonr0H{&V)l)OeKfkfG@LXOjh|1aztJiKWzdWy;!NoUMo_nu)@rBy? zv(@=4XlM17a;{5@uPwh?G1uL@neMuo?M%o223cL$sGXzzpG1#!KhbluXSd_3qo*rw zziDS|*E=tCyw&k&`xDkbAtBx{0ZafBFeOmw?&%`-lCV>GhKby-EIJ7EyNfJ)fYvE! z4%g0Sz(W)9*!QE6WNL6EZZ8z`pj0jZb)~{)Z)@~gelu_D@uTOR+J~Uy{fhH zV0RbcwdFM}Mha-{mt0QiS|M7rrN9eN+5@Wd&#t^Sr=eN;;1?_JzF?}Apk+2!%(OE? ziqT%T->3JoO^Ttny?%!Xcg{Vm_D5u*r7MlsS4KkmUA>_xr*(@}Hx`(;G1F%B{9 zH`kp8dRh&jr6Faz@}&gxB3f}4y_Xh}DM1s;k=Ct`__WGsFtH5ngOQ}JRUD@*|% z_1Es^H7kL#K$jgo6DI=KLy3uSJP}PrhNF>#vB_j=BAgsfxWM;-@`T)gP{@h{TJC=1 z<+p0-St#?xx8TV`C%9)NnX4tZAXIlL`8dfdkQ}ySflM`cC9j)#`RW z9v+LD*BxVK+KfNM_|S}iNmp&H{K(!-^xI}Eng&`^;|du)WHTCD_2wV62<)S@I=g)j z&{9{Qc?VT=?bWljs~4(2xmJDY1`@5@erfeA3X|SmT713s-eUD9uU6;YTe@|*9uHQ3 zdTVLXG|FSl=OVONq%LeUPRq>KZRi9$XGHA;wLocelhi{dHGI>SLam2y_reT6o}nZo z!>QqDc(A$NjVSDHgI`OfyHoYBjn%$&4{NBSJ5h68O+0K{#nPdASWTTRdf1Pz#e>^a z536Z$?>(%cm}v*pB5X9`w%K5NH}NPV+Dt*Q5_0_y|B_6K$zk!BK( zp&>0cBhe!5=(5*wsm!BA#U(%EVW~`zP18Xy1v8A7KdRcF^kl`Pws=>}DGJ zf+nNPy85h1$ZiPlkke#3$~qgh=v6ek>$9&S%A?$x>Zj>Vs%SH~_QNZuub}zI>Y2If z{LLdFu>R`XhV`f)o1wN|k6KZ_pvm=EZ^$@~$@HutyhBct@sIU1XjAHO#}R4H=@=rK zn_89=&~oOVR!d`*+m9(Pnt^6M!D?)3-1S4!EIMSgR8suJKw2maq&aEY%nU;4VsbvO zxQ&7+gDhHdJ!7{ktG9D92umjl{HVxtxdd`J+!S;x(-Qj%Vg`nUf(%OqE;qm_iBdTw z6 { + let logs = ''; + const timestamp = new Date().toISOString(); + + try { + logs += `[${timestamp}] 检查工作目录状态: ${this.projectDir}\n`; + + // 检查工作目录状态 + const status = await GitManager.checkWorkspaceStatus(this.projectDir); + logs += `[${new Date().toISOString()}] 工作目录状态: ${status.status}\n`; + + if ( + status.status === WorkspaceDirStatus.NOT_CREATED || + status.status === WorkspaceDirStatus.EMPTY + ) { + // 目录不存在或为空,需要克隆 + logs += `[${new Date().toISOString()}] 工作目录不存在或为空,开始克隆仓库\n`; + + // 确保父目录存在 + await GitManager.ensureDirectory(this.projectDir); + + // 克隆仓库(注意:如果需要认证,token 应该从环境变量或配置中获取) + await GitManager.cloneRepository( + project.repository, + this.projectDir, + branch, + // TODO: 添加 token 支持 + ); + + logs += `[${new Date().toISOString()}] 仓库克隆成功\n`; + } else if (status.status === WorkspaceDirStatus.NO_GIT) { + // 目录存在但不是 Git 仓库 + throw new Error( + `工作目录 ${this.projectDir} 已存在但不是 Git 仓库,请检查配置`, + ); + } else if (status.status === WorkspaceDirStatus.READY) { + // 已存在 Git 仓库,更新代码 + logs += `[${new Date().toISOString()}] 工作目录已存在 Git 仓库,开始更新代码\n`; + await GitManager.updateRepository(this.projectDir, branch); + logs += `[${new Date().toISOString()}] 代码更新成功\n`; } + + return logs; + } catch (error) { + const errorLog = `[${new Date().toISOString()}] 准备工作目录失败: ${(error as Error).message}\n`; + logs += errorLog; + log.error( + this.TAG, + 'Failed to prepare workspace: %s', + (error as Error).message, + ); + throw new Error(`准备工作目录失败: ${(error as Error).message}`); } } @@ -120,9 +198,11 @@ export class PipelineRunner { * 准备环境变量 * @param pipeline 流水线信息 * @param deployment 部署信息 - * @param projectDir 项目目录路径 */ - private prepareEnvironmentVariables(pipeline: any, deployment: any, projectDir: string): Record { + private prepareEnvironmentVariables( + pipeline: any, + deployment: any, + ): Record { const envVars: Record = {}; // 项目相关信息 @@ -138,9 +218,9 @@ export class PipelineRunner { // 稀疏检出路径(如果有配置的话) envVars.SPARSE_CHECKOUT_PATHS = deployment.sparseCheckoutPaths || ''; - // 工作空间路径和项目路径 - envVars.WORKSPACE = this.workspace; - envVars.PROJECT_DIR = projectDir; + // 工作空间路径(使用配置的项目目录) + envVars.WORKSPACE = this.projectDir; + envVars.PROJECT_DIR = this.projectDir; return envVars; } @@ -168,68 +248,37 @@ export class PipelineRunner { private addTimestampToLines(content: string, isError = false): string { if (!content) return ''; - return content.split('\n') - .filter(line => line.trim() !== '') - .map(line => this.addTimestamp(line, isError)) - .join('\n') + '\n'; - } - - /** - * 确保工作空间目录存在 - */ - private async ensureWorkspace(): Promise { - try { - // 检查目录是否存在,如果不存在则创建 - if (!fs.existsSync(this.workspace)) { - // 创建目录包括所有必要的父目录 - fs.mkdirSync(this.workspace, { recursive: true }); - } - - // 检查目录是否可写 - fs.accessSync(this.workspace, fs.constants.W_OK); - } catch (error) { - throw new Error(`无法访问或创建工作空间目录 "${this.workspace}": ${(error as Error).message}`); - } - } - - /** - * 确保项目目录存在 - * @param projectDir 项目目录路径 - */ - private async ensureProjectDirectory(projectDir: string): Promise { - try { - // 检查目录是否存在,如果不存在则创建 - if (!fs.existsSync(projectDir)) { - fs.mkdirSync(projectDir, { recursive: true }); - } - - // 检查目录是否可写 - fs.accessSync(projectDir, fs.constants.W_OK); - } catch (error) { - throw new Error(`无法访问或创建项目目录 "${projectDir}": ${(error as Error).message}`); - } + return ( + content + .split('\n') + .filter((line) => line.trim() !== '') + .map((line) => this.addTimestamp(line, isError)) + .join('\n') + '\n' + ); } /** * 执行单个步骤 * @param step 步骤对象 * @param envVars 环境变量 - * @param projectDir 项目目录路径 */ - private async executeStep(step: Step, envVars: Record, projectDir: string): Promise { + private async executeStep( + step: Step, + envVars: Record, + ): Promise { let logs = ''; try { // 添加步骤开始执行的时间戳 - logs += this.addTimestamp(`开始执行步骤 "${step.name}"`) + '\n'; + logs += this.addTimestamp(`执行脚本: ${step.script}`) + '\n'; // 使用zx执行脚本,设置项目目录为工作目录和环境变量 const script = step.script; // 通过bash -c执行脚本,确保环境变量能被正确解析 const result = await $({ - cwd: projectDir, - env: { ...process.env, ...envVars } + cwd: this.projectDir, + env: { ...process.env, ...envVars }, })`bash -c ${script}`; if (result.stdout) { @@ -242,10 +291,11 @@ export class PipelineRunner { logs += this.addTimestampToLines(result.stderr, true); } - // 添加步骤执行完成的时间戳 - logs += this.addTimestamp(`步骤 "${step.name}" 执行完成`) + '\n'; + logs += this.addTimestamp(`步骤执行完成`) + '\n'; } catch (error) { - logs += this.addTimestamp(`Error executing step "${step.name}": ${(error as Error).message}`) + '\n'; + const errorMsg = `Error executing step "${step.name}": ${(error as Error).message}`; + logs += this.addTimestamp(errorMsg, true) + '\n'; + log.error(this.TAG, errorMsg); throw error; } diff --git a/apps/web/package.json b/apps/web/package.json index 9641048..f8bd28d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,6 +16,7 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "axios": "^1.11.0", + "dayjs": "^1.11.19", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.8.0", diff --git a/apps/web/src/pages/project/detail/components/DeployModal.tsx b/apps/web/src/pages/project/detail/components/DeployModal.tsx index d577c5d..fe62066 100644 --- a/apps/web/src/pages/project/detail/components/DeployModal.tsx +++ b/apps/web/src/pages/project/detail/components/DeployModal.tsx @@ -6,6 +6,7 @@ import { Modal, Select, } from '@arco-design/web-react'; +import { formatDateTime } from '../../../../utils/time'; import { IconDelete, IconPlus } from '@arco-design/web-react/icon'; import { useCallback, useEffect, useState } from 'react'; import type { Branch, Commit, Pipeline } from '../../types'; @@ -182,7 +183,7 @@ function DeployModal({ {commit.sha.substring(0, 7)} - {new Date(commit.commit.author.date).toLocaleString()} + {formatDateTime(commit.commit.author.date)}
diff --git a/apps/web/src/pages/project/detail/components/DeployRecordItem.tsx b/apps/web/src/pages/project/detail/components/DeployRecordItem.tsx index ab747ff..93f880f 100644 --- a/apps/web/src/pages/project/detail/components/DeployRecordItem.tsx +++ b/apps/web/src/pages/project/detail/components/DeployRecordItem.tsx @@ -1,4 +1,5 @@ import { List, Space, Tag } from '@arco-design/web-react'; +import { formatDateTime } from '../../../../utils/time'; import type { Deployment } from '../../types'; interface DeployRecordItemProps { @@ -76,7 +77,7 @@ function DeployRecordItem({ 执行时间:{' '} - {item.createdAt} + {formatDateTime(item.createdAt)} diff --git a/apps/web/src/pages/project/detail/index.tsx b/apps/web/src/pages/project/detail/index.tsx index 3ebde91..9babeff 100644 --- a/apps/web/src/pages/project/detail/index.tsx +++ b/apps/web/src/pages/project/detail/index.tsx @@ -1,6 +1,7 @@ import { Button, Card, + Descriptions, Dropdown, Empty, Form, @@ -10,19 +11,24 @@ import { Message, Modal, Select, + Space, Switch, Tabs, Tag, Typography, } from '@arco-design/web-react'; import { + IconCode, IconCopy, IconDelete, IconEdit, + IconFolder, + IconHistory, IconMore, IconPlayArrow, IconPlus, IconRefresh, + IconSettings, } from '@arco-design/web-react/icon'; import type { DragEndEvent } from '@dnd-kit/core'; import { @@ -40,9 +46,10 @@ import { verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { useEffect, useState } from 'react'; -import { useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router'; import { useAsyncEffect } from '../../../hooks/useAsyncEffect'; -import type { Deployment, Pipeline, Project, Step } from '../types'; +import { formatDateTime } from '../../../utils/time'; +import type { Deployment, Pipeline, Project, Step, WorkspaceDirStatus, WorkspaceStatus } from '../types'; import DeployModal from './components/DeployModal'; import DeployRecordItem from './components/DeployRecordItem'; import PipelineStepItem from './components/PipelineStepItem'; @@ -59,6 +66,7 @@ interface PipelineWithEnabled extends Pipeline { function ProjectDetailPage() { const [detail, setDetail] = useState(); + const navigate = useNavigate(); // 拖拽传感器配置 const sensors = useSensors( @@ -87,6 +95,10 @@ function ProjectDetailPage() { const [selectedTemplateId, setSelectedTemplateId] = useState(null); const [templates, setTemplates] = useState>([]); + // 项目设置相关状态 + const [projectEditModalVisible, setProjectEditModalVisible] = useState(false); + const [projectForm] = Form.useForm(); + const { id } = useParams(); // 获取可用的流水线模板 @@ -580,6 +592,56 @@ function ProjectDetailPage() { } }; + // 项目设置相关函数 + const handleEditProject = () => { + if (detail) { + projectForm.setFieldsValue({ + name: detail.name, + description: detail.description, + repository: detail.repository, + }); + setProjectEditModalVisible(true); + } + }; + + const handleProjectEditSuccess = async () => { + try { + const values = await projectForm.validate(); + await detailService.updateProject(Number(id), values); + Message.success('项目更新成功'); + setProjectEditModalVisible(false); + + // 刷新项目详情 + if (id) { + const projectDetail = await detailService.getProjectDetail(Number(id)); + setDetail(projectDetail); + } + } catch (error) { + console.error('更新项目失败:', error); + Message.error('更新项目失败'); + } + }; + + const handleDeleteProject = () => { + Modal.confirm({ + title: '删除项目', + content: `确定要删除项目 "${detail?.name}" 吗?删除后将无法恢复。`, + okButtonProps: { + status: 'danger', + }, + onOk: async () => { + try { + await detailService.deleteProject(Number(id)); + Message.success('项目删除成功'); + navigate('/project'); + } catch (error) { + console.error('删除项目失败:', error); + Message.error('删除项目失败'); + } + }, + }); + }; + const selectedRecord = deployRecords.find( (record) => record.id === selectedRecordId, ); @@ -613,6 +675,74 @@ function ProjectDetailPage() { (pipeline) => pipeline.id === selectedPipelineId, ); + // 格式化文件大小 + const formatSize = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; + }; + + // 获取工作目录状态标签 + const getWorkspaceStatusTag = (status: string): { text: string; color: string } => { + const statusMap: Record = { + not_created: { text: '未创建', color: 'gray' }, + empty: { text: '空目录', color: 'orange' }, + no_git: { text: '无Git仓库', color: 'orange' }, + ready: { text: '就绪', color: 'green' }, + }; + return statusMap[status] || { text: '未知', color: 'gray' }; + }; + + // 渲染工作目录状态卡片 + const renderWorkspaceStatus = () => { + if (!detail?.workspaceStatus) return null; + + const { workspaceStatus } = detail; + const statusInfo = getWorkspaceStatusTag(workspaceStatus.status as string); + + return ( + 工作目录状态}> + {statusInfo.text}, + }, + { + label: '目录大小', + value: workspaceStatus.size ? formatSize(workspaceStatus.size) : '-', + }, + { + label: '当前分支', + value: workspaceStatus.gitInfo?.branch || '-', + }, + { + label: '最后提交', + value: workspaceStatus.gitInfo?.lastCommit ? ( + + {workspaceStatus.gitInfo.lastCommit} + {workspaceStatus.gitInfo.lastCommitMessage} + + ) : '-', + }, + ]} + /> + {workspaceStatus.error && ( +
+ {workspaceStatus.error} +
+ )} +
+ ); + }; + return (
@@ -626,13 +756,14 @@ function ProjectDetailPage() { 部署
+
- + 部署记录} key="deployRecords">
{/* 左侧部署记录列表 */}
@@ -671,7 +802,7 @@ function ProjectDetailPage() { {selectedRecord && ( {selectedRecord.branch} · {selectedRecord.env} ·{' '} - {selectedRecord.createdAt} + {formatDateTime(selectedRecord.createdAt)} )}
@@ -707,7 +838,7 @@ function ProjectDetailPage() {
- + 流水线} key="pipeline">
{/* 左侧流水线列表 */}
@@ -823,7 +954,7 @@ function ProjectDetailPage() { {pipeline.steps?.length || 0} 个步骤 - {pipeline.updatedAt} + {formatDateTime(pipeline.updatedAt)}
@@ -913,6 +1044,50 @@ function ProjectDetailPage() {
+ + {/* 项目设置标签页 */} + 项目设置}> +
+ + +
+ + +
+
+ + {/* 工作目录状态 */} + {renderWorkspaceStatus()} +
+
@@ -1053,6 +1228,47 @@ function ProjectDetailPage() { + {/* 编辑项目模态框 */} + setProjectEditModalVisible(false)} + style={{ width: 500 }} + > +
+ + + + + + + + + +
+
+ setDeployModalVisible(false)} diff --git a/apps/web/src/pages/project/detail/service.ts b/apps/web/src/pages/project/detail/service.ts index 02ffc84..554a0d6 100644 --- a/apps/web/src/pages/project/detail/service.ts +++ b/apps/web/src/pages/project/detail/service.ts @@ -192,6 +192,35 @@ class DetailService { }); return data; } + + // 获取项目详情(包含工作目录状态) + async getProjectDetail(id: number) { + const { data } = await net.request>({ + url: `/api/projects/${id}`, + }); + return data; + } + + // 更新项目 + async updateProject( + id: number, + project: Partial<{ name: string; description: string; repository: string }>, + ) { + const { data } = await net.request>({ + url: `/api/projects/${id}`, + method: 'PUT', + data: project, + }); + return data; + } + + // 删除项目 + async deleteProject(id: number) { + await net.request({ + url: `/api/projects/${id}`, + method: 'DELETE', + }); + } } export const detailService = new DetailService(); diff --git a/apps/web/src/pages/project/list/components/CreateProjectModal.tsx b/apps/web/src/pages/project/list/components/CreateProjectModal.tsx index b7dd46a..cfdab7a 100644 --- a/apps/web/src/pages/project/list/components/CreateProjectModal.tsx +++ b/apps/web/src/pages/project/list/components/CreateProjectModal.tsx @@ -1,7 +1,7 @@ import { Button, Form, Input, Message, Modal } from '@arco-design/web-react'; import { useState } from 'react'; import { projectService } from '../service'; -import type { Project } from '../types'; +import type { Project } from '../../types'; interface CreateProjectModalProps { visible: boolean; @@ -97,6 +97,33 @@ function CreateProjectModal({ > + + { + if (!value) { + return cb('工作目录路径不能为空'); + } + if (!value.startsWith('/')) { + return cb('工作目录路径必须是绝对路径(以 / 开头)'); + } + if (value.includes('..') || value.includes('~')) { + return cb('不能包含路径遍历字符(.. 或 ~)'); + } + if (/[<>:"|?*\x00-\x1f]/.test(value)) { + return cb('路径包含非法字符'); + } + cb(); + }, + }, + ]} + > + + ); diff --git a/apps/web/src/pages/project/list/components/ProjectCard.tsx b/apps/web/src/pages/project/list/components/ProjectCard.tsx index 97d0e90..905cc8e 100644 --- a/apps/web/src/pages/project/list/components/ProjectCard.tsx +++ b/apps/web/src/pages/project/list/components/ProjectCard.tsx @@ -1,22 +1,14 @@ import { Avatar, - Button, Card, - Dropdown, - Menu, - Modal, Space, Tag, - Tooltip, Typography, } from '@arco-design/web-react'; import { IconBranch, IconCalendar, IconCloud, - IconDelete, - IconEdit, - IconMore, } from '@arco-design/web-react/icon'; import IconGitea from '@assets/images/gitea.svg?react'; import { useCallback } from 'react'; @@ -27,27 +19,10 @@ const { Text, Paragraph } = Typography; interface ProjectCardProps { project: Project; - onEdit?: (project: Project) => void; - onDelete?: (project: Project) => void; } -function ProjectCard({ project, onEdit, onDelete }: ProjectCardProps) { +function ProjectCard({ project }: ProjectCardProps) { const navigate = useNavigate(); - // 处理删除操作 - const handleDelete = () => { - Modal.confirm({ - title: '确认删除项目', - content: `确定要删除项目 "${project.name}" 吗?此操作不可恢复。`, - okText: '删除', - cancelText: '取消', - okButtonProps: { - status: 'danger', - }, - onOk: () => { - onDelete?.(project); - }, - }); - }; // 获取环境信息 const environments = [ { name: 'staging', color: 'orange', icon: '🚧' }, @@ -109,37 +84,9 @@ function ProjectCard({ project, onEdit, onDelete }: ProjectCardProps) { -
- - 活跃 - - - onEdit?.(project)}> - - 编辑 - - handleDelete()} - className="text-red-500" - > - - 删除 - - - } - position="br" - > -
+ + 活跃 + {/* 项目描述 */} diff --git a/apps/web/src/pages/project/list/index.tsx b/apps/web/src/pages/project/list/index.tsx index bbfd345..1d2692e 100644 --- a/apps/web/src/pages/project/list/index.tsx +++ b/apps/web/src/pages/project/list/index.tsx @@ -4,7 +4,6 @@ import { useState } from 'react'; import { useAsyncEffect } from '../../../hooks/useAsyncEffect'; import type { Project } from '../types'; import CreateProjectModal from './components/CreateProjectModal'; -import EditProjectModal from './components/EditProjectModal'; import ProjectCard from './components/ProjectCard'; import { projectService } from './service'; @@ -12,8 +11,6 @@ const { Text } = Typography; function ProjectPage() { const [projects, setProjects] = useState([]); - const [editModalVisible, setEditModalVisible] = useState(false); - const [editingProject, setEditingProject] = useState(null); const [createModalVisible, setCreateModalVisible] = useState(false); useAsyncEffect(async () => { @@ -21,22 +18,6 @@ function ProjectPage() { setProjects(response.data); }, []); - const handleEditProject = (project: Project) => { - setEditingProject(project); - setEditModalVisible(true); - }; - - const handleEditSuccess = (updatedProject: Project) => { - setProjects((prev) => - prev.map((p) => (p.id === updatedProject.id ? updatedProject : p)), - ); - }; - - const handleEditCancel = () => { - setEditModalVisible(false); - setEditingProject(null); - }; - const handleCreateProject = () => { setCreateModalVisible(true); }; @@ -49,17 +30,6 @@ function ProjectPage() { setCreateModalVisible(false); }; - const handleDeleteProject = async (project: Project) => { - try { - await projectService.delete(project.id); - setProjects((prev) => prev.filter((p) => p.id !== project.id)); - Message.success('项目删除成功'); - } catch (error) { - console.error('删除项目失败:', error); - Message.error('删除项目失败,请稍后重试'); - } - }; - return (
@@ -82,22 +52,11 @@ function ProjectPage() { {projects.map((project) => ( - + ))} - - =18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.9': resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.9': resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.9': resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.9': resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.9': resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.9': resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.9': resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.9': resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.9': resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.9': resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.9': resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.9': resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.9': resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.9': resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.9': resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.9': resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.9': resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.9': resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.9': resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.9': resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.9': resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.9': resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@hapi/bourne@3.0.0': resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} @@ -569,6 +734,9 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prisma/adapter-better-sqlite3@7.0.0': resolution: {integrity: sha512-a0ltJcisHuudnzgD822TwTrvxpQ84ZXUk/3WJLOIGrAM/tysLMb7Q4n0uZeCofvh+UMRoGcT2B6TS3T9/TLMUA==} @@ -627,6 +795,116 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + '@rsbuild/core@1.6.7': resolution: {integrity: sha512-V0INbMrT/LwyhzKmvpupe2oSvPFWaivz7sdriFRp381BJvD0d2pYcq9iRW91bxgMRX78MgTzFYAu868hMAzoSw==} engines: {node: '>=18.12.0'} @@ -907,6 +1185,9 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -916,6 +1197,12 @@ packages: '@types/cookies@0.9.1': resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.0.7': resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} @@ -975,6 +1262,40 @@ packages: '@types/serve-static@1.15.8': resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} + + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} + + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} + + '@vitest/ui@4.0.16': + resolution: {integrity: sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==} + peerDependencies: + vitest: 4.0.16 + + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -982,6 +1303,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1066,6 +1391,10 @@ packages: caniuse-lite@1.0.30001737: resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chevrotain@10.5.0: resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} @@ -1189,8 +1518,8 @@ packages: dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dayjs@1.11.15: - resolution: {integrity: sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} @@ -1329,6 +1658,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -1342,6 +1674,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1349,10 +1686,17 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -1370,9 +1714,24 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + focus-lock@1.3.6: resolution: {integrity: sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==} engines: {node: '>=10'} @@ -1677,6 +2036,9 @@ packages: magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1738,6 +2100,10 @@ packages: engines: {node: '>=10'} hasBin: true + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1793,6 +2159,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -1840,6 +2209,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} @@ -2003,6 +2376,11 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2067,6 +2445,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -2083,6 +2464,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -2101,6 +2486,9 @@ packages: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -2116,6 +2504,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -2159,14 +2550,29 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2247,11 +2653,90 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2314,7 +2799,7 @@ snapshots: b-tween: 0.3.3 b-validate: 1.5.3 compute-scroll-into-view: 1.0.20 - dayjs: 1.11.15 + dayjs: 1.11.19 lodash: 4.17.21 number-precision: 1.6.0 react: 19.2.0 @@ -2539,81 +3024,159 @@ snapshots: '@esbuild/aix-ppc64@0.25.9': optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + '@esbuild/android-arm64@0.25.9': optional: true + '@esbuild/android-arm64@0.27.2': + optional: true + '@esbuild/android-arm@0.25.9': optional: true + '@esbuild/android-arm@0.27.2': + optional: true + '@esbuild/android-x64@0.25.9': optional: true + '@esbuild/android-x64@0.27.2': + optional: true + '@esbuild/darwin-arm64@0.25.9': optional: true + '@esbuild/darwin-arm64@0.27.2': + optional: true + '@esbuild/darwin-x64@0.25.9': optional: true + '@esbuild/darwin-x64@0.27.2': + optional: true + '@esbuild/freebsd-arm64@0.25.9': optional: true + '@esbuild/freebsd-arm64@0.27.2': + optional: true + '@esbuild/freebsd-x64@0.25.9': optional: true + '@esbuild/freebsd-x64@0.27.2': + optional: true + '@esbuild/linux-arm64@0.25.9': optional: true + '@esbuild/linux-arm64@0.27.2': + optional: true + '@esbuild/linux-arm@0.25.9': optional: true + '@esbuild/linux-arm@0.27.2': + optional: true + '@esbuild/linux-ia32@0.25.9': optional: true + '@esbuild/linux-ia32@0.27.2': + optional: true + '@esbuild/linux-loong64@0.25.9': optional: true + '@esbuild/linux-loong64@0.27.2': + optional: true + '@esbuild/linux-mips64el@0.25.9': optional: true + '@esbuild/linux-mips64el@0.27.2': + optional: true + '@esbuild/linux-ppc64@0.25.9': optional: true + '@esbuild/linux-ppc64@0.27.2': + optional: true + '@esbuild/linux-riscv64@0.25.9': optional: true + '@esbuild/linux-riscv64@0.27.2': + optional: true + '@esbuild/linux-s390x@0.25.9': optional: true + '@esbuild/linux-s390x@0.27.2': + optional: true + '@esbuild/linux-x64@0.25.9': optional: true + '@esbuild/linux-x64@0.27.2': + optional: true + '@esbuild/netbsd-arm64@0.25.9': optional: true + '@esbuild/netbsd-arm64@0.27.2': + optional: true + '@esbuild/netbsd-x64@0.25.9': optional: true + '@esbuild/netbsd-x64@0.27.2': + optional: true + '@esbuild/openbsd-arm64@0.25.9': optional: true + '@esbuild/openbsd-arm64@0.27.2': + optional: true + '@esbuild/openbsd-x64@0.25.9': optional: true + '@esbuild/openbsd-x64@0.27.2': + optional: true + '@esbuild/openharmony-arm64@0.25.9': optional: true + '@esbuild/openharmony-arm64@0.27.2': + optional: true + '@esbuild/sunos-x64@0.25.9': optional: true + '@esbuild/sunos-x64@0.27.2': + optional: true + '@esbuild/win32-arm64@0.25.9': optional: true + '@esbuild/win32-arm64@0.27.2': + optional: true + '@esbuild/win32-ia32@0.25.9': optional: true + '@esbuild/win32-ia32@0.27.2': + optional: true + '@esbuild/win32-x64@0.25.9': optional: true + '@esbuild/win32-x64@0.27.2': + optional: true + '@hapi/bourne@3.0.0': {} '@hono/node-server@1.14.2(hono@4.7.10)': @@ -2693,6 +3256,8 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@polka/url@1.0.0-next.29': {} + '@prisma/adapter-better-sqlite3@7.0.0': dependencies: '@prisma/driver-adapter-utils': 7.0.0 @@ -2777,6 +3342,72 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + '@rsbuild/core@1.6.7': dependencies: '@rspack/core': 1.6.4(@swc/helpers@0.5.17) @@ -3051,6 +3682,11 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 24.3.0 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: '@types/node': 24.3.0 @@ -3064,6 +3700,10 @@ snapshots: '@types/keygrip': 1.0.6 '@types/node': 24.3.0 + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.0.7': dependencies: '@types/node': 24.3.0 @@ -3142,6 +3782,56 @@ snapshots: '@types/node': 24.3.0 '@types/send': 0.17.5 + '@vitest/expect@4.0.16': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.0(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5) + + '@vitest/pretty-format@4.0.16': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.16': + dependencies: + '@vitest/utils': 4.0.16 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.16': {} + + '@vitest/ui@4.0.16(vitest@4.0.16)': + dependencies: + '@vitest/utils': 4.0.16 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vitest: 4.0.16(@types/node@24.3.0)(@vitest/ui@4.0.16)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5) + + '@vitest/utils@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + tinyrainbow: 3.0.3 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -3149,6 +3839,8 @@ snapshots: argparse@2.0.1: {} + assertion-error@2.0.1: {} + asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -3242,6 +3934,8 @@ snapshots: caniuse-lite@1.0.30001737: {} + chai@6.2.2: {} + chevrotain@10.5.0: dependencies: '@chevrotain/cst-dts-gen': 10.5.0 @@ -3369,7 +4063,7 @@ snapshots: dateformat@4.6.3: {} - dayjs@1.11.15: {} + dayjs@1.11.19: {} debug@4.4.1: dependencies: @@ -3481,6 +4175,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -3521,12 +4217,47 @@ snapshots: '@esbuild/win32-ia32': 0.25.9 '@esbuild/win32-x64': 0.25.9 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-html@1.0.3: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + expand-template@2.0.3: {} + expect-type@1.3.0: {} + exsolve@1.0.8: {} fast-check@3.23.2: @@ -3539,8 +4270,16 @@ snapshots: fast-safe-stringify@2.1.1: {} + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.8.2: {} + file-uri-to-path@1.0.0: {} + flatted@3.3.3: {} + focus-lock@1.3.6: dependencies: tslib: 2.8.1 @@ -3818,6 +4557,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} mdn-data@2.0.28: {} @@ -3858,6 +4601,8 @@ snapshots: mkdirp@3.0.1: {} + mrmime@2.0.1: {} + ms@2.1.3: {} mysql2@3.15.3: @@ -3913,6 +4658,8 @@ snapshots: object-inspect@1.13.4: {} + obug@2.1.1: {} + ohash@2.0.11: {} on-exit-leak-free@2.1.2: {} @@ -3950,6 +4697,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@4.0.3: {} + pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 @@ -4153,6 +4902,34 @@ snapshots: retry@0.12.0: {} + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + safe-buffer@5.2.1: {} safe-stable-stringify@2.5.0: {} @@ -4213,6 +4990,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -4229,6 +5008,12 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -4244,6 +5029,8 @@ snapshots: sqlstring@2.3.3: {} + stackback@0.0.2: {} + stackframe@1.3.4: {} statuses@1.5.0: {} @@ -4252,6 +5039,8 @@ snapshots: statuses@2.0.2: {} + std-env@3.10.0: {} + std-env@3.9.0: {} string_decoder@1.3.0: @@ -4306,10 +5095,21 @@ snapshots: dependencies: real-require: 0.2.0 + tinybench@2.9.0: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + toidentifier@1.0.1: {} + totalist@3.0.1: {} + tslib@2.8.1: {} tsscmp@1.0.6: {} @@ -4373,10 +5173,68 @@ snapshots: vary@1.1.2: {} + vite@7.3.0(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.3.0 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.1 + tsx: 4.20.5 + + vitest@4.0.16(@types/node@24.3.0)(@vitest/ui@4.0.16)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.0(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.3.0 + '@vitest/ui': 4.0.16(vitest@4.0.16) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrappy@1.0.2: {} yallist@3.1.1: {} -- 2.49.1