prime-dev-cli 2.0.8 → 2.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/projects.json +80 -24
- package/dist/server/.next/BUILD_ID +1 -1
- package/dist/server/.next/app-build-manifest.json +3 -3
- package/dist/server/.next/build-manifest.json +2 -2
- package/dist/server/.next/cache/.rscinfo +1 -1
- package/dist/server/.next/cache/.tsbuildinfo +1 -1
- package/dist/server/.next/cache/eslint/.cache_1qa5vxt +1 -1
- package/dist/server/.next/fallback-build-manifest.json +2 -2
- package/dist/server/.next/next-minimal-server.js.nft.json +1 -1
- package/dist/server/.next/next-server.js.nft.json +1 -1
- package/dist/server/.next/prerender-manifest.json +9 -9
- package/dist/server/.next/server/app/_not-found.html +1 -1
- package/dist/server/.next/server/app/_not-found.rsc +1 -1
- package/dist/server/.next/server/app/api/admin-git-batch-create-branch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/admin-git-batch-delete-branch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/admin-git-batch-merge/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/admin-git-batch-probe-remote-branch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/batch-switch-branch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/batch-switch-submodule-branch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/branch/switch/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/branches/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/check-workspace/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/gwh-git-flow/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/initialize-workspace/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/microapp-proxy-config/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/open-project/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/project-config/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/retry-project/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/submodule-info/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/uat-git-flow/route.js +2 -2
- package/dist/server/.next/server/app/api/uat-git-flow/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/api/uat-git-flow/step/route.js +2 -2
- package/dist/server/.next/server/app/api/uat-git-flow/step/route.js.nft.json +1 -1
- package/dist/server/.next/server/app/configuration/page/app-build-manifest.json +1 -1
- package/dist/server/.next/server/app/configuration/page_client-reference-manifest.js +1 -1
- package/dist/server/.next/server/app/configuration.html +1 -1
- package/dist/server/.next/server/app/configuration.rsc +2 -2
- package/dist/server/.next/server/app/index.html +1 -1
- package/dist/server/.next/server/app/index.rsc +1 -1
- package/dist/server/.next/server/app/initialization/page/app-build-manifest.json +2 -2
- package/dist/server/.next/server/app/initialization/page.js.nft.json +1 -1
- package/dist/server/.next/server/app/initialization/page_client-reference-manifest.js +1 -1
- package/dist/server/.next/server/app/initialization.html +1 -1
- package/dist/server/.next/server/app/initialization.rsc +2 -2
- package/dist/server/.next/server/chunks/_24087f66._.js +1 -1
- package/dist/server/.next/server/chunks/_24087f66._.js.map +1 -1
- package/dist/server/.next/server/chunks/_61c263b3._.js +1 -1
- package/dist/server/.next/server/chunks/_61c263b3._.js.map +1 -1
- package/dist/server/.next/server/chunks/packages_server_42b02aec._.js +3 -0
- package/dist/server/.next/server/chunks/packages_server_42b02aec._.js.map +1 -0
- package/dist/server/.next/server/chunks/packages_server_854cbe00._.js +3 -0
- package/dist/server/.next/server/chunks/packages_server_854cbe00._.js.map +1 -0
- package/dist/server/.next/server/chunks/ssr/_283bdc73._.js +1 -1
- package/dist/server/.next/server/chunks/ssr/_283bdc73._.js.map +1 -1
- package/dist/server/.next/server/chunks/ssr/_c7905e3c._.js +1 -1
- package/dist/server/.next/server/chunks/ssr/_c7905e3c._.js.map +1 -1
- package/dist/server/.next/server/chunks/ssr/packages_server_src_622c27d6._.js +3 -0
- package/dist/server/.next/server/chunks/ssr/packages_server_src_622c27d6._.js.map +1 -0
- package/dist/server/.next/server/pages/404.html +1 -1
- package/dist/server/.next/server/pages/500.html +1 -1
- package/dist/server/.next/server/server-reference-manifest.js +1 -1
- package/dist/server/.next/server/server-reference-manifest.json +1 -1
- package/dist/server/.next/static/chunks/16b23f7570a551e1.js +3 -0
- package/dist/server/.next/static/chunks/{45543bd9f83f509f.js → 4e8de43e1f6c5336.js} +2 -2
- package/dist/server/.next/static/chunks/{8e1056102ab0be45.js.map → 84a2f16086c26ab1.js.map} +1 -1
- package/dist/server/.next/static/chunks/b109f2fb91f36e31.js.map +1 -0
- package/dist/server/.next/static/chunks/{4c54a6f1b63a5c72.js → f3137530db91c282.js} +2 -2
- package/dist/server/.next/static/chunks/{0e959ec3ce9fc799.js.map → fa09d2a29cc42906.js.map} +1 -1
- package/dist/server/.next/trace +2 -2
- package/package.json +1 -1
- package/plugin-marketplace/plugins/prime-dev-cli/skills/prime-workspace/SKILL.md +112 -22
- package/skills/prime-workspace/README.md +20 -2
- package/skills/prime-workspace/SKILL.md +510 -214
- package/skills/prime-workspace/references/api-contracts.md +125 -0
- package/skills/prime-workspace/references/microfrontend-proxy.md +155 -0
- package/dist/server/.next/server/chunks/packages_server_ccc16996._.js +0 -3
- package/dist/server/.next/server/chunks/packages_server_ccc16996._.js.map +0 -1
- package/dist/server/.next/server/chunks/packages_server_d1209331._.js +0 -3
- package/dist/server/.next/server/chunks/packages_server_d1209331._.js.map +0 -1
- package/dist/server/.next/server/chunks/ssr/packages_server_src_65e3de99._.js +0 -3
- package/dist/server/.next/server/chunks/ssr/packages_server_src_65e3de99._.js.map +0 -1
- package/dist/server/.next/static/chunks/4608598ca6cc0991.js.map +0 -1
- package/dist/server/.next/static/chunks/fce82d5fa17f0367.js +0 -3
- /package/dist/server/.next/static/{TtpaZZwrnzkXloBpwEr24 → zjt6jXsNQxDdU-RHF8TC7}/_buildManifest.js +0 -0
- /package/dist/server/.next/static/{TtpaZZwrnzkXloBpwEr24 → zjt6jXsNQxDdU-RHF8TC7}/_clientMiddlewareManifest.json +0 -0
- /package/dist/server/.next/static/{TtpaZZwrnzkXloBpwEr24 → zjt6jXsNQxDdU-RHF8TC7}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# prime-dev-cli 工作台 API 契约
|
|
2
|
+
|
|
3
|
+
`packages/server` 在 `http://localhost:52741` 暴露的全部路由。源文件在 `packages/server/src/app/api/<path>/route.ts`,下表路径均相对 `/api`。
|
|
4
|
+
|
|
5
|
+
**通用约定**
|
|
6
|
+
|
|
7
|
+
- 多数路由返回 `{ success: boolean }`,失败分支带 `error`,成功分支常带 `data` / `message`。
|
|
8
|
+
- 批量 git 路由统一返回 `operationLogs: string[]`、`feedbacks: string[]`、`projectResults: { projectName, success, reason?, rollbackActions? }[]`。
|
|
9
|
+
- 三个端点是 **SSE**(返回 `text/event-stream`,不是 JSON):`/initialize-workspace`、`/process/logs/stream`、`/microapp-proxy-change-stream`。
|
|
10
|
+
- 只有 `/all-projects-envs` 和 `/microapp-proxy-change-stream` 导出 `OPTIONS`(带 CORS)。
|
|
11
|
+
- 所有 git 执行类路由都在**临时仓库**里操作,`finally` 中清理,不改动用户的工作区克隆。
|
|
12
|
+
|
|
13
|
+
## 工作区 / 项目
|
|
14
|
+
|
|
15
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
16
|
+
| --- | --- | --- | --- | --- |
|
|
17
|
+
| `/all-projects-envs` | GET, OPTIONS | 无 | `success, data[], message`;项含 `projectName, envName, host, repo, apiProxies, subApps, microPath` | 汇总所有项目×环境配置,供跨项目查询;带完整 CORS 头 |
|
|
18
|
+
| `/check-workspace` | GET | 无 | `success, workspaceExists, workspacePath, projects[{name,status,message,needsCleanup}], summary{total,existing,pending,incomplete}` | 检查工作区目录与各项目克隆完整度;`status` 取值 `existing/pending/incomplete` |
|
|
19
|
+
| `/initialize-workspace` | GET (SSE) | 无 | SSE 帧 `{type, message, progress, ...}` | 创建工作区 → 并发克隆所有项目 → 生成 env 文件与 mock 目录 |
|
|
20
|
+
| `/cleanup-project` | POST | body `projectName` | `success, message` | 递归删除工作区内的项目目录 |
|
|
21
|
+
| `/retry-project` | POST | body `projectName` | `success, projectName, status, message, error, progress, duration` | 重试单个项目克隆 |
|
|
22
|
+
| `/open-project` | POST | body `projectName`, `editor?`(`cursor`\|`vscode`\|`webstorm`,默认 cursor) | `success, projectPath, editorUrl, cursorUrl` | 校验项目名(防路径遍历)后返回路径与编辑器 URL,由前端打开 |
|
|
23
|
+
| `/project-config` | GET, PUT | GET query `project`;PUT body `projectName` + 二选一:(`envName`, `apiProxies[]`) 或 `projectConfig{mockingIntercept?, mockOpen?}` | GET `data{projectName, repo, mockingIntercept, mockOpen, envs}`;PUT `data{projectName, envName, apiProxies, updatedEnvFile?}` 或 `data{projectName, mockingIntercept, mockOpen}` | 读项目配置;更新环境级代理(校验后重写 env 文件)或项目级 mock 开关 |
|
|
24
|
+
| `/workspace/create` | POST | body `projects[]`, `editor?` | `success, message, workspaceFile, isExisting, editor` | 生成 `~/.prime-workspaces/<排序项目名>.code-workspace` 并调起编辑器;已存在则复用 |
|
|
25
|
+
|
|
26
|
+
### `/initialize-workspace` 的 SSE 帧类型
|
|
27
|
+
|
|
28
|
+
`initialization`、`workspace_created`、`projects_loaded`、`project_existing`、`projects_filtered`、`project_start`、`project_progress`、`project_error`、`command_log`、`project_success`、`project_failed`、`project_env_creation`、`project_env_success`、`project_env_failed`、`project_mock_creation`、`project_mock_success`、`project_mock_failed`、`completion`、`error`。
|
|
29
|
+
|
|
30
|
+
`completion` 带 `result{success, totalProjects, successCount, failedCount, failedProjects, workspacePath, duration}`。单条 `command_log` 超过 8KB 会截断为 `[...日志内容已截断...]`。发完 `completion` 后关闭。
|
|
31
|
+
|
|
32
|
+
### `/project-config` PUT 的校验规则
|
|
33
|
+
|
|
34
|
+
`apiProxies` 条目结构为 `{ prefix, currentProxy, proxyEnv: Record<string,string>, proxyKey?, rewrite?: {mode, targetPrefix?} }`。校验失败返回 400 且**不保存配置**:
|
|
35
|
+
|
|
36
|
+
- `prefix` 必须以 `/` 开头;同一环境内重复 → 400 且 `error` 含 `prefix`
|
|
37
|
+
- `currentProxy: "custom"` 时 `proxyEnv.custom` 不能为空白 → 400 且 `error` 含 `custom`
|
|
38
|
+
- `rewrite.mode` 仅允许 `remove` / `replace`;`replace` 必须带以 `/` 开头的 `targetPrefix` → 否则 400 且 `error` 含 `rewrite.mode` / `targetPrefix`
|
|
39
|
+
|
|
40
|
+
成功时调用一次 `saveProjectsConfig`,并以 `(projectName, envName, config, workspaceRoot)` 调用 `updateProjectEnvFile`。
|
|
41
|
+
|
|
42
|
+
## 进程启停
|
|
43
|
+
|
|
44
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
45
|
+
| --- | --- | --- | --- | --- |
|
|
46
|
+
| `/process/start` | POST, DELETE | POST body `projectName, envName, startCommand`;DELETE query `projectName, envName` | POST `data{pid, startTime, port}`;DELETE `success, message` | POST 在工作区内 spawn(`shell: true`,强制 `NODE_ENV=development`),日志落盘并写 `~/.prime-processes.json`。DELETE 按 PID `SIGTERM`→`SIGKILL`,再按端口兜底 kill(`lsof`/`netstat`),并清日志 |
|
|
47
|
+
| `/process/status` | GET | query `projectName?, envName?`(都传=查单个,否则查全部) | 单个 `data{running, pid, startTime, port, portInUse}`;全部 `data[]`(项含 `key, projectName, envName, running, pid, startTime, port, portInUse`) | 读 `~/.prime-processes.json` 并探测 PID/端口;查全部时顺带探测配置里所有环境端口,把手工启动的项目也标为 `running`(`pid: 0`) |
|
|
48
|
+
| `/process/logs` | GET, DELETE | query `projectName, envName, tail?`(默认 100) | GET `data{logs[], totalLines, tailCount}`;DELETE `success, message` | GET 读日志文件最后 N 行(文件不存在返回空数组);DELETE 清除该进程全部日志 |
|
|
49
|
+
| `/process/logs/stream` | GET (SSE) | query `projectName, envName` | SSE 帧 `{type: 'log'\|'init'\|'info'\|'warn'\|'end', content?/message?}`,并周期发 `: ping` | 先推最后 100 行,再每 2 秒增量 tail;处理日志轮转、单块 100KB 上限、文件被删(推 `end`) |
|
|
50
|
+
|
|
51
|
+
> `/api/process/status/stream` 目录存在但**没有 `route.ts`**,进程状态走 5 秒轮询而非 SSE。
|
|
52
|
+
|
|
53
|
+
## 分支
|
|
54
|
+
|
|
55
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
56
|
+
| --- | --- | --- | --- | --- |
|
|
57
|
+
| `/branches` | GET | query `project`, `subPath?`(如 `submodules/common`) | `data{exists, subPath, currentBranch, branches[]}` | 列远程分支(`ls-remote --heads origin`),当前分支置顶;子仓库不存在/非 git/未被 160000 跟踪时 `exists: false` |
|
|
58
|
+
| `/current-branch` | GET | query `project` | `data{currentBranch, lastCommit{hash,message,author,date}, status{ahead,behind,modified,staged,untracked}}` | 当前分支 + 最近提交(8 位 hash)+ 工作区状态统计 |
|
|
59
|
+
| `/branch/switch` | POST | body `projectName, branchName, subPath?` | `data{currentBranch, changed}, message` | 本地存在则 `checkout`,否则 `checkout -b <branch> origin/<branch>` |
|
|
60
|
+
| `/submodule-info` | GET | query `project`, `subPath?` | `data{exists, subPath, currentBranch, detached, commitHash}` | 子仓库存在性与分支;detached HEAD 时给 `detached: true, commitHash` |
|
|
61
|
+
| `/batch-list-branches` | POST | body `projects[]` | `branches[]`(去重排序), `failedProjects[{projectName, reason}]` | 并发 `ls-remote` 聚合多项目远程分支并去重 |
|
|
62
|
+
| `/batch-list-submodule-branches` | POST | body `projects[]` | `branches[], failedProjects[]` | 聚合各项目 `submodules/common` 分支;仅统计被 160000 跟踪的项目,未跟踪/无子仓库静默跳过 |
|
|
63
|
+
| `/batch-switch-branch` | POST | body `projects[], branchName, dryRun?` | `success, operationLogs[], feedbacks[], projectResults[]` | 批量切换项目分支;`dryRun` 只记命令 |
|
|
64
|
+
| `/batch-switch-submodule-branch` | POST | body `projects[], branchName, dryRun?` | 同上 | 批量切换子仓库分支;未跟踪/无子仓库记为失败 |
|
|
65
|
+
| `/admin-git-batch-create-branch` | POST | body `projects[], createBranch, baseBranch, dryRun?` | 同上(含 `rollbackActions`) | `fetch` → 校验 `origin/baseBranch` →(远端已有则复用)→ `checkout -B createBranch origin/baseBranch` → `push -u` |
|
|
66
|
+
| `/admin-git-batch-delete-branch` | POST | body `projects[], branchName, dryRun?` | 同上 | 批量 `push origin --delete`;远端不存在记失败 |
|
|
67
|
+
| `/admin-git-batch-merge` | POST | body `projects[], targetBranch, sourceBranch, dryRun?` | 同上 | `checkout targetBranch` → 要求工作区干净 → `merge --no-ff origin/sourceBranch` → push;冲突则 `merge --abort` 并回传 `rollbackActions` |
|
|
68
|
+
| `/admin-git-batch-probe-remote-branch` | POST | body `projects[], branchName, dryRun?` | `success, operationLogs[], feedbacks[], projectResults[]{projectName, success, reason?, exists?}` | 探测远端分支是否存在;**不存在也算 `success: true`**,靠 `exists` 区分 |
|
|
69
|
+
| `/uat-git-flow` | POST | body `projects[], createBranch, baseBranch`(必须 `main`), `mergeBranch, runId, dryRun?` | `success, operationLogs[], feedbacks[], projectResults[]`(含 `rollbackActions`) | 一体化 UAT 流:建/切 `createBranch` → `merge origin/mergeBranch` → push → 再把 `createBranch` 合并到 `main` 与 `fix/main`。支持 `runId` 中断回滚 |
|
|
70
|
+
| `/uat-git-flow/step` | POST | body `action`(`start`\|`execute`\|`abort`);`start` 另需 `projects[], createBranch, baseBranch, mergeBranch, runId`;`execute` 另需 `runId, stepId`;`abort` 另需 `runId` | `success, runId, nextStep{stepId, projectName, description, command}\|null, operationLogs[], feedbacks[], isComplete, aborted?` | 分步执行(每步一条 git 命令,服务端维护 run 状态);`stepId` 不匹配 409,run 不存在 404 |
|
|
71
|
+
| `/uat-git-flow/abort` | POST | body `runId` | `success, message` | 标记 UAT run 中断 |
|
|
72
|
+
| `/gwh-git-flow` | POST | body `projects[], createBranch, baseBranch`(必须 `GWH`), `mergeBranch, runId, dryRun?` | 同 `/uat-git-flow` | 武汉协和版本流:固定从 `GWH` 创建,最终合并到 `GWH` 与 `fix/GWH` |
|
|
73
|
+
| `/gwh-git-flow/abort` | POST | body `runId` | `success, message` | 标记 GWH run 中断 |
|
|
74
|
+
|
|
75
|
+
**分支名校验**(`admin-git-batch-*` 与 probe):不含空格、不以 `-` 开头。违反 → 400 且 `feedbacks` 含 `分支名称不合法,请避免空格或以 - 开头。`;缺参数 → 400 且 `feedbacks` 含 `缺少必要参数,请填写完整后重试。`
|
|
76
|
+
|
|
77
|
+
**成功判定不一致,注意**:`batch-switch-branch`、`batch-switch-submodule-branch`、`gwh-git-flow` 用 `every`/`some` 全量判定;`admin-git-batch-create/delete/merge` 看 `feedbacks` 是否含「失败」;probe 用 `projectResults.every(success)`,因此「远端不存在」这类业务性否定**不算失败**。
|
|
78
|
+
|
|
79
|
+
## Mock
|
|
80
|
+
|
|
81
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
82
|
+
| --- | --- | --- | --- | --- |
|
|
83
|
+
| `/check-mock-intercept` | GET | query `project` | `data{mockingIntercept, mockOpen}` | 读项目级两个 mock 开关。**主要消费方是目标项目依赖的 `@prime-lifescience/dev-server`**,它在每次代理请求前/响应后回查本接口 |
|
|
84
|
+
| `/mock-file` | GET, POST, DELETE | GET/DELETE query `project, apiName, method, siteName`;POST body `projectName, apiName, method, siteName, content` | GET `data{projectName, mockFiles[{apiName, methods[{method, setting, files[{siteName,fileName,filePath}]}]}]}`;POST `data{..., settingCreated, createdAt}`;DELETE `data{..., deletedFile}` | 列/建/删场景文件;POST 自动建目录与 `setting.cjs`;DELETE 禁止删最后一个场景并自动迁移 `currentSite` |
|
|
85
|
+
| `/mock-file-content` | GET, PUT | GET query `project, apiName, method, siteName`;PUT body `projectName, apiName, method, siteName, content` | GET `data{projectName, apiName, method, siteName, content, filePath}`;PUT `data{..., filePath, updatedAt}` | 读/写单个场景 `.cjs` 内容(文件不存在 404) |
|
|
86
|
+
| `/mock-setting` | PUT | body `projectName, apiName, method, currentSite` | `data{projectName, apiName, method, currentSite, filePath, updatedAt}` | 切换接口当前场景 |
|
|
87
|
+
| `/mock-open-status` | PUT | body `projectName, apiName, method, siteName, isOpen`(boolean) | `data{projectName, apiName, method, siteName, isOpen, openSites[], filePath, updatedAt}` | 把场景加入/移出 `setting.cjs` 的 `open` |
|
|
88
|
+
| `/get-mock-data` | GET | query `project, url, method` | `data{projectName, apiName, method, currentSite, mockData, filePath}` | 把 url 转成 `.mock/<path>` 目录名,读 `setting.cjs` 当前场景,require 场景 `.cjs` 返回数据(require mockjs;需 `mockOpen` 且场景在 `open`) |
|
|
89
|
+
| `/open-all-mock` | POST | body `projectName` | `success, message, data{projectName, timestamp}` | 为所有 `.cjs` 场景补建/更新 `setting.cjs`,并把场景并入 `open` |
|
|
90
|
+
| `/cleanup-mock` | POST | body `projectName` | `success, message, data{projectName, timestamp}` | 递归清空每个 `setting.cjs` 的 `open`(保留 `currentSite`) |
|
|
91
|
+
| `/delete-mock-api` | DELETE | query `project, apiName` | `success, message, data{projectName, apiName, deletedPath}` | 删除某接口整个目录;`.mock` 空了则一并删除 |
|
|
92
|
+
|
|
93
|
+
### Mock 开关的消费方:`@prime-lifescience/dev-server`
|
|
94
|
+
|
|
95
|
+
工作台本身不拦截请求。目标项目 dev server 依赖的 `@prime-lifescience/dev-server` 把 `http://localhost:52741` **硬编码**为服务地址,在代理链路上实时回查工作台。两个开关方向相反:
|
|
96
|
+
|
|
97
|
+
| 时机 | 读取 | 条件 | 行为 | 响应头信号 |
|
|
98
|
+
| --- | --- | --- | --- | --- |
|
|
99
|
+
| `onProxyReq`(请求发出前) | `GET /api/check-mock-intercept?project=` | `mockOpen` | 再调 `GET /api/get-mock-data`,成功则直接返回 Mock 数据并**中断代理** | `X-FROM-MOCK: true`、`X-Real-Origin: http://localhost:52741` |
|
|
100
|
+
| `onProxyRes`(响应回来时) | 同上 | `mockingIntercept` | 放行真实响应,并将其响应体转成 `module.exports = <JSON>;`,`POST /api/mock-file` 落盘成场景,场景名固定「默认场景」 | `X-MOCK-FILE-CREATED: true` / `X-MOCK-FILE-CREATE-FAIL: true` |
|
|
101
|
+
|
|
102
|
+
- `apiName` 推导:请求路径去掉 query、去掉开头 `/`、`/` 替换为 `_`,空则为 `root`。
|
|
103
|
+
- 只录制 `application/json`、`text/*`、`application/javascript|xml|x-www-form-urlencoded` 响应;带 `content-disposition: attachment` 的跳过。
|
|
104
|
+
- `text/event-stream` 两类拦截都跳过,原样透传。
|
|
105
|
+
- 同一路径重复请求会反复覆盖同名「默认场景」。
|
|
106
|
+
- 该 dev server 也自行读取 env 文件里的 `PRIME_PROXY_PREFIX_MAP` / `PRIME_PROXY_REWRITE_MAP` 建立代理,不经过工作台转发。
|
|
107
|
+
- **工作台未启动时,`check-mock-intercept` 请求失败,Mock 与代理会一起静默失效,不报错。**
|
|
108
|
+
|
|
109
|
+
## 代理与微应用
|
|
110
|
+
|
|
111
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
112
|
+
| --- | --- | --- | --- | --- |
|
|
113
|
+
| `/microapp-proxy-config` | PUT | body `envName, projectName, selectedItems[]` | `success, message, data{projectName, envName, subApps, currentHost, updatedProjects, envFileUpdateFailures}` | 更新环境的 `subApps`;**对每个被选中 env 名遍历所有项目找同名 env**,把其 `/api` 前缀代理设为 `custom` 指向当前宿主 host;有变化则 SSE 广播并重写受影响 env 文件 |
|
|
114
|
+
| `/microapp-proxy-change-stream` | GET, OPTIONS | query `env`, `clientId?` | SSE 帧 `{type: 'connection-established'\|'microapp-proxy-config-change', envName, clientId, timestamp, message, changes{before,after,added,removed}, projectName}` | 按环境订阅微应用代理变更;环境不存在 404;带 CORS |
|
|
115
|
+
|
|
116
|
+
`selectedItems` 是**环境名字符串数组**,写进 `envs[envName].subApps`。注意 `microapp-proxy-config` 只改 `/api` 这一个前缀,但**同名 env 若存在于多个项目会全部被改**。
|
|
117
|
+
|
|
118
|
+
这两个接口是微前端代理的服务端侧。客户端侧是浏览器插件(硬编码 `http://localhost:52741`):它 `fetch /api/all-projects-envs` 拿全量配置存进 `localStorage` 的 `microapp-subapps-details`,劫持 `customElements.define` 改写 `<micro-app>` 的 `url`,并订阅上面的 SSE 流(收到变更后 3 秒整页刷新)。完整实现见 `microfrontend-proxy.md`。
|
|
119
|
+
|
|
120
|
+
## 运维
|
|
121
|
+
|
|
122
|
+
| 路径 | 方法 | 参数 | 响应要点 | 说明 |
|
|
123
|
+
| --- | --- | --- | --- | --- |
|
|
124
|
+
| `/admin-status` | GET | 无 | `success, isAdmin` | 判断 `PRIME_ADMIN === '1' \| 'true'` |
|
|
125
|
+
| `/memory-status` | GET, POST | 无 | GET `data{memory{rss,heapUsed,heapTotal,heapUsagePercent,external,systemTotal?,note?}, logs{totalSize,fileCount}, connections, timestamp}`;POST `success, message` | 内存 / 日志目录 / SSE 连接监控;POST 手动触发内存检查清理 |
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# 微前端代理(MicroApp 代理)实现
|
|
2
|
+
|
|
3
|
+
微前端代理解决的问题:**让页面里 `<micro-app>` 指向的远端子应用,改为指向你本地的 dev server**。
|
|
4
|
+
|
|
5
|
+
参与方有三个,缺一不可:
|
|
6
|
+
|
|
7
|
+
| 角色 | 位置 | 职责 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 工作台 | `packages/server` | 保存 `subApps` 配置、提供 `/api/all-projects-envs`、广播变更 SSE |
|
|
10
|
+
| 浏览器插件 | `packages/prime-dev-browser-plugin` | 在主世界改写 `<micro-app>` 的 `url` |
|
|
11
|
+
| 目标项目 dev server | 宿主页面本身 | 无(它只是被改写的一侧) |
|
|
12
|
+
|
|
13
|
+
> 注意:微前端代理与 `@prime-lifescience/dev-server` 的接口代理是**两条独立链路**。前者改的是页面里 `<micro-app>` 元素的 `url`(浏览器插件做),后者改的是 HTTP 请求的去向(dev server 做)。
|
|
14
|
+
|
|
15
|
+
## 一、配置从哪来
|
|
16
|
+
|
|
17
|
+
### 服务端:`PUT /api/microapp-proxy-config`
|
|
18
|
+
|
|
19
|
+
body `{envName, projectName, selectedItems[]}`,`selectedItems` 是**环境名字符串数组**。
|
|
20
|
+
|
|
21
|
+
它做三件事:
|
|
22
|
+
|
|
23
|
+
1. 把 `selectedItems` 写入 `projectsConfig[projectName].envs[envName].subApps`。
|
|
24
|
+
2. 对每个被选中的环境名,**遍历所有项目**找出同名 env,把它的 `/api` 前缀代理改成 `currentProxy: 'custom'`、`proxyEnv.custom = <当前宿主 host>`。
|
|
25
|
+
- 只改 `/api` 这一个前缀,其它前缀不动。
|
|
26
|
+
- **同名 env 若存在于多个项目,全部都会被改**——这是最容易踩的坑。
|
|
27
|
+
3. 有变化时 `broadcastToEnv(envName, ...)` 推 SSE,然后对受影响项目逐个 `updateProjectEnvFile`。
|
|
28
|
+
|
|
29
|
+
响应:`data{projectName, envName, subApps, currentHost, updatedProjects, envFileUpdateFailures}`。
|
|
30
|
+
|
|
31
|
+
### 插件:拉全量配置存 localStorage
|
|
32
|
+
|
|
33
|
+
主世界脚本 `fetch('http://localhost:52741/api/all-projects-envs')`(该接口带 `Access-Control-Allow-Origin: *`),把结果存进 `localStorage` 的 **`microapp-subapps-details`**:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
{
|
|
37
|
+
currentEnv: { projectName, envName, host },
|
|
38
|
+
subApps: string[], // 当前环境的子应用 env 名列表
|
|
39
|
+
subAppsMap: Record<string, ProjectEnv>,
|
|
40
|
+
allEnvs: ProjectEnv[], // 全量项目×环境,匹配算法的输入
|
|
41
|
+
lastUpdated: number,
|
|
42
|
+
version?: number // 配置版本号
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
每个 `ProjectEnv` 含 `{projectName, envName, host, apiProxies?, proxyEnv?, subApps?, microPath?}`。
|
|
47
|
+
|
|
48
|
+
### 注入方式
|
|
49
|
+
|
|
50
|
+
`entrypoints/micro-inject.content.ts` 是 content script:`matches: ['*://*/*']`、`runAt: 'document_start'`、`world: 'ISOLATED'`,在 `document_start` 立刻把 `/micro-inject-main-world.js` 注入页面主世界(`keepInDom: true`)。
|
|
51
|
+
|
|
52
|
+
必须在主世界、且在 `micro-app` 被 `customElements.define` 之前运行——因为它的核心手段是**劫持 `customElements.define`**。
|
|
53
|
+
|
|
54
|
+
服务地址同样硬编码:`getServerUrl() => 'http://localhost:52741'`。
|
|
55
|
+
|
|
56
|
+
## 二、匹配算法(`findMicroAppConfig`)
|
|
57
|
+
|
|
58
|
+
要给一个 `<micro-app>` 元素决定本地 host,走两级判定。
|
|
59
|
+
|
|
60
|
+
只处理这三个标签:**`micro-app` / `micro-app-ilink` / `micro-app-ijudge`**。
|
|
61
|
+
|
|
62
|
+
### ① microPath 识别(优先)
|
|
63
|
+
|
|
64
|
+
共享微应用宿主(`micro-app.*` 域名)下多个兄弟子应用的 **hostname 相同**,靠域名无法区分,所以用 `microPath` 认身份。
|
|
65
|
+
|
|
66
|
+
遍历所有 `microPath` 非空的 env,判定命中:
|
|
67
|
+
|
|
68
|
+
- `defaultPage.includes(microPath)` —— 元素的 `default-page` 属性包含该值
|
|
69
|
+
- 或 `url.includes(microPath)`
|
|
70
|
+
|
|
71
|
+
**多个命中取 `microPath` 最长(最具体)的那个**,避免 `/micro/360-icrc` 误吸 `/micro/360-icrc-site` 这类前缀冲突。
|
|
72
|
+
|
|
73
|
+
识别出身份后:
|
|
74
|
+
|
|
75
|
+
- 该 env 在 `subApps` 里 → 改写成它的 `host`
|
|
76
|
+
- 该 env **不在** `subApps` 里 → 保持远端,**并且直接返回 null,绝不回落到 hostname 兜底**
|
|
77
|
+
|
|
78
|
+
最后这条很关键:如果回落了,只勾选 `micro-components` 时,`360-icrc-site` 会被同宿主下已勾选的兄弟子应用错误吸附到 8822。
|
|
79
|
+
|
|
80
|
+
### ② hostname 兜底
|
|
81
|
+
|
|
82
|
+
没有 microPath 命中时,遍历所有 env,用 `getNormalizedProxyEnv(env)` 拿它所有可匹配的代理 URL,**只比 hostname**(`targetUrl.hostname === proxyUrlObj.hostname`)。
|
|
83
|
+
|
|
84
|
+
`getNormalizedProxyEnv` 同时兼容新旧的两种结构:
|
|
85
|
+
|
|
86
|
+
- 旧:`env.proxyEnv`
|
|
87
|
+
- 新:`env.apiProxies[].proxyEnv`,key 归一化成 `` `${prefix}::${key}` ``
|
|
88
|
+
|
|
89
|
+
命中的 env 同样**必须在 `subApps` 里**才改写。
|
|
90
|
+
|
|
91
|
+
### 缓存
|
|
92
|
+
|
|
93
|
+
`urlMappingCache`,key 为 `` `${url}||${defaultPage || ''}||${microPathOnly ? 'mp' : 'full'}` ``。三个要素都要进 key,因为结果同时取决于 url、default-page 和是否仅按 microPath 判定。
|
|
94
|
+
|
|
95
|
+
## 三、时序处理(最容易出错的部分)
|
|
96
|
+
|
|
97
|
+
`micro-app` 元素的 `url` 与 `default-page` 属性到达顺序不确定,而 `micro-zoe`(iframe 沙箱)一拿到远端 `url` 就可能立即 `fetch`,造成跨域报错。所以判定被拆成「早期」和「权威」两个阶段:
|
|
98
|
+
|
|
99
|
+
| 阶段 | 判定范围 | 理由 |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| 构造阶段 | 只认 microPath,**不 hostname 兜底** | 属性还没就绪,过早兜底会把共享宿主的元素锁到错误的兄弟子应用 |
|
|
102
|
+
| `setAttribute('url')` / `attributeChangedCallback`,**未连接** | 只认 microPath | 同上,等 `default-page` 或 `connectedCallback` |
|
|
103
|
+
| `setAttribute('url')` / `attributeChangedCallback`,**已连接** | 完整判定(microPath 优先 + hostname 兜底) | 已连接就不会再有 `connectedCallback` 兜底了;不做完整判定的话,远端 url 会被 micro-zoe 放行并立即 fetch,造成偶发 CORS |
|
|
104
|
+
| `default-page` 写入后 | 无条件再判定一次 | 处理 `url` 先于 `default-page` 到达的时序 |
|
|
105
|
+
| `connectedCallback` | **权威判定**,然后才调 `super.connectedCallback()` | 此时属性齐备,确保 super 读到的是正确的本地 url |
|
|
106
|
+
|
|
107
|
+
其它实现细节:
|
|
108
|
+
|
|
109
|
+
- **原始远端 url 备份在 DOM 属性 `data-prime-original-url`**,不用实例字段。因为 micro-zoe 会以代理/借用 `this` 的方式调用元素方法,实例字段在那种 `this` 上查不到,会抛 `xxx is not a function`。所有逻辑都写成模块级的 `tryResolveAndReplaceEl(el, ...)`,接受任意 `this` 形态。
|
|
110
|
+
- 读属性走 `Element.prototype.getAttribute.call(el, ...)`(`rawGetAttr` / `rawSetAttr`),绕过子类或框架的覆写,异常时静默返回而不是抛错。
|
|
111
|
+
- 劫持后的 `PatchedMicroApp` 用 `Object.setPrototypeOf` 维持原型链。
|
|
112
|
+
- 插件还会用 `MutationObserver` + `processExistingMicroApps()` 兜住**动态创建**和**页面已存在**的元素;对已存在元素改写后还会调一次元素的 `reload()`。
|
|
113
|
+
|
|
114
|
+
## 四、`disable-patch-request` 规则
|
|
115
|
+
|
|
116
|
+
`disable-patch-request` 决定 micro-app 是否给子应用请求打补丁。插件只在**确实改写到本地**的元素上强制把它置为 `'false'`(启用补丁),判据是元素当前的 `url` 里是否含 `localhost`(`isMappedToLocal`)。
|
|
117
|
+
|
|
118
|
+
保持远端的元素**尊重页面原始设置**,原样透传——否则会改变线上子应用的请求行为。
|
|
119
|
+
|
|
120
|
+
## 五、配置变更后怎么生效
|
|
121
|
+
|
|
122
|
+
插件订阅:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
GET http://localhost:52741/api/microapp-proxy-change-stream?env=<envName>&clientId=plugin-<时间戳>-<随机串>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
收到 `microapp-proxy-config-change` 后:
|
|
129
|
+
|
|
130
|
+
- 提示 **「MicroApp配置已更新,页面将在3秒后刷新」**
|
|
131
|
+
- 3 秒后 `window.location.reload()` —— 是**整页刷新**,不是局部重渲染
|
|
132
|
+
- 断线重连:最多 **5 次**,间隔 **3 秒**
|
|
133
|
+
|
|
134
|
+
服务端侧由 `packages/server/src/lib/sse-connection-manager.ts` 管理连接:30 秒心跳、2 分钟超时、**每个环境最多 10 个连接**。
|
|
135
|
+
|
|
136
|
+
## 六、排查清单
|
|
137
|
+
|
|
138
|
+
按这个顺序查,基本能定位:
|
|
139
|
+
|
|
140
|
+
1. **插件装了没、启用了没**——这是整条链路的前提,没插件一切配置都不会生效。
|
|
141
|
+
2. **工作台起着没**——`http://localhost:52741` 硬编码。没起来就拉不到 `/api/all-projects-envs`,localStorage 里没有配置,插件直接跳过(日志 `[MicroApp Hack] 跳过:无有效配置`)。
|
|
142
|
+
3. **该环境在不在 `subApps` 里**——这是唯一的「勾选」开关,不在就保持远端。
|
|
143
|
+
4. **是 microPath 场景还是 hostname 场景**——共享宿主(`micro-app.*` 域名)下的兄弟子应用必须配 `microPath`,否则认不出身份;配了 `microPath` 但不在 `subApps` 里会**保持远端且不回落**,表现为「配了却没反应」。
|
|
144
|
+
5. **看控制台日志**——插件会打印 `[MicroApp] URL已替换: <远端> -> <本地> (default-page=...)`、`[MicroApp] microPath映射: ...`、`[MicroApp] microPath识别为「X」但未勾选,保持远端: ...`。最后这条明确告诉你「认出来了但没勾选」。
|
|
145
|
+
6. **改完没反应**——配置变更走 SSE,插件会自动整页刷新;如果没刷新,看 SSE 是否连上(重连上限 5 次,超了就静默了)。
|
|
146
|
+
|
|
147
|
+
## 七、已知坑
|
|
148
|
+
|
|
149
|
+
| 现象 | 原因 |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| 勾了一个子应用,另一个兄弟子应用也被打到本地 | 该元素没配 `microPath`,走了 hostname 兜底;同宿主的兄弟子应用 hostname 相同,会互相吸附 |
|
|
152
|
+
| `/micro/360-icrc` 抢走了 `/micro/360-icrc-site` 的身份 | 应该是取最长命中的,若手动配了重复或包含关系的 microPath 会出现歧义;确保 `microPath` 之间不构成前缀关系 |
|
|
153
|
+
| 子应用指到本地后请求异常/跨域 | `disable-patch-request` 只在已映射元素上被强制为 `false`;若页面自己把它设成了 `true`,映射后会被覆盖——这是预期行为 |
|
|
154
|
+
| 改了 microapp 勾选,别的接口代理也跟着变了 | `microapp-proxy-config` 会把被选中环境的 `/api` 前缀代理改成 `custom` 指向宿主 host,且同名 env 跨项目全改 |
|
|
155
|
+
| 改完 URL 对了但页面还是旧的 | 插件收到 SSE 后 3 秒整页 reload;若 SSE 断了(重连超 5 次)就不会自动刷新,手动刷新即可 |
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports={79810:t=>{"use strict";var{g:e,__dirname:o}=t;{t.s({clearUatGitFlowState:()=>i,createUatGitFlowState:()=>n,isUatGitFlowAborted:()=>d,markUatGitFlowAbort:()=>r});let e=new Map;function a(){let t=Date.now();for(let[o,a]of e.entries())t-a.updatedAt>18e5&&e.delete(o)}function n(t){a(),e.set(t,{aborted:!1,updatedAt:Date.now()})}function r(t){let o=e.get(t);e.set(t,{aborted:!0,updatedAt:Date.now()}),o||a()}function d(t){return a(),e.get(t)?.aborted??!1}function i(t){e.delete(t)}}},26921:function(t){var{g:e,__dirname:o,m:a,e:n}=t}};
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=packages_server_ccc16996._.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["turbopack:///[project]/packages/server/src/app/api/uat-git-flow/state.ts"],"sourcesContent":["type AbortState = {\n aborted: boolean;\n updatedAt: number;\n};\n\nconst abortStates = new Map<string, AbortState>();\nconst ABORT_STATE_TTL = 1000 * 60 * 30;\n\nfunction cleanupAbortStates() {\n const now = Date.now();\n for (const [runId, state] of abortStates.entries()) {\n if (now - state.updatedAt > ABORT_STATE_TTL) {\n abortStates.delete(runId);\n }\n }\n}\n\nexport function createUatGitFlowState(runId: string) {\n cleanupAbortStates();\n abortStates.set(runId, { aborted: false, updatedAt: Date.now() });\n}\n\nexport function markUatGitFlowAbort(runId: string) {\n const existing = abortStates.get(runId);\n abortStates.set(runId, {\n aborted: true,\n updatedAt: Date.now(),\n });\n if (!existing) {\n cleanupAbortStates();\n }\n}\n\nexport function isUatGitFlowAborted(runId: string) {\n cleanupAbortStates();\n return abortStates.get(runId)?.aborted ?? false;\n}\n\nexport function clearUatGitFlowState(runId: string) {\n abortStates.delete(runId);\n}\n"],"names":[],"mappings":"iLAKA,IAAM,EAAc,IAAI,IAGxB,SAAS,IACP,IAAM,EAAM,KAAK,GAAG,GACpB,IAAK,GAAM,CAAC,EAAO,EAAM,GAAI,EAAY,OAAO,GAAI,AAC9C,EAAM,EAAM,SAAS,CALL,EAKQ,IAC1B,CANyB,CAMb,IANkB,EAMZ,CAAC,EAGzB,CAEO,CAN0C,QAMjC,EAAsB,CAAa,EACjD,IACA,EAAY,GAAG,CAAC,EAAO,CAAE,SAAS,EAAO,UAAW,KAAK,GAAG,EAAG,EACjE,CAEO,SAAS,EAAoB,CAAa,EAC/C,IAAM,EAAW,EAAY,GAAG,CAAC,GACjC,EAAY,GAAG,CAAC,EAAO,CACrB,SAAS,EACT,UAAW,KAAK,GAAG,EACrB,GACI,AAAC,GACH,GAEJ,CAEO,GALU,MAKD,EAAoB,CAAa,EAE/C,OADA,IACO,EAAY,GAAG,CAAC,IAAQ,UAAW,CAC5C,CAEO,SAAS,EAAqB,CAAa,EAChD,EAAY,MAAM,CAAC,EACrB"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports={79810:t=>{"use strict";var{g:e,__dirname:o}=t;{t.s({clearUatGitFlowState:()=>i,createUatGitFlowState:()=>n,isUatGitFlowAborted:()=>d,markUatGitFlowAbort:()=>r});let e=new Map;function a(){let t=Date.now();for(let[o,a]of e.entries())t-a.updatedAt>18e5&&e.delete(o)}function n(t){a(),e.set(t,{aborted:!1,updatedAt:Date.now()})}function r(t){let o=e.get(t);e.set(t,{aborted:!0,updatedAt:Date.now()}),o||a()}function d(t){return a(),e.get(t)?.aborted??!1}function i(t){e.delete(t)}}},28814:function(t){var{g:e,__dirname:o,m:a,e:n}=t}};
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=packages_server_d1209331._.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["turbopack:///[project]/packages/server/src/app/api/uat-git-flow/state.ts"],"sourcesContent":["type AbortState = {\n aborted: boolean;\n updatedAt: number;\n};\n\nconst abortStates = new Map<string, AbortState>();\nconst ABORT_STATE_TTL = 1000 * 60 * 30;\n\nfunction cleanupAbortStates() {\n const now = Date.now();\n for (const [runId, state] of abortStates.entries()) {\n if (now - state.updatedAt > ABORT_STATE_TTL) {\n abortStates.delete(runId);\n }\n }\n}\n\nexport function createUatGitFlowState(runId: string) {\n cleanupAbortStates();\n abortStates.set(runId, { aborted: false, updatedAt: Date.now() });\n}\n\nexport function markUatGitFlowAbort(runId: string) {\n const existing = abortStates.get(runId);\n abortStates.set(runId, {\n aborted: true,\n updatedAt: Date.now(),\n });\n if (!existing) {\n cleanupAbortStates();\n }\n}\n\nexport function isUatGitFlowAborted(runId: string) {\n cleanupAbortStates();\n return abortStates.get(runId)?.aborted ?? false;\n}\n\nexport function clearUatGitFlowState(runId: string) {\n abortStates.delete(runId);\n}\n"],"names":[],"mappings":"iLAKA,IAAM,EAAc,IAAI,IAGxB,SAAS,IACP,IAAM,EAAM,KAAK,GAAG,GACpB,IAAK,GAAM,CAAC,EAAO,EAAM,GAAI,EAAY,OAAO,GAAI,AAC9C,EAAM,EAAM,SAAS,CALL,EAKQ,IAC1B,CANyB,CAMb,IANkB,EAMZ,CAAC,EAGzB,CAEO,CAN0C,QAMjC,EAAsB,CAAa,EACjD,IACA,EAAY,GAAG,CAAC,EAAO,CAAE,SAAS,EAAO,UAAW,KAAK,GAAG,EAAG,EACjE,CAEO,SAAS,EAAoB,CAAa,EAC/C,IAAM,EAAW,EAAY,GAAG,CAAC,GACjC,EAAY,GAAG,CAAC,EAAO,CACrB,SAAS,EACT,UAAW,KAAK,GAAG,EACrB,GACI,AAAC,GACH,GAEJ,CAEO,GALU,MAKD,EAAoB,CAAa,EAE/C,OADA,IACO,EAAY,GAAG,CAAC,IAAQ,UAAW,CAC5C,CAEO,SAAS,EAAqB,CAAa,EAChD,EAAY,MAAM,CAAC,EACrB"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports={94011:a=>{"use strict";var{g:b,__dirname:c}=a;{a.s({default:()=>l});var d=a.i(99559),e=a.i(57560),f=a.i(50923),g=a.i(94688),h=a.i(24640),i=a.i(72408),j=a.i(37897),k=a.i(56414);let b="submodules/common";function l({projectName:a,variant:c,onSwitched:l,refreshSignal:m}){let n="sub"===c,[o,p]=(0,e.useState)(null),[q,r]=(0,e.useState)(null),[s,t]=(0,e.useState)(!1),[u,v]=(0,e.useState)([]),[w,x]=(0,e.useState)(!1),[y,z]=(0,e.useState)(null),[A,B]=(0,e.useState)(""),[C,D]=(0,e.useState)(null),[E,F]=(0,e.useState)(null),G=(0,e.useRef)(null),H=a=>{F(a),G.current&&clearTimeout(G.current),G.current=setTimeout(()=>F(null),5e3)},I=(0,e.useCallback)(async()=>{if(n)try{let c=await fetch(`/api/submodule-info?project=${encodeURIComponent(a)}&subPath=${encodeURIComponent(b)}`),d=await c.json();d.success&&d.data&&(r(!!d.data.exists),p(d.data))}catch(b){console.error(`获取子仓库信息失败 (${a}):`,b)}else try{let b=await fetch(`/api/branches?project=${encodeURIComponent(a)}`),c=await b.json();c.success&&c.data?.exists&&p({exists:!0,currentBranch:c.data.currentBranch||null,detached:!1,commitHash:""})}catch(b){console.error(`获取项目分支信息失败 (${a}):`,b)}},[n,a]),J=(0,e.useCallback)(async()=>{x(!0),z(null);try{let c=n?`&subPath=${encodeURIComponent(b)}`:"",d=await fetch(`/api/branches?project=${encodeURIComponent(a)}${c}`),e=await d.json();e.success?v(e.data?.branches||[]):z(e.error||"获取远程分支失败")}catch(b){console.error(`获取远程分支失败 (${a}):`,b),z("网络请求失败")}finally{x(!1)}},[n,a]);(0,e.useEffect)(()=>{I()},[I,m]),(0,e.useEffect)(()=>()=>{G.current&&clearTimeout(G.current)},[]);let K=async c=>{if(!C&&c!==o?.currentBranch){D(c);try{let d=await fetch("/api/branch/switch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n?{projectName:a,branchName:c,subPath:b}:{projectName:a,branchName:c})}),e=await d.json();e.success?(t(!1),B(""),await I(),l?.()):(t(!1),B(""),H(e.error||"切换分支失败"))}catch(b){console.error(`切换分支失败 (${a}):`,b),t(!1),H("切换分支失败: 网络错误")}finally{D(null)}}};if(n&&!1===q)return null;let L=o?o.detached?o.commitHash?`(detached) ${o.commitHash}`:"(detached)":o.currentBranch||"未知分支":"",{visible:M,total:N}=(0,k.filterBranchOptions)(u,A),O=n?"flex items-center gap-1.5 px-2 py-1 bg-purple-50 border border-purple-200 rounded-md min-w-0":"flex items-center gap-1.5 px-2 py-1 bg-blue-50 border border-blue-200 rounded-md min-w-0",P=n?"text-xs font-medium text-purple-700 truncate shrink-0 max-w-[8rem]":"text-xs font-medium text-blue-700 truncate shrink-0 max-w-[8rem]",Q=n?"flex items-center gap-1 text-xs font-medium text-purple-800 hover:bg-purple-100 rounded px-1 transition-colors min-w-0":"flex items-center gap-1 text-xs font-medium text-blue-800 hover:bg-blue-100 rounded px-1 transition-colors min-w-0";return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)("div",{className:O,children:[n&&(0,d.jsx)("span",{className:P,title:`子仓库: ${a}/${b}`,children:"common"}),(0,d.jsxs)(h.DropdownMenu,{open:s,onOpenChange:a=>{t(a),a&&(B(""),J())},children:[(0,d.jsx)(h.DropdownMenuTrigger,{asChild:!0,children:(0,d.jsxs)("button",{type:"button",className:Q,title:n?`点击切换子仓库(${a}/submodules/common)分支`:`点击切换 ${a} 分支`,children:[(0,d.jsx)("span",{className:"truncate max-w-[10rem]",children:L}),(0,d.jsx)(j.ChevronDownIcon,{className:"size-3 opacity-60 shrink-0"})]})}),(0,d.jsxs)(h.DropdownMenuContent,{align:"start",className:"w-72",children:[(0,d.jsx)("div",{className:"p-1",children:(0,d.jsx)(g.Input,{value:A,onChange:a=>B(a.target.value),placeholder:"搜索分支...",className:"h-8"})}),(0,d.jsx)(h.DropdownMenuSeparator,{}),w&&(0,d.jsxs)("div",{className:"flex items-center justify-center gap-2 px-3 py-4 text-sm text-gray-500",children:[(0,d.jsx)("span",{className:"animate-spin w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full"}),"加载远程分支中..."]}),!w&&y&&(0,d.jsxs)("div",{className:"px-3 py-3 text-sm",children:[(0,d.jsx)("p",{className:"text-red-600",children:y}),(0,d.jsx)(f.Button,{variant:"outline",size:"sm",onClick:J,className:"mt-2 h-7 px-2 text-xs",children:"重试"})]}),!w&&!y&&0===u.length&&(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"暂无远程分支"}),!w&&!y&&u.length>0&&0===N&&(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"没有匹配的分支"}),!w&&!y&&N>0&&(0,d.jsxs)("div",{className:"max-h-64 overflow-y-auto",children:[M.map(a=>(0,d.jsxs)(h.DropdownMenuItem,{disabled:null!==C,className:"flex items-center justify-between",onSelect:b=>{b.preventDefault(),K(a)},children:[(0,d.jsx)("span",{className:"min-w-0 flex-1 truncate",children:a}),(0,d.jsxs)("span",{className:"flex items-center gap-2 shrink-0",children:[a===o?.currentBranch&&(0,d.jsx)(i.CheckIcon,{className:"size-4 text-blue-600"}),C===a&&(0,d.jsx)("span",{className:"animate-spin w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full"})]})]},a)),N>M.length&&(0,d.jsxs)("div",{className:"px-3 py-2 text-xs text-gray-400 border-t",children:["共 ",N," 个匹配分支,仅展示前 ",M.length," 项,更多请使用上方搜索"]})]})]})]})]}),E&&(0,d.jsx)("div",{className:"fixed top-16 right-4 z-50 max-w-sm px-4 py-3 bg-red-50 border border-red-200 rounded-md shadow-lg text-sm text-red-600",children:E})]})}}},67450:a=>{"use strict";var{g:b,__dirname:c}=a;{a.s({PROJECT_CONFIG_PATH:()=>c,ProjectStatus:()=>f,WORKSPACE_CONFIG:()=>b});var d=a.i(13442),e=a.i(30331);let b={WORKSPACE_ROOT:(0,e.join)((0,d.homedir)(),"Documents","prime-workspace"),GIT_TIMEOUT:3e5,MAX_CONCURRENT_CLONES:Math.max(2,Math.min((0,d.cpus)().length,8)),MAX_RETRY_ATTEMPTS:3},c=(0,e.join)((0,d.homedir)(),".prime-projects.json");var f=function(a){return a.PENDING="pending",a.EXISTING="existing",a.CLONING="cloning",a.SUCCESS="success",a.FAILED="failed",a.RETRYING="retrying",a.INCOMPLETE="incomplete",a}({})}},90250:a=>{"use strict";var{g:b,__dirname:c}=a;{a.s({buildBatchProbeRemoteBranchPayload:()=>w,buildBatchSwitchBranchPayload:()=>t,buildBatchSwitchSubBranchPayload:()=>v,default:()=>b,mapProbeRemoteBranchResultText:()=>x,validateBatchProbeRemoteBranchForm:()=>r,validateBatchSwitchBranchForm:()=>s,validateBatchSwitchSubBranchForm:()=>u});var d=a.i(99559),e=a.i(57560),f=a.i(40490),g=a.i(50923),h=a.i(2031),i=a.i(94011),j=a.i(94688),k=a.i(58413),l=a.i(24640),m=a.i(72408),n=a.i(37897),o=a.i(22529),p=a.i(67450),q=a.i(56414);function r(a,b){let c=[];return 0===a.length&&c.push("请先选择至少一个项目。"),b.trim()||c.push("请填写远端分支名称。"),c}function s(a,b){let c=[];return 0===a.length&&c.push("请先选择至少一个项目。"),b.trim()||c.push("请选择目标分支。"),c}function t(a,b,c){return{projects:[a],branchName:b.trim(),dryRun:c}}function u(a,b){let c=[];return 0===a.length&&c.push("请先选择至少一个项目。"),b.trim()||c.push("请选择子仓库目标分支。"),c}function v(a,b,c){return{projects:[a],branchName:b.trim(),dryRun:c}}function w(a,b,c){return{projects:[a],branchName:b.trim(),dryRun:c}}function x(a){return!0===a.exists?"存在":!1===a.exists?"不存在":a.success?"成功":"执行失败"}let b=()=>{let a=(0,f.useRouter)(),[b,c]=(0,e.useState)({isInitializing:!1,isCompleted:!1,isLoading:!0,hasCompleted:!1,globalProgress:0,globalMessage:"正在检查工作空间状态...",projects:{}}),[y,z]=(0,e.useState)({selectedProjects:[],createBranch:"release/",baseBranch:"main",mergeBranch:"feature/"}),[A,B]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],runId:""}),[C,D]=(0,e.useState)({selectedProjects:[],createBranch:"release/",baseBranch:"GWH",mergeBranch:"feature/"}),[E,F]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],runId:""}),[G,H]=(0,e.useState)({selectedProjects:[],targetBranch:"main",sourceBranch:""}),[I,J]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[K,L]=(0,e.useState)({selectedProjects:[],createBranch:"",baseBranch:"main"}),[M,N]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[O,P]=(0,e.useState)({selectedProjects:[],branchName:""}),[Q,R]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[S,T]=(0,e.useState)({selectedProjects:[],branchName:""}),[U,V]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[W,X]=(0,e.useState)({selectedProjects:[],branchName:""}),[Y,Z]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[$,_]=(0,e.useState)([]),[aa,ab]=(0,e.useState)(!1),[ac,ad]=(0,e.useState)(null),[ae,af]=(0,e.useState)(!1),[ag,ah]=(0,e.useState)(""),[ai,aj]=(0,e.useState)([]),[ak,al]=(0,e.useState)(!1),[am,an]=(0,e.useState)({selectedProjects:[],branchName:""}),[ao,ap]=(0,e.useState)({isRunning:!1,operationLogs:[],feedbacks:[],progress:0,totalProjects:0,completedProjects:0,currentProject:""}),[aq,ar]=(0,e.useState)([]),[as,at]=(0,e.useState)(!1),[au,av]=(0,e.useState)(null),[aw,ax]=(0,e.useState)(!1),[ay,az]=(0,e.useState)(""),[aA,aB]=(0,e.useState)([]),[aC,aD]=(0,e.useState)(!1),[aE,aF]=(0,e.useState)(!1),[aG,aH]=(0,e.useState)(null),[aI,aJ]=(0,e.useState)(!1),[aK,aL]=(0,e.useState)(!1),[aM,aN]=(0,e.useState)([]),[aO,aP]=(0,e.useState)({}),[aQ,aR]=(0,e.useState)({});(0,e.useEffect)(()=>{aS()},[]),(0,e.useEffect)(()=>{(async()=>{try{let a=await fetch("/api/admin-status"),b=await a.json();aF(!!b?.isAdmin)}catch(a){console.error("获取管理员状态失败:",a),aF(!1)}})()},[]);let aS=async()=>{try{c(a=>({...a,isLoading:!0,globalMessage:"正在检查工作空间状态..."}));let a=await fetch("/api/check-workspace"),b=await a.json();if(b.success){let a={};b.projects.forEach(b=>{a[b.name]={name:b.name,status:b.status,progress:100*(b.status===p.ProjectStatus.EXISTING),message:b.message,canRetry:!1,needsCleanup:b.needsCleanup||!1}}),c(c=>({...c,isLoading:!1,projects:a,globalMessage:b.message||"检查完成"}))}else c(a=>({...a,isLoading:!1,globalMessage:b.error||"检查工作空间状态失败"}))}catch(a){c(a=>({...a,isLoading:!1,globalMessage:"无法连接到服务器,请刷新页面重试"})),console.error("检查工作空间状态失败:",a)}},aT=a=>{switch(a.type){case"initialization":case"workspace_created":case"projects_loaded":if(c(b=>({...b,globalProgress:a.progress||0,globalMessage:a.message||""})),a.projects&&Array.isArray(a.projects)){let b={};a.projects.forEach(a=>{b[a]={name:a,status:p.ProjectStatus.PENDING,progress:0,message:"等待开始...",canRetry:!1}}),c(a=>({...a,projects:b}))}break;case"project_existing":if(a.projectName){let b=a.projectName;c(c=>({...c,globalProgress:a.progress??c.globalProgress,globalMessage:a.message??"",projects:{...c.projects,[b]:{...c.projects[b],status:p.ProjectStatus.EXISTING,message:"项目已存在",progress:100,canRetry:!1}}}))}break;case"project_start":if(a.projectName){let b=a.projectName;c(c=>({...c,globalProgress:a.progress??c.globalProgress,globalMessage:a.message??"",projects:{...c.projects,[b]:{...c.projects[b],status:p.ProjectStatus.CLONING,message:"开始克隆...",progress:0}}}))}break;case"project_progress":if(a.projectName){let b=a.projectName;c(c=>({...c,globalProgress:a.progress??c.globalProgress,projects:{...c.projects,[b]:{...c.projects[b],message:a.message??"",progress:Math.min(a.progress??0,100)}}}))}break;case"project_success":if(a.projectName){let b=a.projectName;c(c=>({...c,globalProgress:a.progress??c.globalProgress,projects:{...c.projects,[b]:{...c.projects[b],status:p.ProjectStatus.SUCCESS,message:"克隆成功",progress:100,canRetry:!1}}}))}break;case"project_failed":if(a.projectName){let b=a.projectName;c(c=>({...c,globalProgress:a.progress??c.globalProgress,projects:{...c.projects,[b]:{...c.projects[b],status:p.ProjectStatus.FAILED,message:"克隆失败",error:a.error,canRetry:!0}}}))}break;case"command_log":if(a.projectName&&a.logType&&void 0!==a.content&&a.timestamp){let b=a.projectName,d=a.logType,e=a.content,f=a.timestamp;c(a=>({...a,projects:{...a.projects,[b]:{...a.projects[b],logs:[...a.projects[b]?.logs||[],{type:d,content:e,timestamp:f}].slice(-1e3)}}}))}break;case"completion":c(b=>({...b,isInitializing:!1,isCompleted:!0,hasCompleted:!0,globalProgress:100,globalMessage:a.message??"初始化完成",completionSummary:a.result?{success:a.result.success,totalProjects:a.result.totalProjects,successCount:a.result.successCount,failedCount:a.result.failedCount,duration:a.result.duration}:void 0}));break;case"error":c(b=>({...b,isInitializing:!1,hasCompleted:!0,globalMessage:a.message??"发生错误",globalProgress:a.progress??b.globalProgress}))}},aU=async(a,b="cursor")=>{c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],isOpening:!0}}}));try{let d=await fetch("/api/open-project",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectName:a,editor:b})}),e=await d.json();if(e.success&&e.editorUrl){let a=document.createElement("a");a.href=e.editorUrl,a.style.display="none",document.body.appendChild(a),a.click(),document.body.removeChild(a)}else console.error("打开项目失败:",e.error);c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],isOpening:!1}}}))}catch(b){c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],isOpening:!1}}})),console.error("打开项目失败:",b)}},aV=async a=>{c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],isCleaningUp:!0}}}));try{let b=await fetch("/api/cleanup-project",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectName:a})}),d=await b.json();d.success?c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],status:p.ProjectStatus.PENDING,message:"已清理,待克隆",isCleaningUp:!1,needsCleanup:!1}}})):c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],isCleaningUp:!1,error:d.error}}}))}catch(b){c(c=>({...c,projects:{...c.projects,[a]:{...c.projects[a],isCleaningUp:!1,error:b instanceof Error?b.message:String(b)}}}))}},aW=async a=>{c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],status:p.ProjectStatus.RETRYING,message:"正在重试...",progress:0,canRetry:!1}}}));try{let b=await fetch("/api/retry-project",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectName:a})}),d=await b.json();c(b=>({...b,projects:{...b.projects,[a]:{...b.projects[a],status:d.success?p.ProjectStatus.SUCCESS:p.ProjectStatus.FAILED,message:d.message,error:d.error,progress:d.success?100:b.projects[a].progress,canRetry:!d.success}}}))}catch(b){c(c=>({...c,projects:{...c.projects,[a]:{...c.projects[a],status:p.ProjectStatus.FAILED,message:"重试失败",error:b instanceof Error?b.message:String(b),canRetry:!0}}}))}},aX=a=>{switch(a){case p.ProjectStatus.EXISTING:case p.ProjectStatus.SUCCESS:return"text-green-600 bg-green-50 border-green-200";case p.ProjectStatus.FAILED:return"text-red-600 bg-red-50 border-red-200";case p.ProjectStatus.INCOMPLETE:return"text-orange-600 bg-orange-50 border-orange-200";case p.ProjectStatus.CLONING:case p.ProjectStatus.RETRYING:return"text-blue-600 bg-blue-50 border-blue-200";case p.ProjectStatus.PENDING:default:return"text-gray-600 bg-gray-50 border-gray-200"}},aY=a=>{switch(a){case p.ProjectStatus.PENDING:return"待克隆";case p.ProjectStatus.EXISTING:return"已存在";case p.ProjectStatus.CLONING:return"克隆中";case p.ProjectStatus.SUCCESS:return"成功";case p.ProjectStatus.FAILED:return"失败";case p.ProjectStatus.INCOMPLETE:return"不完整";case p.ProjectStatus.RETRYING:return"重试中";default:return"未知"}},aZ=()=>{let a=Object.values(b.projects);return a.length>0&&a.every(a=>a.status===p.ProjectStatus.EXISTING)},a$=b=>{a.push(`/configuration?project=${encodeURIComponent(b)}`)},a_=(a,b)=>{z(c=>({...c,[a]:b}))},a0=(a,b)=>{D(c=>({...c,[a]:b}))},a1=(a,b)=>{H(c=>({...c,[a]:b}))},a2=(a,b)=>{L(c=>({...c,[a]:b}))},a3=a=>{P(b=>({...b,branchName:a}))},a4=a=>{T(b=>({...b,branchName:a}))},a5=a=>{X(b=>({...b,branchName:a})),af(!1),ah("")},a6=async()=>{let a=W.selectedProjects.length>0?W.selectedProjects:Object.keys(b.projects);if(0===a.length){ad("暂无可用的项目"),_([]);return}ab(!0),ad(null);try{let b=await fetch("/api/batch-list-branches",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:a})}),c=await b.json();c.success?(_(c.branches||[]),Array.isArray(c.failedProjects)&&c.failedProjects.length>0&&ad(`${c.failedProjects.length} 个项目获取分支失败(已跳过): ${c.failedProjects.map(a=>a.projectName).join(", ")}`)):(ad(c.error||"获取分支列表失败"),_([]))}catch(a){console.error("获取批量分支列表失败:",a),ad("网络请求失败"),_([])}finally{ab(!1)}},a7=async a=>{let b=[];if(0===G.selectedProjects.length&&b.push("请先选择至少一个项目。"),G.targetBranch.trim()||b.push("请填写合入分支名称。"),G.sourceBranch.trim()||b.push("请填写被合入分支名称。"),b.length>0)return void J({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=G.selectedProjects.length;J({isRunning:!0,operationLogs:[`开始批量分支合并,共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let d=[],e=[];for(let b=0;b<G.selectedProjects.length;b++){let f=G.selectedProjects[b];J(a=>({...a,currentProject:f,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${f}`]}));try{let b=await fetch("/api/admin-git-batch-merge",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:[f],targetBranch:G.targetBranch.trim(),sourceBranch:G.sourceBranch.trim(),dryRun:a})}),c=await b.json(),g=Array.isArray(c.operationLogs)?c.operationLogs:[],h=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];J(a=>({...a,operationLogs:[...a.operationLogs,...g],feedbacks:[...a.feedbacks,...h]})),e.push(...h),Array.isArray(c.projectResults)&&d.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";J(b=>({...b,operationLogs:[...b.operationLogs,`[${f}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${f}] ${a}`]})),e.push(`[${f}] ${a}`),d.push({projectName:f,success:!1,reason:a})}}J(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,"批量分支合并执行完成。"],feedbacks:e.length>0?e:a.feedbacks})),d.length>0?(aN(d),aL(!0)):(aN([]),aL(!1))},a8=async a=>{let b=[];if(0===K.selectedProjects.length&&b.push("请先选择至少一个项目。"),K.createBranch.trim()||b.push("请填写新分支名称。"),K.baseBranch.trim()||b.push("请填写来源分支名称。"),b.length>0)return void N({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=K.selectedProjects.length;N({isRunning:!0,operationLogs:[`开始批量新建分支,共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let d=[],e=[];for(let b=0;b<K.selectedProjects.length;b++){let f=K.selectedProjects[b];N(a=>({...a,currentProject:f,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${f}`]}));try{let b=await fetch("/api/admin-git-batch-create-branch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:[f],createBranch:K.createBranch.trim(),baseBranch:K.baseBranch.trim(),dryRun:a})}),c=await b.json(),g=Array.isArray(c.operationLogs)?c.operationLogs:[],h=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];N(a=>({...a,operationLogs:[...a.operationLogs,...g],feedbacks:[...a.feedbacks,...h]})),e.push(...h),Array.isArray(c.projectResults)&&d.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";N(b=>({...b,operationLogs:[...b.operationLogs,`[${f}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${f}] ${a}`]})),e.push(`[${f}] ${a}`),d.push({projectName:f,success:!1,reason:a})}}N(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,"批量新建分支执行完成。"],feedbacks:e.length>0?e:a.feedbacks})),d.length>0?(aN(d),aL(!0)):(aN([]),aL(!1))},a9=async a=>{let b=[];if(0===O.selectedProjects.length&&b.push("请先选择至少一个项目。"),O.branchName.trim()||b.push("请填写远端分支名称。"),b.length>0)return void R({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=O.selectedProjects.length;R({isRunning:!0,operationLogs:[`开始批量删除远端分支,共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let d=[],e=[];for(let b=0;b<O.selectedProjects.length;b++){let f=O.selectedProjects[b];R(a=>({...a,currentProject:f,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${f}`]}));try{let b=await fetch("/api/admin-git-batch-delete-branch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:[f],branchName:O.branchName.trim(),dryRun:a})}),c=await b.json(),g=Array.isArray(c.operationLogs)?c.operationLogs:[],h=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];R(a=>({...a,operationLogs:[...a.operationLogs,...g],feedbacks:[...a.feedbacks,...h]})),e.push(...h),Array.isArray(c.projectResults)&&d.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";R(b=>({...b,operationLogs:[...b.operationLogs,`[${f}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${f}] ${a}`]})),e.push(`[${f}] ${a}`),d.push({projectName:f,success:!1,reason:a})}}R(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,"批量删除远端分支执行完成。"],feedbacks:e.length>0?e:a.feedbacks})),d.length>0?(aN(d),aL(!0)):(aN([]),aL(!1))},ba=async a=>{let b=r(S.selectedProjects,S.branchName);if(b.length>0)return void V({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=S.selectedProjects.length;V({isRunning:!0,operationLogs:[`开始批量探测远端分支,共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let d=[],e=[];for(let b=0;b<S.selectedProjects.length;b++){let f=S.selectedProjects[b];V(a=>({...a,currentProject:f,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${f}`]}));try{let b=await fetch("/api/admin-git-batch-probe-remote-branch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(w(f,S.branchName,a))}),c=await b.json(),g=Array.isArray(c.operationLogs)?c.operationLogs:[],h=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];V(a=>({...a,operationLogs:[...a.operationLogs,...g],feedbacks:[...a.feedbacks,...h]})),e.push(...h),Array.isArray(c.projectResults)&&d.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";V(b=>({...b,operationLogs:[...b.operationLogs,`[${f}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${f}] ${a}`]})),e.push(`[${f}] ${a}`),d.push({projectName:f,success:!1,reason:a})}}V(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,"批量探测远端分支执行完成。"],feedbacks:e.length>0?e:a.feedbacks})),d.length>0?(aN(d),aL(!0)):(aN([]),aL(!1))},bb=async a=>{let b=s(W.selectedProjects,W.branchName);if(b.length>0)return void Z({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=W.selectedProjects.length,d=W.branchName.trim();Z({isRunning:!0,operationLogs:[`开始批量切换分支${a?"(DRY RUN)":""},目标分支: ${d},共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let e=[],f=[];for(let b=0;b<W.selectedProjects.length;b++){let g=W.selectedProjects[b];Z(a=>({...a,currentProject:g,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${g}`]}));try{let b=await fetch("/api/batch-switch-branch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t(g,d,a))}),c=await b.json(),h=Array.isArray(c.operationLogs)?c.operationLogs:[],i=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];Z(a=>({...a,operationLogs:[...a.operationLogs,...h],feedbacks:[...a.feedbacks,...i]})),f.push(...i),Array.isArray(c.projectResults)&&e.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";Z(b=>({...b,operationLogs:[...b.operationLogs,`[${g}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${g}] ${a}`]})),f.push(`[${g}] ${a}`),e.push({projectName:g,success:!1,reason:a})}}let g=e.filter(a=>a.success).length,h=e.length-g;Z(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,`批量切换分支执行完成。总结: 共 ${e.length} 个项目,成功 ${g} 个,失败 ${h} 个。`],feedbacks:f.length>0?f:a.feedbacks})),e.length>0?(aj(e),al(!0)):(aj([]),al(!1)),bc(e.map(a=>a.projectName))},bc=a=>{aR(b=>{let c={...b};return a.forEach(a=>{c[a]=(c[a]||0)+1}),c})},bd=a=>{an(b=>({...b,branchName:a})),ax(!1),az("")},be=async()=>{let a=am.selectedProjects.length>0?am.selectedProjects:Object.keys(b.projects);if(0===a.length){av("暂无可用的项目"),ar([]);return}at(!0),av(null);try{let b=await fetch("/api/batch-list-submodule-branches",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:a})}),c=await b.json();c.success?(ar(c.branches||[]),Array.isArray(c.failedProjects)&&c.failedProjects.length>0&&av(`${c.failedProjects.length} 个项目获取子仓库分支失败(已跳过): ${c.failedProjects.map(a=>a.projectName).join(", ")}`)):(av(c.error||"获取子仓库分支列表失败"),ar([]))}catch(a){console.error("获取批量子仓库分支列表失败:",a),av("网络请求失败"),ar([])}finally{at(!1)}},bf=async a=>{let b=u(am.selectedProjects,am.branchName);if(b.length>0)return void ap({isRunning:!1,operationLogs:[],feedbacks:b,progress:0,totalProjects:0,completedProjects:0,currentProject:""});let c=am.selectedProjects.length,d=am.branchName.trim();ap({isRunning:!0,operationLogs:[`开始批量切换子仓库(submodules/common)分支${a?"(DRY RUN)":""},目标分支: ${d},共 ${c} 个项目...`],feedbacks:[],progress:0,totalProjects:c,completedProjects:0,currentProject:""});let e=[],f=[];for(let b=0;b<am.selectedProjects.length;b++){let g=am.selectedProjects[b];ap(a=>({...a,currentProject:g,completedProjects:b,progress:Math.round(b/c*100),operationLogs:[...a.operationLogs,`[进度] ${b+1}/${c} 开始处理项目 ${g}`]}));try{let b=await fetch("/api/batch-switch-submodule-branch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(v(g,d,a))}),c=await b.json(),h=Array.isArray(c.operationLogs)?c.operationLogs:[],i=Array.isArray(c.feedbacks)?c.feedbacks:[c.error||"执行失败"];ap(a=>({...a,operationLogs:[...a.operationLogs,...h],feedbacks:[...a.feedbacks,...i]})),f.push(...i),Array.isArray(c.projectResults)&&e.push(...c.projectResults)}catch(b){let a=b instanceof Error?b.message:"执行失败";ap(b=>({...b,operationLogs:[...b.operationLogs,`[${g}] 请求失败: ${a}`],feedbacks:[...b.feedbacks,`[${g}] ${a}`]})),f.push(`[${g}] ${a}`),e.push({projectName:g,success:!1,reason:a})}}let g=e.filter(a=>a.success).length,h=e.length-g;ap(a=>({...a,isRunning:!1,progress:100,completedProjects:c,currentProject:"",operationLogs:[...a.operationLogs,`批量切换子仓库分支执行完成。总结: 共 ${e.length} 个项目,成功 ${g} 个,失败 ${h} 个。`],feedbacks:f.length>0?f:a.feedbacks})),e.length>0?(aB(e),aD(!0)):(aB([]),aD(!1)),bc(e.map(a=>a.projectName))},bg=async a=>{let b=[];if(0===y.selectedProjects.length&&b.push("请先选择至少一个项目。"),y.createBranch.trim()||b.push("请填写创建分支名。"),y.baseBranch.trim()||b.push("请填写从 origin 哪个分支创建。"),y.mergeBranch.trim()||b.push("请填写被合并分支名称。"),b.length>0)return void B({isRunning:!1,operationLogs:[],feedbacks:b,runId:""});let c=`uat-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;B({isRunning:!0,operationLogs:[],feedbacks:[],runId:c});try{let b=await fetch("/api/uat-git-flow",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:y.selectedProjects,createBranch:y.createBranch.trim(),baseBranch:"main",mergeBranch:y.mergeBranch.trim(),dryRun:a,runId:c})}),d=await b.json();B({isRunning:!1,operationLogs:Array.isArray(d.operationLogs)?d.operationLogs:[],feedbacks:Array.isArray(d.feedbacks)?d.feedbacks:[d.error||"执行失败"],runId:""}),Array.isArray(d.projectResults)&&(aN(d.projectResults),aL(!0))}catch(a){B({isRunning:!1,operationLogs:[],feedbacks:[a instanceof Error?a.message:"执行失败"],runId:""}),aN([]),aL(!1)}},bh=async a=>{let b=[];if(0===C.selectedProjects.length&&b.push("请先选择至少一个项目。"),C.createBranch.trim()||b.push("请填写创建分支名。"),C.baseBranch.trim()||b.push("请填写从 origin 哪个分支创建。"),C.mergeBranch.trim()||b.push("请填写被合并分支名称。"),b.length>0)return void F({isRunning:!1,operationLogs:[],feedbacks:b,runId:""});let c=`gwh-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;F({isRunning:!0,operationLogs:[],feedbacks:[],runId:c});try{let b=await fetch("/api/gwh-git-flow",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projects:C.selectedProjects,createBranch:C.createBranch.trim(),baseBranch:"GWH",mergeBranch:C.mergeBranch.trim(),dryRun:a,runId:c})}),d=await b.json();F({isRunning:!1,operationLogs:Array.isArray(d.operationLogs)?d.operationLogs:[],feedbacks:Array.isArray(d.feedbacks)?d.feedbacks:[d.error||"执行失败"],runId:""}),Array.isArray(d.projectResults)&&(aN(d.projectResults),aL(!0))}catch(a){F({isRunning:!1,operationLogs:[],feedbacks:[a instanceof Error?a.message:"执行失败"],runId:""}),aN([]),aL(!1)}},bi=async()=>{if(aG&&A.runId)try{let a=await fetch("/api/uat-git-flow/step",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"execute",runId:A.runId,stepId:aG.stepId})}),b=await a.json(),c=Array.isArray(b.operationLogs)?b.operationLogs:[],d=Array.isArray(b.feedbacks)?b.feedbacks:[b.error||"执行失败"];if(!b.success){B(a=>({...a,isRunning:!1,operationLogs:c,feedbacks:d,runId:""})),aH(null),aJ(!1);return}B(a=>({...a,operationLogs:c,feedbacks:d,isRunning:!b.isComplete,runId:b.isComplete?"":a.runId})),b.nextStep?(aH(b.nextStep),aJ(!0)):(aH(null),aJ(!1),Array.isArray(b.projectResults)&&(aN(b.projectResults),aL(!0)))}catch(a){B(b=>({...b,isRunning:!1,operationLogs:b.operationLogs,feedbacks:[...b.feedbacks,a instanceof Error?a.message:"执行失败"],runId:""})),aH(null),aJ(!1)}},bj=async()=>{if(A.runId)try{let a=await fetch("/api/uat-git-flow/abort",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({runId:A.runId})}),b=await a.json();b&&Array.isArray(b.operationLogs)?B(a=>({...a,isRunning:!1,operationLogs:b.operationLogs,feedbacks:Array.isArray(b.feedbacks)?b.feedbacks:a.feedbacks,runId:""})):B(a=>({...a,isRunning:!1,runId:""})),Array.isArray(b.projectResults)&&(aN(b.projectResults),aL(!0)),aH(null),aJ(!1)}catch(a){B(b=>({...b,feedbacks:[...b.feedbacks,a instanceof Error?a.message:"中断失败"]}))}},bk=async()=>{if(E.runId)try{let a=await fetch("/api/gwh-git-flow/abort",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({runId:E.runId})}),b=await a.json();b&&Array.isArray(b.operationLogs)?F(a=>({...a,isRunning:!1,operationLogs:b.operationLogs,feedbacks:Array.isArray(b.feedbacks)?b.feedbacks:a.feedbacks,runId:""})):F(a=>({...a,isRunning:!1,runId:""})),Array.isArray(b.projectResults)&&(aN(b.projectResults),aL(!0))}catch(a){F(b=>({...b,feedbacks:[...b.feedbacks,a instanceof Error?a.message:"中断失败"]}))}},bl=(0,q.filterBranchOptions)($,ag),bm=(0,q.filterBranchOptions)(aq,ay);return(0,d.jsxs)("div",{className:"container mx-auto p-4 sm:p-6 md:p-8",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,d.jsx)("h1",{className:"text-2xl font-bold",children:"项目初始化"}),b.isLoading?(0,d.jsx)(g.Button,{disabled:!0,children:"检查中..."}):(0,d.jsx)(g.Button,{onClick:()=>{c(a=>({...a,isInitializing:!0,isCompleted:!1,hasCompleted:!1,globalProgress:0,globalMessage:"正在连接服务器...",projects:{},completionSummary:void 0}));let a=new EventSource("/api/initialize-workspace");a.onmessage=a=>{try{let b=JSON.parse(a.data);aT(b)}catch(a){console.error("解析 SSE 消息失败:",a)}},a.onerror=()=>{c(a=>a.hasCompleted?a:{...a,isInitializing:!1,globalMessage:"连接服务器失败,请重试"}),a.close()},a.addEventListener("error",()=>{a.close()})},disabled:b.isInitializing,children:b.isInitializing?"初始化中...":"创建项目空间"})]}),(b.isInitializing||b.isCompleted)&&!aZ()&&(0,d.jsxs)("div",{className:"mb-6 p-4 bg-gray-50 rounded-lg",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2",children:[(0,d.jsx)("span",{className:"text-sm font-medium",children:b.globalMessage}),(0,d.jsxs)("span",{className:"text-sm text-gray-600",children:[b.globalProgress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${b.globalProgress}%`}})})]}),b.completionSummary&&!aZ()&&(0,d.jsxs)("div",{className:`mb-6 p-4 rounded-lg border ${b.completionSummary.success?"bg-green-50 border-green-200":"bg-yellow-50 border-yellow-200"}`,children:[(0,d.jsx)("h3",{className:"font-semibold mb-2",children:"初始化完成"}),(0,d.jsxs)("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-sm",children:[(0,d.jsxs)("div",{children:["总项目数: ",b.completionSummary.totalProjects]}),(0,d.jsxs)("div",{className:"text-green-600",children:["成功: ",b.completionSummary.successCount]}),(0,d.jsxs)("div",{className:"text-red-600",children:["失败: ",b.completionSummary.failedCount]}),(0,d.jsxs)("div",{children:["耗时: ",Math.round(b.completionSummary.duration/1e3),"s"]})]})]}),(0,d.jsx)("div",{className:"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",children:Object.keys(b.projects).length>0?Object.values(b.projects).map(a=>(0,d.jsxs)("div",{className:`border rounded-lg p-4 ${aX(a.status)}`,children:[(0,d.jsxs)("div",{className:"flex justify-between items-start mb-2",children:[(0,d.jsx)("h2",{className:"font-semibold cursor-pointer hover:text-blue-600 transition-colors",onClick:()=>a$(a.name),title:"点击进入项目配置",children:a.name}),(0,d.jsx)("span",{className:"text-xs px-2 py-1 rounded-full bg-white",children:aY(a.status)})]}),(0,d.jsx)("p",{className:"text-sm mb-2",children:a.message}),a.status===p.ProjectStatus.CLONING||a.status===p.ProjectStatus.RETRYING?(0,d.jsxs)("div",{className:"mb-2",children:[(0,d.jsx)("div",{className:"w-full bg-white rounded-full h-1",children:(0,d.jsx)("div",{className:"bg-current h-1 rounded-full transition-all duration-300",style:{width:`${a.progress}%`}})}),(0,d.jsxs)("span",{className:"text-xs",children:[a.progress,"%"]})]}):null,a.error&&(0,d.jsx)("p",{className:"text-xs text-red-600 mb-2",children:a.error}),(a.status===p.ProjectStatus.EXISTING||a.status===p.ProjectStatus.SUCCESS)&&(0,d.jsx)("div",{className:"mb-2",children:(0,d.jsx)(i.default,{projectName:a.name,variant:"main",refreshSignal:aQ[a.name]||0,onSwitched:()=>aR(b=>({...b,[a.name]:(b[a.name]||0)+1}))})}),(a.status===p.ProjectStatus.EXISTING||a.status===p.ProjectStatus.SUCCESS)&&(0,d.jsx)("div",{className:"mb-2",children:(0,d.jsx)(i.default,{projectName:a.name,variant:"sub",refreshSignal:aQ[a.name]||0})}),(0,d.jsxs)("div",{className:"space-y-2",children:[a.canRetry&&(0,d.jsx)(g.Button,{size:"sm",variant:"outline",onClick:()=>aW(a.name),className:"w-full",children:"重试"}),a.needsCleanup&&a.status===p.ProjectStatus.INCOMPLETE&&(0,d.jsx)(g.Button,{size:"sm",variant:"destructive",onClick:()=>aV(a.name),disabled:a.isCleaningUp,className:"w-full",children:a.isCleaningUp?"清理中...":"清理项目"}),(a.status===p.ProjectStatus.EXISTING||a.status===p.ProjectStatus.SUCCESS)&&(0,d.jsx)(h.EditorSplitButton,{onOpen:b=>aU(a.name,b),loading:a.isOpening,loadingText:"打开中...",size:"sm",variant:"default",className:"w-full"}),a.logs&&a.logs.length>0&&(0,d.jsx)(g.Button,{size:"sm",variant:"outline",onClick:()=>aP(b=>({...b,[a.name]:!b[a.name]})),className:"w-full",children:aO[a.name]?"隐藏日志":`查看日志 (${a.logs.length})`})]}),aO[a.name]&&a.logs&&a.logs.length>0&&(0,d.jsx)("div",{className:"mt-3 border-t pt-3",children:(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:a.logs.map((a,b)=>(0,d.jsxs)("div",{className:`mb-1 ${"stderr"===a.type?"text-red-400":"progress"===a.type?"text-yellow-400":"text-green-400"}`,children:[(0,d.jsx)("span",{className:"text-gray-500 mr-2",children:new Date(a.timestamp).toLocaleTimeString()}),(0,d.jsxs)("span",{className:"text-blue-400 mr-2",children:["[",a.type,"]"]}),(0,d.jsx)("span",{className:"whitespace-pre-wrap",children:a.content})]},b))})})]},a.name)):(0,d.jsx)("div",{className:"col-span-full text-center text-gray-500 py-8",children:b.isLoading?"正在检查项目状态...":b.isInitializing?"正在加载项目...":'点击"创建项目空间"开始初始化'})}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"UAT上线前 Git 操作流"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>bg(!1),disabled:A.isRunning,children:A.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>bg(!0),disabled:A.isRunning,children:"DRY RUN"}),(0,d.jsx)(g.Button,{variant:"destructive",onClick:bj,disabled:!A.isRunning,children:"中断"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"uat-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"uat-projects",multiple:!0,value:y.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);z(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsxs)("div",{className:"grid gap-4",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"uat-create-branch",children:"创建分支名"}),(0,d.jsx)(j.Input,{id:"uat-create-branch",value:y.createBranch,onChange:a=>a_("createBranch",a.target.value),placeholder:"例如: release/uat-2026-01"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"uat-base-branch",children:"从 origin 哪个分支创建(固定)"}),(0,d.jsx)(j.Input,{id:"uat-base-branch",value:y.baseBranch,disabled:!0,placeholder:"main"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"uat-merge-branch",children:"被合并分支名称"}),(0,d.jsx)(j.Input,{id:"uat-merge-branch",value:y.mergeBranch,onChange:a=>a_("mergeBranch",a.target.value),placeholder:"例如: feature/xxx"})]})]})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===A.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:A.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===A.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:A.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"武汉协和版本上线前 Git 操作流"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>bh(!1),disabled:E.isRunning,children:E.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>bh(!0),disabled:E.isRunning,children:"DRY RUN"}),(0,d.jsx)(g.Button,{variant:"destructive",onClick:bk,disabled:!E.isRunning,children:"中断"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"gwh-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"gwh-projects",multiple:!0,value:C.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);D(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsxs)("div",{className:"grid gap-4",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"gwh-create-branch",children:"创建分支名"}),(0,d.jsx)(j.Input,{id:"gwh-create-branch",value:C.createBranch,onChange:a=>a0("createBranch",a.target.value),placeholder:"例如: release/whxh-2026-03"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"gwh-base-branch",children:"从 origin 哪个分支创建(固定)"}),(0,d.jsx)(j.Input,{id:"gwh-base-branch",value:C.baseBranch,disabled:!0,placeholder:"GWH"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"gwh-merge-branch",children:"被合并分支名称"}),(0,d.jsx)(j.Input,{id:"gwh-merge-branch",value:C.mergeBranch,onChange:a=>a0("mergeBranch",a.target.value),placeholder:"例如: feature/xxx"})]})]})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===E.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:E.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===E.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:E.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量分支合并"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>a7(!1),disabled:I.isRunning,children:I.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>a7(!0),disabled:I.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-merge-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-merge-projects",multiple:!0,value:G.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);H(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsxs)("div",{className:"grid gap-4",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-merge-target-branch",children:"合入分支名称"}),(0,d.jsx)(j.Input,{id:"batch-merge-target-branch",value:G.targetBranch,onChange:a=>a1("targetBranch",a.target.value),placeholder:"例如: main"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-merge-source-branch",children:"被合入分支名称"}),(0,d.jsx)(j.Input,{id:"batch-merge-source-branch",value:G.sourceBranch,onChange:a=>a1("sourceBranch",a.target.value),placeholder:"例如: release/candidate"})]})]})]}),(I.isRunning||I.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",I.completedProjects,"/",I.totalProjects,I.currentProject?` \xb7 当前项目: ${I.currentProject}`:""]}),(0,d.jsxs)("span",{children:[I.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${I.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===I.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:I.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===I.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:I.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量新建分支"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>a8(!1),disabled:M.isRunning,children:M.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>a8(!0),disabled:M.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-create-branch-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-create-branch-projects",multiple:!0,value:K.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);L(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsxs)("div",{className:"grid gap-4",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-create-branch-name",children:"新分支名称"}),(0,d.jsx)(j.Input,{id:"batch-create-branch-name",value:K.createBranch,onChange:a=>a2("createBranch",a.target.value),placeholder:"例如: release/2026-03"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-create-branch-base",children:"来源分支名称"}),(0,d.jsx)(j.Input,{id:"batch-create-branch-base",value:K.baseBranch,onChange:a=>a2("baseBranch",a.target.value),placeholder:"例如: main"})]})]})]}),(M.isRunning||M.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",M.completedProjects,"/",M.totalProjects,M.currentProject?` \xb7 当前项目: ${M.currentProject}`:""]}),(0,d.jsxs)("span",{children:[M.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${M.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===M.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:M.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===M.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:M.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量删除远端分支"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>a9(!1),disabled:Q.isRunning,children:Q.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>a9(!0),disabled:Q.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-delete-branch-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-delete-branch-projects",multiple:!0,value:O.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);P(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsx)("div",{className:"grid gap-4",children:(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-delete-branch-name",children:"远端分支名称"}),(0,d.jsx)(j.Input,{id:"batch-delete-branch-name",value:O.branchName,onChange:a=>a3(a.target.value),placeholder:"例如: release/2026-03"})]})})]}),(Q.isRunning||Q.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",Q.completedProjects,"/",Q.totalProjects,Q.currentProject?` \xb7 当前项目: ${Q.currentProject}`:""]}),(0,d.jsxs)("span",{children:[Q.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${Q.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===Q.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:Q.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===Q.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:Q.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),aE&&(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量探测远端分支"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>ba(!1),disabled:U.isRunning,children:U.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>ba(!0),disabled:U.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-probe-branch-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-probe-branch-projects",multiple:!0,value:S.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);T(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选"})]}),(0,d.jsx)("div",{className:"grid gap-4",children:(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-probe-branch-name",children:"远端分支名称"}),(0,d.jsx)(j.Input,{id:"batch-probe-branch-name",value:S.branchName,onChange:a=>a4(a.target.value),placeholder:"例如: release/2026-03"})]})})]}),(U.isRunning||U.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",U.completedProjects,"/",U.totalProjects,U.currentProject?` \xb7 当前项目: ${U.currentProject}`:""]}),(0,d.jsxs)("span",{children:[U.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${U.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===U.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:U.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===U.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:U.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量切换分支"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>bb(!1),disabled:Y.isRunning,children:Y.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>bb(!0),disabled:Y.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-switch-branch-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-switch-branch-projects",multiple:!0,value:W.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);X(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选;未选择时下拉聚合所有项目分支"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-switch-branch-name",children:"目标分支"}),(0,d.jsxs)(l.DropdownMenu,{open:ae,onOpenChange:a=>{af(a),a&&(ah(""),a6())},children:[(0,d.jsx)(l.DropdownMenuTrigger,{asChild:!0,children:(0,d.jsxs)(g.Button,{variant:"outline",className:"w-full justify-between font-normal",children:[(0,d.jsx)("span",{className:"truncate",children:W.branchName||(aa?"加载中...":"点击选择目标分支")}),(0,d.jsx)(n.ChevronDownIcon,{className:"size-4 opacity-50 shrink-0"})]})}),(0,d.jsxs)(l.DropdownMenuContent,{align:"start",className:"w-72",children:[(0,d.jsx)("div",{className:"p-1",children:(0,d.jsx)(j.Input,{value:ag,onChange:a=>ah(a.target.value),placeholder:"搜索分支...",className:"h-8"})}),(0,d.jsx)(l.DropdownMenuSeparator,{}),aa&&(0,d.jsxs)("div",{className:"flex items-center justify-center gap-2 px-3 py-4 text-sm text-gray-500",children:[(0,d.jsx)("span",{className:"animate-spin w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full"}),"聚合获取各项目远程分支中..."]}),!aa&&ac&&(0,d.jsxs)("div",{className:"px-3 py-3 text-sm",children:[(0,d.jsx)("p",{className:"text-red-600",children:ac}),(0,d.jsx)(g.Button,{variant:"outline",size:"sm",onClick:a6,className:"mt-2 h-7 px-2 text-xs",children:"重试"})]}),!aa&&!ac&&(0,d.jsx)(d.Fragment,{children:0===$.length?(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"暂无可用的远程分支"}):0===bl.total?(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"没有匹配的分支"}):(0,d.jsxs)(d.Fragment,{children:[bl.visible.map(a=>(0,d.jsxs)(l.DropdownMenuItem,{className:"flex items-center justify-between",onSelect:b=>{b.preventDefault(),a5(a)},children:[(0,d.jsx)("span",{className:"min-w-0 flex-1 truncate",children:a}),a===W.branchName&&(0,d.jsx)(m.CheckIcon,{className:"size-4 text-blue-600 shrink-0"})]},a)),bl.total>bl.visible.length&&(0,d.jsxs)("div",{className:"px-3 py-2 text-xs text-gray-400 border-t",children:["共 ",bl.total," 个匹配分支,仅展示前 ",bl.visible.length," 项,更多请使用上方搜索"]})]})})]})]}),W.branchName&&(0,d.jsxs)("p",{className:"text-xs text-gray-500",children:["目标分支: ",(0,d.jsx)("span",{className:"text-blue-600 font-medium",children:W.branchName})]})]})]}),(Y.isRunning||Y.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",Y.completedProjects,"/",Y.totalProjects,Y.currentProject?` \xb7 当前项目: ${Y.currentProject}`:""]}),(0,d.jsxs)("span",{children:[Y.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${Y.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===Y.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:Y.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===Y.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:Y.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),(0,d.jsxs)("div",{className:"mt-8 rounded-lg border bg-white p-4 shadow-sm",children:[(0,d.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4",children:[(0,d.jsx)("h2",{className:"text-lg font-semibold",children:"常用 Git 分支操作 · 批量切换子仓库(submodules/common)分支"}),(0,d.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,d.jsx)(g.Button,{onClick:()=>bf(!1),disabled:ao.isRunning,children:ao.isRunning?"执行中...":"执行"}),(0,d.jsx)(g.Button,{variant:"outline",onClick:()=>bf(!0),disabled:ao.isRunning,children:"DRY RUN"})]})]}),(0,d.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-switch-sub-projects",children:"选择项目"}),(0,d.jsx)("select",{id:"batch-switch-sub-projects",multiple:!0,value:am.selectedProjects,onChange:a=>{let b=Array.from(a.target.selectedOptions).map(a=>a.value);an(a=>({...a,selectedProjects:b}))},className:"w-full min-h-[140px] rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",children:0===Object.keys(b.projects).length?(0,d.jsx)("option",{value:"",disabled:!0,children:"暂无可用项目"}):Object.keys(b.projects).map(a=>(0,d.jsx)("option",{value:a,children:a},a))}),(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"按住 Command / Ctrl 可多选;未选择时下拉聚合所有项目子仓库分支"})]}),(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)(k.Label,{htmlFor:"batch-switch-sub-name",children:"目标分支"}),(0,d.jsxs)(l.DropdownMenu,{open:aw,onOpenChange:a=>{ax(a),a&&(az(""),be())},children:[(0,d.jsx)(l.DropdownMenuTrigger,{asChild:!0,children:(0,d.jsxs)(g.Button,{variant:"outline",className:"w-full justify-between font-normal",children:[(0,d.jsx)("span",{className:"truncate",children:am.branchName||(as?"加载中...":"点击选择目标分支")}),(0,d.jsx)(n.ChevronDownIcon,{className:"size-4 opacity-50 shrink-0"})]})}),(0,d.jsxs)(l.DropdownMenuContent,{align:"start",className:"w-72",children:[(0,d.jsx)("div",{className:"p-1",children:(0,d.jsx)(j.Input,{value:ay,onChange:a=>az(a.target.value),placeholder:"搜索分支...",className:"h-8"})}),(0,d.jsx)(l.DropdownMenuSeparator,{}),as&&(0,d.jsxs)("div",{className:"flex items-center justify-center gap-2 px-3 py-4 text-sm text-gray-500",children:[(0,d.jsx)("span",{className:"animate-spin w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full"}),"聚合获取各项目子仓库远程分支中..."]}),!as&&au&&(0,d.jsxs)("div",{className:"px-3 py-3 text-sm",children:[(0,d.jsx)("p",{className:"text-red-600",children:au}),(0,d.jsx)(g.Button,{variant:"outline",size:"sm",onClick:be,className:"mt-2 h-7 px-2 text-xs",children:"重试"})]}),!as&&!au&&(0,d.jsx)(d.Fragment,{children:0===aq.length?(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"暂无可用的子仓库远程分支"}):0===bm.total?(0,d.jsx)("div",{className:"px-3 py-4 text-sm text-gray-500",children:"没有匹配的分支"}):(0,d.jsxs)(d.Fragment,{children:[bm.visible.map(a=>(0,d.jsxs)(l.DropdownMenuItem,{className:"flex items-center justify-between",onSelect:b=>{b.preventDefault(),bd(a)},children:[(0,d.jsx)("span",{className:"min-w-0 flex-1 truncate",children:a}),a===am.branchName&&(0,d.jsx)(m.CheckIcon,{className:"size-4 text-blue-600 shrink-0"})]},a)),bm.total>bm.visible.length&&(0,d.jsxs)("div",{className:"px-3 py-2 text-xs text-gray-400 border-t",children:["共 ",bm.total," 个匹配分支,仅展示前 ",bm.visible.length," 项,更多请使用上方搜索"]})]})})]})]}),am.branchName&&(0,d.jsxs)("p",{className:"text-xs text-gray-500",children:["目标分支: ",(0,d.jsx)("span",{className:"text-blue-600 font-medium",children:am.branchName})]})]})]}),(ao.isRunning||ao.progress>0)&&(0,d.jsxs)("div",{className:"mt-4 rounded-lg border p-3",children:[(0,d.jsxs)("div",{className:"flex justify-between items-center mb-2 text-sm",children:[(0,d.jsxs)("span",{children:["进度: ",ao.completedProjects,"/",ao.totalProjects,ao.currentProject?` \xb7 当前项目: ${ao.currentProject}`:""]}),(0,d.jsxs)("span",{children:[ao.progress,"%"]})]}),(0,d.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,d.jsx)("div",{className:"bg-blue-600 h-2 rounded-full transition-all duration-300",style:{width:`${ao.progress}%`}})})]}),(0,d.jsxs)("div",{className:"mt-6 grid gap-4 md:grid-cols-2",children:[(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"操作记录"}),0===ao.operationLogs.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无记录"}):(0,d.jsx)("div",{className:"bg-black text-white rounded text-xs font-mono p-2 max-h-48 overflow-y-auto",children:ao.operationLogs.map((a,b)=>(0,d.jsx)("div",{className:"mb-1 whitespace-pre-wrap",children:a},b))})]}),(0,d.jsxs)("div",{className:"rounded-lg border p-3",children:[(0,d.jsx)("h3",{className:"text-sm font-semibold mb-2",children:"反馈信息"}),0===ao.feedbacks.length?(0,d.jsx)("p",{className:"text-xs text-gray-500",children:"暂无反馈"}):(0,d.jsx)("div",{className:"space-y-1 text-sm",children:ao.feedbacks.map((a,b)=>(0,d.jsx)("div",{className:"rounded bg-gray-50 px-2 py-1 text-gray-700",children:a},b))})]})]})]}),(0,d.jsx)(o.Dialog,{open:!1,onOpenChange:a=>{!a&&aG&&A.isRunning||aJ(a)},children:(0,d.jsxs)(o.DialogContent,{showCloseButton:!1,children:[(0,d.jsxs)(o.DialogHeader,{children:[(0,d.jsx)(o.DialogTitle,{children:"确认执行下一步"}),(0,d.jsx)(o.DialogDescription,{children:aG?`${aG.projectName} \xb7 ${aG.description}`:"准备下一步执行"})]}),(0,d.jsx)("div",{className:"rounded-md border bg-gray-50 p-3 text-sm font-mono text-gray-700",children:aG?aG.command:""}),(0,d.jsxs)(o.DialogFooter,{children:[(0,d.jsx)(g.Button,{variant:"outline",onClick:bj,children:"取消执行"}),(0,d.jsx)(g.Button,{onClick:bi,disabled:!aG,children:"确认并执行"})]})]})}),(0,d.jsx)(o.Dialog,{open:aE&&aK,onOpenChange:aL,children:(0,d.jsxs)(o.DialogContent,{children:[(0,d.jsxs)(o.DialogHeader,{children:[(0,d.jsx)(o.DialogTitle,{children:"操作结果"}),(0,d.jsx)(o.DialogDescription,{children:"查看所有项目执行结果"})]}),(0,d.jsx)("div",{className:"space-y-2",children:0===aM.length?(0,d.jsx)("p",{className:"text-sm text-gray-500",children:"暂无结果"}):aM.map(a=>(0,d.jsxs)("div",{className:`rounded-md border px-3 py-2 text-sm ${a.success?"bg-green-50 text-green-700":"bg-red-50 text-red-700"}`,children:[(0,d.jsxs)("div",{className:"flex items-start justify-between",children:[(0,d.jsx)("span",{className:"font-medium",children:a.projectName}),(0,d.jsx)("span",{className:"ml-4 text-right",children:!0===a.exists||!1===a.exists?x(a):a.success?"成功":a.reason||"失败"})]}),!a.success&&a.rollbackActions&&a.rollbackActions.length>0&&(0,d.jsxs)("div",{className:"mt-2 text-xs text-gray-600",children:[(0,d.jsx)("div",{className:"font-semibold text-gray-700",children:"已回滚操作:"}),(0,d.jsx)("ul",{className:"mt-1 list-disc pl-4",children:a.rollbackActions.map((b,c)=>(0,d.jsx)("li",{children:b},`${a.projectName}-rollback-${c}`))})]})]},a.projectName))}),(0,d.jsx)(o.DialogFooter,{children:(0,d.jsx)(g.Button,{onClick:()=>aL(!1),children:"关闭"})})]})}),(0,d.jsx)(o.Dialog,{open:ak,onOpenChange:al,children:(0,d.jsxs)(o.DialogContent,{children:[(0,d.jsxs)(o.DialogHeader,{children:[(0,d.jsx)(o.DialogTitle,{children:"批量切换分支结果"}),(0,d.jsx)(o.DialogDescription,{children:(()=>{let a=ai.filter(a=>a.success).length,b=ai.length-a;return`共 ${ai.length} 个项目,成功 ${a} 个,失败 ${b} 个`})()})]}),(0,d.jsx)("div",{className:"space-y-2",children:0===ai.length?(0,d.jsx)("p",{className:"text-sm text-gray-500",children:"暂无结果"}):ai.map(a=>(0,d.jsx)("div",{className:`rounded-md border px-3 py-2 text-sm ${a.success?"bg-green-50 text-green-700":"bg-red-50 text-red-700"}`,children:(0,d.jsxs)("div",{className:"flex items-start justify-between",children:[(0,d.jsx)("span",{className:"font-medium",children:a.projectName}),(0,d.jsx)("span",{className:"ml-4 text-right",children:a.success?"成功":a.reason||"失败"})]})},a.projectName))}),(0,d.jsx)(o.DialogFooter,{children:(0,d.jsx)(g.Button,{onClick:()=>al(!1),children:"关闭"})})]})}),(0,d.jsx)(o.Dialog,{open:aC,onOpenChange:aD,children:(0,d.jsxs)(o.DialogContent,{children:[(0,d.jsxs)(o.DialogHeader,{children:[(0,d.jsx)(o.DialogTitle,{children:"批量切换子仓库分支结果"}),(0,d.jsx)(o.DialogDescription,{children:(()=>{let a=aA.filter(a=>a.success).length,b=aA.length-a;return`共 ${aA.length} 个项目,成功 ${a} 个,失败 ${b} 个`})()})]}),(0,d.jsx)("div",{className:"space-y-2",children:0===aA.length?(0,d.jsx)("p",{className:"text-sm text-gray-500",children:"暂无结果"}):aA.map(a=>(0,d.jsx)("div",{className:`rounded-md border px-3 py-2 text-sm ${a.success?"bg-green-50 text-green-700":"bg-red-50 text-red-700"}`,children:(0,d.jsxs)("div",{className:"flex items-start justify-between",children:[(0,d.jsx)("span",{className:"font-medium",children:a.projectName}),(0,d.jsx)("span",{className:"ml-4 text-right",children:a.success?"成功":a.reason||"失败"})]})},a.projectName))}),(0,d.jsx)(o.DialogFooter,{children:(0,d.jsx)(g.Button,{onClick:()=>aD(!1),children:"关闭"})})]})})]})}}}};
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=packages_server_src_65e3de99._.js.map
|