zen-gitsync 2.16.45 → 2.16.47
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/package.json +1 -1
- package/src/ui/public/assets/{AgentView-YLlieash.js → AgentView-CsrTJSZP.js} +1 -1
- package/src/ui/public/assets/AppVersionBadge-CxNeSphI.js +2 -0
- package/src/ui/public/assets/AppVersionBadge-D5tJzJfa.css +1 -0
- package/src/ui/public/assets/{BranchSelector-Dxp8ZcBR.js → BranchSelector-BLeOaHuU.js} +1 -1
- package/src/ui/public/assets/{CommitForm-CApcOqdq.js → CommitForm-BKNbRQtI.js} +1 -1
- package/src/ui/public/assets/{CommonDialog-BkP0vc6s.js → CommonDialog-CbnJyylZ.js} +1 -1
- package/src/ui/public/assets/{EditorView-BWnLKzwg.js → EditorView-3G-LmDvr.js} +1 -1
- package/src/ui/public/assets/{FileDiffViewer-Dz0Pm4oj.js → FileDiffViewer-EGmgRSCr.js} +1 -1
- package/src/ui/public/assets/{FlowExecutionViewer-BI3hh4SD.js → FlowExecutionViewer-DUiFF53g.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-BXvmQZcz.js → FlowOrchestrationWorkspace-LgsL8hZm.js} +1 -1
- package/src/ui/public/assets/{LogList-HrkEiBSr.js → LogList-SOJaLdSo.js} +1 -1
- package/src/ui/public/assets/{MindmapView-BCPPuqCL.js → MindmapView-C4tbxu5V.js} +1 -1
- package/src/ui/public/assets/{MonitorView-Cnj7MmD9.js → MonitorView-C1bjd0HR.js} +1 -1
- package/src/ui/public/assets/{ProjectStartupButton-DqjqZz5g.js → ProjectStartupButton-CSTOdWvJ.js} +1 -1
- package/src/ui/public/assets/{RemoteRepoCard-BgUYxylZ.js → RemoteRepoCard-BAAGtTil.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-CjIoOCFI.js → SourceMapView-hj25n1Fz.js} +1 -1
- package/src/ui/public/assets/{SvgIcon-D09jyLT8.js → SvgIcon-DSJyYTg_.js} +1 -1
- package/src/ui/public/assets/{TemplateManager-BPfPLCTs.js → TemplateManager-LnSmcZ7A.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-WKU1IkID.js → UserInputNode-CUfBMDIJ.js} +1 -1
- package/src/ui/public/assets/{WorkbenchView-CJS3HN_I.js → WorkbenchView-SI-uq7au.js} +1 -1
- package/src/ui/public/assets/{_plugin-vue_export-helper-BnAB3nEU.js → _plugin-vue_export-helper-BS5W1_fI.js} +4 -4
- package/src/ui/public/assets/{configStore-CsNLFoMc.js → configStore-CAMCxRH1.js} +1 -1
- package/src/ui/public/assets/gitStore-Ctig7cwX.js +4 -0
- package/src/ui/public/assets/{index-Bm6Jh7F2.css → index-DDLVzTV_.css} +1 -1
- package/src/ui/public/assets/{index-Dr8V0ZRm.js → index-R9wRVknj.js} +5 -5
- package/src/ui/public/assets/{previewSandbox-BTAxBeYa.js → previewSandbox-CFV-c6MA.js} +0 -0
- package/src/ui/public/index.html +10 -10
- package/src/ui/server/routes/npm.js +65 -10
- package/src/ui/server/routes/npm.test.js +146 -0
- package/src/ui/server/utils/gitArgs.js +12 -1
- package/src/ui/server/utils/gitArgs.test.js +33 -0
- package/src/ui/public/assets/AppVersionBadge-CoLw1lcc.js +0 -2
- package/src/ui/public/assets/AppVersionBadge-Dj6H729N.css +0 -1
- package/src/ui/public/assets/gitStore-DriA_V_g.js +0 -4
|
Binary file
|
package/src/ui/public/index.html
CHANGED
|
@@ -10,23 +10,23 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-R9wRVknj.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/element-plus-CgvKuDqm.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/monaco-BScz9v63.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C3wWuk8k.js">
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/configStore-
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BS5W1_fI.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-DSJyYTg_.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-CbnJyylZ.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/configStore-CAMCxRH1.js">
|
|
22
22
|
<link rel="modulepreload" crossorigin href="/assets/socket-io-TBtw0msS.js">
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/gitStore-
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/gitStore-Ctig7cwX.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/useThemeObserver-Dy51enDy.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/flow-mindmap-Cmo6I23w.js">
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/previewSandbox-
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-
|
|
26
|
+
<link rel="modulepreload" crossorigin href="/assets/previewSandbox-CFV-c6MA.js">
|
|
27
|
+
<link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-EGmgRSCr.js">
|
|
28
28
|
<link rel="modulepreload" crossorigin href="/assets/toolsStore-Rvql5dE6.js">
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/TemplateManager-
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/TemplateManager-LnSmcZ7A.js">
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/vue-flow-BCU8bNHJ.js">
|
|
31
31
|
<link rel="stylesheet" crossorigin href="/assets/element-plus-qRNmrFVy.css">
|
|
32
32
|
<link rel="stylesheet" crossorigin href="/assets/monaco-B2lPvkrc.css">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<link rel="stylesheet" crossorigin href="/assets/FileDiffViewer-DEUEoYdY.css">
|
|
39
39
|
<link rel="stylesheet" crossorigin href="/assets/TemplateManager-C0rm8xfC.css">
|
|
40
40
|
<link rel="stylesheet" crossorigin href="/assets/vue-flow-CavUTEzp.css">
|
|
41
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
41
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DDLVzTV_.css">
|
|
42
42
|
</head>
|
|
43
43
|
<body>
|
|
44
44
|
<div id="app"></div>
|
|
@@ -26,6 +26,54 @@ import { getRegistryPath } from '../utils/instanceRegistry.js';
|
|
|
26
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
27
|
const __dirname = path.dirname(__filename);
|
|
28
28
|
|
|
29
|
+
// ── 环境探测(模块级纯函数,便于单测)───────────────────────────────────
|
|
30
|
+
//
|
|
31
|
+
// 判定"要不要 sudo 才能全局装包"这件事,历史上曾经写成 `process.platform === 'win32'`,
|
|
32
|
+
// 这个判断在 MSYS/Cygwin 环境下会翻车:那种 Node 是伪 Unix 构建,platform 报 'linux',
|
|
33
|
+
// 于是走进 Unix 分支、去调 sudo —— 而 MSYS 自带的 sudo 只是个只会输出
|
|
34
|
+
// "sudo: a password is required" 的桩程序,跟真实密码无关,用户无论输什么都装不上。
|
|
35
|
+
//
|
|
36
|
+
// 正确做法(对齐 npm / nvm 的处理思路):
|
|
37
|
+
// 1. 只要不是真 Unix(win32 / MSYS / Cygwin)→ 永远不需要 sudo;
|
|
38
|
+
// 2. 是真 Unix 但已经是 root(uid 0,如装了 setuid 的 node)→ 不需要 sudo;
|
|
39
|
+
// 3. 其余(真 Unix 的普通用户)→ 需要 sudo。
|
|
40
|
+
export function needsSudo({
|
|
41
|
+
platform = process.platform,
|
|
42
|
+
env = process.env,
|
|
43
|
+
getuid = process.getuid,
|
|
44
|
+
} = {}) {
|
|
45
|
+
// MSYS / Cygwin 会把这两个变量注入进程环境,是识别伪 Unix 最可靠的信号
|
|
46
|
+
const isMsysLike = !!(env.MSYSTEM || env.CYGWIN)
|
|
47
|
+
if (isMsysLike) return false
|
|
48
|
+
if (platform === 'win32') return false
|
|
49
|
+
if (typeof getuid === 'function') {
|
|
50
|
+
try {
|
|
51
|
+
if (getuid() === 0) return false
|
|
52
|
+
} catch { /* getuid 在某些环境会抛,落回"需要 sudo" */ }
|
|
53
|
+
}
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 根据环境决定 npm 可执行文件名 / 是否需要 sudo / 是否要走 shell。
|
|
58
|
+
// npm 在 Windows 上是 .cmd 批处理,spawn 必须配合 shell:true 才能找到;
|
|
59
|
+
// MSYS 环境有 node 前缀里的 `npm`(sh shim),直接 spawn 即可。
|
|
60
|
+
export function resolveUpgradeCommand(pkgArgs, opts) {
|
|
61
|
+
const { platform = process.platform, env = process.env } = opts || {}
|
|
62
|
+
const isMsysLike = !!(env.MSYSTEM || env.CYGWIN)
|
|
63
|
+
const isWin = platform === 'win32'
|
|
64
|
+
const sudo = needsSudo({ ...opts, platform, env })
|
|
65
|
+
|
|
66
|
+
if (sudo) {
|
|
67
|
+
// -n 非交互:免密则直接过,需要密码就立刻失败,不把 GUI 挂死在密码提示上
|
|
68
|
+
return { cmd: 'sudo', args: ['-n', 'npm', ...pkgArgs], useShell: false, sudo }
|
|
69
|
+
}
|
|
70
|
+
if (isWin) {
|
|
71
|
+
return { cmd: 'npm.cmd', args: pkgArgs, useShell: true, sudo }
|
|
72
|
+
}
|
|
73
|
+
// 包含 MSYS/Cygwin:用裸 npm,绝不碰 sudo
|
|
74
|
+
return { cmd: 'npm', args: pkgArgs, useShell: false, sudo, isMsysLike }
|
|
75
|
+
}
|
|
76
|
+
|
|
29
77
|
export function registerNpmRoutes({
|
|
30
78
|
app,
|
|
31
79
|
getCurrentProjectPath
|
|
@@ -62,6 +110,8 @@ export function registerNpmRoutes({
|
|
|
62
110
|
return null
|
|
63
111
|
}
|
|
64
112
|
|
|
113
|
+
// 环境探测 / 升级命令拼装 已提到模块顶部(见 import 之后的 needsSudo、resolveUpgradeCommand)
|
|
114
|
+
|
|
65
115
|
// 当前安装的版本(从外层 package.json 读取)
|
|
66
116
|
function getCurrentVersion() {
|
|
67
117
|
try {
|
|
@@ -176,13 +226,12 @@ export function registerNpmRoutes({
|
|
|
176
226
|
}
|
|
177
227
|
const finish = () => { try { res.end() } catch { /* ignore */ } }
|
|
178
228
|
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const cmd
|
|
185
|
-
const finalArgs = isWin ? args : ['-n', npmCmd, ...args]
|
|
229
|
+
// 升级命令按"运行环境"而非"操作系统名"决定要不要 sudo。
|
|
230
|
+
// 之前用 process.platform === 'win32' 判断,MSYS/Cygwin 的伪 Unix Node
|
|
231
|
+
// (platform 报 'linux')会误走 sudo 分支,撞上 MSYS 那个假 sudo 桩程序卡死。
|
|
232
|
+
// 详见 needsSudo / resolveUpgradeCommand 的注释。
|
|
233
|
+
const upgradeArgs = ['install', '-g', 'zen-gitsync', '--registry', 'https://registry.npmjs.org/']
|
|
234
|
+
const { cmd, args: finalArgs, useShell, sudo } = resolveUpgradeCommand(upgradeArgs)
|
|
186
235
|
|
|
187
236
|
activeUpgrade = send
|
|
188
237
|
const cleanup = () => { activeUpgrade = null }
|
|
@@ -193,7 +242,7 @@ export function registerNpmRoutes({
|
|
|
193
242
|
env: { ...process.env, FORCE_COLOR: '0' },
|
|
194
243
|
windowsHide: true,
|
|
195
244
|
// Windows 下调用 .cmd 必须 shell:true
|
|
196
|
-
shell:
|
|
245
|
+
shell: useShell
|
|
197
246
|
})
|
|
198
247
|
} catch (err) {
|
|
199
248
|
send({ type: 'error', message: `启动升级进程失败: ${err.message}` })
|
|
@@ -219,8 +268,14 @@ export function registerNpmRoutes({
|
|
|
219
268
|
latestVersionCache = { value: null, at: 0 }
|
|
220
269
|
} else {
|
|
221
270
|
let hint = ''
|
|
222
|
-
if (
|
|
223
|
-
|
|
271
|
+
if (sudo) {
|
|
272
|
+
// 只有真的走了 sudo 分支才提示提权;Windows / MSYS / root 都不该看到这句
|
|
273
|
+
hint = '(提示:全局安装需要管理员权限,请用管理员权限重启 GUI 后再试,'
|
|
274
|
+
+ '或手动执行 npm install -g zen-gitsync)'
|
|
275
|
+
} else {
|
|
276
|
+
// 无 sudo 仍失败:大概率是 npm prefix 目录无写权限,给出可自救的手动命令
|
|
277
|
+
hint = '(提示:可手动执行 npm install -g zen-gitsync 后重启 GUI;'
|
|
278
|
+
+ '若报 EACCES,请检查 npm prefix -g 目录的写权限)'
|
|
224
279
|
}
|
|
225
280
|
send({ type: 'error', message: `\n升级失败,退出码 ${code}${hint ? ' ' + hint : ''}\n` })
|
|
226
281
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// 自升级命令环境判定回归测试(2026-09-12)
|
|
16
|
+
//
|
|
17
|
+
// 背景:GUI 里点「升级」曾固定执行 `sudo -n npm install -g zen-gitsync` 并报
|
|
18
|
+
// "sudo: 需要密码",用户无论输什么都装不上。根因是判定写成了
|
|
19
|
+
// `process.platform === 'win32'`,而 MSYS/Cygwin 的 Node 是伪 Unix 构建
|
|
20
|
+
// (platform 报 'linux'),于是误走 sudo 分支,撞上 MSYS 那个只会输出
|
|
21
|
+
// "a password is required" 的 sudo 桩程序。
|
|
22
|
+
//
|
|
23
|
+
// 这里守住两条契约:
|
|
24
|
+
// 1. needsSudo() 只在"真 Unix + 非 root"时返回 true;
|
|
25
|
+
// 2. resolveUpgradeCommand() 在 MSYS/Cygwin 下拼出的命令里绝不能出现 sudo。
|
|
26
|
+
import { test } from 'node:test'
|
|
27
|
+
import assert from 'node:assert/strict'
|
|
28
|
+
import { needsSudo, resolveUpgradeCommand } from './npm.js'
|
|
29
|
+
|
|
30
|
+
// ── needsSudo:环境判定 ────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
test('needsSudo: Windows 原生 Node 不需要 sudo', () => {
|
|
33
|
+
assert.equal(needsSudo({ platform: 'win32', env: {}, getuid: undefined }), false)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('needsSudo: MSYS/Cygwin 的伪 Unix Node 不需要 sudo(本次 bug 的核心场景)', () => {
|
|
37
|
+
// MSYS2 / Git Bash:platform 报 linux,但注入 MSYSTEM
|
|
38
|
+
assert.equal(
|
|
39
|
+
needsSudo({ platform: 'linux', env: { MSYSTEM: 'MINGW64' }, getuid: undefined }),
|
|
40
|
+
false
|
|
41
|
+
)
|
|
42
|
+
// Cygwin:platform 报 linux,但注入 CYGWIN
|
|
43
|
+
assert.equal(
|
|
44
|
+
needsSudo({ platform: 'linux', env: { CYGWIN: 'ntsec' }, getuid: undefined }),
|
|
45
|
+
false
|
|
46
|
+
)
|
|
47
|
+
// Git Bash 通常两个都有
|
|
48
|
+
assert.equal(
|
|
49
|
+
needsSudo({ platform: 'linux', env: { MSYSTEM: 'MINGW64', CYGWIN: 'nodosfilewarning' }, getuid: undefined }),
|
|
50
|
+
false
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('needsSudo: 真 Unix 的普通用户需要 sudo', () => {
|
|
55
|
+
assert.equal(needsSudo({ platform: 'linux', env: {}, getuid: () => 1000 }), true)
|
|
56
|
+
assert.equal(needsSudo({ platform: 'darwin', env: {}, getuid: () => 501 }), true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('needsSudo: 真 Unix 的 root(uid 0)不需要 sudo', () => {
|
|
60
|
+
assert.equal(needsSudo({ platform: 'linux', env: {}, getuid: () => 0 }), false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('needsSudo: getuid 缺失时不崩,保守返回需要 sudo', () => {
|
|
64
|
+
assert.equal(needsSudo({ platform: 'linux', env: {}, getuid: undefined }), true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('needsSudo: getuid 抛异常时不崩,保守返回需要 sudo', () => {
|
|
68
|
+
assert.equal(
|
|
69
|
+
needsSudo({ platform: 'linux', env: {}, getuid: () => { throw new Error('EPERM') } }),
|
|
70
|
+
true
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('needsSudo: 默认参数取自 process,不会抛错', () => {
|
|
75
|
+
assert.equal(typeof needsSudo(), 'boolean')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// ── resolveUpgradeCommand:命令拼装 ────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const PKG_ARGS = ['install', '-g', 'zen-gitsync', '--registry', 'https://registry.npmjs.org/']
|
|
81
|
+
|
|
82
|
+
test('resolveUpgradeCommand: Windows 原生走 npm.cmd + shell:true,无 sudo', () => {
|
|
83
|
+
const r = resolveUpgradeCommand(PKG_ARGS, { platform: 'win32', env: {}, getuid: undefined })
|
|
84
|
+
assert.equal(r.cmd, 'npm.cmd')
|
|
85
|
+
assert.equal(r.useShell, true)
|
|
86
|
+
assert.equal(r.sudo, false)
|
|
87
|
+
assert.deepEqual(r.args, PKG_ARGS)
|
|
88
|
+
assert.ok(!r.args.includes('sudo'))
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('resolveUpgradeCommand: MSYS 下绝不出现 sudo(回归本次 bug)', () => {
|
|
92
|
+
const r = resolveUpgradeCommand(PKG_ARGS, {
|
|
93
|
+
platform: 'linux',
|
|
94
|
+
env: { MSYSTEM: 'MINGW64' },
|
|
95
|
+
getuid: undefined,
|
|
96
|
+
})
|
|
97
|
+
assert.equal(r.cmd, 'npm')
|
|
98
|
+
assert.equal(r.sudo, false)
|
|
99
|
+
assert.equal(r.isMsysLike, true)
|
|
100
|
+
assert.ok(
|
|
101
|
+
!r.args.some((a) => String(a).includes('sudo')),
|
|
102
|
+
`MSYS 环境不应拼出 sudo,实际: ${[r.cmd, ...r.args].join(' ')}`
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('resolveUpgradeCommand: Cygwin 下同样不走 sudo', () => {
|
|
107
|
+
const r = resolveUpgradeCommand(PKG_ARGS, {
|
|
108
|
+
platform: 'linux',
|
|
109
|
+
env: { CYGWIN: 'ntsec' },
|
|
110
|
+
getuid: undefined,
|
|
111
|
+
})
|
|
112
|
+
assert.equal(r.sudo, false)
|
|
113
|
+
assert.match(r.cmd, /^npm(\.cmd)?$/)
|
|
114
|
+
assert.ok(!r.args.some((a) => String(a).includes('sudo')))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('resolveUpgradeCommand: 真 Unix 普通用户走 sudo -n', () => {
|
|
118
|
+
const r = resolveUpgradeCommand(PKG_ARGS, { platform: 'linux', env: {}, getuid: () => 1000 })
|
|
119
|
+
assert.equal(r.cmd, 'sudo')
|
|
120
|
+
assert.equal(r.sudo, true)
|
|
121
|
+
// -n 保证非交互:免密直接过,需密码立刻失败,不挂死 GUI
|
|
122
|
+
assert.deepEqual(r.args, ['-n', 'npm', ...PKG_ARGS])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('resolveUpgradeCommand: 真 Unix root 直接裸装 npm,无 sudo', () => {
|
|
126
|
+
const r = resolveUpgradeCommand(PKG_ARGS, { platform: 'linux', env: {}, getuid: () => 0 })
|
|
127
|
+
assert.equal(r.cmd, 'npm')
|
|
128
|
+
assert.equal(r.sudo, false)
|
|
129
|
+
assert.deepEqual(r.args, PKG_ARGS)
|
|
130
|
+
assert.ok(!r.args.includes('sudo'))
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('resolveUpgradeCommand: 完整命令串里不会出现 "sudo -n npm" 之外的提权痕迹', () => {
|
|
134
|
+
// 三个免提权场景逐个检查拼出的完整命令
|
|
135
|
+
const cases = [
|
|
136
|
+
{ platform: 'win32', env: {}, getuid: undefined },
|
|
137
|
+
{ platform: 'linux', env: { MSYSTEM: 'MINGW64' }, getuid: undefined },
|
|
138
|
+
{ platform: 'linux', env: {}, getuid: () => 0 },
|
|
139
|
+
]
|
|
140
|
+
for (const c of cases) {
|
|
141
|
+
const r = resolveUpgradeCommand(PKG_ARGS, c)
|
|
142
|
+
const full = [r.cmd, ...r.args].join(' ')
|
|
143
|
+
assert.ok(!full.includes('sudo'), `不应含 sudo: ${full}`)
|
|
144
|
+
assert.ok(full.includes('zen-gitsync'), `应含包名: ${full}`)
|
|
145
|
+
}
|
|
146
|
+
})
|
|
@@ -131,10 +131,11 @@ export function assertGitConfigValue(value, label = '配置项取值') {
|
|
|
131
131
|
/**
|
|
132
132
|
* 校验 remote URL。
|
|
133
133
|
*
|
|
134
|
-
* 除了 remote 名要过 assertGitRef,URL
|
|
134
|
+
* 除了 remote 名要过 assertGitRef,URL 本身有三个坑:
|
|
135
135
|
* 1. git 支持 `ext::` 传输协议,值是 shell 命令 —— `git remote add origin 'ext::sh -c id'`
|
|
136
136
|
* 会在下次 fetch/pull 时执行它。
|
|
137
137
|
* 2. 以 - 开头同样会被解析成选项。
|
|
138
|
+
* 3. 含空白的 URL 会被 git 按 scp 形式误拆,报出看不懂的错。
|
|
138
139
|
*/
|
|
139
140
|
export function assertGitRemoteUrl(value, label = '远程仓库地址') {
|
|
140
141
|
const s = String(value ?? '').trim()
|
|
@@ -143,6 +144,16 @@ export function assertGitRemoteUrl(value, label = '远程仓库地址') {
|
|
|
143
144
|
// ext:: 后面跟的是要执行的命令,等于把 remote 配置变成 RCE
|
|
144
145
|
if (/^ext::/i.test(s)) throw new HttpError(400, `不支持 ext:: 传输协议`)
|
|
145
146
|
if (/[\x00-\x1f\x7f]/.test(s)) throw new HttpError(400, `${label}含有非法字符`)
|
|
147
|
+
// 空白(空格 / 制表符 / 全角空格)本身写进去不报错,但会在下次 push 时爆掉:
|
|
148
|
+
// git 把 `origin git@gitee.com:x/y.git` 按 scp 形式拆成 user="origin git"、
|
|
149
|
+
// host="gitee.com",最终抛出 `Permission denied, user: 'origin git'` ——
|
|
150
|
+
// 报错里看不出真正的原因是地址多了个前缀。最常见来源是从 `git remote -v`
|
|
151
|
+
// 的输出里复制粘贴(它打印的是「远程名 + 制表符 + 地址」)。
|
|
152
|
+
// 本地路径形式的 remote(如 /c/My Repos/foo.git)允许含空格,故做豁免。
|
|
153
|
+
const looksLikeLocalPath = /^(\/|\.{1,2}\/|~\/|[A-Za-z]:[\\/]|\\\\)/.test(s)
|
|
154
|
+
if (!looksLikeLocalPath && /\s/.test(s)) {
|
|
155
|
+
throw new HttpError(400, `${label}不能包含空格或制表符(检查是否把"远程名 + 地址"一起粘贴了)`)
|
|
156
|
+
}
|
|
146
157
|
return s
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -152,6 +152,39 @@ test('assertGitRemoteUrl: 拒绝 ext:: 传输协议(会执行 shell 命令)', ()
|
|
|
152
152
|
}
|
|
153
153
|
})
|
|
154
154
|
|
|
155
|
+
test('assertGitRemoteUrl: 放行含空格的本地路径(豁免空白检查)', () => {
|
|
156
|
+
// 本地路径形式的 remote 允许含空格,不能被空白检查误伤
|
|
157
|
+
for (const ok of [
|
|
158
|
+
'/c/My Repos/foo.git',
|
|
159
|
+
'./my repo.git',
|
|
160
|
+
'../my repo.git',
|
|
161
|
+
'C:\\My Repos\\foo.git',
|
|
162
|
+
'C:/My Repos/foo.git',
|
|
163
|
+
'\\\\server\\my share\\foo.git'
|
|
164
|
+
]) {
|
|
165
|
+
assert.equal(assertGitRemoteUrl(ok), ok, `应放行本地路径 ${ok}`)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('回归:拒绝把「远程名 + 地址」一起粘贴进来的 URL', () => {
|
|
170
|
+
// 真实事故:用户从 `git remote -v` 的输出里复制粘贴(它的格式是
|
|
171
|
+
// 「远程名 + 制表符 + 地址」),于是地址被存成 "origin git@gitee.com:x/y.git"。
|
|
172
|
+
// 写入时 git 不报错,直到 push 才抛 `Permission denied, user: 'origin git'` ——
|
|
173
|
+
// 因为 git 按 scp 形式把它拆成了 user="origin git"。报错完全指不到真正原因,
|
|
174
|
+
// 必须在写 remote 之前就拦掉。
|
|
175
|
+
for (const bad of [
|
|
176
|
+
'origin git@gitee.com:xze333221/personal-website.git',
|
|
177
|
+
'origin\tgit@gitee.com:xze333221/personal-website.git',
|
|
178
|
+
'origin https://gitee.com/xze333221/personal-website.git',
|
|
179
|
+
'https://gitee.com/xze333221/my repo.git',
|
|
180
|
+
'git@gitee.com:xze333221/personal-website .git',
|
|
181
|
+
// 全角空格同样要拦(中文输入法下很容易打出来)
|
|
182
|
+
'origin\u3000git@gitee.com:xze333221/personal-website.git'
|
|
183
|
+
]) {
|
|
184
|
+
assertRejected(assertGitRemoteUrl, bad, 'assertGitRemoteUrl')
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
155
188
|
test('assertGitConfigKey: 放行前端实际用到的配置项', () => {
|
|
156
189
|
for (const ok of [
|
|
157
190
|
'user.name',
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-C0FnF6B9.js";import{$n as e,B as t,Bn as n,Br as r,D as i,Dn as a,Dr as o,En as s,Et as c,G as l,Gr as u,Hn as d,Ln as f,Nn as p,Sn as m,Wn as h,Y as g,bn as _,i as v,kn as y,nt as b,or as x,pr as S,r as C,rt as w,tr as T,vn as E,xn as D,yn as O,yr as k}from"./element-plus-CgvKuDqm.js";import{n as A,t as j}from"./_plugin-vue_export-helper-BnAB3nEU.js";async function M(e,t){let n=e.body.getReader(),r=new TextDecoder(`utf-8`),i=``;for(;;){let{done:e,value:a}=await n.read();if(e)break;i+=r.decode(a,{stream:!0});let o=i.split(`
|
|
2
|
-
`);i=o.pop()??``;for(let e of o)e.trim()&&t(e)}i.trim()&&t(i)}async function N(){let e=await fetch(`/api/app-version`);return e.ok?e.json():{success:!1,current:``,latest:null,hasUpdate:!1,error:`HTTP ${e.status}`}}async function P(e){let t=await fetch(`/api/app-upgrade`,{method:`POST`});if(!t.ok){let n=`HTTP ${t.status}`;try{let e=await t.json();e?.error&&(n=e.error)}catch{}e({type:`error`,message:n}),e({type:`done`,code:t.status});return}if(!t.body){e({type:`error`,message:`响应无 body`}),e({type:`done`,code:-1});return}await M(t,t=>{try{e(JSON.parse(t))}catch{}})}async function F(){let e=await fetch(`/api/app-restart`,{method:`POST`});if(!e.ok){let t=`HTTP ${e.status}`;try{let n=await e.json();n?.error&&(t=n.error)}catch{}throw Error(t)}if(!e.body)throw Error(`响应无 body`);return new Promise((t,n)=>{let r=!1,i=e=>{r||(r=!0,e())};M(e,e=>{try{let r=JSON.parse(e);r.type===`ready`&&typeof r.port==`number`&&r.port>0?i(()=>t(r.port)):r.type===`timeout`?i(()=>n(Error(`restart.timeout`))):r.type===`error`&&i(()=>n(Error(r.message||`restart.failed`)))}catch{}}).then(()=>{i(()=>n(Error(`restart.stream-closed`)))}).catch(()=>{})})}p(),x(),o();var I={key:3},L={key:4},R={key:5},z={key:0,class:`upgrade-hint`},B={key:0,class:`upgrade-countdown`},V=j(y({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean},logs:{},status:{},countdown:{default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(n,{emit:o}){let l=n,d=o,p=S(null);e(()=>l.logs,async()=>{await f(),p.value&&(p.value.scrollTop=p.value.scrollHeight)});function v(){l.status!==`running`&&(l.status===`success`&&l.countdown>0&&d(`cancel`),d(`update:modelValue`,!1))}function y(){d(`retry`)}function x(){d(`restart`)}function C(){d(`cancel`),d(`update:modelValue`,!1)}return(e,o)=>{let l=g,f=t,S=i;return h(),_(S,{"model-value":n.modelValue,"onUpdate:modelValue":o[0]||=e=>d(`update:modelValue`,e),title:k(A)(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":n.status!==`running`,"append-to-body":``,onClose:v},{footer:T(()=>[n.status===`failed`?(h(),_(f,{key:0,type:`primary`,onClick:y},{default:T(()=>[s(u(k(A)(`@F13B4:重试`)),1)]),_:1})):D(``,!0),n.status===`success`?(h(),_(f,{key:1,type:`primary`,onClick:x},{default:T(()=>[s(u(k(A)(`@F13B4:立即重启并刷新`)),1)]),_:1})):D(``,!0),n.status===`success`?(h(),_(f,{key:2,onClick:C},{default:T(()=>[s(u(k(A)(`@F13B4:稍后手动重启`)),1)]),_:1})):D(``,!0),n.status===`running`?(h(),_(f,{key:3,disabled:``},{default:T(()=>[s(u(k(A)(`@F13B4:升级中`))+`... `,1)]),_:1})):D(``,!0)]),default:T(()=>[O(`div`,{class:r([`upgrade-status`,`is-${n.status}`])},[n.status===`running`?(h(),_(l,{key:0,class:`is-loading`},{default:T(()=>[a(k(c))]),_:1})):n.status===`success`?(h(),_(l,{key:1,color:`#67c23a`},{default:T(()=>[a(k(b))]),_:1})):(h(),_(l,{key:2,color:`#f56c6c`},{default:T(()=>[a(k(w))]),_:1})),n.status===`running`?(h(),m(`span`,I,u(k(A)(`@F13B4:升级中`)),1)):n.status===`success`?(h(),m(`span`,L,u(k(A)(`@F13B4:升级完成`)),1)):(h(),m(`span`,R,u(k(A)(`@F13B4:升级失败`)),1))],2),n.status===`success`?(h(),m(`p`,z,[s(u(k(A)(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),n.countdown>0?(h(),m(`span`,B,u(k(A)(`@F13B4:{seconds} 秒后自动刷新`,{seconds:n.countdown})),1)):D(``,!0)])):D(``,!0),O(`pre`,{ref_key:`logEl`,ref:p,class:`upgrade-log`},u(n.logs||k(A)(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}}}),[[`__scopeId`,`data-v-3de8edac`]]);p(),x(),o();var H={class:`app-version-badge`},U=[`aria-label`],W=[`aria-label`],G=[`title`,`aria-label`],K={class:`upgrade-btn__label`},q={class:`upgrade-btn__version`},J=`https://github.com/xz333221/zen-gitsync/releases`,Y=5,X=`app-version-check`,Z=36e5,Q=j(y({__name:`AppVersionBadge`,setup(e){let t=`2.16.45`,r=S(t),i=E(()=>r.value||t),o=S(null),s=S(!1),c=S(``),f=S(`running`),p=S(0),g=null,_=S(!1);function y(){try{let e=sessionStorage.getItem(X);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>Z?null:n}catch{return null}}function b(e){try{sessionStorage.setItem(X,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function x(e=!1){let t=y();t&&(o.value=t);try{let t=await N();o.value=t,b(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&v({type:`success`,message:A(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||v({type:`warning`,message:A(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function w(){if(o.value?.hasUpdate){try{await C.confirm(A(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:o.value.current,latest:o.value.latest}),A(`@F13B4:升级`),{confirmButtonText:A(`@F13B4:确定升级`),cancelButtonText:A(`@F13B4:取消`),type:`info`})}catch{return}c.value=``,f.value=`running`,s.value=!0,await P(e=>{if(e.type===`done`){if(f.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(X)}catch{}o.value?.latest&&(r.value=o.value.latest),j()}}else e.message&&(c.value+=e.message)})}}function j(){M(),p.value=Y,g=setInterval(()=>{--p.value,p.value<=0&&(g&&=(clearInterval(g),null),L())},1e3)}function M(){g&&=(clearInterval(g),null),p.value=0}function I(){M()}async function L(){if(M(),!_.value){_.value=!0;try{let e=await F(),{pathname:t,search:n,hash:r}=window.location;window.location.href=`http://localhost:${e}${t}${n}${r}`}catch(e){let t=e?.message||``,n={"restart.timeout":`@F13B4:重启超时`,"restart.stream-closed":`@F13B4:重启失败,请刷新页面重试`,"restart.failed":`@F13B4:重启失败`}[t];v({type:`warning`,message:n?A(n):A(`@F13B4:重启失败 {error}`,{error:t})}),_.value=!1}}}return n(()=>{M()}),d(()=>{x(!0)}),(e,t)=>{let n=l;return h(),m(`div`,H,[a(n,{content:k(A)(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:T(()=>[O(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":k(A)(`@F13B4:在 GitHub 上查看源码`)},[...t[1]||=[O(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[O(`path`,{d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z`})],-1)]],8,U)]),_:1},8,[`content`]),a(n,{content:k(A)(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:T(()=>[O(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":k(A)(`@F13B4:在 npm 上查看包`)},[...t[2]||=[O(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[O(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)]],8,W)]),_:1},8,[`content`]),a(n,{content:k(A)(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:T(()=>[O(`a`,{href:J,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+u(i.value),1)]),_:1},8,[`content`]),o.value?.hasUpdate?(h(),m(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:k(A)(`@F13B4:发现新版本 {version},点击升级`,{version:o.value.latest??``}),"aria-label":k(A)(`@F13B4:发现新版本 {version},点击升级`,{version:o.value.latest??``}),onClick:w},[t[3]||=O(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),O(`span`,K,u(k(A)(`@F13B4:升级`)),1),O(`span`,q,`v`+u(o.value.latest),1)],8,G)):D(``,!0),a(V,{modelValue:s.value,"onUpdate:modelValue":t[0]||=e=>s.value=e,logs:c.value,status:f.value,countdown:p.value,onRetry:w,onRestart:L,onCancel:I},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}}}),[[`__scopeId`,`data-v-bc7c0d5c`]]);export{Q as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.upgrade-status[data-v-3de8edac]{align-items:center;gap:var(--spacing-base);margin-bottom:var(--spacing-md);font-size:14px;font-weight:500;display:flex}.upgrade-status.is-running[data-v-3de8edac]{color:var(--el-color-primary)}.upgrade-status.is-success[data-v-3de8edac]{color:var(--el-color-success)}.upgrade-status.is-failed[data-v-3de8edac]{color:var(--el-color-danger)}.upgrade-status .is-loading[data-v-3de8edac]{animation:2s linear infinite rotating-3de8edac}.upgrade-log[data-v-3de8edac]{color:#d4d4d4;white-space:pre-wrap;word-break:break-all;background:#1e1e1e;border-radius:4px;height:320px;margin:0;padding:12px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;line-height:1.6;overflow:auto}.upgrade-hint[data-v-3de8edac]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-success);color:var(--el-color-success);background:#67c23a14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-countdown[data-v-3de8edac]{background:var(--el-color-success);color:#fff;border-radius:10px;margin-left:8px;padding:1px 8px;font-size:12px;font-weight:600;animation:1s ease-in-out infinite pulse-3de8edac;display:inline-block}@keyframes pulse-3de8edac{0%,to{opacity:1}50%{opacity:.55}}@keyframes rotating-3de8edac{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.app-version-badge[data-v-bc7c0d5c]{align-items:center;gap:var(--spacing-base);margin-left:auto;display:inline-flex}.badge-icon-link[data-v-bc7c0d5c]{width:22px;height:22px;color:var(--text-tertiary);opacity:.7;border-radius:4px;justify-content:center;align-items:center;text-decoration:none;transition:color .18s,opacity .18s,background .18s;display:inline-flex}.badge-icon-link[data-v-bc7c0d5c]:hover{opacity:1;color:var(--text-secondary);background:var(--bg-hover,#0000000f)}.badge-icon-link--npm[data-v-bc7c0d5c]:hover{color:var(--color-npm)}.badge-icon-link[data-v-bc7c0d5c]:first-child:hover{color:var(--text-primary)}.version-link[data-v-bc7c0d5c]{color:var(--text-secondary);border-radius:3px;padding:2px 6px;font-family:monospace;font-size:11px;text-decoration:none;transition:color .2s,background-color .2s}.version-link[data-v-bc7c0d5c]:hover{color:var(--color-primary);background-color:#0000000a}.upgrade-btn[data-v-bc7c0d5c]{appearance:none;cursor:pointer;color:#fff;background:linear-gradient(135deg, var(--color-primary) 0%, #3a8ee6 100%);height:22px;box-shadow:var(--shadow-sm);transition:transform var(--transition-fast) var(--ease-custom), box-shadow var(--transition-fast) var(--ease-custom), filter var(--transition-fast) var(--ease-custom);white-space:nowrap;-webkit-user-select:none;user-select:none;border:0;border-radius:11px;outline:none;align-items:center;gap:5px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:11px;font-weight:600;line-height:1;display:inline-flex}.upgrade-btn[data-v-bc7c0d5c]:hover{filter:brightness(1.08);box-shadow:var(--shadow-md);transform:translateY(-1px)}.upgrade-btn[data-v-bc7c0d5c]:active{filter:brightness(.96);box-shadow:var(--shadow-sm);transform:translateY(0)}.upgrade-btn[data-v-bc7c0d5c]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.upgrade-btn__dot[data-v-bc7c0d5c]{background:#fff;border-radius:50%;flex-shrink:0;width:6px;height:6px;animation:1.6s ease-in-out infinite upgrade-pulse-bc7c0d5c;box-shadow:0 0 0 2px #ffffff59}.upgrade-btn__label[data-v-bc7c0d5c]{letter-spacing:.5px}.upgrade-btn__version[data-v-bc7c0d5c]{background:#ffffff38;border-radius:6px;padding:1px 5px;font-family:monospace;font-size:10px;font-weight:500;line-height:1.2}@keyframes upgrade-pulse-bc7c0d5c{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.8)}}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-C0FnF6B9.js";import{Hn as e,Nn as t,Un as n,i as r,or as i,pr as a,vn as o}from"./element-plus-CgvKuDqm.js";import{p as s}from"./vendor-C3wWuk8k.js";import{n as c}from"./_plugin-vue_export-helper-BnAB3nEU.js";import{t as ee}from"./socket-io-TBtw0msS.js";import{t as te}from"./configStore-CsNLFoMc.js";var l=new Set([`5173`,`4173`,`5544`]),u=3e3;function d(){if(typeof window>`u`)return u;let e=window.location.port||`80`;if(l.has(e)){{let e=5545;if(!Number.isNaN(e))return e}return u}let t=parseInt(e,10);return Number.isNaN(t)?u:t}i(),t();var f=800,ne=2e3,re=d(),p=s(`git`,()=>{let t=te(),i=a(``),s=a([]),l=a(``),u=a(``),d=a(!1),p=a(!1),m=a(!1),h=a(0),g=a(``),_=a(!1),v=a(!1),y=a(!1),b=a(!1),x=a(!1),ie=a(!1),S=a(0),C=a(0),w=a(!1),T=a(``),ae=a(0),E=a(``),D=a(null),O=a(``),oe=a(``),k=a([]),se=a({staged:[],unstaged:[],untracked:[]}),A=a([]),j=a(!1),M=a(!1),N=a(!1),P=a(!1),F=a(!1),I=a(1),L=a(!0),ce=a(0),le=0,ue=a([]),de=a(!1),fe=a(!1),R=a(!1),z=a(!1);function pe(){i.value=``,s.value=[],l.value=``,u.value=``,d.value=!1,p.value=!1,m.value=!1,h.value=0,S.value=0,C.value=0,w.value=!1,T.value=``,ae.value=0,v.value=!1,y.value=!1,b.value=!1,x.value=!1,g.value=``,_.value=!1,k.value=[],se.value={staged:[],unstaged:[],untracked:[]},A.value=[],j.value=!1,M.value=!1,N.value=!1,P.value=!1,F.value=!1,I.value=1,L.value=!0,ce.value=0}async function B(e=!1,t=!1){if(m.value)try{console.log(c(`@C298B:获取分支状态...`));let n=`/api/branch-status`,r=[];e&&r.push(`force=true`),t&&r.push(`countOnly=true`),r.length>0&&(n+=`?`+r.join(`&`));let i=await(await fetch(n)).json();if(i){let e=S.value,t=C.value,n=w.value,r=T.value;if(S.value=i.ahead||0,C.value=i.behind||0,w.value=i.hasUpstream||!1,T.value=i.upstreamBranch||``,console.log(`${c(`@C298B:分支状态更新:领先 `)}${S.value}${c(`@C298B: 个提交,落后 `)}${C.value}${c(`@C298B: 个提交,上游分支:`)}${w.value?T.value:c(`@C298B:无`)}`),S.value!==e||C.value!==t||w.value!==n||T.value!==r){let n=Date.now();n-le>=ne?(le=n,console.log(`[getBranchStatus] 检测到分支状态变化 (ahead: ${e}→${S.value}, behind: ${t}→${C.value}, upstream: ${r||`无`}→${T.value||`无`}), 自动刷新右侧提交历史`),W(!1)):console.log(`[getBranchStatus] 分支状态变化但在 ${ne}ms 节流窗口内,跳过自动刷新`)}}}catch(e){console.error(`获取分支状态失败:`,e),S.value=0,C.value=0,w.value=!1,T.value=``}}async function me(){let e=Date.now();if(e-h.value<1e3)return console.log(`使用缓存的Git仓库状态:`,m.value?`是`:c(`@C298B:不是`)),m.value;try{let t=await(await fetch(`/api/current_directory`)).json();return m.value=t.isGitRepo===!0,h.value=e,console.log(`${c(`@C298B:当前目录`)}${m.value?c(`@C298B:是`):c(`@C298B:不是`)}${c(`@C298B:Git仓库`)}`),m.value}catch(t){return console.error(`检查Git仓库状态失败:`,t),m.value=!1,h.value=e,!1}}async function V(e=!1){try{let t=await(await fetch(e?`/api/branch?force=true`:`/api/branch`)).json();t.branch&&(i.value=t.branch,console.log(`${c(`@C298B:当前分支更新为: `)}${t.branch}${e?c(`@C298B: (强制刷新)`):``}`))}catch(e){console.error(`获取分支信息失败:`,e)}}async function H(){if(m.value)try{console.log(c(`@C298B:获取所有分支...`));let e=await(await fetch(`/api/branches`)).json();e.branches&&Array.isArray(e.branches)&&(s.value=e.branches,ae.value=Date.now(),console.log(`${c(`@C298B:获取到`)}${e.branches.length}${c(`@C298B:个分支`)}`))}catch(e){console.error(`获取所有分支信息失败:`,e)}}async function he(e){console.log(`切换到分支:`,e);try{d.value=!0;let t=await(await fetch(`/api/checkout`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({branch:e})})).json();return t.success?(r({message:`${c(`@C298B:已切换到分支: `)}${t.branch||e}`,type:`success`}),await V(!0),await B(!0),!0):(r({message:`${c(`@C298B:切换分支失败: `)}${t.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:切换分支失败: `)}${e.message}`,type:`error`}),!1}finally{d.value=!1}}async function ge(){try{let e=await(await fetch(`/api/user-info`)).json();l.value=e.name||``,u.value=e.email||``}catch(e){console.error(`获取用户信息失败:`,e)}}async function _e(e,t){if(!e.trim())return r({message:c(`@C298B:分支名称不能为空`),type:`warning`}),!1;try{p.value=!0;let n=await(await fetch(`/api/create-branch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({newBranchName:e,baseBranch:t||i.value})})).json();return n.success?(r({message:`${c(`@C298B:已创建并切换到分支: `)}${e}`,type:`success`}),await V(!0),await B(!0),await H(),!0):(r({message:`${c(`@C298B:创建分支失败: `)}${n.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:创建分支失败: `)}${e.message}`,type:`error`}),!1}finally{p.value=!1}}async function ve(){try{let e=await(await fetch(`/api/clear-user-config`,{method:`POST`})).json();return e.success?(l.value=``,u.value=``,r({message:c(`@C298B:已清除Git用户配置`),type:`success`}),!0):(r({message:`${c(`@C298B:清除配置失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:清除配置失败: `)}${e.message}`,type:`error`}),!1}}async function ye(e,t){try{let n=await(await fetch(`/api/restore-user-config`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:e,email:t})})).json();return n.success?(l.value=e,u.value=t,r({message:c(`@C298B:已恢复Git用户配置`),type:`success`}),!0):(r({message:`${c(`@C298B:恢复配置失败: `)}${n.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:恢复配置失败: `)}${e.message}`,type:`error`}),!1}}async function be(){if(!m.value)return r({message:c(`@C298B:当前目录不是Git仓库`),type:`warning`}),{success:!1,needsMerge:!1,error:c(`@C298B:当前目录不是Git仓库`),fullError:``,pullOutput:``};try{y.value=!0;let e=await(await fetch(`/api/pull`,{method:`POST`})).json();return e.success?(r({message:c(`@C298B:拉取成功`),type:`success`}),await Promise.all([B(!0),H(),we()]),{success:!0,hadChanges:!0}):{success:!1,needsMerge:e.needsMerge||!1,error:e.error||``,fullError:e.fullError||e.error||``,pullOutput:e.pullOutput||``}}catch(e){return{success:!1,needsMerge:!1,error:e.message,fullError:e.message,pullOutput:``}}finally{y.value=!1}}async function xe(){if(!m.value)return!1;if(!w.value)return console.log(`[bootFetch] no upstream, skip`),!1;try{b.value=!0;let e=C.value;console.log(`[bootFetch] start silent fetch --all, beforeBehind=${e}`);let t=await(await fetch(`/api/fetch-all`,{method:`POST`})).json();if(!t.success)return console.warn(`[bootFetch] fetch --all failed:`,t.error),!1;await B(!0);let n=C.value,r=n<e;return console.log(`[bootFetch] afterBehind=${n}, hasNewRemoteCommits=${r}`),r?(await W(!0),!0):!1}catch(e){return console.warn(`[bootFetch] silent fetch error (ignored):`,e.message),!1}finally{b.value=!1}}async function Se(){if(!m.value)return r({message:c(`@C298B:当前目录不是Git仓库`),type:`warning`}),!1;try{b.value=!0;let e=await(await fetch(`/api/fetch-all`,{method:`POST`})).json();return e.success?(r({message:c(`@C298B:获取所有远程分支信息成功`),type:`success`}),await Promise.all([B(),H()]),!0):(r({message:`${c(`@C298B:获取远程分支信息失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:获取远程分支信息失败: `)}${e.message}`,type:`error`}),!1}finally{b.value=!1}}async function Ce(){D.value&&(Ze(),D.value.disconnect());try{let e=`http://localhost:${re}`;if(console.log(`尝试连接Socket.IO服务器:`,e),D.value=ee(e,{reconnectionDelayMax:1e4,reconnection:!0,reconnectionAttempts:10,reconnectionDelay:1e3,timeout:2e4,autoConnect:!0,forceNew:!0,transports:[`websocket`,`polling`]}),!D.value){console.error(`Socket.IO初始化失败: socket为null`);return}console.log(c(`@C298B:Socket.IO客户端已创建,开始注册事件监听器...`)),D.value.on(`connect`,()=>{console.log(`✅ Socket.IO连接成功,事件监听器已生效,Socket ID:`,D.value?.id)}),D.value.on(`project_info`,e=>{O.value=e.projectPath,oe.value=e.projectRoomId,console.log(`${c(`@C298B:当前项目: `)}${e.projectPath}`),console.log(`${c(`@C298B:房间ID: `)}${e.projectRoomId}`)}),D.value.on(`project_changed`,e=>{console.log(`${c(`@C298B:项目已切换: `)}${e.oldProjectPath} -> ${e.newProjectPath}`),O.value=e.newProjectPath,oe.value=e.newProjectRoomId,D.value&&D.value.emit(`join_room`,e.newProjectRoomId),console.log(`${c(`@C298B:已加入新项目房间: `)}${e.newProjectRoomId}`)}),D.value.on(`disconnect`,e=>{console.log(`与Socket.IO服务器断开连接:`,e)}),D.value.on(`git_status_update`,e=>{if(console.log(`✅ 成功监听到 git_status_update 事件:`,new Date().toLocaleTimeString()),console.log(`git_status_update data ==>`,e),console.log(`currentProjectPath.value ==>`,O.value),console.log(`data.projectPath ==>`,e.projectPath),e.projectPath&&O.value&&e.projectPath!==O.value){console.log(`${c(`@C298B:忽略不同项目的状态更新: `)}${e.projectPath}`);return}console.log(c(`@C298B:正在更新 Git 文件状态...`)),e.porcelain!==void 0&&U(e.porcelain)}),D.value.on(`connect_error`,e=>{console.error(`Socket连接错误:`,e.message)}),D.value.on(`connect_timeout`,()=>{console.error(c(`@C298B:Socket连接超时`))}),D.value.on(`reconnect`,e=>{console.log(`${c(`@C298B:Socket重连成功,尝试次数: `)}${e}`)}),D.value.on(`reconnect_attempt`,e=>{console.log(`${c(`@C298B:Socket尝试重连,第 `)}${e}${c(`@C298B: 次尝试`)}`)}),D.value.on(`reconnect_error`,e=>{console.error(`Socket重连错误:`,e.message)}),D.value.on(`reconnect_failed`,()=>{console.error(c(`@C298B:Socket重连失败,已达到最大重试次数`))}),console.log(c(`@C298B:Socket.IO事件监听器注册完成:connect, project_info, project_changed, git_status_update, monitoring_status`)),D.value&&!D.value.connected&&(console.log(c(`@C298B:Socket未连接,尝试手动连接...`)),D.value.connect())}catch(e){console.error(`Socket.IO连接初始化失败:`,e)}}function U(e){if(e===void 0||e===``){A.value=[];return}let t=e.split(`
|
|
2
|
-
`),n=[];for(let e of t){let t=e.match(/^([ MADRCU\?]{2})\s+(.+)$/);if(t){let e=``,r=t[1],i=r.charAt(0),a=r.charAt(1);e=r===`UU`||r===`AA`||r===`DD`||r===`AU`||r===`UA`||r===`DU`||r===`UD`?`conflicted`:i===`A`||i===`M`||i===`D`||i===`R`?`added`:i===` `&&a===`M`?`modified`:i===` `&&a===`D`?`deleted`:i===` `&&a===`A`?`intent-to-add`:r===`??`?`untracked`:`other`,n.push({path:t[2],type:e})}}A.value=n}async function W(e=!0){if(!m.value){console.log(c(`@C298B:当前目录不是Git仓库,跳过加载提交历史`));return}try{j.value=!0,console.log(c(`@C298B:开始加载提交历史...`));let e=new Date().getTime(),t=await(await fetch(`/api/log?page=1&with_parents=true&_t=${e}`)).json();t&&t.data&&Array.isArray(t.data)?(k.value=[...t.data],console.log(`${c(`@C298B:提交历史加载完成,共 `)}${k.value.length}${c(`@C298B: 条记录`)}`)):(console.warn(`API返回的提交历史格式不正确:`,t),k.value=[])}catch(t){console.error(`获取提交历史失败:`,t),e&&r({message:`${c(`@C298B:获取提交历史失败: `)}${t.message}`,type:`error`})}finally{j.value=!1}}async function we(){console.log(c(`@C298B:刷新提交历史...`)),I.value=1,L.value=!1,await W(!0),ce.value=k.value.length,console.log(c(`@C298B:提交历史刷新完成`))}async function G(){if(!m.value){console.log(c(`@C298B:当前目录不是Git仓库,跳过加载Git状态`));return}try{M.value=!0,await K()}catch(e){console.error(`获取Git状态失败:`,e),r({message:`${c(`@C298B:获取Git状态失败: `)}${e.message}`,type:`error`})}finally{M.value=!1}}async function K(){if(console.log(c(`@C298B:开始获取Git状态(porcelain格式)...`)),!m.value){console.log(c(`@C298B:当前目录不是Git仓库,跳过加载Git状态`));return}try{let e=await(await fetch(`/api/status_porcelain`)).json();e.status===void 0?A.value=[]:U(e.status),ie.value=!!e.isMergeInProgress,e.isMergeInProgress&&e.mergeMessage&&!E.value&&(E.value=String(e.mergeMessage))}catch(e){console.error(`获取Git状态(porcelain)失败:`,e),r({message:`${c(`@C298B:获取Git状态(porcelain)失败: `)}${e.message}`,type:`error`}),A.value=[]}}async function Te(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{N.value=!0;let e=A.value.filter(e=>{let n=e.path.replace(/\\/g,`/`);return!t.lockedFiles.some(e=>{let t=e.replace(/\\/g,`/`);return n===t})});if(e.length===0)return r({message:c(`@C298B:没有需要暂存的文件(所有文件都被锁定)`),type:`warning`}),!1;if(e.length===A.value.length){let e=await(await fetch(`/api/add`,{method:`POST`})).json();return e.success?(r({message:c(`@C298B:文件已添加到暂存区`),type:`success`}),await K(),!0):(r({message:`${c(`@C298B:添加文件失败: `)}${e.error}`,type:`error`}),!1)}{let t=await(await fetch(`/api/add-filtered`,{method:`POST`})).json();if(t.success){let t=A.value.length-e.length,n=`${c(`@C298B:已添加 `)}${e.length}${c(`@C298B: 个文件到暂存区`)}`;return t>0&&(n+=`${c(`@C298B:,跳过 `)}${t}${c(`@C298B: 个锁定文件`)}`),r({message:n,type:`success`}),await K(),!0}return r({message:`${c(`@C298B:添加文件失败: `)}${t.error}`,type:`error`}),!1}}catch(e){return r({message:`${c(`@C298B:添加文件失败: `)}${e.message}`,type:`error`}),!1}finally{N.value=!1}}async function Ee(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{N.value=!0;let e=await(await fetch(`/api/add-all`,{method:`POST`})).json();return e.success?(r({message:c(`@C298B:所有文件已添加到暂存区`),type:`success`}),await K(),!0):(r({message:`${c(`@C298B:添加文件失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:添加文件失败: `)}${e.message}`,type:`error`}),!1}finally{N.value=!1}}async function De(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{N.value=!0;let t=await(await fetch(`/api/add-file`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filePath:e})})).json();return t.success?(r({message:c(`@C298B:文件已暂存`),type:`success`}),G(),!0):(r({message:`${c(`@C298B:暂存文件失败: `)}${t.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:暂存文件失败: `)}${e.message}`,type:`error`}),!1}finally{N.value=!1}}async function Oe(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{F.value=!0;let t=await(await fetch(`/api/unstage-file`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filePath:e})})).json();return t.success?(r({message:c(`@C298B:已取消暂存文件`),type:`success`}),G(),!0):(r({message:`${c(`@C298B:取消暂存失败: `)}${t.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:取消暂存失败: `)}${e.message}`,type:`error`}),!1}finally{F.value=!1}}function q(e){return new Promise(t=>setTimeout(t,e))}async function J(e,t=!1,n={}){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;let{autoRefresh:a=!0}=n;try{P.value=!0;let n=await(await fetch(`/api/commit`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({message:e,hasNewlines:e.includes(`
|
|
3
|
-
`),noVerify:t})})).json();if(n.success){let e=Array.isArray(n.skippedLockedFiles)?n.skippedLockedFiles:[],t=c(`@C298B:提交成功`);if(e.length>0&&(t+=c(`@C298B:,已跳过 `)+e.length+c(`@C298B: 个锁定文件`)),r({message:t,type:`success`}),a&&(await K(),await W(!1),B(!0)),g.value&&!w.value&&i.value)try{let e=await(await fetch(`/api/git/push-with-upstream`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({branch:i.value})})).json();e.success?(w.value=!0,T.value=`origin/${i.value}`,S.value=0,C.value=0,r.success(c(`@C298B:已自动推送并设置上游分支`)),await H(),B(!0)):r.warning(`${c(`@C298B:自动推送建上游失败: `)}${e.error||``}`)}catch(e){r.warning(`${c(`@C298B:自动推送建上游失败: `)}${e.message}`)}return!0}{let e=Array.isArray(n.skippedLockedFiles)?n.skippedLockedFiles:[],t=n.error||``;return e.length>0&&(t+=`(${c(`@C298B:跳过 `)}${e.length}${c(`@C298B: 个锁定文件`)})`),r({message:`${c(`@C298B:commitChanges 提交失败: `)}${t}`,type:`error`}),!1}}catch(e){return r({message:`${c(`@C298B:提交失败: `)}${e.message}`,type:`error`}),!1}finally{P.value=!1}}async function ke(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{v.value=!0;let e=await(await fetch(`/api/push`,{method:`POST`})).json();return e.success?(S.value=0,C.value=0,await Promise.all([G(),W()]),console.log(`推送成功,分支状态已设置为同步 (ahead=0, behind=0)`),!0):(e.errorCode===`EMPTY_REPO`?r({message:e.error||c(`@C298B:当前仓库没有任何提交,请先暂存并提交至少一个文件后再推送。`),type:`warning`}):r({message:`${c(`@C298B:推送失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:推送失败: `)}${e.message}`,type:`error`}),!1}finally{v.value=!1}}async function Ae(e){return m.value?new Promise(t=>{try{v.value=!0,fetch(`/api/push-with-progress`,{method:`POST`}).then(n=>{let i=n.body?.getReader(),a=new TextDecoder;if(!i)throw Error(`无法读取响应流`);(async()=>{try{for(;;){let{done:n,value:o}=await i.read();if(n)break;let s=a.decode(o,{stream:!0}).split(`
|
|
4
|
-
`);for(let n of s)if(n.startsWith(`data: `))try{let i=JSON.parse(n.slice(6));if(e&&e(i),i.type===`error`){v.value=!1,i.errorCode===`EMPTY_REPO`?r({message:i.error||c(`@C298B:当前仓库没有任何提交,请先暂存并提交至少一个文件后再推送。`),type:`warning`}):r({message:`${c(`@C298B:推送失败: `)}${i.error||c(`@C298B:未知错误`)}`,type:`error`}),t(!1);return}i.type===`complete`&&(v.value=!1,i.success?(S.value=0,C.value=0,Promise.all([G(),W()]),t(!0)):(r({message:`${c(`@C298B:推送失败: `)}${i.error}`,type:`error`}),t(!1)))}catch(e){console.error(`解析SSE数据失败:`,e)}}}catch(e){console.error(`读取流失败:`,e),v.value=!1,r({message:`${c(`@C298B:推送失败: `)}${e.message}`,type:`error`}),t(!1)}})()}).catch(e=>{v.value=!1,r({message:`${c(`@C298B:推送失败: `)}${e.message}`,type:`error`}),t(!1)})}catch(e){v.value=!1,r({message:`${c(`@C298B:推送失败: `)}${e.message}`,type:`error`}),t(!1)}}):(r.warning(c(`@C298B:当前目录不是Git仓库`)),!1)}async function je(e,t={}){if(!m.value)return r({message:c(`@C298B:当前目录不是Git仓库`),type:`warning`}),!1;if(!e)return r({message:c(`@C298B:请选择要合并的分支`),type:`warning`}),!1;if(e===i.value)return r({message:c(`@C298B:不能合并当前分支到自身`),type:`warning`}),!1;let n=`Merge branch '${e}' into ${i.value}`;try{x.value=!0;let a=await fetch(`/api/merge`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({branch:e,...t})}),o=await a.json();return a.status===409?(E.value=t.message||n,r({message:c(`@C298B:合并分支时发生冲突,请手动解决`),type:`warning`,duration:5e3}),!1):(E.value=``,o.success?(r({message:`${c(`@C298B:成功合并分支 `)}${e}${c(`@C298B: 到 `)}${i.value}`,type:`success`}),await G(),await B(),!0):(r({message:`${c(`@C298B:合并分支失败: `)}${o.error}`,type:`error`}),!1))}catch(e){return r({message:`${c(`@C298B:合并分支失败: `)}${e.message}`,type:`error`}),!1}finally{x.value=!1}}async function Me(e,t=!1){if(console.log(c(`@C298B:开始暂存并提交操作...`)),!await Te())return!1;console.log(c(`@C298B:暂存完成,等待Git操作完成...`)),await q(f),console.log(c(`@C298B:开始提交更改...`));let n=await J(e,t);return console.log(c(`@C298B:暂存并提交操作完成`)),n}async function Ne(e,t=!1){try{if(console.log(c(`@C298B:开始暂存文件...`)),!await Te()||(console.log(c(`@C298B:暂存完成,等待Git操作完成...`)),await q(f),console.log(c(`@C298B:开始提交更改...`)),!await J(e,t)))return!1;console.log(c(`@C298B:提交完成,等待Git操作完成...`)),await q(f),console.log(c(`@C298B:开始推送到远程...`));let n=await ke();return console.log(c(`@C298B:一键推送操作完成,状态已统一刷新`)),n}catch(e){console.error(`一键推送操作失败:`,e);try{(await(await fetch(`/api/remove-lock`,{method:`POST`})).json()).success&&r({message:c(`@C298B:检测到Git锁定文件冲突,已自动清理,请重试操作`),type:`warning`})}catch(e){console.error(`清理锁定文件失败:`,e)}r({message:`${c(`@C298B:操作失败: `)}${e.message}`,type:`error`});try{await Promise.all([G(),W(),B()])}catch(e){console.error(`刷新状态失败:`,e)}return!1}}async function Pe(e,t=!1){if(!Z.value||Q.value.size===0)return Me(e,t);let n=$.value;return n.length===0?(r.warning(c(`@C298B:请选择要暂存的文件`)),!1):(console.log(c(`@C298B:开始暂存所选文件并提交...`,{n:n.length})),await dt(n)?(await q(f),J(e,t)):!1)}async function Fe(e,t=!1){if(!Z.value||Q.value.size===0)return Ne(e,t);let n=$.value;return n.length===0?(r.warning(c(`@C298B:请选择要暂存的文件`)),!1):!await dt(n)||(await q(f),!await J(e,t))?!1:(await q(f),ke())}async function Ie(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{F.value=!0;let e=await(await fetch(`/api/reset-head`,{method:`POST`})).json();return e.success?(r({message:c(`@C298B:已重置暂存区`),type:`success`}),G(),!0):(r({message:`${c(`@C298B:重置暂存区失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:重置暂存区失败: `)}${e.message}`,type:`error`}),!1}finally{F.value=!1}}async function Le(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{F.value=!0;let t=await(await fetch(`/api/reset-to-remote`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({branch:e})})).json();return t.success?(r({message:`${c(`@C298B:已重置分支 `)}${e}${c(`@C298B: 到远程状态`)}`,type:`success`}),G(),W(),await V(!0),await H(),!0):(r({message:`${c(`@C298B:重置分支失败: `)}${t.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:重置分支失败: `)}${e.message}`,type:`error`}),!1}finally{F.value=!1}}async function Re(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{F.value=!0;let e=await(await fetch(`/api/discard-all-changes`,{method:`POST`})).json();return e.success?(r.success(c(`@C298B:已清除所有本地更改`)),G(),W(),!0):(r.error(`${c(`@C298B:操作失败: `)}${e.error}`),!1)}catch(e){return r.error(`${c(`@C298B:操作失败: `)}${e.message}`),!1}finally{F.value=!1}}async function ze(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;if(!Array.isArray(e)||e.length===0)return r.warning(c(`@C298B:未选择任何文件`)),!1;try{F.value=!0;let t=await(await fetch(`/api/revert_files`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filePaths:e})})).json();if(t.success){let n=Number(t?.count??e.length),i=Array.isArray(t?.results)?t.results.filter(e=>!e?.success).map(e=>String(e?.path||``)):[];return i.length>0?r.warning(`${c(`@C298B:已清除 `)}${n-i.length}${c(`@C298B: 个文件,失败 `)}${i.length}${c(`@C298B: 个`)}`):r.success(`${c(`@C298B:已清除 `)}${n}${c(`@C298B: 个所选文件`)}`),G(),W(),!0}return r.error(`${c(`@C298B:操作失败: `)}${t.error}`),!1}catch(e){return r.error(`${c(`@C298B:操作失败: `)}${e.message}`),!1}finally{F.value=!1}}async function Be(){if(m.value)try{_.value=!0,console.log(c(`@C298B:获取远程仓库地址...`));let e=await(await fetch(`/api/remote-url`)).json();e.success?(g.value=e.url||``,console.log(`${c(`@C298B:获取到远程仓库地址: `)}${g.value}`)):(console.warn(`获取远程仓库地址失败:`,e.error),g.value=``)}catch(e){console.error(`获取远程仓库地址失败:`,e),g.value=``}finally{_.value=!1}}async function Ve(){try{let e=await(await fetch(`/api/git-init`,{method:`POST`})).json();return e.success?(await me(),!0):(r.error(e.error||c(`@C298B:Git仓库初始化失败`)),!1)}catch(e){return r.error(`${c(`@C298B:Git仓库初始化失败: `)}${e.message}`),!1}}async function He(e,t=`origin`){try{let n=await(await fetch(`/api/add-remote`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:t,url:e})})).json();return n.success?(await Be(),!0):(r.error(n.error||c(`@C298B:添加远程仓库失败`)),!1)}catch(e){return r.error(`${c(`@C298B:添加远程仓库失败: `)}${e.message}`),!1}}async function Ue(){try{let e=await(await fetch(`/api/attach-remote-branch`,{method:`POST`})).json();return e.success?(e.attached?(r.success(c(`@C298B:已检出远程分支 {branch} 并建立跟踪`,{branch:e.branch})),await Promise.all([V(!0),H(),B(!0),W(!1),K()])):e.empty&&r.info(c(`@C298B:远程仓库为空,提交首个 commit 后可一键推送建上游`)),e):(r.warning(`${c(`@C298B:关联远程分支失败: `)}${e.error||``}`),e)}catch(e){return r.warning(`${c(`@C298B:关联远程分支失败: `)}${e.message}`),{success:!1,error:e.message}}}async function We(){try{let e=await(await fetch(`/api/diff-head`)).json();return e.success?e.diff?(await navigator.clipboard.writeText(e.diff),r({message:c(`@C298B:已复制全量 Diff`),type:`success`}),!0):(r({message:c(`@C298B:没有可复制的 Diff`),type:`warning`}),!1):(r({message:`${c(`@C298B:复制失败: `)}${e.error}`,type:`error`}),!1)}catch(e){return r({message:`${c(`@C298B:复制失败: `)}${e.message}`,type:`error`}),!1}}let Ge=[`github.com`,`gitlab.com`,`bitbucket.org`,`gitee.com`,`coding.net`,`codeup.aliyun.com`],Y=o(()=>{let e=g.value.trim();if(!e)return null;if(/^https?:\/\//i.test(e))return e.replace(/\.git$/i,``);let t=e.match(/^[\w.-]+@([^:]+):(.+?)(?:\.git)?$/i);if(t){let e=t[1],n=t[2];return Ge.includes(e.toLowerCase())?`https://${e}/${n}`:null}let n=e.match(/^ssh:\/\/(?:[\w.-]+@)?([^/:]+)(?::\d+)?\/(.+?)(?:\.git)?$/i);if(n){let e=n[1],t=n[2];return Ge.includes(e.toLowerCase())?`https://${e}/${t}`:null}return null}),Ke=o(()=>Y.value!==null);function qe(){let e=Y.value;return e?(window.open(e,`_blank`,`noopener,noreferrer`),!0):(r({message:c(`@C298B:该远程仓库地址无法识别为可访问的网页地址`),type:`warning`}),!1)}async function Je(){if(!g.value)return r({message:c(`@C298B:没有可复制的远程仓库地址`),type:`warning`}),!1;try{return await navigator.clipboard.writeText(g.value),r({message:c(`@C298B:已复制远程仓库地址`),type:`success`}),!0}catch(e){return console.error(`复制远程仓库地址失败:`,e),r({message:`${c(`@C298B:复制失败: `)}${e.message}`,type:`error`}),!1}}async function Ye(){if(!g.value)return r({message:c(`@C298B:没有可复制的远程仓库地址`),type:`warning`}),!1;try{let e=`git clone ${g.value}`;return await navigator.clipboard.writeText(e),r({message:c(`@F13B4:已复制克隆命令`),type:`success`}),!0}catch(e){return console.error(`复制克隆命令失败:`,e),r({message:`${c(`@C298B:复制失败: `)}${e.message}`,type:`error`}),!1}}e(()=>{Ce()});function Xe(){D.value&&=(Ze(),D.value.disconnect(),null)}function Ze(){if(D.value)for(let e of[`connect`,`project_info`,`project_changed`,`disconnect`,`git_status_update`,`connect_error`,`connect_timeout`,`reconnect`,`reconnect_attempt`,`reconnect_error`,`reconnect_failed`])try{D.value.off(e)}catch{}}n(()=>{Xe()});async function X(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),[];try{de.value=!0;let e=await(await fetch(`/api/stash-list`)).json();return e.success?(ue.value=e.stashes,e.stashes):(r.error(`${c(`@C298B:获取stash列表失败: `)}${e.error}`),[])}catch(e){return console.error(`获取stash列表失败:`,e),r.error(`${c(`@C298B:获取stash列表失败: `)}${e.message}`),[]}finally{de.value=!1}}async function Qe(e,t=!1,n=!0){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{z.value=!0;let i=await(await fetch(`/api/stash-save`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({message:e,includeUntracked:t,excludeLocked:n})})).json();return i.success?(r.success(i.message),await X(),await G(),!0):(r.warning(i.message),!1)}catch(e){return console.error(`保存stash失败:`,e),r.error(`${c(`@C298B:保存stash失败: `)}${e.message}`),!1}finally{z.value=!1}}async function $e(e,t,n=!1){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;if(!e||e.length===0)return r.warning(c(`@C298B:请选择要储藏的文件`)),!1;try{z.value=!0;let i=await(await fetch(`/api/stash-save-partial`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({files:e,message:t,includeUntracked:n})})).json();return i.success?(r.success(i.message),await X(),await G(),!0):(r.warning(i.message),!1)}catch(e){return console.error(`保存部分stash失败:`,e),r.error(`${c(`@C298B:保存stash失败: `)}${e.message}`),!1}finally{z.value=!1}}async function et(e,t=!1){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{fe.value=!0;let n=await fetch(`/api/stash-apply`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({stashId:e,pop:t})}),i=await n.json();return n.status===409?(r.warning(c(`@C298B:应用stash时发生冲突,请手动解决`)),!1):i.success?(r.success(i.message),await X(),await G(),!0):(r.error(`${c(`@C298B:应用stash失败: `)}${i.error}`),!1)}catch(e){return console.error(`应用stash失败:`,e),r.error(`${c(`@C298B:应用stash失败: `)}${e.message}`),!1}finally{fe.value=!1}}async function tt(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{R.value=!0;let t=await(await fetch(`/api/stash-drop`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({stashId:e})})).json();return t.success?(r.success(t.message),await X(),!0):(r.error(`${c(`@C298B:删除stash失败: `)}${t.error}`),!1)}catch(e){return console.error(`删除stash失败:`,e),r.error(`${c(`@C298B:删除stash失败: `)}${e.message}`),!1}finally{R.value=!1}}async function nt(){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;try{R.value=!0;let e=await(await fetch(`/api/stash-clear`,{method:`POST`})).json();return e.success?(r.success(e.message),ue.value=[],!0):(r.error(`${c(`@C298B:清空stash失败: `)}${e.error}`),!1)}catch(e){return console.error(`清空stash失败:`,e),r.error(`${c(`@C298B:清空stash失败: `)}${e.message}`),!1}finally{R.value=!1}}let rt=o(()=>A.value.some(e=>e.type===`conflicted`)),Z=a(!1),Q=a(new Set);function it(){Z.value=!Z.value,Z.value||(Q.value=new Set)}function at(){Z.value=!0}function ot(){Z.value=!1,Q.value=new Set}function st(e){let t=new Set(Q.value);t.has(e)?t.delete(e):t.add(e),Q.value=t}function ct(){Q.value=new Set(A.value.map(e=>e.path))}function lt(){Q.value=new Set}let $=o(()=>{if(!Z.value)return[];let e=new Set(t.lockedFiles.map(e=>e.replace(/\\/g,`/`))),n=[];for(let t of Q.value){let r=t.replace(/\\/g,`/`);if(e.has(r))continue;let i=A.value.find(e=>e.path===t);i&&[`modified`,`deleted`,`untracked`,`intent-to-add`].includes(i.type)&&n.push(t)}return n}),ut=o(()=>$.value.length>0);async function dt(e){if(!m.value)return r.warning(c(`@C298B:当前目录不是Git仓库`)),!1;let t=[...new Set(e.filter(e=>typeof e==`string`&&e.length>0))];if(t.length===0)return r.warning(c(`@C298B:请选择要暂存的文件`)),!1;try{N.value=!0;let e=await(await fetch(`/api/add-files`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filePaths:t})})).json();if(e.success){let n=e.successCount??t.length;r.success(n>0?`${c(`@C298B:已暂存 `)}${n}${c(`@C298B: 个所选文件`)}`:c(`@C298B:文件已添加到暂存区`));let i=new Set(Q.value);for(let e of t)i.delete(e);return Q.value=i,await K(),!0}return r.error(`${c(`@C298B:暂存失败: `)}${e.error||``}`),!1}catch(e){return r.error(`${c(`@C298B:暂存失败: `)}${e.message}`),!1}finally{N.value=!1}}return{currentBranch:i,allBranches:s,userName:l,userEmail:u,isChangingBranch:d,isCreatingBranch:p,isGitRepo:m,lastCheckedTime:h,branchAhead:S,branchBehind:C,hasUpstream:w,upstreamBranch:T,lastBranchesTime:ae,remoteUrl:g,isLoadingRemoteUrl:_,remoteWebUrl:Y,isRemoteBrowsable:Ke,openRemoteWebUrl:qe,hasConflictedFiles:rt,stashes:ue,isLoadingStashes:de,isApplyingStash:fe,isDroppingStash:R,isSavingStash:z,log:k,status:se,fileList:A,isLoadingLog:j,isLoadingStatus:M,isAddingFiles:N,isCommiting:P,isResetting:F,currentPage:I,hasMoreData:L,totalCommits:ce,isPushing:v,isGitPulling:y,isGitFetching:b,isGitMerging:x,isMergeInProgress:ie,pendingMergeMessage:E,isSelectionMode:Z,selectedFiles:Q,selectedUnstagedPaths:$,hasSelectableFiles:ut,toggleSelectionMode:it,enterSelectionMode:at,exitSelectionMode:ot,toggleFileSelection:st,selectAllFiles:ct,clearSelection:lt,$reset:pe,checkGitRepo:me,getCurrentBranch:V,getAllBranches:H,changeBranch:he,getUserInfo:ge,createBranch:_e,clearUserConfig:ve,restoreUserConfig:ye,getBranchStatus:B,gitPull:be,gitFetchAll:Se,bootFetch:xe,initSocketConnection:Ce,parseStatusPorcelain:U,fetchLog:W,refreshLog:we,fetchStatus:G,fetchStatusPorcelain:K,addToStage:Te,addAllToStage:Ee,addFileToStage:De,stageFiles:dt,unstageFile:Oe,commitChanges:J,pushToRemote:ke,pushToRemoteWithProgress:Ae,addAndCommit:Me,addCommitAndPush:Ne,stageSelectedAndCommit:Pe,stageSelectedAndCommitAndPush:Fe,resetHead:Ie,resetToRemote:Le,discardAllChanges:Re,discardSelectedFiles:ze,getRemoteUrl:Be,addRemote:He,attachRemoteBranch:Ue,gitInit:Ve,copyRemoteUrl:Je,copyCloneCommand:Ye,copyCurrentDiff:We,mergeBranch:je,getStashList:X,saveStash:Qe,savePartialStash:$e,applyStash:et,dropStash:tt,clearAllStashes:nt,socket:D,disconnectSocket:Xe}});export{d as n,p as t};
|